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