clean up code to conform to coding guidelines
[asterisk/asterisk.git] / main / http.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file 
21  * \brief http server for AMI access
22  *
23  * \author Mark Spencer <markster@digium.com>
24  *
25  * This program implements a tiny http server
26  * and was inspired by micro-httpd by Jef Poskanzer 
27  * 
28  * \ref AstHTTP - AMI over the http protocol
29  */
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include <time.h>
36 #include <sys/time.h>
37 #include <sys/stat.h>
38 #include <sys/signal.h>
39 #include <fcntl.h>
40
41 #ifdef ENABLE_UPLOADS
42 #include <gmime/gmime.h>
43 #endif /* ENABLE_UPLOADS */
44
45 #include "asterisk/paths.h"     /* use ast_config_AST_DATA_DIR */
46 #include "asterisk/network.h"
47 #include "asterisk/cli.h"
48 #include "asterisk/tcptls.h"
49 #include "asterisk/http.h"
50 #include "asterisk/utils.h"
51 #include "asterisk/strings.h"
52 #include "asterisk/config.h"
53 #include "asterisk/stringfields.h"
54 #include "asterisk/ast_version.h"
55 #include "asterisk/manager.h"
56 #include "asterisk/_private.h"
57
58 #define MAX_PREFIX 80
59
60 /* See http.h for more information about the SSL implementation */
61 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
62 #define DO_SSL  /* comment in/out if you want to support ssl */
63 #endif
64
65 static struct ast_tls_config http_tls_cfg;
66
67 static void *httpd_helper_thread(void *arg);
68
69 /*!
70  * we have up to two accepting threads, one for http, one for https
71  */
72 static struct server_args http_desc = {
73         .accept_fd = -1,
74         .master = AST_PTHREADT_NULL,
75         .tls_cfg = NULL,
76         .poll_timeout = -1,
77         .name = "http server",
78         .accept_fn = ast_tcptls_server_root,
79         .worker_fn = httpd_helper_thread,
80 };
81
82 static struct server_args https_desc = {
83         .accept_fd = -1,
84         .master = AST_PTHREADT_NULL,
85         .tls_cfg = &http_tls_cfg,
86         .poll_timeout = -1,
87         .name = "https server",
88         .accept_fn = ast_tcptls_server_root,
89         .worker_fn = httpd_helper_thread,
90 };
91
92 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri);      /*!< list of supported handlers */
93
94 #ifdef ENABLE_UPLOADS
95 struct ast_http_post_mapping {
96         AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
97         char *from;
98         char *to;
99 };
100
101 static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
102
103 struct mime_cbinfo {
104         int count;
105         const char *post_dir;
106 };
107 #endif /* ENABLE_UPLOADS */
108
109 /* all valid URIs must be prepended by the string in prefix. */
110 static char prefix[MAX_PREFIX];
111 static int enablestatic;
112
113 /*! \brief Limit the kinds of files we're willing to serve up */
114 static struct {
115         const char *ext;
116         const char *mtype;
117 } mimetypes[] = {
118         { "png", "image/png" },
119         { "jpg", "image/jpeg" },
120         { "js", "application/x-javascript" },
121         { "wav", "audio/x-wav" },
122         { "mp3", "audio/mpeg" },
123         { "svg", "image/svg+xml" },
124         { "svgz", "image/svg+xml" },
125         { "gif", "image/gif" },
126 };
127
128 struct http_uri_redirect {
129         AST_LIST_ENTRY(http_uri_redirect) entry;
130         char *dest;
131         char target[0];
132 };
133
134 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
135
136 static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
137 {
138         int x;
139
140         if (ftype) {
141                 for (x = 0; x < ARRAY_LEN(mimetypes); x++) {
142                         if (!strcasecmp(ftype, mimetypes[x].ext)) {
143                                 return mimetypes[x].mtype;
144                         }
145                 }
146         }
147
148         snprintf(wkspace, wkspacelen, "text/%s", S_OR(ftype, "plain"));
149
150         return wkspace;
151 }
152
153 static struct ast_str *static_callback(struct ast_tcptls_session_instance *ser, const char *uri, enum ast_http_method method,
154                                        struct ast_variable *vars, int *status, char **title, int *contentlength)
155 {
156         char *path;
157         char *ftype;
158         const char *mtype;
159         char wkspace[80];
160         struct stat st;
161         int len;
162         int fd;
163         struct timeval tv = ast_tvnow();
164         char buf[256];
165         struct ast_tm tm;
166
167         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
168            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
169         if (!enablestatic || ast_strlen_zero(uri)) {
170                 goto out403;
171         }
172
173         /* Disallow any funny filenames at all */
174         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0])) {
175                 goto out403;
176         }
177
178         if (strstr(uri, "/..")) {
179                 goto out403;
180         }
181                 
182         if ((ftype = strrchr(uri, '.'))) {
183                 ftype++;
184         }
185
186         mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
187         
188         /* Cap maximum length */
189         if ((len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5)) {
190                 goto out403;
191         }
192                 
193         path = alloca(len);
194         sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
195         if (stat(path, &st)) {
196                 goto out404;
197         }
198
199         if (S_ISDIR(st.st_mode)) {
200                 goto out404;
201         }
202
203         if ((fd = open(path, O_RDONLY)) < 0) {
204                 goto out403;
205         }
206
207         ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
208         fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
209                 "Server: Asterisk/%s\r\n"
210                 "Date: %s\r\n"
211                 "Connection: close\r\n"
212                 "Cache-Control: no-cache, no-store\r\n"
213                 "Content-Length: %d\r\n"
214                 "Content-type: %s\r\n\r\n",
215                 ast_get_version(), buf, (int) st.st_size, mtype);
216
217         while ((len = read(fd, buf, sizeof(buf))) > 0) {
218                 fwrite(buf, 1, len, ser->f);
219         }
220
221         close(fd);
222
223         return NULL;
224
225 out404:
226         *status = 404;
227         *title = ast_strdup("Not Found");
228
229         return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
230
231 out403:
232         *status = 403;
233         *title = ast_strdup("Access Denied");
234
235         return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
236 }
237
238
239 static struct ast_str *httpstatus_callback(struct ast_tcptls_session_instance *ser, const char *uri, enum ast_http_method method,
240                                            struct ast_variable *vars, int *status, char **title, int *contentlength)
241 {
242         struct ast_str *out = ast_str_create(512);
243         struct ast_variable *v;
244
245         if (out == NULL) {
246                 return out;
247         }
248
249         ast_str_append(&out, 0,
250                        "\r\n"
251                        "<title>Asterisk HTTP Status</title>\r\n"
252                        "<body bgcolor=\"#ffffff\">\r\n"
253                        "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
254                        "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
255         ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
256         ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
257                        ast_inet_ntoa(http_desc.oldsin.sin_addr));
258         ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
259                        ntohs(http_desc.oldsin.sin_port));
260
261         if (http_tls_cfg.enabled) {
262                 ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
263                                ntohs(https_desc.oldsin.sin_port));
264         }
265
266         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
267
268         for (v = vars; v; v = v->next) {
269                 if (strncasecmp(v->name, "cookie_", 7)) {
270                         ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
271                 }
272         }
273
274         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
275
276         for (v = vars; v; v = v->next) {
277                 if (!strncasecmp(v->name, "cookie_", 7)) {
278                         ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
279                 }
280         }
281
282         ast_str_append(&out, 0, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
283         return out;
284 }
285
286 static struct ast_http_uri statusuri = {
287         .callback = httpstatus_callback,
288         .description = "Asterisk HTTP General Status",
289         .uri = "httpstatus",
290         .supports_get = 1,
291 };
292         
293 static struct ast_http_uri staticuri = {
294         .callback = static_callback,
295         .description = "Asterisk HTTP Static Delivery",
296         .uri = "static",
297         .has_subtree = 1,
298         .static_content = 1,
299         .supports_get = 1,
300 };
301         
302 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
303 {
304         struct ast_str *out = ast_str_create(512);
305
306         if (out == NULL) {
307                 return out;
308         }
309
310         ast_str_set(&out, 0,
311                     "Content-type: text/html\r\n"
312                     "%s"
313                     "\r\n"
314                     "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
315                     "<html><head>\r\n"
316                     "<title>%d %s</title>\r\n"
317                     "</head><body>\r\n"
318                     "<h1>%s</h1>\r\n"
319                     "<p>%s</p>\r\n"
320                     "<hr />\r\n"
321                     "<address>Asterisk Server</address>\r\n"
322                     "</body></html>\r\n",
323                     (extra_header ? extra_header : ""), status, title, title, text);
324
325         return out;
326 }
327
328 /*! \brief 
329  * Link the new uri into the list. 
330  *
331  * They are sorted by length of
332  * the string, not alphabetically. Duplicate entries are not replaced,
333  * but the insertion order (using <= and not just <) makes sure that
334  * more recent insertions hide older ones.
335  * On a lookup, we just scan the list and stop at the first matching entry.
336  */
337 int ast_http_uri_link(struct ast_http_uri *urih)
338 {
339         struct ast_http_uri *uri;
340         int len = strlen(urih->uri);
341
342         AST_RWLIST_WRLOCK(&uris);
343
344         if (AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len) {
345                 AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
346                 AST_RWLIST_UNLOCK(&uris);
347
348                 return 0;
349         }
350
351         AST_RWLIST_TRAVERSE(&uris, uri, entry) {
352                 if (AST_RWLIST_NEXT(uri, entry) 
353                     && strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len) {
354                         AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
355                         AST_RWLIST_UNLOCK(&uris); 
356
357                         return 0;
358                 }
359         }
360
361         AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
362
363         AST_RWLIST_UNLOCK(&uris);
364         
365         return 0;
366 }       
367
368 void ast_http_uri_unlink(struct ast_http_uri *urih)
369 {
370         AST_RWLIST_WRLOCK(&uris);
371         AST_RWLIST_REMOVE(&uris, urih, entry);
372         AST_RWLIST_UNLOCK(&uris);
373 }
374
375 #ifdef ENABLE_UPLOADS
376 /*! \note This assumes that the post_mappings list is locked */
377 static struct ast_http_post_mapping *find_post_mapping(const char *uri)
378 {
379         struct ast_http_post_mapping *post_map;
380
381         if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
382                 ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
383
384                 return NULL;
385         }
386
387         uri += strlen(prefix);
388         if (*uri == '/') {
389                 uri++;
390         }
391         
392         AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
393                 if (!strcmp(uri, post_map->from)) {
394                         return post_map;
395                 }
396         }
397
398         return NULL;
399 }
400
401 static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
402 {
403         char filename[PATH_MAX];
404         GMimeDataWrapper *content;
405         GMimeStream *stream;
406         int fd;
407
408         snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
409
410         ast_debug(1, "Posting raw data to %s\n", filename);
411
412         if ((fd = open(filename, O_CREAT | O_WRONLY, 0666)) == -1) {
413                 ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
414
415                 return;
416         }
417
418         stream = g_mime_stream_fs_new(fd);
419
420         content = g_mime_part_get_content_object(part);
421         g_mime_data_wrapper_write_to_stream(content, stream);
422         g_mime_stream_flush(stream);
423
424         g_object_unref(content);
425         g_object_unref(stream);
426 }
427
428 static GMimeMessage *parse_message(FILE *f)
429 {
430         GMimeMessage *message;
431         GMimeParser *parser;
432         GMimeStream *stream;
433
434         stream = g_mime_stream_file_new(f);
435
436         parser = g_mime_parser_new_with_stream(stream);
437         g_mime_parser_set_respect_content_length(parser, 1);
438         
439         g_object_unref(stream);
440
441         message = g_mime_parser_construct_message(parser);
442
443         g_object_unref(parser);
444
445         return message;
446 }
447
448 static void process_message_callback(GMimeObject *part, gpointer user_data)
449 {
450         struct mime_cbinfo *cbinfo = user_data;
451
452         cbinfo->count++;
453
454         /* We strip off the headers before we get here, so should only see GMIME_IS_PART */
455         if (GMIME_IS_MESSAGE_PART(part)) {
456                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PART\n");
457                 return;
458         } else if (GMIME_IS_MESSAGE_PARTIAL(part)) {
459                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
460                 return;
461         } else if (GMIME_IS_MULTIPART(part)) {
462                 GList *l;
463                 
464                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MULTIPART, trying to process subparts\n");
465                 l = GMIME_MULTIPART(part)->subparts;
466                 while (l) {
467                         process_message_callback(l->data, cbinfo);
468                         l = l->next;
469                 }
470         } else if (GMIME_IS_PART(part)) {
471                 const char *filename;
472
473                 if (ast_strlen_zero(filename = g_mime_part_get_filename(GMIME_PART(part)))) {
474                         ast_debug(1, "Skipping part with no filename\n");
475                         return;
476                 }
477
478                 post_raw(GMIME_PART(part), cbinfo->post_dir, filename);
479         } else {
480                 ast_log(LOG_ERROR, "Encountered unknown MIME part. This should never happen!\n");
481         }
482 }
483
484 static int process_message(GMimeMessage *message, const char *post_dir)
485 {
486         struct mime_cbinfo cbinfo = {
487                 .count = 0,
488                 .post_dir = post_dir,
489         };
490
491         g_mime_message_foreach_part(message, process_message_callback, &cbinfo);
492
493         return cbinfo.count;
494 }
495
496 static struct ast_str *handle_post(struct ast_tcptls_session_instance *ser, char *uri, 
497                                    int *status, char **title, int *contentlength, struct ast_variable *headers,
498                                    struct ast_variable *cookies)
499 {
500         char buf[4096];
501         FILE *f;
502         size_t res;
503         struct ast_variable *var;
504         int content_len = 0;
505         struct ast_http_post_mapping *post_map;
506         const char *post_dir;
507         unsigned long ident = 0;
508         GMimeMessage *message;
509         int message_count = 0;
510
511         for (var = cookies; var; var = var->next) {
512                 if (strcasecmp(var->name, "mansession_id")) {
513                         continue;
514                 }
515
516                 if (sscanf(var->value, "%lx", &ident) != 1) {
517                         *status = 400;
518                         *title = ast_strdup("Bad Request");
519
520                         return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
521                 }
522
523                 if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
524                         *status = 401;
525                         *title = ast_strdup("Unauthorized");
526
527                         return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
528                 }
529
530                 break;
531         }
532         if (!var) {
533                 *status = 401;
534                 *title = ast_strdup("Unauthorized");
535
536                 return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
537         }
538
539         if (!(f = tmpfile())) {
540                 return NULL;
541         }
542
543         for (var = headers; var; var = var->next) {
544                 if (!strcasecmp(var->name, "Content-Length")) {
545                         if ((sscanf(var->value, "%u", &content_len)) != 1) {
546                                 ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
547                                 fclose(f);
548
549                                 return NULL;
550                         }
551                         ast_debug(1, "Got a Content-Length of %d\n", content_len);
552                 } else if (!strcasecmp(var->name, "Content-Type")) {
553                         fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
554                 }
555         }
556
557         for (res = sizeof(buf); content_len; content_len -= res) {
558                 if (content_len < res) {
559                         res = content_len;
560                 }
561                 fread(buf, 1, res, ser->f);
562                 fwrite(buf, 1, res, f);
563         }
564
565         if (fseek(f, SEEK_SET, 0)) {
566                 ast_debug(1, "Failed to seek temp file back to beginning.\n");
567                 fclose(f);
568
569                 return NULL;
570         }
571
572         AST_RWLIST_RDLOCK(&post_mappings);
573         if (!(post_map = find_post_mapping(uri))) {
574                 ast_debug(1, "%s is not a valid URI for POST\n", uri);
575                 AST_RWLIST_UNLOCK(&post_mappings);
576                 *status = 404;
577                 *title = ast_strdup("Not Found");
578
579                 return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
580         }
581
582         post_dir = ast_strdupa(post_map->to);
583         post_map = NULL;
584         AST_RWLIST_UNLOCK(&post_mappings);
585
586         ast_debug(1, "Going to post files to dir %s\n", post_dir);
587
588         message = parse_message(f); /* Takes ownership and will close f */
589
590         if (!message) {
591                 ast_log(LOG_ERROR, "Error parsing MIME data\n");
592                 *status = 400;
593                 *title = ast_strdup("Bad Request");
594
595                 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
596         }
597
598         if (!(message_count = process_message(message, post_dir))) {
599                 ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
600                 *status = 400;
601                 *title = ast_strdup("Bad Request");
602
603                 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
604         }
605
606         *status = 200;
607         *title = ast_strdup("OK");
608
609         return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
610 }
611 #endif /* ENABLE_UPLOADS */
612
613 static struct ast_str *handle_uri(struct ast_tcptls_session_instance *ser, char *uri, int *status, 
614                                   char **title, int *contentlength, struct ast_variable **cookies, 
615                                   unsigned int *static_content)
616 {
617         char *c;
618         struct ast_str *out = NULL;
619         char *params = uri;
620         struct ast_http_uri *urih=NULL;
621         int l;
622         struct ast_variable *vars=NULL, *v, *prev = NULL;
623         struct http_uri_redirect *redirect;
624
625         strsep(&params, "?");
626
627         /* Extract arguments from the request and store them in variables. */
628         if (params) {
629                 char *var, *val;
630
631                 while ((val = strsep(&params, "&"))) {
632                         var = strsep(&val, "=");
633                         if (val) {
634                                 ast_uri_decode(val);
635                         } else {
636                                 val = "";
637                         }
638                         ast_uri_decode(var);
639                         if ((v = ast_variable_new(var, val, ""))) {
640                                 if (vars) {
641                                         prev->next = v;
642                                 } else {
643                                         vars = v;
644                                 }
645                                 prev = v;
646                         }
647                 }
648         }
649         /*
650          * Append the cookies to the variables (the only reason to have them
651          * at the end is to avoid another pass of the cookies list to find
652          * the tail).
653          */
654         if (prev) {
655                 prev->next = *cookies;
656         } else {
657                 vars = *cookies;
658         }
659         *cookies = NULL;
660
661         ast_uri_decode(uri);
662
663         AST_RWLIST_RDLOCK(&uri_redirects);
664         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
665                 if (!strcasecmp(uri, redirect->target)) {
666                         char buf[512];
667
668                         snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
669                         out = ast_http_error(302, "Moved Temporarily", buf,
670                                              "There is no spoon...");
671                         *status = 302;
672                         *title = ast_strdup("Moved Temporarily");
673
674                         break;
675                 }
676         }
677         AST_RWLIST_UNLOCK(&uri_redirects);
678
679         if (redirect) {
680                 goto cleanup;
681         }
682
683         /* We want requests to start with the prefix and '/' */
684         if ((l = strlen(prefix)) && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
685                 uri += l + 1;
686                 /* scan registered uris to see if we match one. */
687                 AST_RWLIST_RDLOCK(&uris);
688                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
689                         l = strlen(urih->uri);
690                         c = uri + l;    /* candidate */
691
692                         if (strncasecmp(urih->uri, uri, l) /* no match */
693                             || (*c && *c != '/')) { /* substring */
694                                 continue;
695                         }
696
697                         if (*c == '/') {
698                                 c++;
699                         }
700
701                         if (!*c || urih->has_subtree) {
702                                 uri = c;
703
704                                 break;
705                         }
706                 }
707                 if (!urih) {
708                         AST_RWLIST_UNLOCK(&uris);
709                 }
710         }
711         if (urih) {
712                 *static_content = urih->static_content;
713                 out = urih->callback(ser, uri, AST_HTTP_GET, vars, status, title, contentlength);
714                 AST_RWLIST_UNLOCK(&uris);
715         } else {
716                 out = ast_http_error(404, "Not Found", NULL,
717                         "The requested URL was not found on this server.");
718                 *status = 404;
719                 *title = ast_strdup("Not Found");
720         }
721
722 cleanup:
723         ast_variables_destroy(vars);
724
725         return out;
726 }
727
728 #ifdef DO_SSL
729 #if defined(HAVE_FUNOPEN)
730 #define HOOK_T int
731 #define LEN_T int
732 #else
733 #define HOOK_T ssize_t
734 #define LEN_T size_t
735 #endif
736
737 /*!
738  * replacement read/write functions for SSL support.
739  * We use wrappers rather than SSL_read/SSL_write directly so
740  * we can put in some debugging.
741  */
742 /*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
743 {
744         int i = SSL_read(cookie, buf, len-1);
745 #if 0
746         if (i >= 0)
747                 buf[i] = '\0';
748         ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
749 #endif
750         return i;
751 }
752
753 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
754 {
755 #if 0
756         char *s = alloca(len+1);
757         strncpy(s, buf, len);
758         s[len] = '\0';
759         ast_verbose("ssl write size %d <%s>\n", (int)len, s);
760 #endif
761         return SSL_write(cookie, buf, len);
762 }
763
764 static int ssl_close(void *cookie)
765 {
766         close(SSL_get_fd(cookie));
767         SSL_shutdown(cookie);
768         SSL_free(cookie);
769         return 0;
770 }*/
771 #endif  /* DO_SSL */
772
773 static void *httpd_helper_thread(void *data)
774 {
775         char buf[4096];
776         char cookie[4096];
777         struct ast_tcptls_session_instance *ser = data;
778         struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
779         char *uri, *title=NULL;
780         int status = 200, contentlength = 0;
781         struct ast_str *out = NULL;
782         unsigned int static_content = 0;
783
784         if (!fgets(buf, sizeof(buf), ser->f)) {
785                 goto done;
786         }
787
788         uri = ast_skip_nonblanks(buf);  /* Skip method */
789         if (*uri) {
790                 *uri++ = '\0';
791         }
792
793         uri = ast_skip_blanks(uri);     /* Skip white space */
794
795         if (*uri) {                     /* terminate at the first blank */
796                 char *c = ast_skip_nonblanks(uri);
797
798                 if (*c) {
799                         *c = '\0';
800                 }
801         }
802
803         /* process "Cookie: " lines */
804         while (fgets(cookie, sizeof(cookie), ser->f)) {
805                 char *vname, *vval;
806                 int l;
807
808                 /* Trim trailing characters */
809                 ast_trim_blanks(cookie);
810                 if (ast_strlen_zero(cookie)) {
811                         break;
812                 }
813                 if (strncasecmp(cookie, "Cookie: ", 8)) {
814                         char *name, *value;
815
816                         value = ast_strdupa(cookie);
817                         name = strsep(&value, ":");
818                         if (!value) {
819                                 continue;
820                         }
821                         value = ast_skip_blanks(value);
822                         if (ast_strlen_zero(value)) {
823                                 continue;
824                         }
825                         var = ast_variable_new(name, value, "");
826                         if (!var) { 
827                                 continue;
828                         }
829                         var->next = headers;
830                         headers = var;
831
832                         continue;
833                 }
834
835                 /* TODO - The cookie parsing code below seems to work   
836                    in IE6 and FireFox 1.5.  However, it is not entirely 
837                    correct, and therefore may not work in all           
838                    circumstances.                                       
839                       For more details see RFC 2109 and RFC 2965        */
840         
841                 /* FireFox cookie strings look like:                    
842                      Cookie: mansession_id="********"                   
843                    InternetExplorer's look like:                        
844                      Cookie: $Version="1"; mansession_id="********"     */
845                 
846                 /* If we got a FireFox cookie string, the name's right  
847                     after "Cookie: "                                    */
848                 vname = ast_skip_blanks(cookie + 8);
849                         
850                 /* If we got an IE cookie string, we need to skip to    
851                     past the version to get to the name                 */
852                 if (*vname == '$') {
853                         strsep(&vname, ";");
854                         if (!vname) {   /* no name ? */
855                                 continue;
856                         }
857                         vname = ast_skip_blanks(vname);
858                 }
859                 if (!(vval = strchr(vname, '='))) {
860                         continue;
861                 }
862                 /* Ditch the = and the quotes */
863                 *vval++ = '\0';
864                 if (*vval) {
865                         vval++;
866                 }
867                 if ((l = strlen(vval))) {
868                         vval[l - 1] = '\0';     /* trim trailing quote */
869                 }
870                 if ((var = ast_variable_new(vname, vval, ""))) {
871                         if (prev)
872                                 prev->next = var;
873                         else
874                                 vars = var;
875                         prev = var;
876                 }
877         }
878
879         if (!*uri) {
880                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
881         } else if (!strcasecmp(buf, "post")) {
882 #ifdef ENABLE_UPLOADS
883                 out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
884 #else
885                 out = ast_http_error(501, "Not Implemented", NULL,
886                         "Attempt to use unimplemented / unsupported method");
887 #endif /* ENABLE_UPLOADS */
888         } else if (strcasecmp(buf, "get")) {
889                 out = ast_http_error(501, "Not Implemented", NULL,
890                         "Attempt to use unimplemented / unsupported method");
891         } else {        /* try to serve it */
892                 out = handle_uri(ser, uri, &status, &title, &contentlength, &vars, &static_content);
893         }
894
895         /* If they aren't mopped up already, clean up the cookies */
896         if (vars) {
897                 ast_variables_destroy(vars);
898         }
899         /* Clean up all the header information pulled as well */
900         if (headers) {
901                 ast_variables_destroy(headers);
902         }
903
904         if (out) {
905                 struct timeval tv = ast_tvnow();
906                 char timebuf[256];
907                 struct ast_tm tm;
908
909                 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
910                 fprintf(ser->f,
911                         "HTTP/1.1 %d %s\r\n"
912                         "Server: Asterisk/%s\r\n"
913                         "Date: %s\r\n"
914                         "Connection: close\r\n"
915                         "%s",
916                         status, title ? title : "OK", ast_get_version(), timebuf,
917                         static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
918                         /* We set the no-cache headers only for dynamic content.
919                         * If you want to make sure the static file you requested is not from cache,
920                         * append a random variable to your GET request.  Ex: 'something.html?r=109987734'
921                         */
922                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
923                         fprintf(ser->f, "%s", out->str);
924                 } else {
925                         char *tmp = strstr(out->str, "\r\n\r\n");
926
927                         if (tmp) {
928                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
929                                 /* first write the header, then the body */
930                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
931                                 fwrite(tmp + 4, 1, contentlength, ser->f);
932                         }
933                 }
934                 ast_free(out);
935         }
936
937         if (title) {
938                 ast_free(title);
939         }
940
941 done:
942         fclose(ser->f);
943         ser = ast_tcptls_session_instance_destroy(ser);
944
945         return NULL;
946 }
947
948 /*!
949  * \brief Add a new URI redirect
950  * The entries in the redirect list are sorted by length, just like the list
951  * of URI handlers.
952  */
953 static void add_redirect(const char *value)
954 {
955         char *target, *dest;
956         struct http_uri_redirect *redirect, *cur;
957         unsigned int target_len;
958         unsigned int total_len;
959
960         dest = ast_strdupa(value);
961         dest = ast_skip_blanks(dest);
962         target = strsep(&dest, " ");
963         target = ast_skip_blanks(target);
964         target = strsep(&target, " "); /* trim trailing whitespace */
965
966         if (!dest) {
967                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
968                 return;
969         }
970
971         target_len = strlen(target) + 1;
972         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
973
974         if (!(redirect = ast_calloc(1, total_len))) {
975                 return;
976         }
977
978         redirect->dest = redirect->target + target_len;
979         strcpy(redirect->target, target);
980         strcpy(redirect->dest, dest);
981
982         AST_RWLIST_WRLOCK(&uri_redirects);
983
984         target_len--; /* So we can compare directly with strlen() */
985         if (AST_RWLIST_EMPTY(&uri_redirects) 
986             || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len) {
987                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
988                 AST_RWLIST_UNLOCK(&uri_redirects);
989
990                 return;
991         }
992
993         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
994                 if (AST_RWLIST_NEXT(cur, entry) 
995                     && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len) {
996                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
997                         AST_RWLIST_UNLOCK(&uri_redirects); 
998
999                         return;
1000                 }
1001         }
1002
1003         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
1004
1005         AST_RWLIST_UNLOCK(&uri_redirects);
1006 }
1007
1008 #ifdef ENABLE_UPLOADS
1009 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
1010 {
1011         if (post_map->from) {
1012                 ast_free(post_map->from);
1013         }
1014         if (post_map->to) {
1015                 ast_free(post_map->to);
1016         }
1017         ast_free(post_map);
1018 }
1019
1020 static void destroy_post_mappings(void)
1021 {
1022         struct ast_http_post_mapping *post_map;
1023
1024         AST_RWLIST_WRLOCK(&post_mappings);
1025         while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry))) {
1026                 destroy_post_mapping(post_map);
1027         }
1028         AST_RWLIST_UNLOCK(&post_mappings);
1029 }
1030
1031 static void add_post_mapping(const char *from, const char *to)
1032 {
1033         struct ast_http_post_mapping *post_map;
1034
1035         if (!(post_map = ast_calloc(1, sizeof(*post_map)))) {
1036                 return;
1037         }
1038
1039         if (!(post_map->from = ast_strdup(from))) {
1040                 destroy_post_mapping(post_map);
1041
1042                 return;
1043         }
1044
1045         if (!(post_map->to = ast_strdup(to))) {
1046                 destroy_post_mapping(post_map);
1047
1048                 return;
1049         }
1050
1051         AST_RWLIST_WRLOCK(&post_mappings);
1052         AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
1053         AST_RWLIST_UNLOCK(&post_mappings);
1054 }
1055 #endif /* ENABLE_UPLOADS */
1056
1057 static int __ast_http_load(int reload)
1058 {
1059         struct ast_config *cfg;
1060         struct ast_variable *v;
1061         int enabled=0;
1062         int newenablestatic=0;
1063         struct hostent *hp;
1064         struct ast_hostent ahp;
1065         char newprefix[MAX_PREFIX] = "";
1066         int have_sslbindaddr = 0;
1067         struct http_uri_redirect *redirect;
1068         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1069
1070         if ((cfg = ast_config_load("http.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
1071                 return 0;
1072         }
1073
1074         /* default values */
1075         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
1076         http_desc.sin.sin_port = htons(8088);
1077
1078         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
1079         https_desc.sin.sin_port = htons(8089);
1080
1081         http_tls_cfg.enabled = 0;
1082         if (http_tls_cfg.certfile) {
1083                 ast_free(http_tls_cfg.certfile);
1084         }
1085         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
1086         if (http_tls_cfg.cipher) {
1087                 ast_free(http_tls_cfg.cipher);
1088         }
1089         http_tls_cfg.cipher = ast_strdup("");
1090
1091         AST_RWLIST_WRLOCK(&uri_redirects);
1092         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
1093                 ast_free(redirect);
1094         }
1095         AST_RWLIST_UNLOCK(&uri_redirects);
1096
1097 #ifdef ENABLE_UPLOADS
1098         destroy_post_mappings();
1099 #endif /* ENABLE_UPLOADS */
1100
1101         if (cfg) {
1102                 v = ast_variable_browse(cfg, "general");
1103                 for (; v; v = v->next) {
1104                         if (!strcasecmp(v->name, "enabled")) {
1105                                 enabled = ast_true(v->value);
1106                         } else if (!strcasecmp(v->name, "sslenable")) {
1107                                 http_tls_cfg.enabled = ast_true(v->value);
1108                         } else if (!strcasecmp(v->name, "sslbindport")) {
1109                                 https_desc.sin.sin_port = htons(atoi(v->value));
1110                         } else if (!strcasecmp(v->name, "sslcert")) {
1111                                 ast_free(http_tls_cfg.certfile);
1112                                 http_tls_cfg.certfile = ast_strdup(v->value);
1113                         } else if (!strcasecmp(v->name, "sslcipher")) {
1114                                 ast_free(http_tls_cfg.cipher);
1115                                 http_tls_cfg.cipher = ast_strdup(v->value);
1116                         } else if (!strcasecmp(v->name, "enablestatic")) {
1117                                 newenablestatic = ast_true(v->value);
1118                         } else if (!strcasecmp(v->name, "bindport")) {
1119                                 http_desc.sin.sin_port = htons(atoi(v->value));
1120                         } else if (!strcasecmp(v->name, "sslbindaddr")) {
1121                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1122                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
1123                                         have_sslbindaddr = 1;
1124                                 } else {
1125                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1126                                 }
1127                         } else if (!strcasecmp(v->name, "bindaddr")) {
1128                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1129                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
1130                                 } else {
1131                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1132                                 }
1133                         } else if (!strcasecmp(v->name, "prefix")) {
1134                                 if (!ast_strlen_zero(v->value)) {
1135                                         newprefix[0] = '/';
1136                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1137                                 } else {
1138                                         newprefix[0] = '\0';
1139                                 }
1140                         } else if (!strcasecmp(v->name, "redirect")) {
1141                                 add_redirect(v->value);
1142                         } else {
1143                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1144                         }
1145                 }
1146
1147 #ifdef ENABLE_UPLOADS
1148                 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) {
1149                         add_post_mapping(v->name, v->value);
1150                 }
1151 #endif /* ENABLE_UPLOADS */
1152
1153                 ast_config_destroy(cfg);
1154         }
1155
1156         if (!have_sslbindaddr) {
1157                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
1158         }
1159         if (enabled) {
1160                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
1161         }
1162         if (strcmp(prefix, newprefix)) {
1163                 ast_copy_string(prefix, newprefix, sizeof(prefix));
1164         }
1165         enablestatic = newenablestatic;
1166         ast_tcptls_server_start(&http_desc);
1167         if (ast_ssl_setup(https_desc.tls_cfg)) {
1168                 ast_tcptls_server_start(&https_desc);
1169         }
1170
1171         return 0;
1172 }
1173
1174 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1175 {
1176         struct ast_http_uri *urih;
1177         struct http_uri_redirect *redirect;
1178
1179 #ifdef ENABLE_UPLOADS
1180         struct ast_http_post_mapping *post_map;
1181 #endif /* ENABLE_UPLOADS */
1182
1183         switch (cmd) {
1184         case CLI_INIT:
1185                 e->command = "http show status";
1186                 e->usage = 
1187                         "Usage: http show status\n"
1188                         "       Lists status of internal HTTP engine\n";
1189                 return NULL;
1190         case CLI_GENERATE:
1191                 return NULL;
1192         }
1193         
1194         if (a->argc != 3) {
1195                 return CLI_SHOWUSAGE;
1196         }
1197         ast_cli(a->fd, "HTTP Server Status:\n");
1198         ast_cli(a->fd, "Prefix: %s\n", prefix);
1199         if (!http_desc.oldsin.sin_family) {
1200                 ast_cli(a->fd, "Server Disabled\n\n");
1201         } else {
1202                 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
1203                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
1204                         ntohs(http_desc.oldsin.sin_port));
1205                 if (http_tls_cfg.enabled) {
1206                         ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1207                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
1208                                 ntohs(https_desc.oldsin.sin_port));
1209                 }
1210         }
1211
1212         ast_cli(a->fd, "Enabled URI's:\n");
1213         AST_RWLIST_RDLOCK(&uris);
1214         if (AST_RWLIST_EMPTY(&uris)) {
1215                 ast_cli(a->fd, "None.\n");
1216         } else {
1217                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
1218                         ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : ""), urih->description);
1219                 }
1220         }
1221         AST_RWLIST_UNLOCK(&uris);
1222
1223         ast_cli(a->fd, "\nEnabled Redirects:\n");
1224         AST_RWLIST_RDLOCK(&uri_redirects);
1225         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
1226                 ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
1227         }
1228         if (AST_RWLIST_EMPTY(&uri_redirects)) {
1229                 ast_cli(a->fd, "  None.\n");
1230         }
1231         AST_RWLIST_UNLOCK(&uri_redirects);
1232
1233
1234 #ifdef ENABLE_UPLOADS
1235         ast_cli(a->fd, "\nPOST mappings:\n");
1236         AST_RWLIST_RDLOCK(&post_mappings);
1237         AST_LIST_TRAVERSE(&post_mappings, post_map, entry) {
1238                 ast_cli(a->fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
1239         }
1240         ast_cli(a->fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
1241         AST_RWLIST_UNLOCK(&post_mappings);
1242 #endif /* ENABLE_UPLOADS */
1243
1244         return CLI_SUCCESS;
1245 }
1246
1247 int ast_http_reload(void)
1248 {
1249         return __ast_http_load(1);
1250 }
1251
1252 static struct ast_cli_entry cli_http[] = {
1253         AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
1254 };
1255
1256 int ast_http_init(void)
1257 {
1258 #ifdef ENABLE_UPLOADS
1259         g_mime_init(0);
1260 #endif /* ENABLE_UPLOADS */
1261
1262         ast_http_uri_link(&statusuri);
1263         ast_http_uri_link(&staticuri);
1264         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1265
1266         return __ast_http_load(0);
1267 }