d8471740cfcd8c54f2c8ed53229d43ecd059d039
[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 server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
149 {
150         char *path;
151         char *ftype;
152         const char *mtype;
153         char wkspace[80];
154         struct stat st;
155         int len;
156         int fd;
157         time_t t;
158         char buf[256];
159
160         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
161            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
162         if (!enablestatic || ast_strlen_zero(uri))
163                 goto out403;
164         /* Disallow any funny filenames at all */
165         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
166                 goto out403;
167         if (strstr(uri, "/.."))
168                 goto out403;
169                 
170         if ((ftype = strrchr(uri, '.')))
171                 ftype++;
172         mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
173         
174         /* Cap maximum length */
175         len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
176         if (len > 1024)
177                 goto out403;
178                 
179         path = alloca(len);
180         sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
181         if (stat(path, &st))
182                 goto out404;
183         if (S_ISDIR(st.st_mode))
184                 goto out404;
185         fd = open(path, O_RDONLY);
186         if (fd < 0)
187                 goto out403;
188
189         time(&t);
190         strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
191         fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
192                 "Server: Asterisk/%s\r\n"
193                 "Date: %s\r\n"
194                 "Connection: close\r\n"
195                 "Cache-Control: no-cache, no-store\r\n"
196                 "Content-Length: %d\r\n"
197                 "Content-type: %s\r\n\r\n",
198                 ASTERISK_VERSION, buf, (int) st.st_size, mtype);
199
200         while ((len = read(fd, buf, sizeof(buf))) > 0)
201                 fwrite(buf, 1, len, ser->f);
202
203         close(fd);
204         return NULL;
205
206 out404:
207         *status = 404;
208         *title = ast_strdup("Not Found");
209         return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
210
211 out403:
212         *status = 403;
213         *title = ast_strdup("Access Denied");
214         return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
215 }
216
217
218 static struct ast_str *httpstatus_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
219 {
220         struct ast_str *out = ast_str_create(512);
221         struct ast_variable *v;
222
223         if (out == NULL)
224                 return out;
225
226         ast_str_append(&out, 0,
227                 "\r\n"
228                 "<title>Asterisk HTTP Status</title>\r\n"
229                 "<body bgcolor=\"#ffffff\">\r\n"
230                 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
231                 "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
232
233         ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
234         ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
235                         ast_inet_ntoa(http_desc.oldsin.sin_addr));
236         ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
237                         ntohs(http_desc.oldsin.sin_port));
238         if (http_tls_cfg.enabled)
239                 ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
240                         ntohs(https_desc.oldsin.sin_port));
241         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
242         for (v = vars; v; v = v->next) {
243                 if (strncasecmp(v->name, "cookie_", 7))
244                         ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
245         }
246         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
247         for (v = vars; v; v = v->next) {
248                 if (!strncasecmp(v->name, "cookie_", 7))
249                         ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
250         }
251         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");
252         return out;
253 }
254
255 static struct ast_http_uri statusuri = {
256         .callback = httpstatus_callback,
257         .description = "Asterisk HTTP General Status",
258         .uri = "httpstatus",
259         .has_subtree = 0,
260 };
261         
262 static struct ast_http_uri staticuri = {
263         .callback = static_callback,
264         .description = "Asterisk HTTP Static Delivery",
265         .uri = "static",
266         .has_subtree = 1,
267         .static_content = 1,
268 };
269         
270 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
271 {
272         struct ast_str *out = ast_str_create(512);
273         if (out == NULL)
274                 return out;
275         ast_str_set(&out, 0,
276                 "Content-type: text/html\r\n"
277                 "%s"
278                 "\r\n"
279                 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
280                 "<html><head>\r\n"
281                 "<title>%d %s</title>\r\n"
282                 "</head><body>\r\n"
283                 "<h1>%s</h1>\r\n"
284                 "<p>%s</p>\r\n"
285                 "<hr />\r\n"
286                 "<address>Asterisk Server</address>\r\n"
287                 "</body></html>\r\n",
288                         (extra_header ? extra_header : ""), status, title, title, text);
289         return out;
290 }
291
292 /*! \brief 
293  * Link the new uri into the list. 
294  *
295  * They are sorted by length of
296  * the string, not alphabetically. Duplicate entries are not replaced,
297  * but the insertion order (using <= and not just <) makes sure that
298  * more recent insertions hide older ones.
299  * On a lookup, we just scan the list and stop at the first matching entry.
300  */
301 int ast_http_uri_link(struct ast_http_uri *urih)
302 {
303         struct ast_http_uri *uri;
304         int len = strlen(urih->uri);
305
306         AST_RWLIST_WRLOCK(&uris);
307
308         if ( AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len ) {
309                 AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
310                 AST_RWLIST_UNLOCK(&uris);
311                 return 0;
312         }
313
314         AST_RWLIST_TRAVERSE(&uris, uri, entry) {
315                 if ( AST_RWLIST_NEXT(uri, entry) 
316                         && strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len ) {
317                         AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
318                         AST_RWLIST_UNLOCK(&uris); 
319                         return 0;
320                 }
321         }
322
323         AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
324
325         AST_RWLIST_UNLOCK(&uris);
326         
327         return 0;
328 }       
329
330 void ast_http_uri_unlink(struct ast_http_uri *urih)
331 {
332         AST_RWLIST_WRLOCK(&uris);
333         AST_RWLIST_REMOVE(&uris, urih, entry);
334         AST_RWLIST_UNLOCK(&uris);
335 }
336
337 /*! \note This assumes that the post_mappings list is locked */
338 static struct ast_http_post_mapping *find_post_mapping(const char *uri)
339 {
340         struct ast_http_post_mapping *post_map;
341
342         if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
343                 ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
344                 return NULL;
345         }
346
347         uri += strlen(prefix);
348         if (*uri == '/')
349                 uri++;
350         
351         AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
352                 if (!strcmp(uri, post_map->from))
353                         return post_map;
354         }
355
356         return NULL;
357 }
358
359 static int get_filename(struct mm_mimepart *part, char *fn, size_t fn_len)
360 {
361         const char *filename;
362
363         filename = mm_content_getdispositionparambyname(part->type, "filename");
364
365         if (ast_strlen_zero(filename))
366                 return -1;
367
368         ast_copy_string(fn, filename, fn_len);
369
370         return 0;
371 }
372
373 static void post_raw(struct mm_mimepart *part, const char *post_dir, const char *fn)
374 {
375         char filename[PATH_MAX];
376         FILE *f;
377         const char *body;
378         size_t body_len;
379
380         snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
381
382         ast_debug(1, "Posting raw data to %s\n", filename);
383
384         if (!(f = fopen(filename, "w"))) {
385                 ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
386                 return;
387         }
388
389         if (!(body = mm_mimepart_getbody(part, 0))) {
390                 ast_debug(1, "Couldn't get the mimepart body\n");
391                 fclose(f);
392                 return;
393         }
394         body_len = mm_mimepart_getlength(part);
395
396         ast_debug(1, "Body length is %ld\n", (long int)body_len);
397
398         fwrite(body, 1, body_len, f);
399
400         fclose(f);
401 }
402
403 static struct ast_str *handle_post(struct server_instance *ser, char *uri, 
404         int *status, char **title, int *contentlength, struct ast_variable *headers,
405         struct ast_variable *cookies)
406 {
407         char buf;
408         FILE *f;
409         size_t res;
410         struct ast_variable *var;
411         int content_len = 0;
412         MM_CTX *ctx;
413         int mm_res, i;
414         struct ast_http_post_mapping *post_map;
415         const char *post_dir;
416         unsigned long ident = 0;
417
418         for (var = cookies; var; var = var->next) {
419                 if (strcasecmp(var->name, "mansession_id"))
420                         continue;
421
422                 if (sscanf(var->value, "%lx", &ident) != 1) {
423                         *status = 400;
424                         *title = ast_strdup("Bad Request");
425                         return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
426                 }
427
428                 if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
429                         *status = 401;
430                         *title = ast_strdup("Unauthorized");
431                         return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
432                 }
433
434                 break;
435         }
436         if (!var) {
437                 *status = 401;
438                 *title = ast_strdup("Unauthorized");
439                 return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
440         }
441
442         if (!(f = tmpfile()))
443                 return NULL;
444
445         for (var = headers; var; var = var->next) {
446                 if (!strcasecmp(var->name, "Content-Length")) {
447                         if ((sscanf(var->value, "%u", &content_len)) != 1) {
448                                 ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
449                                 fclose(f);
450                                 return NULL;
451                         }
452                         ast_debug(1, "Got a Content-Length of %d\n", content_len);
453                 } else if (!strcasecmp(var->name, "Content-Type"))
454                         fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
455         }
456
457         while ((res = fread(&buf, 1, 1, ser->f))) {
458                 fwrite(&buf, 1, 1, f);
459                 content_len--;
460                 if (!content_len)
461                         break;
462         }
463
464         if (fseek(f, SEEK_SET, 0)) {
465                 ast_debug(1, "Failed to seek temp file back to beginning.\n");
466                 fclose(f);
467                 return NULL;
468         }
469
470         AST_RWLIST_RDLOCK(&post_mappings);
471         if (!(post_map = find_post_mapping(uri))) {
472                 ast_debug(1, "%s is not a valid URI for POST\n", uri);
473                 AST_RWLIST_UNLOCK(&post_mappings);
474                 fclose(f);
475                 *status = 404;
476                 *title = ast_strdup("Not Found");
477                 return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
478         }
479         post_dir = ast_strdupa(post_map->to);
480         post_map = NULL;
481         AST_RWLIST_UNLOCK(&post_mappings);
482
483         ast_debug(1, "Going to post files to dir %s\n", post_dir);
484
485         if (!(ctx = mm_context_new())) {
486                 fclose(f);
487                 return NULL;
488         }
489
490         mm_res = mm_parse_fileptr(ctx, f, MM_PARSE_LOOSE, 0);
491         fclose(f);
492         if (mm_res == -1) {
493                 ast_log(LOG_ERROR, "Error parsing MIME data\n");
494                 mm_context_free(ctx);
495                 *status = 400;
496                 *title = ast_strdup("Bad Request");
497                 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
498         }
499
500         mm_res = mm_context_countparts(ctx);
501         if (!mm_res) {
502                 ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
503                 mm_context_free(ctx);
504                 *status = 400;
505                 *title = ast_strdup("Bad Request");
506                 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
507         }
508
509         if (option_debug) {
510                 if (mm_context_iscomposite(ctx))
511                         ast_debug(1, "Found %d MIME parts\n", mm_res - 1);
512                 else
513                         ast_debug(1, "We have a flat (not multi-part) message\n");
514         }
515
516         for (i = 1; i < mm_res; i++) {
517                 struct mm_mimepart *part;
518                 char fn[PATH_MAX];
519
520                 if (!(part = mm_context_getpart(ctx, i))) {
521                         ast_debug(1, "Failed to get mime part num %d\n", i);
522                         continue;
523                 }
524
525                 if (get_filename(part, fn, sizeof(fn))) {
526                         ast_debug(1, "Failed to retrieve a filename for part num %d\n", i);
527                         continue;
528                 }
529         
530                 if (!part->type) {
531                         ast_debug(1, "This part has no content struct?\n");
532                         continue;
533                 }
534
535                 /* XXX This assumes the MIME part body is not encoded! */
536                 post_raw(part, post_dir, fn);
537         }
538
539         mm_context_free(ctx);
540
541         *status = 200;
542         *title = ast_strdup("OK");
543         return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
544 }
545
546 static struct ast_str *handle_uri(struct server_instance *ser, char *uri, int *status, 
547         char **title, int *contentlength, struct ast_variable **cookies, 
548         unsigned int *static_content)
549 {
550         char *c;
551         struct ast_str *out = NULL;
552         char *params = uri;
553         struct ast_http_uri *urih=NULL;
554         int l;
555         struct ast_variable *vars=NULL, *v, *prev = NULL;
556         struct http_uri_redirect *redirect;
557
558         strsep(&params, "?");
559         /* Extract arguments from the request and store them in variables. */
560         if (params) {
561                 char *var, *val;
562
563                 while ((val = strsep(&params, "&"))) {
564                         var = strsep(&val, "=");
565                         if (val)
566                                 ast_uri_decode(val);
567                         else 
568                                 val = "";
569                         ast_uri_decode(var);
570                         if ((v = ast_variable_new(var, val))) {
571                                 if (vars)
572                                         prev->next = v;
573                                 else
574                                         vars = v;
575                                 prev = v;
576                         }
577                 }
578         }
579         /*
580          * Append the cookies to the variables (the only reason to have them
581          * at the end is to avoid another pass of the cookies list to find
582          * the tail).
583          */
584         if (prev)
585                 prev->next = *cookies;
586         else
587                 vars = *cookies;
588         *cookies = NULL;
589         ast_uri_decode(uri);
590
591         AST_RWLIST_RDLOCK(&uri_redirects);
592         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
593                 if (!strcasecmp(uri, redirect->target)) {
594                         char buf[512];
595                         snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
596                         out = ast_http_error(302, "Moved Temporarily", buf,
597                                 "There is no spoon...");
598                         *status = 302;
599                         *title = ast_strdup("Moved Temporarily");
600                         break;
601                 }
602         }
603         AST_RWLIST_UNLOCK(&uri_redirects);
604         if (redirect)
605                 goto cleanup;
606
607         /* We want requests to start with the prefix and '/' */
608         l = strlen(prefix);
609         if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
610                 uri += l + 1;
611                 /* scan registered uris to see if we match one. */
612                 AST_RWLIST_RDLOCK(&uris);
613                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
614                         l = strlen(urih->uri);
615                         c = uri + l;    /* candidate */
616                         if (strncasecmp(urih->uri, uri, l) /* no match */
617                             || (*c && *c != '/')) /* substring */
618                                 continue;
619                         if (*c == '/')
620                                 c++;
621                         if (!*c || urih->has_subtree) {
622                                 uri = c;
623                                 break;
624                         }
625                 }
626                 if (!urih)
627                         AST_RWLIST_UNLOCK(&uris);
628         }
629         if (urih) {
630                 if (urih->static_content)
631                         *static_content = 1;
632                 out = urih->callback(ser, uri, vars, status, title, contentlength);
633                 AST_RWLIST_UNLOCK(&uris);
634         } else {
635                 out = ast_http_error(404, "Not Found", NULL,
636                         "The requested URL was not found on this server.");
637                 *status = 404;
638                 *title = ast_strdup("Not Found");
639         }
640
641 cleanup:
642         ast_variables_destroy(vars);
643         return out;
644 }
645
646 #ifdef DO_SSL
647 #if defined(HAVE_FUNOPEN)
648 #define HOOK_T int
649 #define LEN_T int
650 #else
651 #define HOOK_T ssize_t
652 #define LEN_T size_t
653 #endif
654 /*!
655  * replacement read/write functions for SSL support.
656  * We use wrappers rather than SSL_read/SSL_write directly so
657  * we can put in some debugging.
658  */
659 static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
660 {
661         int i = SSL_read(cookie, buf, len-1);
662 #if 0
663         if (i >= 0)
664                 buf[i] = '\0';
665         ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
666 #endif
667         return i;
668 }
669
670 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
671 {
672 #if 0
673         char *s = alloca(len+1);
674         strncpy(s, buf, len);
675         s[len] = '\0';
676         ast_verbose("ssl write size %d <%s>\n", (int)len, s);
677 #endif
678         return SSL_write(cookie, buf, len);
679 }
680
681 static int ssl_close(void *cookie)
682 {
683         close(SSL_get_fd(cookie));
684         SSL_shutdown(cookie);
685         SSL_free(cookie);
686         return 0;
687 }
688 #endif  /* DO_SSL */
689
690 /*!
691  * creates a FILE * from the fd passed by the accept thread.
692  * This operation is potentially expensive (certificate verification),
693  * so we do it in the child thread context.
694  */
695 static void *make_file_from_fd(void *data)
696 {
697         struct server_instance *ser = data;
698
699         /*
700          * open a FILE * as appropriate.
701          */
702         if (!ser->parent->tls_cfg)
703                 ser->f = fdopen(ser->fd, "w+");
704 #ifdef DO_SSL
705         else if ( (ser->ssl = SSL_new(ser->parent->tls_cfg->ssl_ctx)) ) {
706                 SSL_set_fd(ser->ssl, ser->fd);
707                 if (SSL_accept(ser->ssl) == 0)
708                         ast_verbose(" error setting up ssl connection");
709                 else {
710 #if defined(HAVE_FUNOPEN)       /* the BSD interface */
711                         ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close);
712
713 #elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */
714                         static const cookie_io_functions_t cookie_funcs = {
715                                 ssl_read, ssl_write, NULL, ssl_close
716                         };
717                         ser->f = fopencookie(ser->ssl, "w+", cookie_funcs);
718 #else
719                         /* could add other methods here */
720 #endif
721                 }
722                 if (!ser->f)    /* no success opening descriptor stacking */
723                         SSL_free(ser->ssl);
724         }
725 #endif /* DO_SSL */
726
727         if (!ser->f) {
728                 close(ser->fd);
729                 ast_log(LOG_WARNING, "FILE * open failed!\n");
730                 ast_free(ser);
731                 return NULL;
732         }
733         return ser->parent->worker_fn(ser);
734 }
735
736 static void *httpd_helper_thread(void *data)
737 {
738         char buf[4096];
739         char cookie[4096];
740         struct server_instance *ser = data;
741         struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
742         char *uri, *title=NULL;
743         int status = 200, contentlength = 0;
744         struct ast_str *out = NULL;
745         unsigned int static_content = 0;
746
747         if (!fgets(buf, sizeof(buf), ser->f))
748                 goto done;
749
750         uri = ast_skip_nonblanks(buf);  /* Skip method */
751         if (*uri)
752                 *uri++ = '\0';
753
754         uri = ast_skip_blanks(uri);     /* Skip white space */
755
756         if (*uri) {                     /* terminate at the first blank */
757                 char *c = ast_skip_nonblanks(uri);
758                 if (*c)
759                         *c = '\0';
760         }
761
762         /* process "Cookie: " lines */
763         while (fgets(cookie, sizeof(cookie), ser->f)) {
764                 char *vname, *vval;
765                 int l;
766
767                 /* Trim trailing characters */
768                 ast_trim_blanks(cookie);
769                 if (ast_strlen_zero(cookie))
770                         break;
771                 if (strncasecmp(cookie, "Cookie: ", 8)) {
772                         char *name, *value;
773
774                         value = ast_strdupa(cookie);
775                         name = strsep(&value, ":");
776                         if (!value)
777                                 continue;
778                         value = ast_skip_blanks(value);
779                         if (ast_strlen_zero(value))
780                                 continue;
781                         var = ast_variable_new(name, value);
782                         if (!var)
783                                 continue;
784                         var->next = headers;
785                         headers = var;
786                         continue;
787                 }
788
789                 /* TODO - The cookie parsing code below seems to work   
790                    in IE6 and FireFox 1.5.  However, it is not entirely 
791                    correct, and therefore may not work in all           
792                    circumstances.                                       
793                       For more details see RFC 2109 and RFC 2965        */
794         
795                 /* FireFox cookie strings look like:                    
796                      Cookie: mansession_id="********"                   
797                    InternetExplorer's look like:                        
798                      Cookie: $Version="1"; mansession_id="********"     */
799                 
800                 /* If we got a FireFox cookie string, the name's right  
801                     after "Cookie: "                                    */
802                 vname = ast_skip_blanks(cookie + 8);
803                         
804                 /* If we got an IE cookie string, we need to skip to    
805                     past the version to get to the name                 */
806                 if (*vname == '$') {
807                         strsep(&vname, ";");
808                         if (!vname)     /* no name ? */
809                                 continue;
810                         vname = ast_skip_blanks(vname);
811                 }
812                 vval = strchr(vname, '=');
813                 if (!vval)
814                         continue;
815                 /* Ditch the = and the quotes */
816                 *vval++ = '\0';
817                 if (*vval)
818                         vval++;
819                 if ( (l = strlen(vval)) )
820                         vval[l - 1] = '\0';     /* trim trailing quote */
821                 var = ast_variable_new(vname, vval);
822                 if (var) {
823                         if (prev)
824                                 prev->next = var;
825                         else
826                                 vars = var;
827                         prev = var;
828                 }
829         }
830
831         if (!*uri)
832                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
833         else if (!strcasecmp(buf, "post")) 
834                 out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
835         else if (strcasecmp(buf, "get")) 
836                 out = ast_http_error(501, "Not Implemented", NULL,
837                         "Attempt to use unimplemented / unsupported method");
838         else    /* try to serve it */
839                 out = handle_uri(ser, uri, &status, &title, &contentlength, &vars, &static_content);
840
841         /* If they aren't mopped up already, clean up the cookies */
842         if (vars)
843                 ast_variables_destroy(vars);
844
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 }