Code

8608551f41593c2787712165ac64545dda26edd7
[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 <stdarg.h>
39 #include <stdlib.h>
40 #include <string.h>
42 #include <sys/types.h>
43 #include <sys/socket.h>
44 #include <netdb.h>
46 #include <libssh2.h>
48 #include <libxml/tree.h>
49 #include <libxml/parser.h>
51 #ifndef LIBXML_PUSH_ENABLED
52 #       error "libxml has not been compiled with push parser support"
53 #endif
55 /*
56  * private data structures
57  */
59 struct junos {
60         char *hostname;
61         char *username;
62         char *password;
64         void *access;
66         xmlParserCtxtPtr xml_ctx;
68         junos_error_t err;
69 };
71 /*
72  * private helper functions
73  */
75 static int
76 meth_append_arg(junos_strbuf_t *body, junos_strbuf_t *attrs,
77                 int arg_type, va_list *ap)
78 {
79         char *name = va_arg(*ap, char *);
80         ssize_t status;
82         if (! name) {
83                 errno = EINVAL;
84                 return -1;
85         }
87         switch (arg_type) {
88                 case JUNOS_ARG_TOGGLE: /* fall thru */
89                 case JUNOS_ARG_TOGGLE_NO:
90                         {
91                                 int value = va_arg(*ap, int);
92                                 if (value)
93                                         status = junos_strbuf_sprintf(body,
94                                                         "    <%s/>\n", name);
95                                 else if (arg_type == JUNOS_ARG_TOGGLE_NO)
96                                         status = junos_strbuf_sprintf(body,
97                                                         "    <no-%s/>\n", name);
98                         }
99                         break;
100                 case JUNOS_ARG_STRING:
101                         {
102                                 char *value = va_arg(*ap, char *);
103                                 status = junos_strbuf_sprintf(body,
104                                                 "    <%s>%s</%s>\n", name,
105                                                 value ? value : "", name);
106                         }
107                         break;
108                 case JUNOS_ARG_INTEGER:
109                         {
110                                 int value = va_arg(*ap, int);
111                                 status = junos_strbuf_sprintf(body,
112                                                 "    <%s>%i</%s>\n",
113                                                 name, value, name);
114                         }
115                         break;
116                 case JUNOS_ARG_DOUBLE:
117                         {
118                                 double value = va_arg(*ap, double);
119                                 status = junos_strbuf_sprintf(body,
120                                                 "    <%s>%lf</%s>\n",
121                                                 name, value, name);
122                         }
123                         break;
124                 case JUNOS_ARG_DOM:
125                         {
126                                 status = junos_strbuf_sprintf(body,
127                                                 "    %s\n", name);
128                         }
129                         break;
130                 case JUNOS_ATTR_STRING:
131                         {
132                                 char *value = va_arg(*ap, char *);
133                                 status = junos_strbuf_sprintf(attrs,
134                                                 " %s=\"%s\"",
135                                                 name, value ? value : "");
136                         }
137                         break;
138                 case JUNOS_ATTR_INTEGER:
139                         {
140                                 int value = va_arg(*ap, int);
141                                 status = junos_strbuf_sprintf(attrs,
142                                                 " %s=\"%s\"", name, value);
143                         }
144                         break;
145                 case JUNOS_ATTR_DOUBLE:
146                         {
147                                 double value = va_arg(*ap, double);
148                                 status = junos_strbuf_sprintf(attrs,
149                                                 " %s=\"%s\"", name, value);
150                         }
151                         break;
152                 default:
153                         errno = EINVAL;
154                         return -1;
155                         break;
156         }
157         if (status < 0)
158                 return -1;
159         return 0;
160 } /* meth_append_arg */
162 static ssize_t
163 read_lines(junos_t *junos, char *buf, size_t buf_len)
165         ssize_t count = 0;
167         while (42) {
168                 ssize_t status;
170                 /* junos_ssh_recv requires at least two bytes */
171                 if (buf_len - 2 < (size_t)count) {
172                         dprintf("Receive buffer too small\n");
173                         break;
174                 }
176                 status = junos_ssh_recv(junos->access,
177                                 buf + count, buf_len - (size_t)count);
178                 if (status < 0) {
179                         count = -1;
180                         break;
181                 }
183                 if (! status)
184                         if (count)
185                                 break;
186                         /* else: retry */
188                 count += status;
190                 if (buf[count - 1] == '\n')
191                         break;
192         }
193         return count;
194 } /* read_line */
196 /*
197  * public API
198  */
200 int
201 junos_init(void)
203         int status;
205         status = libssh2_init(/* flags = */ 0);
206         if (status < 0) {
207                 dprintf("Failed to initialize libssh2 (status %d)\n", status);
208                 return status;
209         }
211         LIBXML_TEST_VERSION;
212         return 0;
213 } /* junos_init */
215 junos_t *
216 junos_new(char *hostname, char *username, char *password)
218         junos_t *junos;
220         if ((! hostname) || (! username))
221                 return NULL;
223         junos = calloc(1, sizeof(*junos));
224         if (! junos)
225                 return NULL;
227         junos->hostname = strdup(hostname);
228         junos->username = strdup(username);
229         if (password)
230                 junos->password = strdup(password);
232         if ((! junos->hostname) || (! junos->username)
233                         || (password && (! junos->password))) {
234                 junos_free(junos);
235                 return NULL;
236         }
238         junos->access  = NULL;
239         junos->xml_ctx = NULL;
241         junos_clear_error(junos);
242         return junos;
243 } /* junos_new */
245 char *
246 junos_get_hostname(junos_t *junos)
248         if (! junos)
249                 return NULL;
250         return junos->hostname;
251 } /* junos_get_hostname */
253 char *
254 junos_get_username(junos_t *junos)
256         if (! junos)
257                 return NULL;
258         return junos->username;
259 } /* junos_get_username */
261 char *
262 junos_get_password(junos_t *junos)
264         if (! junos)
265                 return NULL;
266         return junos->password;
267 } /* junos_get_password */
269 void
270 junos_free(junos_t *junos)
272         if (! junos)
273                 return;
275         junos_disconnect(junos);
277         if (junos->hostname)
278                 free(junos->hostname);
279         if (junos->username)
280                 free(junos->username);
281         if (junos->password)
282                 free(junos->password);
284         free(junos);
285 } /* junos_free */
287 int
288 junos_connect(junos_t *junos)
290         char recv_buf[4096];
291         ssize_t count = 0;
292         ssize_t status;
294         char *tmp;
296         char js_handshake[] = "<?xml version=\"1.0\" encoding=\"us-ascii\"?>"
297                 "<junoscript version=\"1.0\" os=\"libJUNOS\">";
299         if (! junos)
300                 return -1;
302         junos->access = junos_ssh_new(junos);
303         if (! junos->access)
304                 return -1;
306         if (junos_ssh_connect(junos->access))
307                 return -1;
309         while (42) {
310                 status = read_lines(junos, recv_buf + count,
311                                 sizeof(recv_buf) - (size_t)count);
312                 if (status < 0)
313                         break;
315                 count += status;
317                 if ((tmp = strstr(recv_buf, "<?xml"))
318                                 && strstr(tmp, "<junoscript"))
319                         break;
320         }
322         dprintf("Header: %s", recv_buf);
324         /* don't send the trailing null byte */
325         status = junos_ssh_send(junos->access,
326                         js_handshake, sizeof(js_handshake) - 1);
327         if (status != (ssize_t)sizeof(js_handshake) - 1) {
328                 dprintf("Failed to send JUNOScript handshake (status %d)\n",
329                                 (int)status);
330                 return -1;
331         }
333         read_lines(junos, recv_buf, sizeof(recv_buf));
334         dprintf(" ->  %s", recv_buf);
335         return 0;
336 } /* junos_connect */
338 int
339 junos_disconnect(junos_t *junos)
341         if (! junos)
342                 return -1;
344         if (junos->access)
345                 junos_ssh_free(junos->access);
346         junos->access = NULL;
348         return 0;
349 } /* junos_disconnect */
351 xmlDocPtr
352 junos_invoke_method(junos_t *junos, const char *name, ...)
354         junos_strbuf_t *method_buf;
355         junos_strbuf_t *body_buf;
356         junos_strbuf_t *attr_buf;
358         char  *method_string;
359         size_t method_len;
360         char  *body_string;
361         char  *attr_string;
363         char recv_buf[4096];
364         ssize_t status;
366         int xml_status;
367         xmlDocPtr doc;
369         va_list ap;
370         int arg_type;
372         if ((! junos) || (! name)) {
373                 junos_set_error(junos, JUNOS_SYS_ERROR, EINVAL,
374                                 "junos_invoke_method() requires the "
375                                 "'junos' and 'name' arguments");
376                 return NULL;
377         }
379         if (! junos->access) {
380                 junos_set_error(junos, JUNOS_SYS_ERROR, EINVAL,
381                                 "Please call junos_connect() before invoking a method");
382                 return NULL;
383         }
385         errno = 0;
386         method_buf = junos_strbuf_new(1024);
387         body_buf   = junos_strbuf_new(1024);
388         attr_buf   = junos_strbuf_new(1024);
390 #define BUF_FREE() \
391         do { \
392                 junos_strbuf_free(method_buf); \
393                 method_buf    = NULL; \
394                 method_string = NULL; \
395                 method_len    = 0;    \
396                 junos_strbuf_free(body_buf); \
397                 body_buf      = NULL; \
398                 body_string   = NULL; \
399                 junos_strbuf_free(attr_buf); \
400                 attr_buf      = NULL; \
401                 attr_string   = NULL; \
402         } while (0)
404         if ((! method_buf) || (! body_buf) || (! attr_buf)) {
405                 junos_set_error(junos, JUNOS_SYS_ERROR, errno,
406                                 "Failed to allocate string buffers");
407                 BUF_FREE();
408                 return NULL;
409         }
411         va_start(ap, name);
412         while ((arg_type = va_arg(ap, int)) != JUNOS_NO_ARGS) {
413                 if (meth_append_arg(body_buf, attr_buf, arg_type, &ap)) {
414                         BUF_FREE();
415                         junos_set_error(junos, JUNOS_SYS_ERROR, errno,
416                                         "Failed to append argument (type %d) to method '%s'",
417                                         arg_type, name);
418                         return NULL;
419                 }
420         }
421         va_end(ap);
423         body_string = junos_strbuf_string(body_buf);
424         attr_string = junos_strbuf_string(attr_buf);
426         if (body_string[0])
427                 junos_strbuf_sprintf(method_buf,
428                                 "<rpc>\n"
429                                 "  <%s%s>\n%s"
430                                 "  </%s>\n"
431                                 "</rpc>",
432                                 name, attr_string, body_string, name);
433         else
434                 junos_strbuf_sprintf(method_buf,
435                                 "<rpc>\n"
436                                 "  <%s%s/>\n"
437                                 "</rpc>",
438                                 name, attr_string);
440         method_string = junos_strbuf_string(method_buf);
441         method_len    = junos_strbuf_len(method_buf);
443         dprintf(" -> %s\n", method_string);
444         status = junos_ssh_send(junos->access, method_string, method_len);
446         if (status != (ssize_t)method_len) {
447                 dprintf("Failed to send method '%s' (status %d)\n",
448                                 method_string, (int)status);
449                 BUF_FREE();
450                 return NULL;
451         }
453         BUF_FREE();
455         errno = 0;
456         junos->xml_ctx = xmlCreatePushParserCtxt(/* sax = */ NULL,
457                         /* user_data = */ NULL,
458                         /* chunk = */ NULL, /* size = */ 0,
459                         /* filename = */ NULL);
460         if (! junos->xml_ctx) {
461                 junos_set_error(junos, JUNOS_SYS_ERROR, errno,
462                                 "Failed to create XML parser context");
463                 return NULL;
464         }
466         while (42) {
467                 status = read_lines(junos, recv_buf, sizeof(recv_buf));
468                 if (status < 0)
469                         break;
471                 dprintf(" ->  %s", recv_buf);
473                 xml_status = xmlParseChunk(junos->xml_ctx, recv_buf, (int)status,
474                                 /* terminate = */ 0);
475                 if (xml_status) {
476                         junos_set_error(junos, JUNOS_XML_ERROR, xml_status,
477                                         "XML parsing failed");
478                         break;
479                 }
481                 if (strstr(recv_buf, "</rpc-reply>"))
482                         break;
483         }
485         /* finish parser */
486         xmlParseChunk(junos->xml_ctx, "", 0, /* terminate = */ 1);
488         doc = junos->xml_ctx->myDoc;
489         if (xml_status || (! junos->xml_ctx->wellFormed)) {
490                 if ((! xml_status) && (! status))
491                         junos_set_error(junos, JUNOS_XML_ERROR, -1,
492                                         "XML validation failed");
493                 if (status >= 0)
494                         status = -1;
495         }
497         xmlFreeParserCtxt(junos->xml_ctx);
498         junos->xml_ctx = NULL;
500         if (status < 0) {
501                 xmlFreeDoc(doc);
502                 return NULL;
503         }
505         return doc;
506 } /* junos_invoke_method */
508 /* error handling */
510 const char *
511 junos_get_errstr(junos_t *junos)
513         if (! junos)
514                 return NULL;
515         return junos->err.errmsg;
516 } /* junos_get_errstr */
518 void
519 junos_clear_error(junos_t *junos)
521         junos_error_t no_error = JUNOS_NO_ERROR;
523         if (! junos)
524                 return;
526         junos->err = no_error;
527 } /* junos_clear_error */
529 int
530 junos_set_error(junos_t *junos, int type, int error,
531                 char *msg_prefix, ...)
533         va_list ap;
534         int status;
536         va_start(ap, msg_prefix);
537         status = junos_set_verror(junos, type, error, msg_prefix, ap);
538         va_end(ap);
540         return status;
541 } /* junos_set_error */
543 int
544 junos_set_verror(junos_t *junos, int type, int error,
545                 char *msg_prefix, va_list ap)
547         junos_error_t *err;
549         char errbuf[1024];
550         const char *err_msg;
552         char prefix[1024];
554         int status = 0;
556         if (! junos)
557                 return -1;
559         err = &junos->err;
561         err->type  = type;
562         err->error = error;
564         vsnprintf(prefix, sizeof(prefix), msg_prefix, ap);
565         prefix[sizeof(prefix) - 1] = '\0';
567         switch (type) {
568                 case JUNOS_OK:
569                         snprintf(err->errmsg, sizeof(err->errmsg),
570                                         "i%s: success", prefix);
571                         break;
572                 case JUNOS_SYS_ERROR:
573                         {
574                                 int failed = 0;
576 #if STRERROR_R_CHAR_P
577                                 errbuf[0] = '\0';
578                                 err_msg = strerror_r(error, errbuf, sizeof(errbuf));
579                                 if (! err_msg)
580                                         err_msg = errbuf;
581                                 if (! err_msg[0])
582                                         failed = 1;
583 #else /* STRERROR_R_CHAR_P */
584                                 failed = strerror_r(error, errbuf, sizeof(errbuf));
585                                 err_msg = errbuf;
586 #endif /* STRERROR_R_CHAR_P */
588                                 if (failed)
589                                         snprintf(err->errmsg, sizeof(err->errmsg),
590                                                         "%s: system error #%i", prefix, error);
591                                 else
592                                         snprintf(err->errmsg, sizeof(err->errmsg),
593                                                         "%s: %s", prefix, err_msg);
594                         }
595                         break;
596                 case JUNOS_GAI_ERROR:
597                         if (error == EAI_SYSTEM)
598                                 return junos_set_error(junos, JUNOS_SYS_ERROR, error,
599                                                 "%s: network address translation failed", prefix);
601                         err_msg = gai_strerror(error);
602                         if (err_msg)
603                                 snprintf(err->errmsg, sizeof(err->errmsg),
604                                                 "%s: %s", prefix, err_msg);
605                         else
606                                 snprintf(err->errmsg, sizeof(err->errmsg),
607                                                 "%s: network address translation error #%i",
608                                                 prefix, error);
609                         break;
610                 case JUNOS_XML_ERROR:
611                         {
612                                 xmlErrorPtr xml_err;
614                                 if (! junos->xml_ctx) /* don't touch any error information */
615                                         return 0;
617                                 xml_err = xmlCtxtGetLastError(junos->xml_ctx);
618                                 if (! xml_err)
619                                         return 0;
621                                 err->error = xml_err->code;
622                                 snprintf(err->errmsg, sizeof(err->errmsg),
623                                                 "%s: %s", prefix, xml_err->message);
624                         }
625                         break;
626                 case JUNOS_ACCESS_ERROR:
627                         status = junos_set_ssh_error(err, junos->access,
628                                         "%s", prefix);
629                         break;
630                 default:
631                         return -1;
632                         break;
633         }
635         err->errmsg[sizeof(err->errmsg) - 1] = '\0';
636         dprintf("ERROR: %s\n", err->errmsg);
638         return status;
639 } /* junos_set_verror */
641 /* features */
643 unsigned int
644 libjunos_version(void)
646         return LIBJUNOS_VERSION;
647 } /* libjunos_version */
649 const char *
650 libjunos_version_string(void)
652         return LIBJUNOS_VERSION_STRING;
653 } /* libjunos_version_string */
655 const char *
656 libjunos_version_extra(void)
658         return LIBJUNOS_VERSION_EXTRA;
659 } /* libjunos_version_extra */
661 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */