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)
164 {
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)
202 {
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)
217 {
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)
247 {
248 if (! junos)
249 return NULL;
250 return junos->hostname;
251 } /* junos_get_hostname */
253 char *
254 junos_get_username(junos_t *junos)
255 {
256 if (! junos)
257 return NULL;
258 return junos->username;
259 } /* junos_get_username */
261 char *
262 junos_get_password(junos_t *junos)
263 {
264 if (! junos)
265 return NULL;
266 return junos->password;
267 } /* junos_get_password */
269 void
270 junos_free(junos_t *junos)
271 {
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)
289 {
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)
340 {
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, ...)
353 {
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)
512 {
513 if (! junos)
514 return NULL;
515 return junos->err.errmsg;
516 } /* junos_get_errstr */
518 void
519 junos_clear_error(junos_t *junos)
520 {
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, ...)
532 {
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)
546 {
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)
645 {
646 return LIBJUNOS_VERSION;
647 } /* libjunos_version */
649 const char *
650 libjunos_version_string(void)
651 {
652 return LIBJUNOS_VERSION_STRING;
653 } /* libjunos_version_string */
655 const char *
656 libjunos_version_extra(void)
657 {
658 return LIBJUNOS_VERSION_EXTRA;
659 } /* libjunos_version_extra */
661 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */