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