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