Fix a couple of compiler warnings.
[routeros-api.git] / src / main.c
1 /**
2  * librouteros - src/main.c
3  * Copyright (C) 2009  Florian octo Forster
4  *
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.
8  *
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.
13  *
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
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #ifndef _ISOC99_SOURCE
23 # define _ISOC99_SOURCE
24 #endif
25
26 #ifndef _POSIX_C_SOURCE
27 # define _POSIX_C_SOURCE 200112L
28 #endif
29
30 #include "config.h"
31
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <unistd.h>
35 #include <stdint.h>
36 #include <inttypes.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <assert.h>
40
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 #include <netdb.h>
44
45 #include <gcrypt.h>
46
47 #include "routeros_api.h"
48
49 #if WITH_DEBUG
50 # define ros_debug(...) fprintf (stdout, __VA_ARGS__)
51 #else
52 # define ros_debug(...) /**/
53 #endif
54
55 /* FIXME */
56 char *strdup (const char *);
57
58 /*
59  * Private structures
60  */
61 struct ros_connection_s
62 {
63         int fd;
64 };
65
66 struct ros_reply_s
67 {
68         unsigned int params_num;
69         char *status;
70         char **keys;
71         char **values;
72
73         ros_reply_t *next;
74 };
75
76 struct ros_login_data_s
77 {
78         const char *username;
79         const char *password;
80 };
81 typedef struct ros_login_data_s ros_login_data_t;
82
83 /*
84  * Private functions
85  */
86 static int read_exact (int fd, void *buffer, size_t buffer_size) /* {{{ */
87 {
88         char *buffer_ptr;
89         size_t have_bytes;
90
91         if ((fd < 0) || (buffer == NULL))
92                 return (EINVAL);
93
94         if (buffer_size == 0)
95                 return (0);
96
97         buffer_ptr = buffer;
98         have_bytes = 0;
99         while (have_bytes < buffer_size)
100         {
101                 size_t want_bytes;
102                 ssize_t status;
103
104                 want_bytes = buffer_size - have_bytes;
105                 errno = 0;
106                 status = read (fd, buffer_ptr, want_bytes);
107                 if (status < 0)
108                 {
109                         if (errno == EAGAIN)
110                                 continue;
111                         else
112                                 return (status);
113                 }
114
115                 assert (((size_t) status) <= want_bytes);
116                 have_bytes += status;
117                 buffer_ptr += status;
118         }
119
120         return (0);
121 } /* }}} int read_exact */
122
123 static ros_reply_t *reply_alloc (void) /* {{{ */
124 {
125         ros_reply_t *r;
126
127         r = malloc (sizeof (*r));
128         if (r == NULL)
129                 return (NULL);
130
131         memset (r, 0, sizeof (*r));
132         r->keys = NULL;
133         r->values = NULL;
134         r->next = NULL;
135
136         return (r);
137 } /* }}} ros_reply_s *reply_alloc */
138
139 static int reply_add_keyval (ros_reply_t *r, const char *key, /* {{{ */
140                 const char *val)
141 {
142         char **tmp;
143
144         tmp = realloc (r->keys, (r->params_num + 1) * sizeof (*tmp));
145         if (tmp == NULL)
146                 return (ENOMEM);
147         r->keys = tmp;
148
149         tmp = realloc (r->values, (r->params_num + 1) * sizeof (*tmp));
150         if (tmp == NULL)
151                 return (ENOMEM);
152         r->values = tmp;
153
154         r->keys[r->params_num] = strdup (key);
155         if (r->keys[r->params_num] == NULL)
156                 return (ENOMEM);
157
158         r->values[r->params_num] = strdup (val);
159         if (r->values[r->params_num] == NULL)
160         {
161                 free (r->keys[r->params_num]);
162                 r->keys[r->params_num] = NULL;
163                 return (ENOMEM);
164         }
165
166         r->params_num++;
167         return (0);
168 } /* }}} int reply_add_keyval */
169
170 #if WITH_DEBUG
171 static void reply_dump (const ros_reply_t *r) /* {{{ */
172 {
173         if (r == NULL)
174                 return;
175
176         printf ("=== BEGIN REPLY ===\n"
177                         "Address: %p\n"
178                         "Status: %s\n",
179                         (void *) r, r->status);
180         if (r->params_num > 0)
181         {
182                 unsigned int i;
183
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]);
187         }
188         if (r->next != NULL)
189                 printf ("Next: %p\n", (void *) r->next);
190         printf ("=== END REPLY ===\n");
191
192         reply_dump (r->next);
193 } /* }}} void reply_dump */
194 #else
195 # define reply_dump(foo) /**/
196 #endif
197
198 static void reply_free (ros_reply_t *r) /* {{{ */
199 {
200         ros_reply_t *next;
201         unsigned int i;
202
203         if (r == NULL)
204                 return;
205
206         next = r->next;
207
208         for (i = 0; i < r->params_num; i++)
209         {
210                 free (r->keys[i]);
211                 free (r->values[i]);
212         }
213
214         free (r->keys);
215         free (r->values);
216
217         free (r);
218
219         reply_free (next);
220 } /* }}} void reply_free */
221
222 static int buffer_init (char **ret_buffer, size_t *ret_buffer_size) /* {{{ */
223 {
224         if ((ret_buffer == NULL) || (ret_buffer_size == NULL))
225                 return (EINVAL);
226
227         if (*ret_buffer_size < 1)
228                 return (EINVAL);
229
230         return (0);
231 } /* }}} int buffer_init */
232
233 static int buffer_add (char **ret_buffer, size_t *ret_buffer_size, /* {{{ */
234                 const char *string)
235 {
236         char *buffer;
237         size_t buffer_size;
238         size_t string_size;
239         size_t req_size;
240
241         if ((ret_buffer == NULL) || (ret_buffer_size == NULL) || (string == NULL))
242                 return (EINVAL);
243
244         buffer = *ret_buffer;
245         buffer_size = *ret_buffer_size;
246
247         string_size = strlen (string);
248         if (string_size == 0)
249                 return (EINVAL);
250
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;
259         else
260                 req_size = 1 + string_size;
261
262         if (*ret_buffer_size < req_size)
263                 return (ENOMEM);
264
265         if (string_size >= 0x10000000)
266         {
267                 buffer[0] = 0xF0;
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;
272                 buffer += 5;
273                 buffer_size -= 5;
274         }
275         else if (string_size >= 0x200000)
276         {
277                 buffer[0] = (string_size >> 24) & 0x1f;
278                 buffer[0] |= 0xE0;
279                 buffer[1] = (string_size >> 16) & 0xff;
280                 buffer[2] = (string_size >>  8) & 0xff;
281                 buffer[3] = (string_size      ) & 0xff;
282                 buffer += 4;
283                 buffer_size -= 4;
284         }
285         else if (string_size >= 0x4000)
286         {
287                 buffer[0] = (string_size >> 16) & 0x3f;
288                 buffer[0] |= 0xC0;
289                 buffer[1] = (string_size >>  8) & 0xff;
290                 buffer[2] = (string_size      ) & 0xff;
291                 buffer += 3;
292                 buffer_size -= 3;
293         }
294         else if (string_size >= 0x80)
295         {
296                 buffer[0] = (string_size >>  8) & 0x7f;
297                 buffer[0] |= 0x80;
298                 buffer[1] = (string_size      ) & 0xff;
299                 buffer += 2;
300                 buffer_size -= 2;
301         }
302         else /* if (string_size <= 0x7f) */
303         {
304                 buffer[0] = (char) string_size;
305                 buffer += 1;
306                 buffer_size -= 1;
307         }
308
309         assert (buffer_size >= string_size);
310         memcpy (buffer, string, string_size);
311         buffer += string_size;
312         buffer_size -= string_size;
313
314         *ret_buffer = buffer;
315         *ret_buffer_size = buffer_size;
316
317         return (0);
318 } /* }}} int buffer_add */
319
320 static int buffer_end (char **ret_buffer, size_t *ret_buffer_size) /* {{{ */
321 {
322         if ((ret_buffer == NULL) || (ret_buffer_size == NULL))
323                 return (EINVAL);
324
325         if (*ret_buffer_size < 1)
326                 return (EINVAL);
327
328         /* Add empty word. */
329         (*ret_buffer)[0] = 0;
330         (*ret_buffer)++;
331         (*ret_buffer_size)--;
332
333         return (0);
334 } /* }}} int buffer_end */
335
336 static int send_command (ros_connection_t *c, /* {{{ */
337                 const char *command,
338                 size_t args_num, const char * const *args)
339 {
340         char buffer[4096];
341         char *buffer_ptr;
342         size_t buffer_size;
343
344         size_t i;
345         int status;
346
347         /* FIXME: For debugging only */
348         memset (buffer, 0, sizeof (buffer));
349
350         buffer_ptr = buffer;
351         buffer_size = sizeof (buffer);
352
353         status = buffer_init (&buffer_ptr, &buffer_size);
354         if (status != 0)
355                 return (status);
356
357         ros_debug ("send_command: command = %s;\n", command);
358         status = buffer_add (&buffer_ptr, &buffer_size, command);
359         if (status != 0)
360                 return (status);
361
362         for (i = 0; i < args_num; i++)
363         {
364                 if (args[i] == NULL)
365                         return (EINVAL);
366
367                 ros_debug ("send_command: arg[%zu] = %s;\n", i, args[i]);
368                 status = buffer_add (&buffer_ptr, &buffer_size, args[i]);
369                 if (status != 0)
370                         return (status);
371         }
372
373         status = buffer_end (&buffer_ptr, &buffer_size);
374         if (status != 0)
375                 return (status);
376
377         buffer_ptr = buffer;
378         buffer_size = sizeof (buffer) - buffer_size;
379         while (buffer_size > 0)
380         {
381                 ssize_t bytes_written;
382
383                 errno = 0;
384                 bytes_written = write (c->fd, buffer_ptr, buffer_size);
385                 if (bytes_written < 0)
386                 {
387                         if (errno == EAGAIN)
388                                 continue;
389                         else
390                                 return (errno);
391                 }
392                 assert (((size_t) bytes_written) <= buffer_size);
393
394                 buffer_ptr += bytes_written;
395                 buffer_size -= bytes_written;
396         } /* while (buffer_size > 0) */
397
398         return (0);
399 } /* }}} int send_command */
400
401 static int read_word (ros_connection_t *c, /* {{{ */
402                 char *buffer, size_t *buffer_size)
403 {
404         size_t req_size;
405         uint8_t word_length[5];
406         int status;
407
408         if ((buffer == NULL) || (*buffer_size < 1))
409                 return (EINVAL);
410
411         /* read one byte from the socket */
412         status = read_exact (c->fd, word_length, 1);
413         if (status != 0)
414                 return (status);
415
416         /* Calculate `req_size' */
417         if (((unsigned char) word_length[0]) == 0xF0) /* {{{ */
418         {
419                 status = read_exact (c->fd, &word_length[1], 4);
420                 if (status != 0)
421                         return (status);
422
423                 req_size = (word_length[1] << 24)
424                         | (word_length[2] << 16)
425                         | (word_length[3] << 8)
426                         | word_length[4];
427         }
428         else if ((word_length[0] & 0xE0) == 0xE0)
429         {
430                 status = read_exact (c->fd, &word_length[1], 3);
431                 if (status != 0)
432                         return (status);
433
434                 req_size = ((word_length[0] & 0x1F) << 24)
435                         | (word_length[1] << 16)
436                         | (word_length[2] << 8)
437                         | word_length[3];
438         }
439         else if ((word_length[0] & 0xC0) == 0xC0)
440         {
441                 status = read_exact (c->fd, &word_length[1], 2);
442                 if (status != 0)
443                         return (status);
444
445                 req_size = ((word_length[0] & 0x3F) << 16)
446                         | (word_length[1] << 8)
447                         | word_length[2];
448         }
449         else if ((word_length[0] & 0x80) == 0x80)
450         {
451                 status = read_exact (c->fd, &word_length[1], 1);
452                 if (status != 0)
453                         return (status);
454
455                 req_size = ((word_length[0] & 0x7F) << 8)
456                         | word_length[1];
457         }
458         else if ((word_length[0] & 0x80) == 0)
459         {
460                 req_size = (size_t) word_length[0];
461         }
462         else
463         {
464                 /* First nibble is `F' but second nibble is not `0'. */
465                 return (EPROTO);
466         } /* }}} */
467
468         if (*buffer_size < req_size)
469                 return (ENOMEM);
470
471         /* Empty word. This ends a `sentence' and must therefore be valid. */
472         if (req_size == 0)
473         {
474                 buffer[0] = 0;
475                 *buffer_size = 0;
476                 return (0);
477         }
478
479         status = read_exact (c->fd, buffer, req_size);
480         if (status != 0)
481                 return (status);
482         *buffer_size = req_size;
483
484         return (0);
485 } /* }}} int buffer_decode_next */
486
487 static ros_reply_t *receive_sentence (ros_connection_t *c) /* {{{ */
488 {
489         char buffer[4096];
490         size_t buffer_size;
491         int status;
492
493         ros_reply_t *r;
494
495         r = reply_alloc ();
496         if (r == NULL)
497                 return (NULL);
498
499         while (42)
500         {
501                 buffer_size = sizeof (buffer) - 1;
502                 status = read_word (c, buffer, &buffer_size);
503                 if (status != 0)
504                         break;
505                 assert (buffer_size < sizeof (buffer));
506                 buffer[buffer_size] = 0;
507
508                 /* Empty word means end of reply */
509                 if (buffer_size == 0)
510                         break;
511
512                 if (buffer[0] == '!') /* {{{ */
513                 {
514                         if (r->status != NULL)
515                                 free (r->status);
516                         r->status = strdup (&buffer[1]);
517                         if (r->status == NULL)
518                                 break;
519                 } /* }}} if (buffer[0] == '!') */
520                 else if (buffer[0] == '=') /* {{{ */
521                 {
522                         char *key = &buffer[1];
523                         char *val;
524
525                         key = &buffer[1];
526                         val = strchr (key, '=');
527                         if (val == NULL)
528                         {
529                                 fprintf (stderr, "Ignoring misformed word: %s\n", buffer);
530                                 continue;
531                         }
532                         *val = 0;
533                         val++;
534
535                         reply_add_keyval (r, key, val);
536                 } /* }}} if (buffer[0] == '=') */
537                 else
538                 {
539                         ros_debug ("receive_sentence: Ignoring unknown word: %s\n", buffer);
540                 }
541         } /* while (42) */
542         
543         if (r->status == NULL)
544         {
545                 reply_free (r);
546                 return (NULL);
547         }
548
549         return (r);
550 } /* }}} ros_reply_t *receive_sentence */
551
552 static ros_reply_t *receive_reply (ros_connection_t *c) /* {{{ */
553 {
554         ros_reply_t *head;
555         ros_reply_t *tail;
556
557         head = NULL;
558         tail = NULL;
559
560         while (42)
561         {
562                 ros_reply_t *tmp;
563
564                 tmp = receive_sentence (c);
565                 if (tmp == NULL)
566                         break;
567
568                 if (tail == NULL)
569                 {
570                         head = tmp;
571                         tail = tmp;
572                 }
573                 else
574                 {
575                         tail->next = tmp;
576                         tail = tmp;
577                 }
578
579                 if (strcmp ("done", tmp->status) == 0)
580                         break;
581         } /* while (42) */
582         
583         return (head);
584 } /* }}} ros_reply_t *receive_reply */
585
586 static int create_socket (const char *node, const char *service) /* {{{ */
587 {
588         struct addrinfo  ai_hint;
589         struct addrinfo *ai_list;
590         struct addrinfo *ai_ptr;
591         int status;
592
593         ros_debug ("create_socket (node = %s, service = %s);\n",
594                         node, service);
595
596         memset (&ai_hint, 0, sizeof (ai_hint));
597 #ifdef AI_ADDRCONFIG
598         ai_hint.ai_flags |= AI_ADDRCONFIG;
599 #endif
600         ai_hint.ai_family = AF_UNSPEC;
601         ai_hint.ai_socktype = SOCK_STREAM;
602
603         ai_list = NULL;
604         status = getaddrinfo (node, service, &ai_hint, &ai_list);
605         if (status != 0)
606                 return (-1);
607         assert (ai_list != NULL);
608
609         for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
610         {
611                 int fd;
612                 int status;
613
614                 fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
615                                 ai_ptr->ai_protocol);
616                 if (fd < 0)
617                 {
618                         ros_debug ("create_socket: socket(2) failed.\n");
619                         continue;
620                 }
621
622                 status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
623                 if (status != 0)
624                 {
625                         ros_debug ("create_socket: connect(2) failed.\n");
626                         close (fd);
627                         continue;
628                 }
629
630                 return (fd);
631         }
632
633         freeaddrinfo (ai_list);
634
635         return (-1);
636 } /* }}} int create_socket */
637
638 static int login2_handler (ros_connection_t *c, const ros_reply_t *r, /* {{{ */
639                 void *user_data)
640 {
641         if (r == NULL)
642                 return (EINVAL);
643
644         reply_dump (r);
645
646         if (strcmp (r->status, "trap") == 0)
647         {
648                 ros_debug ("login2_handler: Logging in failed: %s.\n",
649                                 ros_reply_param_val_by_key (r, "message"));
650                 return (EACCES);
651         }
652         else if (strcmp (r->status, "done") != 0)
653         {
654                 ros_debug ("login2_handler: Unexpected status: %s.\n", r->status);
655                 return (EPROTO);
656         }
657
658         return (0);
659 } /* }}} int login2_handler */
660
661 static void hash_binary_to_hex (char hex[33], uint8_t binary[16]) /* {{{ */
662 {
663         int i;
664
665         for (i = 0; i < 16; i++)
666         {
667                 char tmp[3];
668                 snprintf (tmp, 3, "%02"PRIx8, binary[i]);
669                 tmp[2] = 0;
670                 hex[2*i] = tmp[0];
671                 hex[2*i+1] = tmp[1];
672         }
673         hex[32] = 0;
674 } /* }}} void hash_binary_to_hex */
675
676 static void hash_hex_to_binary (uint8_t binary[16], char hex[33]) /* {{{ */
677 {
678         int i;
679
680         for (i = 0; i < 16; i++)
681         {
682                 char tmp[3];
683
684                 tmp[0] = hex[2*i];
685                 tmp[1] = hex[2*i + 1];
686                 tmp[2] = 0;
687
688                 binary[i] = (uint8_t) strtoul (tmp, /* endptr = */ NULL, /* base = */ 16);
689         }
690 } /* }}} void hash_hex_to_binary */
691
692 static void make_password_hash (char response_hex[33], /* {{{ */
693                 const char *password, size_t password_length, char challenge_hex[33])
694 {
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;
699
700         hash_hex_to_binary (challenge_bin, challenge_hex);
701
702         data_buffer[0] = 0;
703         memcpy (&data_buffer[1], password, password_length);
704         memcpy (&data_buffer[1+password_length], challenge_bin, 16);
705
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);
710
711         hash_binary_to_hex (response_hex, response_bin);
712 } /* }}} void make_password_hash */
713
714 static int login_handler (ros_connection_t *c, const ros_reply_t *r, /* {{{ */
715                 void *user_data)
716 {
717         const char *ret;
718         char challenge_hex[33];
719         char response_hex[33];
720         ros_login_data_t *login_data;
721
722         const char *params[2];
723         char param_name[1024];
724         char param_response[64];
725
726         if (r == NULL)
727                 return (EINVAL);
728
729         /* The expected result looks like this:
730          * -- 8< --
731          *  !done 
732          *  =ret=ebddd18303a54111e2dea05a92ab46b4
733          * -- >8 --
734          */
735         reply_dump (r);
736
737         if (strcmp (r->status, "done") != 0)
738         {
739                 ros_debug ("login_handler: Unexpected status: %s.\n", r->status);
740                 return (EPROTO);
741         }
742
743         login_data = user_data;
744         if (login_data == NULL)
745                 return (EINVAL);
746
747         ret = ros_reply_param_val_by_key (r, "ret");
748         if (ret == NULL)
749         {
750                 ros_debug ("login_handler: Reply does not have parameter \"ret\".\n");
751                 return (EPROTO);
752         }
753         ros_debug ("login_handler: ret = %s;\n", ret);
754
755         if (strlen (ret) != 32)
756         {
757                 ros_debug ("login_handler: Unexpected length of the \"ret\" argument.\n");
758                 return (EPROTO);
759         }
760         strcpy (challenge_hex, ret);
761
762         make_password_hash (response_hex, 
763                         login_data->password, strlen (login_data->password),
764                         challenge_hex);
765
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;
771
772         return (ros_query (c, "/login", 2, params, login2_handler,
773                                 /* user data = */ NULL));
774 } /* }}} int login_handler */
775
776 /*
777  * Public functions
778  */
779 ros_connection_t *ros_connect (const char *node, const char *service, /* {{{ */
780                 const char *username, const char *password)
781 {
782         int fd;
783         ros_connection_t *c;
784         int status;
785         ros_login_data_t user_data;
786
787         if ((node == NULL) || (username == NULL) || (password == NULL))
788                 return (NULL);
789
790         fd = create_socket (node, (service != NULL) ? service : ROUTEROS_API_PORT);
791         if (fd < 0)
792                 return (NULL);
793
794         c = malloc (sizeof (*c));
795         if (c == NULL)
796         {
797                 close (fd);
798                 return (NULL);
799         }
800         memset (c, 0, sizeof (*c));
801
802         c->fd = fd;
803
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);
808
809         if (status != 0)
810         {
811                 ros_disconnect (c);
812                 errno = status;
813                 return (NULL);
814         }
815
816         return (c);
817 } /* }}} ros_connection_t *ros_connect */
818
819 int ros_disconnect (ros_connection_t *c) /* {{{ */
820 {
821         if (c == NULL)
822                 return (EINVAL);
823
824         if (c->fd >= 0)
825         {
826                 close (c->fd);
827                 c->fd = -1;
828         }
829
830         free (c);
831
832         return (0);
833 } /* }}} int ros_disconnect */
834
835 int ros_query (ros_connection_t *c, /* {{{ */
836                 const char *command,
837                 size_t args_num, const char * const *args,
838                 ros_reply_handler_t handler, void *user_data)
839 {
840         int status;
841         ros_reply_t *r;
842
843         status = send_command (c, command, args_num, args);
844         if (status != 0)
845                 return (status);
846
847         r = receive_reply (c);
848         if (r == NULL)
849                 return (EPROTO);
850
851         /* Call the callback function with the data we received. */
852         status = (*handler) (c, r, user_data);
853
854         /* Free the allocated memory ... */
855         reply_free (r);
856
857         /* ... and return. */
858         return (status);
859 } /* }}} int ros_query */
860
861 const ros_reply_t *ros_reply_next (const ros_reply_t *r) /* {{{ */
862 {
863         if (r == NULL)
864                 return (NULL);
865
866         return (r->next);
867 } /* }}} ros_reply_t *ros_reply_next */
868
869 int ros_reply_num (const ros_reply_t *r) /* {{{ */
870 {
871         int ret;
872         const ros_reply_t *ptr;
873
874         ret = 0;
875         for (ptr = r; ptr != NULL; ptr = ptr->next)
876                 ret++;
877
878         return (ret);
879 } /* }}} int ros_reply_num */
880
881 const char *ros_reply_status (const ros_reply_t *r) /* {{{ */
882 {
883         if (r == NULL)
884                 return (NULL);
885         return (r->status);
886 } /* }}} char *ros_reply_status */
887
888 const char *ros_reply_param_key_by_index (const ros_reply_t *r, /* {{{ */
889                 unsigned int index)
890 {
891         if (r == NULL)
892                 return (NULL);
893
894         if (index >= r->params_num)
895                 return (NULL);
896
897         return (r->keys[index]);
898 } /* }}} char *ros_reply_param_key_by_index */
899
900 const char *ros_reply_param_val_by_index (const ros_reply_t *r, /* {{{ */
901                 unsigned int index)
902 {
903         if (r == NULL)
904                 return (NULL);
905
906         if (index >= r->params_num)
907                 return (NULL);
908
909         return (r->values[index]);
910 } /* }}} char *ros_reply_param_key_by_index */
911
912 const char *ros_reply_param_val_by_key (const ros_reply_t *r, /* {{{ */
913                 const char *key)
914 {
915         unsigned int i;
916
917         if ((r == NULL) || (key == NULL))
918                 return (NULL);
919
920         for (i = 0; i < r->params_num; i++)
921                 if (strcmp (r->keys[i], key) == 0)
922                         return (r->values[i]);
923
924         return (NULL);
925 } /* }}} char *ros_reply_param_val_by_key */
926
927 int ros_version (void) /* {{{ */
928 {
929         return (ROS_VERSION);
930 } /* }}} int ros_version */
931
932 const char *ros_version_string (void) /* {{{ */
933 {
934         return (ROS_VERSION_STRING);
935 } /* }}} char *ros_version_string */
936
937 /* vim: set ts=2 sw=2 noet fdm=marker : */