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