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