Code

6a0fea6ef00223cb1705dd3aaca9b0afea7092f2
[libjunos.git] / src / junos.c
1 /*
2  * libJUNOS - src/junos.c
3  * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 /*
29  * Base object used to manage the connection to a JUNOS device.
30  */
32 #include "junos.h"
34 #include "libjunos_features.h"
36 #include <errno.h>
38 #include <stdlib.h>
39 #include <string.h>
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 #include <netdb.h>
45 #include <libssh2.h>
47 #include <libxml/tree.h>
48 #include <libxml/parser.h>
50 #ifndef LIBXML_PUSH_ENABLED
51 #       error "libxml has not been compiled with push parser support"
52 #endif
54 /*
55  * private data structures
56  */
58 struct junos {
59         char *hostname;
60         char *username;
61         char *password;
63         void *access;
65         xmlParserCtxtPtr xml_ctx;
67         junos_error_t err;
68 };
70 /*
71  * private helper functions
72  */
74 static ssize_t
75 read_lines(junos_t *junos, char *buf, size_t buf_len)
76 {
77         ssize_t count = 0;
79         while (42) {
80                 ssize_t status;
82                 /* junos_ssh_recv requires at least two bytes */
83                 if (buf_len - 2 < (size_t)count) {
84                         dprintf("Receive buffer too small\n");
85                         break;
86                 }
88                 status = junos_ssh_recv(junos->access,
89                                 buf + count, buf_len - (size_t)count);
90                 if (status < 0) {
91                         count = -1;
92                         break;
93                 }
95                 if (! status)
96                         if (count)
97                                 break;
98                         /* else: retry */
100                 count += status;
102                 if (buf[count - 1] == '\n')
103                         break;
104         }
105         return count;
106 } /* read_line */
108 /*
109  * public API
110  */
112 int
113 junos_init(void)
115         int status;
117         status = libssh2_init(/* flags = */ 0);
118         if (status < 0) {
119                 dprintf("Failed to initialize libssh2 (status %d)\n", status);
120                 return status;
121         }
123         LIBXML_TEST_VERSION;
124         return 0;
125 } /* junos_init */
127 junos_t *
128 junos_new(char *hostname, char *username, char *password)
130         junos_t *junos;
132         if ((! hostname) || (! username))
133                 return NULL;
135         junos = calloc(1, sizeof(*junos));
136         if (! junos)
137                 return NULL;
139         junos->hostname = strdup(hostname);
140         junos->username = strdup(username);
141         if (password)
142                 junos->password = strdup(password);
144         if ((! junos->hostname) || (! junos->username)
145                         || (password && (! junos->password))) {
146                 junos_free(junos);
147                 return NULL;
148         }
150         junos->access  = NULL;
151         junos->xml_ctx = NULL;
153         junos_clear_error(junos);
154         return junos;
155 } /* junos_new */
157 char *
158 junos_get_hostname(junos_t *junos)
160         if (! junos)
161                 return NULL;
162         return junos->hostname;
163 } /* junos_get_hostname */
165 char *
166 junos_get_username(junos_t *junos)
168         if (! junos)
169                 return NULL;
170         return junos->username;
171 } /* junos_get_username */
173 char *
174 junos_get_password(junos_t *junos)
176         if (! junos)
177                 return NULL;
178         return junos->password;
179 } /* junos_get_password */
181 void
182 junos_free(junos_t *junos)
184         if (! junos)
185                 return;
187         junos_disconnect(junos);
189         if (junos->hostname)
190                 free(junos->hostname);
191         if (junos->username)
192                 free(junos->username);
193         if (junos->password)
194                 free(junos->password);
196         free(junos);
197 } /* junos_free */
199 int
200 junos_connect(junos_t *junos)
202         char recv_buf[4096];
203         ssize_t count = 0;
204         ssize_t status;
206         char *tmp;
208         char js_handshake[] = "<?xml version=\"1.0\" encoding=\"us-ascii\"?>"
209                 "<junoscript version=\"1.0\" os=\"libJUNOS\">";
211         if (! junos)
212                 return -1;
214         junos->access = junos_ssh_new(junos);
215         if (! junos->access)
216                 return -1;
218         if (junos_ssh_connect(junos->access))
219                 return -1;
221         while (42) {
222                 status = read_lines(junos, recv_buf + count,
223                                 sizeof(recv_buf) - (size_t)count);
224                 if (status < 0)
225                         break;
227                 count += status;
229                 if ((tmp = strstr(recv_buf, "<?xml"))
230                                 && strstr(tmp, "<junoscript"))
231                         break;
232         }
234         dprintf("Header: %s", recv_buf);
236         /* don't send the trailing null byte */
237         status = junos_ssh_send(junos->access,
238                         js_handshake, sizeof(js_handshake) - 1);
239         if (status != (ssize_t)sizeof(js_handshake) - 1) {
240                 dprintf("Failed to send JUNOScript handshake (status %d)\n",
241                                 (int)status);
242                 return -1;
243         }
245         read_lines(junos, recv_buf, sizeof(recv_buf));
246         dprintf(" ->  %s", recv_buf);
247         return 0;
248 } /* junos_connect */
250 int
251 junos_disconnect(junos_t *junos)
253         if (! junos)
254                 return -1;
256         if (junos->access)
257                 junos_ssh_free(junos->access);
258         junos->access = NULL;
260         return 0;
261 } /* junos_disconnect */
263 xmlDocPtr
264 junos_simple_command(junos_t *junos, const char *cmd)
266         char cmd_string[1024];
267         char recv_buf[4096];
268         ssize_t status;
270         int xml_status;
272         xmlDocPtr doc;
274         if ((! junos) || (! cmd)) {
275                 junos_set_error(junos, JUNOS_SYS_ERROR, EINVAL,
276                                 "junos_simple_command() requires the "
277                                 "'junos' and 'cmd' arguments");
278                 return NULL;
279         }
281         if (! junos->access) {
282                 junos_set_error(junos, JUNOS_SYS_ERROR, EINVAL,
283                                 "Please call junos_connect() before submitting commands");
284                 return NULL;
285         }
287         snprintf(cmd_string, sizeof(cmd_string),
288                         "<rpc><%s/></rpc>", cmd);
289         status = junos_ssh_send(junos->access, cmd_string, strlen(cmd_string));
290         if (status != (ssize_t)strlen(cmd_string)) {
291                 dprintf("Failed to send cmd '%s' (status %d)\n",
292                                 cmd_string, (int)status);
293                 return NULL;
294         }
296         errno = 0;
297         junos->xml_ctx = xmlCreatePushParserCtxt(/* sax = */ NULL,
298                         /* user_data = */ NULL,
299                         /* chunk = */ NULL, /* size = */ 0,
300                         /* filename = */ NULL);
301         if (! junos->xml_ctx) {
302                 junos_set_error(junos, JUNOS_SYS_ERROR, errno,
303                                 "Failed to create XML parser context");
304                 return NULL;
305         }
307         while (42) {
308                 status = read_lines(junos, recv_buf, sizeof(recv_buf));
309                 if (status < 0)
310                         break;
312                 dprintf(" ->  %s", recv_buf);
314                 xml_status = xmlParseChunk(junos->xml_ctx, recv_buf, (int)status,
315                                 /* terminate = */ 0);
316                 if (xml_status) {
317                         junos_set_error(junos, JUNOS_XML_ERROR, xml_status,
318                                         "XML parsing failed");
319                         break;
320                 }
322                 if (strstr(recv_buf, "</rpc-reply>"))
323                         break;
324         }
326         /* finish parser */
327         xmlParseChunk(junos->xml_ctx, "", 0, /* terminate = */ 1);
329         doc = junos->xml_ctx->myDoc;
330         if (xml_status || (! junos->xml_ctx->wellFormed)) {
331                 if ((! xml_status) && (! status))
332                         junos_set_error(junos, JUNOS_XML_ERROR, -1,
333                                         "XML validation failed");
334                 if (status >= 0)
335                         status = -1;
336         }
338         xmlFreeParserCtxt(junos->xml_ctx);
339         junos->xml_ctx = NULL;
341         if (status < 0) {
342                 xmlFreeDoc(doc);
343                 return NULL;
344         }
346         return doc;
347 } /* junos_simple_command */
349 /* error handling */
351 const char *
352 junos_get_errstr(junos_t *junos)
354         if (! junos)
355                 return NULL;
356         return junos->err.errmsg;
357 } /* junos_get_errstr */
359 void
360 junos_clear_error(junos_t *junos)
362         junos_error_t no_error = JUNOS_NO_ERROR;
364         if (! junos)
365                 return;
367         junos->err = no_error;
368 } /* junos_clear_error */
370 int
371 junos_set_error(junos_t *junos, int type, int error,
372                 char *msg_prefix, ...)
374         va_list ap;
375         int status;
377         va_start(ap, msg_prefix);
378         status = junos_set_verror(junos, type, error, msg_prefix, ap);
379         va_end(ap);
381         return status;
382 } /* junos_set_error */
384 int
385 junos_set_verror(junos_t *junos, int type, int error,
386                 char *msg_prefix, va_list ap)
388         junos_error_t *err;
390         char errbuf[1024];
391         const char *err_msg;
393         char prefix[1024];
395         int status = 0;
397         if (! junos)
398                 return -1;
400         err = &junos->err;
402         err->type  = type;
403         err->error = error;
405         vsnprintf(prefix, sizeof(prefix), msg_prefix, ap);
406         prefix[sizeof(prefix) - 1] = '\0';
408         switch (type) {
409                 case JUNOS_OK:
410                         snprintf(err->errmsg, sizeof(err->errmsg),
411                                         "i%s: success", prefix);
412                         break;
413                 case JUNOS_SYS_ERROR:
414                         {
415                                 int failed = 0;
417 #if STRERROR_R_CHAR_P
418                                 errbuf[0] = '\0';
419                                 err_msg = strerror_r(error, errbuf, sizeof(errbuf));
420                                 if (! err_msg)
421                                         err_msg = errbuf;
422                                 if (! err_msg[0])
423                                         failed = 1;
424 #else /* STRERROR_R_CHAR_P */
425                                 failed = strerror_r(error, errbuf, sizeof(errbuf));
426                                 err_msg = errbuf;
427 #endif /* STRERROR_R_CHAR_P */
429                                 if (failed)
430                                         snprintf(err->errmsg, sizeof(err->errmsg),
431                                                         "%s: system error #%i", prefix, error);
432                                 else
433                                         snprintf(err->errmsg, sizeof(err->errmsg),
434                                                         "%s: %s", prefix, err_msg);
435                         }
436                         break;
437                 case JUNOS_GAI_ERROR:
438                         if (error == EAI_SYSTEM)
439                                 return junos_set_error(junos, JUNOS_SYS_ERROR, error,
440                                                 "%s: network address translation failed", prefix);
442                         err_msg = gai_strerror(error);
443                         if (err_msg)
444                                 snprintf(err->errmsg, sizeof(err->errmsg),
445                                                 "%s: %s", prefix, err_msg);
446                         else
447                                 snprintf(err->errmsg, sizeof(err->errmsg),
448                                                 "%s: network address translation error #%i",
449                                                 prefix, error);
450                         break;
451                 case JUNOS_XML_ERROR:
452                         {
453                                 xmlErrorPtr xml_err;
455                                 if (! junos->xml_ctx) /* don't touch any error information */
456                                         return 0;
458                                 xml_err = xmlCtxtGetLastError(junos->xml_ctx);
459                                 if (! xml_err)
460                                         return 0;
462                                 err->error = xml_err->code;
463                                 snprintf(err->errmsg, sizeof(err->errmsg),
464                                                 "%s: %s", prefix, xml_err->message);
465                         }
466                         break;
467                 case JUNOS_ACCESS_ERROR:
468                         status = junos_set_ssh_error(err, junos->access,
469                                         "%s", prefix);
470                         break;
471                 default:
472                         return -1;
473                         break;
474         }
476         err->errmsg[sizeof(err->errmsg) - 1] = '\0';
477         dprintf("ERROR: %s\n", err->errmsg);
479         return status;
480 } /* junos_set_verror */
482 /* features */
484 unsigned int
485 libjunos_version(void)
487         return LIBJUNOS_VERSION;
488 } /* libjunos_version */
490 const char *
491 libjunos_version_string(void)
493         return LIBJUNOS_VERSION_STRING;
494 } /* libjunos_version_string */
496 const char *
497 libjunos_version_extra(void)
499         return LIBJUNOS_VERSION_EXTRA;
500 } /* libjunos_version_extra */
502 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */