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