Code

Renamed 'command' to 'method'.
[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_method(junos_t *junos, const char *name)
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)
355         if (! junos)
356                 return NULL;
357         return junos->err.errmsg;
358 } /* junos_get_errstr */
360 void
361 junos_clear_error(junos_t *junos)
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, ...)
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)
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)
488         return LIBJUNOS_VERSION;
489 } /* libjunos_version */
491 const char *
492 libjunos_version_string(void)
494         return LIBJUNOS_VERSION_STRING;
495 } /* libjunos_version_string */
497 const char *
498 libjunos_version_extra(void)
500         return LIBJUNOS_VERSION_EXTRA;
501 } /* libjunos_version_extra */
503 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */