(issue #10724)
[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 <sys/types.h>
36 #include <stdio.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <time.h>
40 #include <string.h>
41 #include <netinet/in.h>
42 #include <sys/time.h>
43 #include <sys/socket.h>
44 #include <sys/stat.h>
45 #include <sys/signal.h>
46 #include <arpa/inet.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <pthread.h>
50
51 #include "minimime/mm.h"
52
53 #include "asterisk/cli.h"
54 #include "asterisk/http.h"
55 #include "asterisk/utils.h"
56 #include "asterisk/strings.h"
57 #include "asterisk/options.h"
58 #include "asterisk/config.h"
59 #include "asterisk/stringfields.h"
60 #include "asterisk/version.h"
61 #include "asterisk/manager.h"
62
63 #define MAX_PREFIX 80
64 #define DEFAULT_PREFIX "/asterisk"
65
66 /* See http.h for more information about the SSL implementation */
67 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
68 #define DO_SSL  /* comment in/out if you want to support ssl */
69 #endif
70
71 static struct tls_config http_tls_cfg;
72
73 static void *httpd_helper_thread(void *arg);
74
75 /*!
76  * we have up to two accepting threads, one for http, one for https
77  */
78 static struct server_args http_desc = {
79         .accept_fd = -1,
80         .master = AST_PTHREADT_NULL,
81         .tls_cfg = NULL,
82         .poll_timeout = -1,
83         .name = "http server",
84         .accept_fn = server_root,
85         .worker_fn = httpd_helper_thread,
86 };
87
88 static struct server_args https_desc = {
89         .accept_fd = -1,
90         .master = AST_PTHREADT_NULL,
91         .tls_cfg = &http_tls_cfg,
92         .poll_timeout = -1,
93         .name = "https server",
94         .accept_fn = server_root,
95         .worker_fn = httpd_helper_thread,
96 };
97
98 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri);      /*!< list of supported handlers */
99
100 struct ast_http_post_mapping {
101         AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
102         char *from;
103         char *to;
104 };
105
106 static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
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 server_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                 ASTERISK_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 server_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 /*! \note This assumes that the post_mappings list is locked */
338 static struct ast_http_post_mapping *find_post_mapping(const char *uri)
339 {
340         struct ast_http_post_mapping *post_map;
341
342         if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
343                 ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
344                 return NULL;
345         }
346
347         uri += strlen(prefix);
348         if (*uri == '/')
349                 uri++;
350         
351         AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
352                 if (!strcmp(uri, post_map->from))
353                         return post_map;
354         }
355
356         return NULL;
357 }
358
359 static int get_filename(struct mm_mimepart *part, char *fn, size_t fn_len)
360 {
361         const char *filename;
362
363         filename = mm_content_getdispositionparambyname(part->type, "filename");
364
365         if (ast_strlen_zero(filename))
366                 return -1;
367
368         ast_copy_string(fn, filename, fn_len);
369
370         return 0;
371 }
372
373 static void post_raw(struct mm_mimepart *part, const char *post_dir, const char *fn)
374 {
375         char filename[PATH_MAX];
376         FILE *f;
377         const char *body;
378         size_t body_len;
379
380         snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
381
382         ast_debug(1, "Posting raw data to %s\n", filename);
383
384         if (!(f = fopen(filename, "w"))) {
385                 ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
386                 return;
387         }
388
389         if (!(body = mm_mimepart_getbody(part, 0))) {
390                 ast_debug(1, "Couldn't get the mimepart body\n");
391                 fclose(f);
392                 return;
393         }
394         body_len = mm_mimepart_getlength(part);
395
396         ast_debug(1, "Body length is %ld\n", (long int)body_len);
397
398         fwrite(body, 1, body_len, f);
399
400         fclose(f);
401 }
402
403 static struct ast_str *handle_post(struct server_instance *ser, char *uri, 
404         int *status, char **title, int *contentlength, struct ast_variable *headers,
405         struct ast_variable *cookies)
406 {
407         char buf;
408         FILE *f;
409         size_t res;
410         struct ast_variable *var;
411         int content_len = 0;
412         MM_CTX *ctx;
413         int mm_res, i;
414         struct ast_http_post_mapping *post_map;
415         const char *post_dir;
416         unsigned long ident = 0;
417
418         for (var = cookies; var; var = var->next) {
419                 if (strcasecmp(var->name, "mansession_id"))
420                         continue;
421
422                 if (sscanf(var->value, "%lx", &ident) != 1) {
423                         *status = 400;
424                         *title = ast_strdup("Bad Request");
425                         return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
426                 }
427
428                 if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
429                         *status = 401;
430                         *title = ast_strdup("Unauthorized");
431                         return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
432                 }
433
434                 break;
435         }
436         if (!var) {
437                 *status = 401;
438                 *title = ast_strdup("Unauthorized");
439                 return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
440         }
441
442         if (!(f = tmpfile()))
443                 return NULL;
444
445         for (var = headers; var; var = var->next) {
446                 if (!strcasecmp(var->name, "Content-Length")) {
447                         if ((sscanf(var->value, "%u", &content_len)) != 1) {
448                                 ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
449                                 fclose(f);
450                                 return NULL;
451                         }
452                         ast_debug(1, "Got a Content-Length of %d\n", content_len);
453                 } else if (!strcasecmp(var->name, "Content-Type"))
454                         fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
455         }
456
457         while ((res = fread(&buf, 1, 1, ser->f))) {
458                 fwrite(&buf, 1, 1, f);
459                 content_len--;
460                 if (!content_len)
461                         break;
462         }
463
464         if (fseek(f, SEEK_SET, 0)) {
465                 ast_debug(1, "Failed to seek temp file back to beginning.\n");
466                 fclose(f);
467                 return NULL;
468         }
469
470         AST_RWLIST_RDLOCK(&post_mappings);
471         if (!(post_map = find_post_mapping(uri))) {
472                 ast_debug(1, "%s is not a valid URI for POST\n", uri);
473                 AST_RWLIST_UNLOCK(&post_mappings);
474                 fclose(f);
475                 *status = 404;
476                 *title = ast_strdup("Not Found");
477                 return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
478         }
479         post_dir = ast_strdupa(post_map->to);
480         post_map = NULL;
481         AST_RWLIST_UNLOCK(&post_mappings);
482
483         ast_debug(1, "Going to post files to dir %s\n", post_dir);
484
485         if (!(ctx = mm_context_new())) {
486                 fclose(f);
487                 return NULL;
488         }
489
490         mm_res = mm_parse_fileptr(ctx, f, MM_PARSE_LOOSE, 0);
491         fclose(f);
492         if (mm_res == -1) {
493                 ast_log(LOG_ERROR, "Error parsing MIME data\n");
494                 mm_context_free(ctx);
495                 *status = 400;
496                 *title = ast_strdup("Bad Request");
497                 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
498         }
499
500         mm_res = mm_context_countparts(ctx);
501         if (!mm_res) {
502                 ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
503                 mm_context_free(ctx);
504                 *status = 400;
505                 *title = ast_strdup("Bad Request");
506                 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
507         }
508
509         if (option_debug) {
510                 if (mm_context_iscomposite(ctx))
511                         ast_debug(1, "Found %d MIME parts\n", mm_res - 1);
512                 else
513                         ast_debug(1, "We have a flat (not multi-part) message\n");
514         }
515
516         for (i = 1; i < mm_res; i++) {
517                 struct mm_mimepart *part;
518                 char fn[PATH_MAX];
519
520                 if (!(part = mm_context_getpart(ctx, i))) {
521                         ast_debug(1, "Failed to get mime part num %d\n", i);
522                         continue;
523                 }
524
525                 if (get_filename(part, fn, sizeof(fn))) {
526                         ast_debug(1, "Failed to retrieve a filename for part num %d\n", i);
527                         continue;
528                 }
529         
530                 if (!part->type) {
531                         ast_debug(1, "This part has no content struct?\n");
532                         continue;
533                 }
534
535                 /* XXX This assumes the MIME part body is not encoded! */
536                 post_raw(part, post_dir, fn);
537         }
538
539         mm_context_free(ctx);
540
541         *status = 200;
542         *title = ast_strdup("OK");
543         return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
544 }
545
546 static struct ast_str *handle_uri(struct server_instance *ser, char *uri, int *status, 
547         char **title, int *contentlength, struct ast_variable **cookies, 
548         unsigned int *static_content)
549 {
550         char *c;
551         struct ast_str *out = NULL;
552         char *params = uri;
553         struct ast_http_uri *urih=NULL;
554         int l;
555         struct ast_variable *vars=NULL, *v, *prev = NULL;
556         struct http_uri_redirect *redirect;
557
558         strsep(&params, "?");
559         /* Extract arguments from the request and store them in variables. */
560         if (params) {
561                 char *var, *val;
562
563                 while ((val = strsep(&params, "&"))) {
564                         var = strsep(&val, "=");
565                         if (val)
566                                 ast_uri_decode(val);
567                         else 
568                                 val = "";
569                         ast_uri_decode(var);
570                         if ((v = ast_variable_new(var, val, ""))) {
571                                 if (vars)
572                                         prev->next = v;
573                                 else
574                                         vars = v;
575                                 prev = v;
576                         }
577                 }
578         }
579         /*
580          * Append the cookies to the variables (the only reason to have them
581          * at the end is to avoid another pass of the cookies list to find
582          * the tail).
583          */
584         if (prev)
585                 prev->next = *cookies;
586         else
587                 vars = *cookies;
588         *cookies = NULL;
589         ast_uri_decode(uri);
590
591         AST_RWLIST_RDLOCK(&uri_redirects);
592         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
593                 if (!strcasecmp(uri, redirect->target)) {
594                         char buf[512];
595                         snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
596                         out = ast_http_error(302, "Moved Temporarily", buf,
597                                 "There is no spoon...");
598                         *status = 302;
599                         *title = ast_strdup("Moved Temporarily");
600                         break;
601                 }
602         }
603         AST_RWLIST_UNLOCK(&uri_redirects);
604         if (redirect)
605                 goto cleanup;
606
607         /* We want requests to start with the prefix and '/' */
608         l = strlen(prefix);
609         if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
610                 uri += l + 1;
611                 /* scan registered uris to see if we match one. */
612                 AST_RWLIST_RDLOCK(&uris);
613                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
614                         l = strlen(urih->uri);
615                         c = uri + l;    /* candidate */
616                         if (strncasecmp(urih->uri, uri, l) /* no match */
617                             || (*c && *c != '/')) /* substring */
618                                 continue;
619                         if (*c == '/')
620                                 c++;
621                         if (!*c || urih->has_subtree) {
622                                 uri = c;
623                                 break;
624                         }
625                 }
626                 if (!urih)
627                         AST_RWLIST_UNLOCK(&uris);
628         }
629         if (urih) {
630                 if (urih->static_content)
631                         *static_content = 1;
632                 out = urih->callback(ser, uri, vars, status, title, contentlength);
633                 AST_RWLIST_UNLOCK(&uris);
634         } else {
635                 out = ast_http_error(404, "Not Found", NULL,
636                         "The requested URL was not found on this server.");
637                 *status = 404;
638                 *title = ast_strdup("Not Found");
639         }
640
641 cleanup:
642         ast_variables_destroy(vars);
643         return out;
644 }
645
646 #ifdef DO_SSL
647 #if defined(HAVE_FUNOPEN)
648 #define HOOK_T int
649 #define LEN_T int
650 #else
651 #define HOOK_T ssize_t
652 #define LEN_T size_t
653 #endif
654 /*!
655  * replacement read/write functions for SSL support.
656  * We use wrappers rather than SSL_read/SSL_write directly so
657  * we can put in some debugging.
658  */
659 static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
660 {
661         int i = SSL_read(cookie, buf, len-1);
662 #if 0
663         if (i >= 0)
664                 buf[i] = '\0';
665         ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
666 #endif
667         return i;
668 }
669
670 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
671 {
672 #if 0
673         char *s = alloca(len+1);
674         strncpy(s, buf, len);
675         s[len] = '\0';
676         ast_verbose("ssl write size %d <%s>\n", (int)len, s);
677 #endif
678         return SSL_write(cookie, buf, len);
679 }
680
681 static int ssl_close(void *cookie)
682 {
683         close(SSL_get_fd(cookie));
684         SSL_shutdown(cookie);
685         SSL_free(cookie);
686         return 0;
687 }
688 #endif  /* DO_SSL */
689
690 /*!
691  * creates a FILE * from the fd passed by the accept thread.
692  * This operation is potentially expensive (certificate verification),
693  * so we do it in the child thread context.
694  */
695 static void *make_file_from_fd(void *data)
696 {
697         struct server_instance *ser = data;
698
699         /*
700          * open a FILE * as appropriate.
701          */
702         if (!ser->parent->tls_cfg)
703                 ser->f = fdopen(ser->fd, "w+");
704 #ifdef DO_SSL
705         else if ( (ser->ssl = SSL_new(ser->parent->tls_cfg->ssl_ctx)) ) {
706                 SSL_set_fd(ser->ssl, ser->fd);
707                 if (SSL_accept(ser->ssl) == 0)
708                         ast_verbose(" error setting up ssl connection");
709                 else {
710 #if defined(HAVE_FUNOPEN)       /* the BSD interface */
711                         ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close);
712
713 #elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */
714                         static const cookie_io_functions_t cookie_funcs = {
715                                 ssl_read, ssl_write, NULL, ssl_close
716                         };
717                         ser->f = fopencookie(ser->ssl, "w+", cookie_funcs);
718 #else
719                         /* could add other methods here */
720 #endif
721                 }
722                 if (!ser->f)    /* no success opening descriptor stacking */
723                         SSL_free(ser->ssl);
724         }
725 #endif /* DO_SSL */
726
727         if (!ser->f) {
728                 close(ser->fd);
729                 ast_log(LOG_WARNING, "FILE * open failed!\n");
730                 ast_free(ser);
731                 return NULL;
732         }
733         return ser->parent->worker_fn(ser);
734 }
735
736 static void *httpd_helper_thread(void *data)
737 {
738         char buf[4096];
739         char cookie[4096];
740         struct server_instance *ser = data;
741         struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
742         char *uri, *title=NULL;
743         int status = 200, contentlength = 0;
744         struct ast_str *out = NULL;
745         unsigned int static_content = 0;
746
747         if (!fgets(buf, sizeof(buf), ser->f))
748                 goto done;
749
750         uri = ast_skip_nonblanks(buf);  /* Skip method */
751         if (*uri)
752                 *uri++ = '\0';
753
754         uri = ast_skip_blanks(uri);     /* Skip white space */
755
756         if (*uri) {                     /* terminate at the first blank */
757                 char *c = ast_skip_nonblanks(uri);
758                 if (*c)
759                         *c = '\0';
760         }
761
762         /* process "Cookie: " lines */
763         while (fgets(cookie, sizeof(cookie), ser->f)) {
764                 char *vname, *vval;
765                 int l;
766
767                 /* Trim trailing characters */
768                 ast_trim_blanks(cookie);
769                 if (ast_strlen_zero(cookie))
770                         break;
771                 if (strncasecmp(cookie, "Cookie: ", 8)) {
772                         char *name, *value;
773
774                         value = ast_strdupa(cookie);
775                         name = strsep(&value, ":");
776                         if (!value)
777                                 continue;
778                         value = ast_skip_blanks(value);
779                         if (ast_strlen_zero(value))
780                                 continue;
781                         var = ast_variable_new(name, value, "");
782                         if (!var)
783                                 continue;
784                         var->next = headers;
785                         headers = var;
786                         continue;
787                 }
788
789                 /* TODO - The cookie parsing code below seems to work   
790                    in IE6 and FireFox 1.5.  However, it is not entirely 
791                    correct, and therefore may not work in all           
792                    circumstances.                                       
793                       For more details see RFC 2109 and RFC 2965        */
794         
795                 /* FireFox cookie strings look like:                    
796                      Cookie: mansession_id="********"                   
797                    InternetExplorer's look like:                        
798                      Cookie: $Version="1"; mansession_id="********"     */
799                 
800                 /* If we got a FireFox cookie string, the name's right  
801                     after "Cookie: "                                    */
802                 vname = ast_skip_blanks(cookie + 8);
803                         
804                 /* If we got an IE cookie string, we need to skip to    
805                     past the version to get to the name                 */
806                 if (*vname == '$') {
807                         strsep(&vname, ";");
808                         if (!vname)     /* no name ? */
809                                 continue;
810                         vname = ast_skip_blanks(vname);
811                 }
812                 vval = strchr(vname, '=');
813                 if (!vval)
814                         continue;
815                 /* Ditch the = and the quotes */
816                 *vval++ = '\0';
817                 if (*vval)
818                         vval++;
819                 if ( (l = strlen(vval)) )
820                         vval[l - 1] = '\0';     /* trim trailing quote */
821                 var = ast_variable_new(vname, vval, "");
822                 if (var) {
823                         if (prev)
824                                 prev->next = var;
825                         else
826                                 vars = var;
827                         prev = var;
828                 }
829         }
830
831         if (!*uri)
832                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
833         else if (!strcasecmp(buf, "post")) 
834                 out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
835         else if (strcasecmp(buf, "get")) 
836                 out = ast_http_error(501, "Not Implemented", NULL,
837                         "Attempt to use unimplemented / unsupported method");
838         else    /* try to serve it */
839                 out = handle_uri(ser, uri, &status, &title, &contentlength, &vars, &static_content);
840
841         /* If they aren't mopped up already, clean up the cookies */
842         if (vars)
843                 ast_variables_destroy(vars);
844         /* Clean up all the header information pulled as well */
845         if (headers)
846                 ast_variables_destroy(headers);
847
848         if (out) {
849                 struct timeval tv = ast_tvnow();
850                 char timebuf[256];
851                 struct ast_tm tm;
852
853                 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
854                 fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
855                                 "Server: Asterisk/%s\r\n"
856                                 "Date: %s\r\n"
857                                 "Connection: close\r\n"
858                                 "%s",
859                         status, title ? title : "OK", ASTERISK_VERSION, timebuf,
860                         static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
861                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
862                         fprintf(ser->f, "%s", out->str);
863                 } else {
864                         char *tmp = strstr(out->str, "\r\n\r\n");
865
866                         if (tmp) {
867                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
868                                 /* first write the header, then the body */
869                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
870                                 fwrite(tmp + 4, 1, contentlength, ser->f);
871                         }
872                 }
873                 ast_free(out);
874         }
875         if (title)
876                 ast_free(title);
877
878 done:
879         fclose(ser->f);
880         ast_free(ser);
881         return NULL;
882 }
883
884 void *server_root(void *data)
885 {
886         struct server_args *desc = data;
887         int fd;
888         struct sockaddr_in sin;
889         socklen_t sinlen;
890         struct server_instance *ser;
891         pthread_t launched;
892         
893         for (;;) {
894                 int i, flags;
895
896                 if (desc->periodic_fn)
897                         desc->periodic_fn(desc);
898                 i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout);
899                 if (i <= 0)
900                         continue;
901                 sinlen = sizeof(sin);
902                 fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen);
903                 if (fd < 0) {
904                         if ((errno != EAGAIN) && (errno != EINTR))
905                                 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
906                         continue;
907                 }
908                 ser = ast_calloc(1, sizeof(*ser));
909                 if (!ser) {
910                         ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
911                         close(fd);
912                         continue;
913                 }
914                 flags = fcntl(fd, F_GETFL);
915                 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
916                 ser->fd = fd;
917                 ser->parent = desc;
918                 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
919                         
920                 if (ast_pthread_create_detached_background(&launched, NULL, make_file_from_fd, ser)) {
921                         ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
922                         close(ser->fd);
923                         ast_free(ser);
924                 }
925
926         }
927         return NULL;
928 }
929
930 int ssl_setup(struct tls_config *cfg)
931 {
932 #ifndef DO_SSL
933         cfg->enabled = 0;
934         return 0;
935 #else
936         if (!cfg->enabled)
937                 return 0;
938         SSL_load_error_strings();
939         SSLeay_add_ssl_algorithms();
940         cfg->ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
941         if (!ast_strlen_zero(cfg->certfile)) {
942                 if (SSL_CTX_use_certificate_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
943                     SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
944                     SSL_CTX_check_private_key(cfg->ssl_ctx) == 0 ) {
945                         ast_verbose("ssl cert error <%s>", cfg->certfile);
946                         sleep(2);
947                         cfg->enabled = 0;
948                         return 0;
949                 }
950         }
951         if (!ast_strlen_zero(cfg->cipher)) {
952                 if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) {
953                         ast_verbose("ssl cipher error <%s>", cfg->cipher);
954                         sleep(2);
955                         cfg->enabled = 0;
956                         return 0;
957                 }
958         }
959         ast_verbose("ssl cert ok");
960         return 1;
961 #endif
962 }
963
964 /*!
965  * This is a generic (re)start routine for a TCP server,
966  * which does the socket/bind/listen and starts a thread for handling
967  * accept().
968  */
969 void server_start(struct server_args *desc)
970 {
971         int flags;
972         int x = 1;
973         
974         /* Do nothing if nothing has changed */
975         if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
976                 ast_debug(1, "Nothing changed in %s\n", desc->name);
977                 return;
978         }
979         
980         desc->oldsin = desc->sin;
981         
982         /* Shutdown a running server if there is one */
983         if (desc->master != AST_PTHREADT_NULL) {
984                 pthread_cancel(desc->master);
985                 pthread_kill(desc->master, SIGURG);
986                 pthread_join(desc->master, NULL);
987         }
988         
989         if (desc->accept_fd != -1)
990                 close(desc->accept_fd);
991
992         /* If there's no new server, stop here */
993         if (desc->sin.sin_family == 0)
994                 return;
995
996         desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
997         if (desc->accept_fd < 0) {
998                 ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n",
999                         desc->name, strerror(errno));
1000                 return;
1001         }
1002         
1003         setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
1004         if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {
1005                 ast_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n",
1006                         desc->name,
1007                         ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
1008                         strerror(errno));
1009                 goto error;
1010         }
1011         if (listen(desc->accept_fd, 10)) {
1012                 ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name);
1013                 goto error;
1014         }
1015         flags = fcntl(desc->accept_fd, F_GETFL);
1016         fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
1017         if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {
1018                 ast_log(LOG_NOTICE, "Unable to launch %s on %s:%d: %s\n",
1019                         desc->name,
1020                         ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
1021                         strerror(errno));
1022                 goto error;
1023         }
1024         return;
1025
1026 error:
1027         close(desc->accept_fd);
1028         desc->accept_fd = -1;
1029 }
1030
1031 /*!
1032  * \brief Add a new URI redirect
1033  * The entries in the redirect list are sorted by length, just like the list
1034  * of URI handlers.
1035  */
1036 static void add_redirect(const char *value)
1037 {
1038         char *target, *dest;
1039         struct http_uri_redirect *redirect, *cur;
1040         unsigned int target_len;
1041         unsigned int total_len;
1042
1043         dest = ast_strdupa(value);
1044         dest = ast_skip_blanks(dest);
1045         target = strsep(&dest, " ");
1046         target = ast_skip_blanks(target);
1047         target = strsep(&target, " "); /* trim trailing whitespace */
1048
1049         if (!dest) {
1050                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
1051                 return;
1052         }
1053
1054         target_len = strlen(target) + 1;
1055         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
1056
1057         if (!(redirect = ast_calloc(1, total_len)))
1058                 return;
1059
1060         redirect->dest = redirect->target + target_len;
1061         strcpy(redirect->target, target);
1062         strcpy(redirect->dest, dest);
1063
1064         AST_RWLIST_WRLOCK(&uri_redirects);
1065
1066         target_len--; /* So we can compare directly with strlen() */
1067         if ( AST_RWLIST_EMPTY(&uri_redirects) 
1068                 || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len ) {
1069                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
1070                 AST_RWLIST_UNLOCK(&uri_redirects);
1071                 return;
1072         }
1073
1074         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
1075                 if ( AST_RWLIST_NEXT(cur, entry) 
1076                         && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len ) {
1077                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
1078                         AST_RWLIST_UNLOCK(&uri_redirects); 
1079                         return;
1080                 }
1081         }
1082
1083         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
1084
1085         AST_RWLIST_UNLOCK(&uri_redirects);
1086 }
1087
1088 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
1089 {
1090         if (post_map->from)
1091                 ast_free(post_map->from);
1092         if (post_map->to)
1093                 ast_free(post_map->to);
1094         ast_free(post_map);
1095 }
1096
1097 static void destroy_post_mappings(void)
1098 {
1099         struct ast_http_post_mapping *post_map;
1100
1101         AST_RWLIST_WRLOCK(&post_mappings);
1102         while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry)))
1103                 destroy_post_mapping(post_map);
1104         AST_RWLIST_UNLOCK(&post_mappings);
1105 }
1106
1107 static void add_post_mapping(const char *from, const char *to)
1108 {
1109         struct ast_http_post_mapping *post_map;
1110
1111         if (!(post_map = ast_calloc(1, sizeof(*post_map))))
1112                 return;
1113
1114         if (!(post_map->from = ast_strdup(from))) {
1115                 destroy_post_mapping(post_map);
1116                 return;
1117         }
1118
1119         if (!(post_map->to = ast_strdup(to))) {
1120                 destroy_post_mapping(post_map);
1121                 return;
1122         }
1123
1124         AST_RWLIST_WRLOCK(&post_mappings);
1125         AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
1126         AST_RWLIST_UNLOCK(&post_mappings);
1127 }
1128
1129 static int __ast_http_load(int reload)
1130 {
1131         struct ast_config *cfg;
1132         struct ast_variable *v;
1133         int enabled=0;
1134         int newenablestatic=0;
1135         struct hostent *hp;
1136         struct ast_hostent ahp;
1137         char newprefix[MAX_PREFIX];
1138         int have_sslbindaddr = 0;
1139         struct http_uri_redirect *redirect;
1140         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1141
1142         if ((cfg = ast_config_load("http.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
1143                 return 0;
1144
1145         /* default values */
1146         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
1147         http_desc.sin.sin_port = htons(8088);
1148
1149         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
1150         https_desc.sin.sin_port = htons(8089);
1151
1152         strcpy(newprefix, DEFAULT_PREFIX);
1153
1154         http_tls_cfg.enabled = 0;
1155         if (http_tls_cfg.certfile)
1156                 ast_free(http_tls_cfg.certfile);
1157         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
1158         if (http_tls_cfg.cipher)
1159                 ast_free(http_tls_cfg.cipher);
1160         http_tls_cfg.cipher = ast_strdup("");
1161
1162         AST_RWLIST_WRLOCK(&uri_redirects);
1163         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry)))
1164                 ast_free(redirect);
1165         AST_RWLIST_UNLOCK(&uri_redirects);
1166
1167         destroy_post_mappings();
1168
1169         if (cfg) {
1170                 v = ast_variable_browse(cfg, "general");
1171                 for (; v; v = v->next) {
1172                         if (!strcasecmp(v->name, "enabled"))
1173                                 enabled = ast_true(v->value);
1174                         else if (!strcasecmp(v->name, "sslenable"))
1175                                 http_tls_cfg.enabled = ast_true(v->value);
1176                         else if (!strcasecmp(v->name, "sslbindport"))
1177                                 https_desc.sin.sin_port = htons(atoi(v->value));
1178                         else if (!strcasecmp(v->name, "sslcert")) {
1179                                 ast_free(http_tls_cfg.certfile);
1180                                 http_tls_cfg.certfile = ast_strdup(v->value);
1181                         } else if (!strcasecmp(v->name, "sslcipher")) {
1182                                 ast_free(http_tls_cfg.cipher);
1183                                 http_tls_cfg.cipher = ast_strdup(v->value);
1184                         }
1185                         else if (!strcasecmp(v->name, "enablestatic"))
1186                                 newenablestatic = ast_true(v->value);
1187                         else if (!strcasecmp(v->name, "bindport"))
1188                                 http_desc.sin.sin_port = htons(atoi(v->value));
1189                         else if (!strcasecmp(v->name, "sslbindaddr")) {
1190                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1191                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
1192                                         have_sslbindaddr = 1;
1193                                 } else {
1194                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1195                                 }
1196                         } else if (!strcasecmp(v->name, "bindaddr")) {
1197                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1198                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
1199                                 } else {
1200                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1201                                 }
1202                         } else if (!strcasecmp(v->name, "prefix")) {
1203                                 if (!ast_strlen_zero(v->value)) {
1204                                         newprefix[0] = '/';
1205                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1206                                 } else {
1207                                         newprefix[0] = '\0';
1208                                 }
1209                         } else if (!strcasecmp(v->name, "redirect")) {
1210                                 add_redirect(v->value);
1211                         } else {
1212                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1213                         }
1214                 }
1215
1216                 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
1217                         add_post_mapping(v->name, v->value);
1218
1219                 ast_config_destroy(cfg);
1220         }
1221         if (!have_sslbindaddr)
1222                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
1223         if (enabled)
1224                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
1225         if (strcmp(prefix, newprefix))
1226                 ast_copy_string(prefix, newprefix, sizeof(prefix));
1227         enablestatic = newenablestatic;
1228         server_start(&http_desc);
1229         if (ssl_setup(https_desc.tls_cfg))
1230                 server_start(&https_desc);
1231
1232         return 0;
1233 }
1234
1235 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1236 {
1237         struct ast_http_uri *urih;
1238         struct http_uri_redirect *redirect;
1239         struct ast_http_post_mapping *post_map;
1240         switch (cmd) {
1241         case CLI_INIT:
1242                 e->command = "http show status";
1243                 e->usage = 
1244                         "Usage: http show status\n"
1245                         "       Lists status of internal HTTP engine\n";
1246                 return NULL;
1247         case CLI_GENERATE:
1248                 return NULL;
1249         }
1250         
1251         if (a->argc != 3)
1252                 return CLI_SHOWUSAGE;
1253         ast_cli(a->fd, "HTTP Server Status:\n");
1254         ast_cli(a->fd, "Prefix: %s\n", prefix);
1255         if (!http_desc.oldsin.sin_family)
1256                 ast_cli(a->fd, "Server Disabled\n\n");
1257         else {
1258                 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
1259                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
1260                         ntohs(http_desc.oldsin.sin_port));
1261                 if (http_tls_cfg.enabled)
1262                         ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1263                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
1264                                 ntohs(https_desc.oldsin.sin_port));
1265         }
1266
1267         ast_cli(a->fd, "Enabled URI's:\n");
1268         AST_RWLIST_RDLOCK(&uris);
1269         if (AST_RWLIST_EMPTY(&uris)) {
1270                 ast_cli(a->fd, "None.\n");
1271         } else {
1272                 AST_RWLIST_TRAVERSE(&uris, urih, entry)
1273                         ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
1274         }
1275         AST_RWLIST_UNLOCK(&uris);
1276
1277         ast_cli(a->fd, "\nEnabled Redirects:\n");
1278         AST_RWLIST_RDLOCK(&uri_redirects);
1279         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
1280                 ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
1281         if (AST_RWLIST_EMPTY(&uri_redirects))
1282                 ast_cli(a->fd, "  None.\n");
1283         AST_RWLIST_UNLOCK(&uri_redirects);
1284
1285
1286         ast_cli(a->fd, "\nPOST mappings:\n");
1287         AST_RWLIST_RDLOCK(&post_mappings);
1288         AST_LIST_TRAVERSE(&post_mappings, post_map, entry)
1289                 ast_cli(a->fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
1290         ast_cli(a->fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
1291         AST_RWLIST_UNLOCK(&post_mappings);
1292
1293         return CLI_SUCCESS;
1294 }
1295
1296 int ast_http_reload(void)
1297 {
1298         return __ast_http_load(1);
1299 }
1300
1301 static struct ast_cli_entry cli_http[] = {
1302         NEW_CLI(handle_show_http, "Display HTTP server status"),
1303 };
1304
1305 int ast_http_init(void)
1306 {
1307         mm_library_init();
1308         mm_codec_registerdefaultcodecs();
1309
1310         ast_http_uri_link(&statusuri);
1311         ast_http_uri_link(&staticuri);
1312         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1313
1314         return __ast_http_load(0);
1315 }