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