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)
114 {
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)
129 {
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)
159 {
160 if (! junos)
161 return NULL;
162 return junos->hostname;
163 } /* junos_get_hostname */
165 char *
166 junos_get_username(junos_t *junos)
167 {
168 if (! junos)
169 return NULL;
170 return junos->username;
171 } /* junos_get_username */
173 char *
174 junos_get_password(junos_t *junos)
175 {
176 if (! junos)
177 return NULL;
178 return junos->password;
179 } /* junos_get_password */
181 void
182 junos_free(junos_t *junos)
183 {
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)
201 {
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)
252 {
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_method(junos_t *junos, const char *name)
265 {
266 char method_string[1024];
267 char recv_buf[4096];
268 ssize_t status;
270 int xml_status;
272 xmlDocPtr doc;
274 if ((! junos) || (! name)) {
275 junos_set_error(junos, JUNOS_SYS_ERROR, EINVAL,
276 "junos_simple_method() requires the "
277 "'junos' and 'name' arguments");
278 return NULL;
279 }
281 if (! junos->access) {
282 junos_set_error(junos, JUNOS_SYS_ERROR, EINVAL,
283 "Please call junos_connect() before invoking a method");
284 return NULL;
285 }
287 snprintf(method_string, sizeof(method_string),
288 "<rpc><%s/></rpc>", name);
289 status = junos_ssh_send(junos->access,
290 method_string, strlen(method_string));
291 if (status != (ssize_t)strlen(method_string)) {
292 dprintf("Failed to send method '%s' (status %d)\n",
293 method_string, (int)status);
294 return NULL;
295 }
297 errno = 0;
298 junos->xml_ctx = xmlCreatePushParserCtxt(/* sax = */ NULL,
299 /* user_data = */ NULL,
300 /* chunk = */ NULL, /* size = */ 0,
301 /* filename = */ NULL);
302 if (! junos->xml_ctx) {
303 junos_set_error(junos, JUNOS_SYS_ERROR, errno,
304 "Failed to create XML parser context");
305 return NULL;
306 }
308 while (42) {
309 status = read_lines(junos, recv_buf, sizeof(recv_buf));
310 if (status < 0)
311 break;
313 dprintf(" -> %s", recv_buf);
315 xml_status = xmlParseChunk(junos->xml_ctx, recv_buf, (int)status,
316 /* terminate = */ 0);
317 if (xml_status) {
318 junos_set_error(junos, JUNOS_XML_ERROR, xml_status,
319 "XML parsing failed");
320 break;
321 }
323 if (strstr(recv_buf, "</rpc-reply>"))
324 break;
325 }
327 /* finish parser */
328 xmlParseChunk(junos->xml_ctx, "", 0, /* terminate = */ 1);
330 doc = junos->xml_ctx->myDoc;
331 if (xml_status || (! junos->xml_ctx->wellFormed)) {
332 if ((! xml_status) && (! status))
333 junos_set_error(junos, JUNOS_XML_ERROR, -1,
334 "XML validation failed");
335 if (status >= 0)
336 status = -1;
337 }
339 xmlFreeParserCtxt(junos->xml_ctx);
340 junos->xml_ctx = NULL;
342 if (status < 0) {
343 xmlFreeDoc(doc);
344 return NULL;
345 }
347 return doc;
348 } /* junos_simple_method */
350 /* error handling */
352 const char *
353 junos_get_errstr(junos_t *junos)
354 {
355 if (! junos)
356 return NULL;
357 return junos->err.errmsg;
358 } /* junos_get_errstr */
360 void
361 junos_clear_error(junos_t *junos)
362 {
363 junos_error_t no_error = JUNOS_NO_ERROR;
365 if (! junos)
366 return;
368 junos->err = no_error;
369 } /* junos_clear_error */
371 int
372 junos_set_error(junos_t *junos, int type, int error,
373 char *msg_prefix, ...)
374 {
375 va_list ap;
376 int status;
378 va_start(ap, msg_prefix);
379 status = junos_set_verror(junos, type, error, msg_prefix, ap);
380 va_end(ap);
382 return status;
383 } /* junos_set_error */
385 int
386 junos_set_verror(junos_t *junos, int type, int error,
387 char *msg_prefix, va_list ap)
388 {
389 junos_error_t *err;
391 char errbuf[1024];
392 const char *err_msg;
394 char prefix[1024];
396 int status = 0;
398 if (! junos)
399 return -1;
401 err = &junos->err;
403 err->type = type;
404 err->error = error;
406 vsnprintf(prefix, sizeof(prefix), msg_prefix, ap);
407 prefix[sizeof(prefix) - 1] = '\0';
409 switch (type) {
410 case JUNOS_OK:
411 snprintf(err->errmsg, sizeof(err->errmsg),
412 "i%s: success", prefix);
413 break;
414 case JUNOS_SYS_ERROR:
415 {
416 int failed = 0;
418 #if STRERROR_R_CHAR_P
419 errbuf[0] = '\0';
420 err_msg = strerror_r(error, errbuf, sizeof(errbuf));
421 if (! err_msg)
422 err_msg = errbuf;
423 if (! err_msg[0])
424 failed = 1;
425 #else /* STRERROR_R_CHAR_P */
426 failed = strerror_r(error, errbuf, sizeof(errbuf));
427 err_msg = errbuf;
428 #endif /* STRERROR_R_CHAR_P */
430 if (failed)
431 snprintf(err->errmsg, sizeof(err->errmsg),
432 "%s: system error #%i", prefix, error);
433 else
434 snprintf(err->errmsg, sizeof(err->errmsg),
435 "%s: %s", prefix, err_msg);
436 }
437 break;
438 case JUNOS_GAI_ERROR:
439 if (error == EAI_SYSTEM)
440 return junos_set_error(junos, JUNOS_SYS_ERROR, error,
441 "%s: network address translation failed", prefix);
443 err_msg = gai_strerror(error);
444 if (err_msg)
445 snprintf(err->errmsg, sizeof(err->errmsg),
446 "%s: %s", prefix, err_msg);
447 else
448 snprintf(err->errmsg, sizeof(err->errmsg),
449 "%s: network address translation error #%i",
450 prefix, error);
451 break;
452 case JUNOS_XML_ERROR:
453 {
454 xmlErrorPtr xml_err;
456 if (! junos->xml_ctx) /* don't touch any error information */
457 return 0;
459 xml_err = xmlCtxtGetLastError(junos->xml_ctx);
460 if (! xml_err)
461 return 0;
463 err->error = xml_err->code;
464 snprintf(err->errmsg, sizeof(err->errmsg),
465 "%s: %s", prefix, xml_err->message);
466 }
467 break;
468 case JUNOS_ACCESS_ERROR:
469 status = junos_set_ssh_error(err, junos->access,
470 "%s", prefix);
471 break;
472 default:
473 return -1;
474 break;
475 }
477 err->errmsg[sizeof(err->errmsg) - 1] = '\0';
478 dprintf("ERROR: %s\n", err->errmsg);
480 return status;
481 } /* junos_set_verror */
483 /* features */
485 unsigned int
486 libjunos_version(void)
487 {
488 return LIBJUNOS_VERSION;
489 } /* libjunos_version */
491 const char *
492 libjunos_version_string(void)
493 {
494 return LIBJUNOS_VERSION_STRING;
495 } /* libjunos_version_string */
497 const char *
498 libjunos_version_extra(void)
499 {
500 return LIBJUNOS_VERSION_EXTRA;
501 } /* libjunos_version_extra */
503 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */