Add cleanup code for html/telnet server
[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(void) {
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   if (olsr_cnf->comport_http > 0) {
107     if ((comsocket_http = olsr_com_openport(olsr_cnf->comport_http)) == -1) {
108       return;
109     }
110
111     add_olsr_socket(comsocket_http, &olsr_com_parse_request, NULL, NULL,
112         SP_PR_READ);
113   }
114   if (olsr_cnf->comport_txt > 0) {
115     if ((comsocket_txt = olsr_com_openport(olsr_cnf->comport_txt)) == -1) {
116       return;
117     }
118
119     add_olsr_socket(comsocket_txt, &olsr_com_parse_request, NULL, NULL,
120         SP_PR_READ);
121   }
122
123   connection_http_count = 0;
124   connection_txt_count = 0;
125
126   list_head_init(&olsr_comport_head);
127
128   olsr_com_init_http();
129   olsr_com_init_txt();
130 }
131
132 void olsr_com_destroy(void) {
133   while (!list_is_empty(&olsr_comport_head)) {
134     struct comport_connection *con;
135
136     con = comport_node2con(olsr_comport_head.next);
137     olsr_com_cleanup_session(con);
138   }
139
140   olsr_com_destroy_http();
141   olsr_com_destroy_txt();
142 }
143
144 void olsr_com_activate_output(struct comport_connection *con) {
145   enable_olsr_socket(con->fd, &olsr_com_parse_connection, NULL, SP_PR_WRITE);
146 }
147
148 static int olsr_com_openport(int port) {
149   struct sockaddr_storage sst;
150   uint32_t yes = 1;
151   socklen_t addrlen;
152
153 #if !defined REMOVE_LOG_WARN
154   char ipchar = olsr_cnf->ip_version == AF_INET ? '4' : '6';
155 #endif
156
157   /* Init ipc socket */
158   int s = socket(olsr_cnf->ip_version, SOCK_STREAM, 0);
159   if (s == -1) {
160     OLSR_WARN(LOG_COMPORT, "Cannot open %d com-socket for IPv%c: %s\n", port, ipchar, strerror(errno));
161     return -1;
162   }
163
164   if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof(yes)) < 0) {
165     OLSR_WARN(LOG_COMPORT, "Com-port %d SO_REUSEADDR for IPv%c failed: %s\n", port, ipchar, strerror(errno));
166     CLOSESOCKET(s);
167     return -1;
168   }
169
170   /* Bind the socket */
171
172   /* complete the socket structure */
173   memset(&sst, 0, sizeof(sst));
174   if (olsr_cnf->ip_version == AF_INET) {
175     struct sockaddr_in *addr4 = (struct sockaddr_in *) &sst;
176     addr4->sin_family = AF_INET;
177     addrlen = sizeof(*addr4);
178 #ifdef SIN6_LEN
179     addr4->sin_len = addrlen;
180 #endif
181     addr4->sin_addr.s_addr = INADDR_ANY;
182     addr4->sin_port = htons(port);
183   } else {
184     struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &sst;
185     addr6->sin6_family = AF_INET6;
186     addrlen = sizeof(*addr6);
187 #ifdef SIN6_LEN
188     addr6->sin6_len = addrlen;
189 #endif
190     addr6->sin6_addr = in6addr_any;
191     addr6->sin6_port = htons(port);
192   }
193
194   /* bind the socket to the port number */
195   if (bind(s, (struct sockaddr *) &sst, addrlen) == -1) {
196     OLSR_WARN(LOG_COMPORT, "Com-port %d bind failed for IPv%c: %s\n", port, ipchar, strerror(errno));
197     CLOSESOCKET(s);
198     return -1;
199   }
200
201   /* show that we are willing to listen */
202   if (listen(s, 1) == -1) {
203     OLSR_WARN(LOG_COMPORT, "Com-port %d listen for IPv%c failed %s\n", port, ipchar, strerror(errno));
204     CLOSESOCKET(s);
205     return -1;
206   }
207
208   return s;
209 }
210
211 static void olsr_com_parse_request(int fd, void *data __attribute__ ((unused)), unsigned int flags __attribute__ ((unused))) {
212   struct comport_connection *con;
213   struct sockaddr_storage addr;
214   socklen_t addrlen;
215   int sock;
216 #if !defined REMOVE_LOG_DEBUG
217   struct ipaddr_str buf;
218 #endif
219
220   addrlen = sizeof(addr);
221   sock = accept(fd, (struct sockaddr *) &addr, &addrlen);
222   if (sock < 0) {
223     return;
224   }
225
226   con = olsr_cookie_malloc(connection_cookie);
227   abuf_init(&con->in, 1024);
228   abuf_init(&con->out, 0);
229
230   con->is_http = fd == comsocket_http;
231   con->is_csv = false;
232   con->fd = sock;
233
234   if (olsr_cnf->ip_version == AF_INET6) {
235     struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &addr;
236     con->addr.v6 = addr6->sin6_addr;
237   } else {
238     struct sockaddr_in *addr4 = (struct sockaddr_in *) &addr;
239     con->addr.v4 = addr4->sin_addr;
240   }
241
242   if (con->is_http) {
243     if (connection_http_count < olsr_cnf->comport_http_limit) {
244       connection_http_count++;
245       con->state = HTTP_LOGIN;
246       con->send_as = PLAIN;
247       con->timeout_value = HTTP_TIMEOUT;
248     } else {
249       con->state = SEND_AND_QUIT;
250       con->send_as = HTTP_503_SERVICE_UNAVAILABLE;
251     }
252   } else { /* !con->is_http */
253     if (connection_txt_count < olsr_cnf->comport_txt_limit) {
254       connection_txt_count++;
255       con->state = INTERACTIVE;
256       con->send_as = PLAIN;
257       con->timeout_value = TXT_TIMEOUT;
258     } else {
259       abuf_puts(&con->out, "Too many txt connections, sorry...\n");
260       con->state = SEND_AND_QUIT;
261       con->send_as = PLAIN;
262     }
263   }
264
265   OLSR_DEBUG(LOG_COMPORT, "Got connection through socket %d from %s.\n",
266       sock, olsr_ip_to_string(&buf, &con->addr));
267
268   con->timeout = olsr_start_timer(con->timeout_value, 0, false,
269       &olsr_com_timeout_handler, con, connection_timeout);
270
271   add_olsr_socket(sock, &olsr_com_parse_connection, NULL, con, SP_PR_READ
272       | SP_PR_WRITE);
273
274   list_add_after(&olsr_comport_head, &con->node);
275 }
276
277 static void olsr_com_cleanup_session(struct comport_connection *con) {
278   if (con->is_http) {
279     connection_http_count--;
280   } else {
281     connection_txt_count--;
282   }
283
284   list_remove(&con->node);
285
286   if (con->stop_handler) {
287     con->stop_handler(con);
288   }
289   remove_olsr_socket(con->fd, &olsr_com_parse_connection, NULL);
290   CLOSESOCKET(con->fd);
291
292   abuf_free(&con->in);
293   abuf_free(&con->out);
294
295   olsr_cookie_free(connection_cookie, con);
296 }
297
298 static void olsr_com_timeout_handler(void *data) {
299   struct comport_connection *con = data;
300   olsr_com_cleanup_session(con);
301 }
302
303 static void olsr_com_parse_connection(int fd, void *data, unsigned int flags) {
304   struct comport_connection *con = data;
305 #if !defined(REMOVE_LOG_WARN)
306   struct ipaddr_str buf;
307 #endif
308
309   OLSR_DEBUG(LOG_COMPORT, "Parsing connection of socket %d\n", fd);
310   /* read data if necessary */
311   if (flags & SP_PR_READ) {
312     char buffer[1024];
313     int len;
314
315     len = recv(fd, buffer, sizeof(buffer), 0);
316     if (len > 0) {
317       OLSR_DEBUG(LOG_COMPORT, "  recv returned %d\n", len);
318       if (con->state != SEND_AND_QUIT) {
319         abuf_memcpy(&con->in, buffer, len);
320       }
321
322       if (con->in.len > COMPORT_MAX_INPUTBUFFER) {
323         if (con->state == INTERACTIVE) {
324           abuf_puts(&con->out, "Sorry, input buffer overflow...\n");
325         } else if (con->state == HTTP_LOGIN) {
326           con->send_as = HTTP_413_REQUEST_TOO_LARGE;
327         }
328         con->state = SEND_AND_QUIT;
329       }
330     } else if (len < 0) {
331       OLSR_WARN(LOG_COMPORT, "Error while reading from communication stream with %s: %s\n",
332           olsr_ip_to_string(&buf, &con->addr), strerror(errno));
333       con->state = CLEANUP;
334     }
335   }
336
337   switch (con->state) {
338     case HTTP_LOGIN:
339       olsr_com_parse_http(con, flags);
340       break;
341     case INTERACTIVE:
342       olsr_com_parse_txt(con, flags);
343       break;
344     default:
345       break;
346   }
347
348   /* maybe we have to create an error message */
349   if (con->out.len == 0 && con->state == SEND_AND_QUIT && con->send_as != PLAIN
350       && con->send_as != HTTP_PLAIN && con->send_as != HTTP_200_OK) {
351     olsr_com_create_httperror(con);
352   }
353
354   /* send data if necessary */
355   if (con->out.len > 0) {
356     if (con->state == SEND_AND_QUIT && con->send_as != PLAIN) {
357       /* create header */
358       olsr_com_build_httpheader(con);
359       con->send_as = PLAIN;
360     }
361
362     if (flags & SP_PR_WRITE) {
363       int len;
364
365       len = send(fd, con->out.buf, con->out.len, 0);
366       if (len > 0) {
367         OLSR_DEBUG(LOG_COMPORT, "  send returned %d\n", len);
368         abuf_pull(&con->out, len);
369       } else if (len < 0) {
370         OLSR_WARN(LOG_COMPORT, "Error while writing to communication stream with %s: %s\n",
371             olsr_ip_to_string(&buf, &con->addr), strerror(errno));
372         con->state = CLEANUP;
373       }
374     } else {
375       OLSR_DEBUG(LOG_COMPORT, "  activating output in scheduler\n");
376       enable_olsr_socket(fd, &olsr_com_parse_connection, NULL, SP_PR_WRITE);
377     }
378   }
379   if (con->out.len == 0) {
380     OLSR_DEBUG(LOG_COMPORT, "  deactivating output in scheduler\n");
381     disable_olsr_socket(fd, &olsr_com_parse_connection, NULL, SP_PR_WRITE);
382     if (con->state == SEND_AND_QUIT) {
383       con->state = CLEANUP;
384     }
385   }
386
387   /* end of connection ? */
388   if (con->state == CLEANUP) {
389     OLSR_DEBUG(LOG_COMPORT, "  cleanup\n");
390     /* clean up connection by calling timeout directly */
391     olsr_stop_timer(con->timeout);
392     olsr_com_cleanup_session(con);
393   }
394   return;
395 }
396
397 static void olsr_com_parse_http(struct comport_connection *con,
398     unsigned int flags  __attribute__ ((unused))) {
399   char *para_keyvalue[MAX_HTTP_PARA * 2] = { NULL }, *para = NULL, *str = NULL;
400   int para_count = 0;
401   char req_type[11] = { 0 };
402   char filename[252] = { 0 };
403   char filename_copy[252] = { 0 };
404   char http_version[11] = { 0 };
405
406   int idx = 0;
407   int i = 0;
408
409   /*
410    * find end of http header, might be useful for POST to keep the index !
411    * (implemented as a finite element automaton)
412    */
413
414   while (i < 5) {
415     switch (con->in.buf[idx++]) {
416       case '\0':
417         i = 6;
418         break;
419       case '\r':
420         i = (i == 3) ? 4 : 2;
421         break;
422       case '\n':
423         if (i == 1 || i == 4) {
424           i = 5;
425         } else if (i == 2) {
426           i = 3;
427         } else {
428           i = 1;
429         }
430         break;
431       default:
432         i = 0;
433         break;
434     }
435   }
436
437   if (i != 5) {
438     OLSR_DEBUG(LOG_COMPORT, "  need end of http header, still waiting...\n");
439     return;
440   }
441
442   /* got http header */
443   if (sscanf(con->in.buf, "%10s %250s %10s\n", req_type, filename, http_version)
444       != 3 || (strcmp(http_version, "HTTP/1.1") != 0 && strcmp(http_version,
445       "HTTP/1.0") != 0)) {
446     con->send_as = HTTP_400_BAD_REQ;
447     con->state = SEND_AND_QUIT;
448     return;
449   }
450
451   OLSR_DEBUG(LOG_COMPORT, "HTTP Request: %s %s %s\n", req_type, filename, http_version);
452
453   /* store a copy for the http_handlers */
454   strcpy(filename_copy, filename);
455   if (strcmp(req_type, "POST") == 0) {
456     /* load the rest of the header for POST commands */
457     char *lengthField = strstr(con->in.buf, "\nContent-Length:");
458     int clen = 0;
459
460     if (lengthField == NULL) {
461       con->send_as = HTTP_400_BAD_REQ;
462       con->state = SEND_AND_QUIT;
463       return;
464     }
465
466     sscanf(lengthField, "%*s %d\n", &clen);
467     if (con->in.len < idx + clen) {
468       /* we still need more data */
469       return;
470     }
471     para = &con->in.buf[idx];
472   }
473
474   /* strip the URL marker away */
475   str = strchr(filename, '#');
476   if (str) {
477     *str = 0;
478   }
479
480   /* we have everything to process the http request */
481   con->state = SEND_AND_QUIT;
482   if (strcmp(req_type, "GET") == 0) {
483     /* HTTP-GET request */
484     para = strchr(filename, '?');
485     if (para != NULL) {
486       *para++ = 0;
487     }
488   } else if (strcmp(req_type, "POST") != 0) {
489     con->send_as = HTTP_501_NOT_IMPLEMENTED;
490     return;
491   }
492
493   /* handle HTTP GET & POST including parameters */
494   while (para && para_count < MAX_HTTP_PARA * 2) {
495     /* split the string at the next '=' (the key/value splitter) */
496     str = strchr(para, '=');
497     if (!str) {
498       break;
499     }
500     *str++ = 0;
501
502     /* we have null terminated key at *para. Now decode the key */
503     para_keyvalue[para_count++] = para;
504     olsr_com_decode_url(para);
505
506     /* split the string at the next '&' (the splitter of multiple key/value pairs */
507     para = strchr(str, '&');
508     if (para) {
509       *para++ = 0;
510     }
511
512     /* we have a null terminated value at *str, Now decode it */
513     olsr_com_decode_url(str);
514     para_keyvalue[para_count++] = str;
515   }
516
517   /* create body */
518   i = strlen(filename);
519
520   /*
521    * add a '/' at the end if it's not there to detect
522    *  paths without terminating '/' from the browser
523    */
524   if (filename[i - 1] != '/') {
525     strcat(filename, "/");
526   }
527
528   while (i > 0) {
529     if (olsr_com_handle_htmlsite(con, filename, filename_copy, para_count >> 1,
530         para_keyvalue)) {
531       return;
532     }
533
534     /* try to find a handler for a path prefix */
535     do {
536       filename[i--] = 0;
537     } while (i > 0 && filename[i] != '/');
538   }
539   con->send_as = HTTP_404_NOT_FOUND;
540 }
541
542 static void olsr_com_parse_txt(struct comport_connection *con,
543     unsigned int flags  __attribute__ ((unused))) {
544   enum olsr_txtcommand_result res;
545   char *eol;
546   int len;
547   bool processedCommand = false, chainCommands = false;
548   uint32_t old_timeout;
549
550   old_timeout = con->timeout_value;
551
552   /* loop over input */
553   while (con->in.len > 0 && con->state == INTERACTIVE) {
554     char *para = NULL, *cmd = NULL, *next = NULL;
555
556     /* search for end of line */
557     eol = memchr(con->in.buf, '\n', con->in.len);
558
559     if (eol == NULL) {
560       break;
561     }
562
563     /* terminate line with a 0 */
564     if (eol != con->in.buf && eol[-1] == '\r') {
565       eol[-1] = 0;
566     }
567     *eol++ = 0;
568
569     /* handle line */
570     OLSR_DEBUG(LOG_COMPORT, "Interactive console: %s\n", con->in.buf);
571     cmd = &con->in.buf[0];
572     processedCommand = true;
573
574     if (cmd[0] == '/') {
575       cmd++;
576       chainCommands = true;
577     }
578     while (cmd) {
579       len = con->out.len;
580
581       /* handle difference between multicommand and singlecommand mode */
582       if (!chainCommands) {
583         para = strchr(cmd, ' ');
584         if (para != NULL) {
585           *para++ = 0;
586         }
587       } else {
588         next = strchr(cmd, '/');
589         if (next) {
590           *next++ = 0;
591         }
592       }
593
594       /* if we are doing continous output, stop it ! */
595       if (con->stop_handler) {
596         con->stop_handler(con);
597         con->stop_handler = NULL;
598       }
599
600       if (strlen(cmd) != 0) {
601         res = olsr_com_handle_txtcommand(con, cmd, para);
602         switch (res) {
603           case CONTINUE:
604             break;
605           case CONTINOUS:
606             break;
607           case ABUF_ERROR:
608             con->out.len = len;
609             abuf_appendf(&con->out,
610                 "Error in autobuffer during command '%s'.\n", cmd);
611             break;
612           case UNKNOWN:
613             con->out.len = len;
614             abuf_appendf(&con->out, "Error, unknown command '%s'\n", cmd);
615             break;
616           case QUIT:
617             con->state = SEND_AND_QUIT;
618             break;
619         }
620         /* put an empty line behind each command if not in csv mode */
621         if (!con->is_csv) {
622           abuf_puts(&con->out, "\n");
623         }
624       }
625       cmd = next;
626     }
627
628     /* remove line from input buffer */
629     abuf_pull(&con->in, eol - con->in.buf);
630
631     if (con->in.buf[0] == '/') {
632       /* end of multiple command line */
633       con->state = SEND_AND_QUIT;
634     }
635   }
636
637   if (old_timeout != con->timeout_value) {
638     olsr_set_timer(&con->timeout, con->timeout_value, 0, false, &olsr_com_timeout_handler, con, connection_timeout);
639   }
640
641   /* print prompt */
642   if (processedCommand && con->state == INTERACTIVE && !con->is_csv) {
643     abuf_puts(&con->out, "> ");
644   }
645 }
646