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