Fix segfault if http/telnet port is not available
[olsrd.git] / src / olsr_comport.c
1 /*
2  * The olsr.org Optimized Link-State Routing daemon(olsrd)
3  * Copyright (c) 2004-2009, the olsr.org team - see HISTORY file
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  *
10  * * Redistributions of source code must retain the above copyright
11  *   notice, this list of conditions and the following disclaimer.
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in
14  *   the documentation and/or other materials provided with the
15  *   distribution.
16  * * Neither the name of olsr.org, olsrd nor the names of its
17  *   contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *
33  * Visit http://www.olsr.org for more information.
34  *
35  * If you find this software useful feel free to make a donation
36  * to the project. For more information see the website or contact
37  * the copyright holders.
38  *
39  */
40 #include <ctype.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45 #include <errno.h>
46 #ifdef WIN32
47 #include <io.h>
48 #else
49 #include <netdb.h>
50 #endif
51
52 #include "defs.h"
53 #include "olsr_logging.h"
54 #include "olsr_cfg.h"
55 #include "scheduler.h"
56 #include "olsr_cookie.h"
57 #include "common/autobuf.h"
58 #include "common/avl.h"
59 #include "common/list.h"
60 #include "ipcalc.h"
61 #include "olsr.h"
62 #include "olsr_comport_http.h"
63 #include "olsr_comport_txt.h"
64 #include "olsr_comport.h"
65
66 #define HTTP_TIMEOUT  30000
67 #define TXT_TIMEOUT 60000
68
69 #define MAX_HTTP_PARA 10
70 #define COMPORT_MAX_INPUTBUFFER 65536
71
72 struct list_node olsr_comport_head;
73
74 /* server socket */
75 static int comsocket_http = 0;
76 static int comsocket_txt = 0;
77
78 static struct olsr_cookie_info *connection_cookie;
79 static struct olsr_cookie_info *connection_timeout;
80
81 /* counter for open connections */
82 static int connection_http_count;
83 static int connection_txt_count;
84
85 static int olsr_com_openport(int port);
86
87 static void olsr_com_parse_request(int fd, void *data, unsigned int flags);
88 static void olsr_com_parse_connection(int fd, void *data, unsigned int flags);
89 static void olsr_com_cleanup_session(struct comport_connection *con);
90 static void olsr_com_parse_http(struct comport_connection *con,
91     unsigned int flags);
92 static void olsr_com_parse_txt(struct comport_connection *con,
93     unsigned int flags);
94
95 static void olsr_com_timeout_handler(void *);
96
97 void olsr_com_init(bool failfast) {
98   connection_cookie = olsr_alloc_cookie("comport connections",
99       OLSR_COOKIE_TYPE_MEMORY);
100   olsr_cookie_set_memory_size(connection_cookie,
101       sizeof(struct comport_connection));
102
103   connection_timeout = olsr_alloc_cookie("comport timout",
104       OLSR_COOKIE_TYPE_TIMER);
105
106   connection_http_count = 0;
107   connection_txt_count = 0;
108
109   list_head_init(&olsr_comport_head);
110
111   olsr_com_init_http();
112   olsr_com_init_txt();
113
114   if (olsr_cnf->comport_http > 0) {
115     if ((comsocket_http = olsr_com_openport(olsr_cnf->comport_http)) == -1) {
116       if (failfast) {
117         olsr_exit(1);
118       }
119     }
120     else {
121       add_olsr_socket(comsocket_http, &olsr_com_parse_request, NULL, NULL,
122           SP_PR_READ);
123     }
124   }
125   if (olsr_cnf->comport_txt > 0) {
126     if ((comsocket_txt = olsr_com_openport(olsr_cnf->comport_txt)) == -1) {
127       if (failfast) {
128         olsr_exit(1);
129       }
130     }
131     else {
132       add_olsr_socket(comsocket_txt, &olsr_com_parse_request, NULL, NULL,
133           SP_PR_READ);
134     }
135   }
136 }
137
138 void olsr_com_destroy(void) {
139   while (!list_is_empty(&olsr_comport_head)) {
140     struct comport_connection *con;
141
142     if (NULL != (con = comport_node2con(olsr_comport_head.next))) {
143       olsr_com_cleanup_session(con);
144     }
145   }
146
147   olsr_com_destroy_http();
148   olsr_com_destroy_txt();
149 }
150
151 void olsr_com_activate_output(struct comport_connection *con) {
152   enable_olsr_socket(con->fd, &olsr_com_parse_connection, NULL, SP_PR_WRITE);
153 }
154
155 static int olsr_com_openport(int port) {
156   struct sockaddr_storage sst;
157   uint32_t yes = 1;
158   socklen_t addrlen;
159
160 #if !defined REMOVE_LOG_WARN
161   char ipchar = olsr_cnf->ip_version == AF_INET ? '4' : '6';
162 #endif
163
164   /* Init ipc socket */
165   int s = socket(olsr_cnf->ip_version, SOCK_STREAM, 0);
166   if (s == -1) {
167     OLSR_WARN(LOG_COMPORT, "Cannot open %d com-socket for IPv%c: %s\n", port, ipchar, strerror(errno));
168     return -1;
169   }
170
171   if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof(yes)) < 0) {
172     OLSR_WARN(LOG_COMPORT, "Com-port %d SO_REUSEADDR for IPv%c failed: %s\n", port, ipchar, strerror(errno));
173     CLOSESOCKET(s);
174     return -1;
175   }
176
177   /* Bind the socket */
178
179   /* complete the socket structure */
180   memset(&sst, 0, sizeof(sst));
181   if (olsr_cnf->ip_version == AF_INET) {
182     struct sockaddr_in *addr4 = (struct sockaddr_in *) &sst;
183     addr4->sin_family = AF_INET;
184     addrlen = sizeof(*addr4);
185 #ifdef SIN6_LEN
186     addr4->sin_len = addrlen;
187 #endif
188     addr4->sin_addr.s_addr = INADDR_ANY;
189     addr4->sin_port = htons(port);
190   } else {
191     struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &sst;
192     addr6->sin6_family = AF_INET6;
193     addrlen = sizeof(*addr6);
194 #ifdef SIN6_LEN
195     addr6->sin6_len = addrlen;
196 #endif
197     addr6->sin6_addr = in6addr_any;
198     addr6->sin6_port = htons(port);
199   }
200
201   /* bind the socket to the port number */
202   if (bind(s, (struct sockaddr *) &sst, addrlen) == -1) {
203     OLSR_WARN(LOG_COMPORT, "Com-port %d bind failed for IPv%c: %s\n", port, ipchar, strerror(errno));
204     CLOSESOCKET(s);
205     return -1;
206   }
207
208   /* show that we are willing to listen */
209   if (listen(s, 1) == -1) {
210     OLSR_WARN(LOG_COMPORT, "Com-port %d listen for IPv%c failed %s\n", port, ipchar, strerror(errno));
211     CLOSESOCKET(s);
212     return -1;
213   }
214
215   return s;
216 }
217
218 static void olsr_com_parse_request(int fd, void *data __attribute__ ((unused)), unsigned int flags __attribute__ ((unused))) {
219   struct comport_connection *con;
220   struct sockaddr_storage addr;
221   socklen_t addrlen;
222   int sock;
223 #if !defined REMOVE_LOG_DEBUG
224   struct ipaddr_str buf;
225 #endif
226
227   addrlen = sizeof(addr);
228   sock = accept(fd, (struct sockaddr *) &addr, &addrlen);
229   if (sock < 0) {
230     return;
231   }
232
233   con = olsr_cookie_malloc(connection_cookie);
234   abuf_init(&con->in, 1024);
235   abuf_init(&con->out, 0);
236
237   con->is_http = fd == comsocket_http;
238   con->fd = sock;
239
240   if (olsr_cnf->ip_version == AF_INET6) {
241     struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &addr;
242     con->addr.v6 = addr6->sin6_addr;
243   } else {
244     struct sockaddr_in *addr4 = (struct sockaddr_in *) &addr;
245     con->addr.v4 = addr4->sin_addr;
246   }
247
248   if (con->is_http) {
249     if (connection_http_count < olsr_cnf->comport_http_limit) {
250       connection_http_count++;
251       con->state = HTTP_LOGIN;
252       con->send_as = PLAIN;
253       con->timeout_value = HTTP_TIMEOUT;
254     } else {
255       con->state = SEND_AND_QUIT;
256       con->send_as = HTTP_503_SERVICE_UNAVAILABLE;
257     }
258   } else { /* !con->is_http */
259     if (connection_txt_count < olsr_cnf->comport_txt_limit) {
260       connection_txt_count++;
261       con->state = INTERACTIVE;
262       con->send_as = PLAIN;
263       con->timeout_value = TXT_TIMEOUT;
264     } else {
265       abuf_puts(&con->out, "Too many txt connections, sorry...\n");
266       con->state = SEND_AND_QUIT;
267       con->send_as = PLAIN;
268     }
269   }
270
271   OLSR_DEBUG(LOG_COMPORT, "Got connection through socket %d from %s.\n",
272       sock, olsr_ip_to_string(&buf, &con->addr));
273
274   con->timeout = olsr_start_timer(con->timeout_value, 0, false,
275       &olsr_com_timeout_handler, con, connection_timeout);
276
277   add_olsr_socket(sock, &olsr_com_parse_connection, NULL, con, SP_PR_READ
278       | SP_PR_WRITE);
279
280   list_add_after(&olsr_comport_head, &con->node);
281 }
282
283 static void olsr_com_cleanup_session(struct comport_connection *con) {
284   if (con->is_http) {
285     connection_http_count--;
286   } else {
287     connection_txt_count--;
288   }
289
290   list_remove(&con->node);
291
292   if (con->stop_handler) {
293     con->stop_handler(con);
294   }
295   remove_olsr_socket(con->fd, &olsr_com_parse_connection, NULL);
296   CLOSESOCKET(con->fd);
297
298   abuf_free(&con->in);
299   abuf_free(&con->out);
300
301   olsr_cookie_free(connection_cookie, con);
302 }
303
304 static void olsr_com_timeout_handler(void *data) {
305   struct comport_connection *con = data;
306   olsr_com_cleanup_session(con);
307 }
308
309 static void olsr_com_parse_connection(int fd, void *data, unsigned int flags) {
310   struct comport_connection *con = data;
311 #if !defined(REMOVE_LOG_WARN)
312   struct ipaddr_str buf;
313 #endif
314
315   OLSR_DEBUG(LOG_COMPORT, "Parsing connection of socket %d\n", fd);
316   /* read data if necessary */
317   if (flags & SP_PR_READ) {
318     char buffer[1024];
319     int len;
320
321     len = recv(fd, buffer, sizeof(buffer), 0);
322     if (len > 0) {
323       OLSR_DEBUG(LOG_COMPORT, "  recv returned %d\n", len);
324       if (con->state != SEND_AND_QUIT) {
325         abuf_memcpy(&con->in, buffer, len);
326       }
327
328       if (con->in.len > COMPORT_MAX_INPUTBUFFER) {
329         if (con->state == INTERACTIVE) {
330           abuf_puts(&con->out, "Sorry, input buffer overflow...\n");
331         } else if (con->state == HTTP_LOGIN) {
332           con->send_as = HTTP_413_REQUEST_TOO_LARGE;
333         }
334         con->state = SEND_AND_QUIT;
335       }
336     } else if (len < 0) {
337       OLSR_WARN(LOG_COMPORT, "Error while reading from communication stream with %s: %s\n",
338           olsr_ip_to_string(&buf, &con->addr), strerror(errno));
339       con->state = CLEANUP;
340     }
341   }
342
343   switch (con->state) {
344     case HTTP_LOGIN:
345       olsr_com_parse_http(con, flags);
346       break;
347     case INTERACTIVE:
348       olsr_com_parse_txt(con, flags);
349       break;
350     default:
351       break;
352   }
353
354   /* maybe we have to create an error message */
355   if (con->out.len == 0 && con->state == SEND_AND_QUIT && con->send_as != PLAIN
356       && con->send_as != HTTP_PLAIN && con->send_as != HTTP_200_OK) {
357     olsr_com_create_httperror(con);
358   }
359
360   /* send data if necessary */
361   if (con->out.len > 0) {
362     if (con->state == SEND_AND_QUIT && con->send_as != PLAIN) {
363       /* create header */
364       olsr_com_build_httpheader(con);
365       con->send_as = PLAIN;
366     }
367
368     if (flags & SP_PR_WRITE) {
369       int len;
370
371       len = send(fd, con->out.buf, con->out.len, 0);
372       if (len > 0) {
373         OLSR_DEBUG(LOG_COMPORT, "  send returned %d\n", len);
374         abuf_pull(&con->out, len);
375       } else if (len < 0) {
376         OLSR_WARN(LOG_COMPORT, "Error while writing to communication stream with %s: %s\n",
377             olsr_ip_to_string(&buf, &con->addr), strerror(errno));
378         con->state = CLEANUP;
379       }
380     } else {
381       OLSR_DEBUG(LOG_COMPORT, "  activating output in scheduler\n");
382       enable_olsr_socket(fd, &olsr_com_parse_connection, NULL, SP_PR_WRITE);
383     }
384   }
385   if (con->out.len == 0) {
386     OLSR_DEBUG(LOG_COMPORT, "  deactivating output in scheduler\n");
387     disable_olsr_socket(fd, &olsr_com_parse_connection, NULL, SP_PR_WRITE);
388     if (con->state == SEND_AND_QUIT) {
389       con->state = CLEANUP;
390     }
391   }
392
393   /* end of connection ? */
394   if (con->state == CLEANUP) {
395     OLSR_DEBUG(LOG_COMPORT, "  cleanup\n");
396     /* clean up connection by calling timeout directly */
397     olsr_stop_timer(con->timeout);
398     olsr_com_cleanup_session(con);
399   }
400   return;
401 }
402
403 static void olsr_com_parse_http(struct comport_connection *con,
404     unsigned int flags  __attribute__ ((unused))) {
405   char *para_keyvalue[MAX_HTTP_PARA * 2] = { NULL }, *para = NULL, *str = NULL;
406   int para_count = 0;
407   char req_type[11] = { 0 };
408   char filename[252] = { 0 };
409   char filename_copy[252] = { 0 };
410   char http_version[11] = { 0 };
411
412   int idx = 0;
413   int i = 0;
414
415   /*
416    * find end of http header, might be useful for POST to keep the index !
417    * (implemented as a finite element automaton)
418    */
419
420   while (i < 5) {
421     switch (con->in.buf[idx++]) {
422       case '\0':
423         i = 6;
424         break;
425       case '\r':
426         i = (i == 3) ? 4 : 2;
427         break;
428       case '\n':
429         if (i == 1 || i == 4) {
430           i = 5;
431         } else if (i == 2) {
432           i = 3;
433         } else {
434           i = 1;
435         }
436         break;
437       default:
438         i = 0;
439         break;
440     }
441   }
442
443   if (i != 5) {
444     OLSR_DEBUG(LOG_COMPORT, "  need end of http header, still waiting...\n");
445     return;
446   }
447
448   /* got http header */
449   if (sscanf(con->in.buf, "%10s %250s %10s\n", req_type, filename, http_version)
450       != 3 || (strcmp(http_version, "HTTP/1.1") != 0 && strcmp(http_version,
451       "HTTP/1.0") != 0)) {
452     con->send_as = HTTP_400_BAD_REQ;
453     con->state = SEND_AND_QUIT;
454     return;
455   }
456
457   OLSR_DEBUG(LOG_COMPORT, "HTTP Request: %s %s %s\n", req_type, filename, http_version);
458
459   /* store a copy for the http_handlers */
460   strcpy(filename_copy, filename);
461   if (strcmp(req_type, "POST") == 0) {
462     /* load the rest of the header for POST commands */
463     char *lengthField = strstr(con->in.buf, "\nContent-Length:");
464     int clen = 0;
465
466     if (lengthField == NULL) {
467       con->send_as = HTTP_400_BAD_REQ;
468       con->state = SEND_AND_QUIT;
469       return;
470     }
471
472     sscanf(lengthField, "%*s %d\n", &clen);
473     if (con->in.len < idx + clen) {
474       /* we still need more data */
475       return;
476     }
477     para = &con->in.buf[idx];
478   }
479
480   /* strip the URL marker away */
481   str = strchr(filename, '#');
482   if (str) {
483     *str = 0;
484   }
485
486   /* we have everything to process the http request */
487   con->state = SEND_AND_QUIT;
488   olsr_com_decode_url(filename);
489
490   if (strcmp(req_type, "GET") == 0) {
491     /* HTTP-GET request */
492     para = strchr(filename, '?');
493     if (para != NULL) {
494       *para++ = 0;
495     }
496   } else if (strcmp(req_type, "POST") != 0) {
497     con->send_as = HTTP_501_NOT_IMPLEMENTED;
498     return;
499   }
500
501   /* handle HTTP GET & POST including parameters */
502   while (para && para_count < MAX_HTTP_PARA * 2) {
503     /* split the string at the next '=' (the key/value splitter) */
504     str = strchr(para, '=');
505     if (!str) {
506       break;
507     }
508     *str++ = 0;
509
510     /* we have null terminated key at *para. Now decode the key */
511     para_keyvalue[para_count++] = para;
512
513     /* split the string at the next '&' (the splitter of multiple key/value pairs */
514     para = strchr(str, '&');
515     if (para) {
516       *para++ = 0;
517     }
518
519     /* we have a null terminated value at *str, Now decode it */
520     para_keyvalue[para_count++] = str;
521   }
522
523   /* create body */
524   i = strlen(filename);
525
526   /*
527    * add a '/' at the end if it's not there to detect
528    *  paths without terminating '/' from the browser
529    */
530   if (filename[i - 1] != '/') {
531     strcat(filename, "/");
532   }
533
534   while (i > 0) {
535     if (olsr_com_handle_htmlsite(con, filename, filename_copy, para_count >> 1,
536         para_keyvalue)) {
537       return;
538     }
539
540     /* try to find a handler for a path prefix */
541     if (i > 0 && filename[i] == '/') {
542       filename[i--] = 0;
543     }
544     else {
545       do {
546         filename[i--] = 0;
547       } while (i > 0 && filename[i] != '/');
548     }
549   }
550   con->send_as = HTTP_404_NOT_FOUND;
551 }
552
553 static void olsr_com_parse_txt(struct comport_connection *con,
554     unsigned int flags  __attribute__ ((unused))) {
555   static char defaultCommand[] = "/link/neigh/topology/hna/mid/routes";
556   static char tmpbuf[128];
557
558   enum olsr_txtcommand_result res;
559   char *eol;
560   int len;
561   bool processedCommand = false, chainCommands = false;
562   uint32_t old_timeout;
563
564   old_timeout = con->timeout_value;
565
566   /* loop over input */
567   while (con->in.len > 0 && con->state == INTERACTIVE) {
568     char *para = NULL, *cmd = NULL, *next = NULL;
569
570     /* search for end of line */
571     eol = memchr(con->in.buf, '\n', con->in.len);
572
573     if (eol == NULL) {
574       break;
575     }
576
577     /* terminate line with a 0 */
578     if (eol != con->in.buf && eol[-1] == '\r') {
579       eol[-1] = 0;
580     }
581     *eol++ = 0;
582
583     /* handle line */
584     OLSR_DEBUG(LOG_COMPORT, "Interactive console: %s\n", con->in.buf);
585     cmd = &con->in.buf[0];
586     processedCommand = true;
587
588     /* apply default command */
589     if (strcmp(cmd, "/") == 0) {
590       strcpy(tmpbuf, defaultCommand);
591       cmd = tmpbuf;
592     }
593
594     if (cmd[0] == '/') {
595       cmd++;
596       chainCommands = true;
597     }
598     while (cmd) {
599       len = con->out.len;
600
601       /* handle difference between multicommand and singlecommand mode */
602       if (chainCommands) {
603         next = strchr(cmd, '/');
604         if (next) {
605           *next++ = 0;
606         }
607       }
608       para = strchr(cmd, ' ');
609       if (para != NULL) {
610         *para++ = 0;
611       }
612
613       /* if we are doing continous output, stop it ! */
614       if (con->stop_handler) {
615         con->stop_handler(con);
616         con->stop_handler = NULL;
617       }
618
619       if (strlen(cmd) != 0) {
620         res = olsr_com_handle_txtcommand(con, cmd, para);
621         switch (res) {
622           case CONTINUE:
623             break;
624           case CONTINOUS:
625             break;
626           case ABUF_ERROR:
627             con->out.len = len;
628             abuf_appendf(&con->out,
629                 "Error in autobuffer during command '%s'.\n", cmd);
630             break;
631           case UNKNOWN:
632             con->out.len = len;
633             abuf_appendf(&con->out, "Error, unknown command '%s'\n", cmd);
634             break;
635           case QUIT:
636             con->state = SEND_AND_QUIT;
637             break;
638         }
639         /* put an empty line behind each command */
640         if (con->show_echo) {
641           abuf_puts(&con->out, "\n");
642         }
643       }
644       cmd = next;
645     }
646
647     /* remove line from input buffer */
648     abuf_pull(&con->in, eol - con->in.buf);
649
650     if (con->in.buf[0] == '/') {
651       /* end of multiple command line */
652       con->state = SEND_AND_QUIT;
653     }
654   }
655
656   /* reset timeout */
657   olsr_set_timer(&con->timeout, con->timeout_value, 0, false, &olsr_com_timeout_handler, con, connection_timeout);
658
659   /* print prompt */
660   if (processedCommand && con->state == INTERACTIVE && con->show_echo) {
661     abuf_puts(&con->out, "> ");
662   }
663 }
664