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