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