2 * librouteros - src/main.c
3 * Copyright (C) 2009 Florian octo Forster
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; only version 2 of the License is applicable.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 * Florian octo Forster <octo at verplant.org>
22 #ifndef _ISOC99_SOURCE
23 # define _ISOC99_SOURCE
26 #ifndef _POSIX_C_SOURCE
27 # define _POSIX_C_SOURCE 200112L
41 #include <sys/types.h>
42 #include <sys/socket.h>
47 #include "routeros_api.h"
50 # define ros_debug(...) fprintf (stdout, __VA_ARGS__)
52 # define ros_debug(...) /**/
56 char *strdup (const char *);
61 struct ros_connection_s
68 unsigned int params_num;
76 struct ros_login_data_s
81 typedef struct ros_login_data_s ros_login_data_t;
86 static int read_exact (int fd, void *buffer, size_t buffer_size) /* {{{ */
91 if ((fd < 0) || (buffer == NULL))
99 while (have_bytes < buffer_size)
104 want_bytes = buffer_size - have_bytes;
106 status = read (fd, buffer_ptr, want_bytes);
115 assert (((size_t) status) <= want_bytes);
116 have_bytes += status;
117 buffer_ptr += status;
121 } /* }}} int read_exact */
123 static ros_reply_t *reply_alloc (void) /* {{{ */
127 r = malloc (sizeof (*r));
131 memset (r, 0, sizeof (*r));
137 } /* }}} ros_reply_s *reply_alloc */
139 static int reply_add_keyval (ros_reply_t *r, const char *key, /* {{{ */
144 tmp = realloc (r->keys, (r->params_num + 1) * sizeof (*tmp));
149 tmp = realloc (r->values, (r->params_num + 1) * sizeof (*tmp));
154 r->keys[r->params_num] = strdup (key);
155 if (r->keys[r->params_num] == NULL)
158 r->values[r->params_num] = strdup (val);
159 if (r->values[r->params_num] == NULL)
161 free (r->keys[r->params_num]);
162 r->keys[r->params_num] = NULL;
168 } /* }}} int reply_add_keyval */
171 static void reply_dump (const ros_reply_t *r) /* {{{ */
176 printf ("=== BEGIN REPLY ===\n"
179 (void *) r, r->status);
180 if (r->params_num > 0)
184 printf ("Arguments:\n");
185 for (i = 0; i < r->params_num; i++)
186 printf (" %3u: %s = %s\n", i, r->keys[i], r->values[i]);
189 printf ("Next: %p\n", (void *) r->next);
190 printf ("=== END REPLY ===\n");
192 reply_dump (r->next);
193 } /* }}} void reply_dump */
195 # define reply_dump(foo) /**/
198 static void reply_free (ros_reply_t *r) /* {{{ */
208 for (i = 0; i < r->params_num; i++)
220 } /* }}} void reply_free */
222 static int buffer_init (char **ret_buffer, size_t *ret_buffer_size) /* {{{ */
224 if ((ret_buffer == NULL) || (ret_buffer_size == NULL))
227 if (*ret_buffer_size < 1)
231 } /* }}} int buffer_init */
233 static int buffer_add (char **ret_buffer, size_t *ret_buffer_size, /* {{{ */
241 if ((ret_buffer == NULL) || (ret_buffer_size == NULL) || (string == NULL))
244 buffer = *ret_buffer;
245 buffer_size = *ret_buffer_size;
247 string_size = strlen (string);
248 if (string_size == 0)
251 if (string_size >= 0x10000000)
252 req_size = 5 + string_size;
253 else if (string_size >= 0x200000)
254 req_size = 4 + string_size;
255 else if (string_size >= 0x4000)
256 req_size = 3 + string_size;
257 else if (string_size >= 0x80)
258 req_size = 2 + string_size;
260 req_size = 1 + string_size;
262 if (*ret_buffer_size < req_size)
265 if (string_size >= 0x10000000)
268 buffer[1] = (string_size >> 24) & 0xff;
269 buffer[2] = (string_size >> 16) & 0xff;
270 buffer[3] = (string_size >> 8) & 0xff;
271 buffer[4] = (string_size ) & 0xff;
275 else if (string_size >= 0x200000)
277 buffer[0] = (string_size >> 24) & 0x1f;
279 buffer[1] = (string_size >> 16) & 0xff;
280 buffer[2] = (string_size >> 8) & 0xff;
281 buffer[3] = (string_size ) & 0xff;
285 else if (string_size >= 0x4000)
287 buffer[0] = (string_size >> 16) & 0x3f;
289 buffer[1] = (string_size >> 8) & 0xff;
290 buffer[2] = (string_size ) & 0xff;
294 else if (string_size >= 0x80)
296 buffer[0] = (string_size >> 8) & 0x7f;
298 buffer[1] = (string_size ) & 0xff;
302 else /* if (string_size <= 0x7f) */
304 buffer[0] = (char) string_size;
309 assert (buffer_size >= string_size);
310 memcpy (buffer, string, string_size);
311 buffer += string_size;
312 buffer_size -= string_size;
314 *ret_buffer = buffer;
315 *ret_buffer_size = buffer_size;
318 } /* }}} int buffer_add */
320 static int buffer_end (char **ret_buffer, size_t *ret_buffer_size) /* {{{ */
322 if ((ret_buffer == NULL) || (ret_buffer_size == NULL))
325 if (*ret_buffer_size < 1)
328 /* Add empty word. */
329 (*ret_buffer)[0] = 0;
331 (*ret_buffer_size)--;
334 } /* }}} int buffer_end */
336 static int send_command (ros_connection_t *c, /* {{{ */
338 size_t args_num, const char * const *args)
347 /* FIXME: For debugging only */
348 memset (buffer, 0, sizeof (buffer));
351 buffer_size = sizeof (buffer);
353 status = buffer_init (&buffer_ptr, &buffer_size);
357 ros_debug ("send_command: command = %s;\n", command);
358 status = buffer_add (&buffer_ptr, &buffer_size, command);
362 for (i = 0; i < args_num; i++)
367 ros_debug ("send_command: arg[%zu] = %s;\n", i, args[i]);
368 status = buffer_add (&buffer_ptr, &buffer_size, args[i]);
373 status = buffer_end (&buffer_ptr, &buffer_size);
378 buffer_size = sizeof (buffer) - buffer_size;
379 while (buffer_size > 0)
381 ssize_t bytes_written;
384 bytes_written = write (c->fd, buffer_ptr, buffer_size);
385 if (bytes_written < 0)
392 assert (((size_t) bytes_written) <= buffer_size);
394 buffer_ptr += bytes_written;
395 buffer_size -= bytes_written;
396 } /* while (buffer_size > 0) */
399 } /* }}} int send_command */
401 static int read_word (ros_connection_t *c, /* {{{ */
402 char *buffer, size_t *buffer_size)
405 uint8_t word_length[5];
408 if ((buffer == NULL) || (*buffer_size < 1))
411 /* read one byte from the socket */
412 status = read_exact (c->fd, word_length, 1);
416 /* Calculate `req_size' */
417 if (((unsigned char) word_length[0]) == 0xF0) /* {{{ */
419 status = read_exact (c->fd, &word_length[1], 4);
423 req_size = (word_length[1] << 24)
424 | (word_length[2] << 16)
425 | (word_length[3] << 8)
428 else if ((word_length[0] & 0xE0) == 0xE0)
430 status = read_exact (c->fd, &word_length[1], 3);
434 req_size = ((word_length[0] & 0x1F) << 24)
435 | (word_length[1] << 16)
436 | (word_length[2] << 8)
439 else if ((word_length[0] & 0xC0) == 0xC0)
441 status = read_exact (c->fd, &word_length[1], 2);
445 req_size = ((word_length[0] & 0x3F) << 16)
446 | (word_length[1] << 8)
449 else if ((word_length[0] & 0x80) == 0x80)
451 status = read_exact (c->fd, &word_length[1], 1);
455 req_size = ((word_length[0] & 0x7F) << 8)
458 else if ((word_length[0] & 0x80) == 0)
460 req_size = (size_t) word_length[0];
464 /* First nibble is `F' but second nibble is not `0'. */
468 if (*buffer_size < req_size)
471 /* Empty word. This ends a `sentence' and must therefore be valid. */
479 status = read_exact (c->fd, buffer, req_size);
482 *buffer_size = req_size;
485 } /* }}} int buffer_decode_next */
487 static ros_reply_t *receive_sentence (ros_connection_t *c) /* {{{ */
501 buffer_size = sizeof (buffer) - 1;
502 status = read_word (c, buffer, &buffer_size);
505 assert (buffer_size < sizeof (buffer));
506 buffer[buffer_size] = 0;
508 /* Empty word means end of reply */
509 if (buffer_size == 0)
512 if (buffer[0] == '!') /* {{{ */
514 if (r->status != NULL)
516 r->status = strdup (&buffer[1]);
517 if (r->status == NULL)
519 } /* }}} if (buffer[0] == '!') */
520 else if (buffer[0] == '=') /* {{{ */
522 char *key = &buffer[1];
526 val = strchr (key, '=');
529 fprintf (stderr, "Ignoring misformed word: %s\n", buffer);
535 reply_add_keyval (r, key, val);
536 } /* }}} if (buffer[0] == '=') */
539 ros_debug ("receive_sentence: Ignoring unknown word: %s\n", buffer);
543 if (r->status == NULL)
550 } /* }}} ros_reply_t *receive_sentence */
552 static ros_reply_t *receive_reply (ros_connection_t *c) /* {{{ */
564 tmp = receive_sentence (c);
579 if (strcmp ("done", tmp->status) == 0)
584 } /* }}} ros_reply_t *receive_reply */
586 static int create_socket (const char *node, const char *service) /* {{{ */
588 struct addrinfo ai_hint;
589 struct addrinfo *ai_list;
590 struct addrinfo *ai_ptr;
593 ros_debug ("create_socket (node = %s, service = %s);\n",
596 memset (&ai_hint, 0, sizeof (ai_hint));
598 ai_hint.ai_flags |= AI_ADDRCONFIG;
600 ai_hint.ai_family = AF_UNSPEC;
601 ai_hint.ai_socktype = SOCK_STREAM;
604 status = getaddrinfo (node, service, &ai_hint, &ai_list);
607 assert (ai_list != NULL);
609 for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
614 fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
615 ai_ptr->ai_protocol);
618 ros_debug ("create_socket: socket(2) failed.\n");
622 status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
625 ros_debug ("create_socket: connect(2) failed.\n");
633 freeaddrinfo (ai_list);
636 } /* }}} int create_socket */
638 static int login2_handler (ros_connection_t *c, const ros_reply_t *r, /* {{{ */
646 if (strcmp (r->status, "trap") == 0)
648 ros_debug ("login2_handler: Logging in failed: %s.\n",
649 ros_reply_param_val_by_key (r, "message"));
652 else if (strcmp (r->status, "done") != 0)
654 ros_debug ("login2_handler: Unexpected status: %s.\n", r->status);
659 } /* }}} int login2_handler */
661 static void hash_binary_to_hex (char hex[33], uint8_t binary[16]) /* {{{ */
665 for (i = 0; i < 16; i++)
668 snprintf (tmp, 3, "%02"PRIx8, binary[i]);
674 } /* }}} void hash_binary_to_hex */
676 static void hash_hex_to_binary (uint8_t binary[16], char hex[33]) /* {{{ */
680 for (i = 0; i < 16; i++)
685 tmp[1] = hex[2*i + 1];
688 binary[i] = (uint8_t) strtoul (tmp, /* endptr = */ NULL, /* base = */ 16);
690 } /* }}} void hash_hex_to_binary */
692 static void make_password_hash (char response_hex[33], /* {{{ */
693 const char *password, size_t password_length, char challenge_hex[33])
695 uint8_t challenge_bin[16];
696 uint8_t response_bin[16];
697 char data_buffer[password_length+17];
698 gcry_md_hd_t md_handle;
700 hash_hex_to_binary (challenge_bin, challenge_hex);
703 memcpy (&data_buffer[1], password, password_length);
704 memcpy (&data_buffer[1+password_length], challenge_bin, 16);
706 gcry_md_open (&md_handle, GCRY_MD_MD5, /* flags = */ 0);
707 gcry_md_write (md_handle, data_buffer, sizeof (data_buffer));
708 memcpy (response_bin, gcry_md_read (md_handle, GCRY_MD_MD5), 16);
709 gcry_md_close (md_handle);
711 hash_binary_to_hex (response_hex, response_bin);
712 } /* }}} void make_password_hash */
714 static int login_handler (ros_connection_t *c, const ros_reply_t *r, /* {{{ */
718 char challenge_hex[33];
719 char response_hex[33];
720 ros_login_data_t *login_data;
722 const char *params[2];
723 char param_name[1024];
724 char param_response[64];
729 /* The expected result looks like this:
732 * =ret=ebddd18303a54111e2dea05a92ab46b4
737 if (strcmp (r->status, "done") != 0)
739 ros_debug ("login_handler: Unexpected status: %s.\n", r->status);
743 login_data = user_data;
744 if (login_data == NULL)
747 ret = ros_reply_param_val_by_key (r, "ret");
750 ros_debug ("login_handler: Reply does not have parameter \"ret\".\n");
753 ros_debug ("login_handler: ret = %s;\n", ret);
755 if (strlen (ret) != 32)
757 ros_debug ("login_handler: Unexpected length of the \"ret\" argument.\n");
760 strcpy (challenge_hex, ret);
762 make_password_hash (response_hex,
763 login_data->password, strlen (login_data->password),
766 snprintf (param_name, sizeof (param_name), "=name=%s", login_data->username);
767 snprintf (param_response, sizeof (param_response),
768 "=response=00%s", response_hex);
769 params[0] = param_name;
770 params[1] = param_response;
772 return (ros_query (c, "/login", 2, params, login2_handler,
773 /* user data = */ NULL));
774 } /* }}} int login_handler */
779 ros_connection_t *ros_connect (const char *node, const char *service, /* {{{ */
780 const char *username, const char *password)
785 ros_login_data_t user_data;
787 if ((node == NULL) || (username == NULL) || (password == NULL))
790 fd = create_socket (node, (service != NULL) ? service : ROUTEROS_API_PORT);
794 c = malloc (sizeof (*c));
800 memset (c, 0, sizeof (*c));
804 user_data.username = username;
805 user_data.password = password;
806 status = ros_query (c, "/login", /* args num = */ 0, /* args = */ NULL,
807 login_handler, &user_data);
817 } /* }}} ros_connection_t *ros_connect */
819 int ros_disconnect (ros_connection_t *c) /* {{{ */
833 } /* }}} int ros_disconnect */
835 int ros_query (ros_connection_t *c, /* {{{ */
837 size_t args_num, const char * const *args,
838 ros_reply_handler_t handler, void *user_data)
843 status = send_command (c, command, args_num, args);
847 r = receive_reply (c);
851 /* Call the callback function with the data we received. */
852 status = (*handler) (c, r, user_data);
854 /* Free the allocated memory ... */
857 /* ... and return. */
859 } /* }}} int ros_query */
861 const ros_reply_t *ros_reply_next (const ros_reply_t *r) /* {{{ */
867 } /* }}} ros_reply_t *ros_reply_next */
869 int ros_reply_num (const ros_reply_t *r) /* {{{ */
872 const ros_reply_t *ptr;
875 for (ptr = r; ptr != NULL; ptr = ptr->next)
879 } /* }}} int ros_reply_num */
881 const char *ros_reply_status (const ros_reply_t *r) /* {{{ */
886 } /* }}} char *ros_reply_status */
888 const char *ros_reply_param_key_by_index (const ros_reply_t *r, /* {{{ */
894 if (index >= r->params_num)
897 return (r->keys[index]);
898 } /* }}} char *ros_reply_param_key_by_index */
900 const char *ros_reply_param_val_by_index (const ros_reply_t *r, /* {{{ */
906 if (index >= r->params_num)
909 return (r->values[index]);
910 } /* }}} char *ros_reply_param_key_by_index */
912 const char *ros_reply_param_val_by_key (const ros_reply_t *r, /* {{{ */
917 if ((r == NULL) || (key == NULL))
920 for (i = 0; i < r->params_num; i++)
921 if (strcmp (r->keys[i], key) == 0)
922 return (r->values[i]);
925 } /* }}} char *ros_reply_param_val_by_key */
927 int ros_version (void) /* {{{ */
929 return (ROS_VERSION);
930 } /* }}} int ros_version */
932 const char *ros_version_string (void) /* {{{ */
934 return (ROS_VERSION_STRING);
935 } /* }}} char *ros_version_string */
937 /* vim: set ts=2 sw=2 noet fdm=marker : */