Merge in ast_strftime branch, which changes timestamps to be accurate to the microsec...
[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         struct timeval tv = ast_tvnow();
158         char buf[256];
159         struct ast_tm tm;
160
161         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
162            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
163         if (!enablestatic || ast_strlen_zero(uri))
164                 goto out403;
165         /* Disallow any funny filenames at all */
166         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
167                 goto out403;
168         if (strstr(uri, "/.."))
169                 goto out403;
170                 
171         if ((ftype = strrchr(uri, '.')))
172                 ftype++;
173         mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
174         
175         /* Cap maximum length */
176         len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
177         if (len > 1024)
178                 goto out403;
179                 
180         path = alloca(len);
181         sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
182         if (stat(path, &st))
183                 goto out404;
184         if (S_ISDIR(st.st_mode))
185                 goto out404;
186         fd = open(path, O_RDONLY);
187         if (fd < 0)
188                 goto out403;
189
190         ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
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                 struct timeval tv = ast_tvnow();
847                 char timebuf[256];
848                 struct ast_tm tm;
849
850                 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
851                 fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
852                                 "Server: Asterisk/%s\r\n"
853                                 "Date: %s\r\n"
854                                 "Connection: close\r\n"
855                                 "%s",
856                         status, title ? title : "OK", ASTERISK_VERSION, timebuf,
857                         static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
858                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
859                         fprintf(ser->f, "%s", out->str);
860                 } else {
861                         char *tmp = strstr(out->str, "\r\n\r\n");
862
863                         if (tmp) {
864                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
865                                 /* first write the header, then the body */
866                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
867                                 fwrite(tmp + 4, 1, contentlength, ser->f);
868                         }
869                 }
870                 ast_free(out);
871         }
872         if (title)
873                 ast_free(title);
874
875 done:
876         fclose(ser->f);
877         ast_free(ser);
878         return NULL;
879 }
880
881 void *server_root(void *data)
882 {
883         struct server_args *desc = data;
884         int fd;
885         struct sockaddr_in sin;
886         socklen_t sinlen;
887         struct server_instance *ser;
888         pthread_t launched;
889         
890         for (;;) {
891                 int i, flags;
892
893                 if (desc->periodic_fn)
894                         desc->periodic_fn(desc);
895                 i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout);
896                 if (i <= 0)
897                         continue;
898                 sinlen = sizeof(sin);
899                 fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen);
900                 if (fd < 0) {
901                         if ((errno != EAGAIN) && (errno != EINTR))
902                                 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
903                         continue;
904                 }
905                 ser = ast_calloc(1, sizeof(*ser));
906                 if (!ser) {
907                         ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
908                         close(fd);
909                         continue;
910                 }
911                 flags = fcntl(fd, F_GETFL);
912                 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
913                 ser->fd = fd;
914                 ser->parent = desc;
915                 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
916                         
917                 if (ast_pthread_create_detached_background(&launched, NULL, make_file_from_fd, ser)) {
918                         ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
919                         close(ser->fd);
920                         ast_free(ser);
921                 }
922
923         }
924         return NULL;
925 }
926
927 int ssl_setup(struct tls_config *cfg)
928 {
929 #ifndef DO_SSL
930         cfg->enabled = 0;
931         return 0;
932 #else
933         if (!cfg->enabled)
934                 return 0;
935         SSL_load_error_strings();
936         SSLeay_add_ssl_algorithms();
937         cfg->ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
938         if (!ast_strlen_zero(cfg->certfile)) {
939                 if (SSL_CTX_use_certificate_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
940                     SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
941                     SSL_CTX_check_private_key(cfg->ssl_ctx) == 0 ) {
942                         ast_verbose("ssl cert error <%s>", cfg->certfile);
943                         sleep(2);
944                         cfg->enabled = 0;
945                         return 0;
946                 }
947         }
948         if (!ast_strlen_zero(cfg->cipher)) {
949                 if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) {
950                         ast_verbose("ssl cipher error <%s>", cfg->cipher);
951                         sleep(2);
952                         cfg->enabled = 0;
953                         return 0;
954                 }
955         }
956         ast_verbose("ssl cert ok");
957         return 1;
958 #endif
959 }
960
961 /*!
962  * This is a generic (re)start routine for a TCP server,
963  * which does the socket/bind/listen and starts a thread for handling
964  * accept().
965  */
966 void server_start(struct server_args *desc)
967 {
968         int flags;
969         int x = 1;
970         
971         /* Do nothing if nothing has changed */
972         if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
973                 ast_debug(1, "Nothing changed in %s\n", desc->name);
974                 return;
975         }
976         
977         desc->oldsin = desc->sin;
978         
979         /* Shutdown a running server if there is one */
980         if (desc->master != AST_PTHREADT_NULL) {
981                 pthread_cancel(desc->master);
982                 pthread_kill(desc->master, SIGURG);
983                 pthread_join(desc->master, NULL);
984         }
985         
986         if (desc->accept_fd != -1)
987                 close(desc->accept_fd);
988
989         /* If there's no new server, stop here */
990         if (desc->sin.sin_family == 0)
991                 return;
992
993         desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
994         if (desc->accept_fd < 0) {
995                 ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n",
996                         desc->name, strerror(errno));
997                 return;
998         }
999         
1000         setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
1001         if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {
1002                 ast_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n",
1003                         desc->name,
1004                         ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
1005                         strerror(errno));
1006                 goto error;
1007         }
1008         if (listen(desc->accept_fd, 10)) {
1009                 ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name);
1010                 goto error;
1011         }
1012         flags = fcntl(desc->accept_fd, F_GETFL);
1013         fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
1014         if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {
1015                 ast_log(LOG_NOTICE, "Unable to launch %s on %s:%d: %s\n",
1016                         desc->name,
1017                         ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
1018                         strerror(errno));
1019                 goto error;
1020         }
1021         return;
1022
1023 error:
1024         close(desc->accept_fd);
1025         desc->accept_fd = -1;
1026 }
1027
1028 /*!
1029  * \brief Add a new URI redirect
1030  * The entries in the redirect list are sorted by length, just like the list
1031  * of URI handlers.
1032  */
1033 static void add_redirect(const char *value)
1034 {
1035         char *target, *dest;
1036         struct http_uri_redirect *redirect, *cur;
1037         unsigned int target_len;
1038         unsigned int total_len;
1039
1040         dest = ast_strdupa(value);
1041         dest = ast_skip_blanks(dest);
1042         target = strsep(&dest, " ");
1043         target = ast_skip_blanks(target);
1044         target = strsep(&target, " "); /* trim trailing whitespace */
1045
1046         if (!dest) {
1047                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
1048                 return;
1049         }
1050
1051         target_len = strlen(target) + 1;
1052         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
1053
1054         if (!(redirect = ast_calloc(1, total_len)))
1055                 return;
1056
1057         redirect->dest = redirect->target + target_len;
1058         strcpy(redirect->target, target);
1059         strcpy(redirect->dest, dest);
1060
1061         AST_RWLIST_WRLOCK(&uri_redirects);
1062
1063         target_len--; /* So we can compare directly with strlen() */
1064         if ( AST_RWLIST_EMPTY(&uri_redirects) 
1065                 || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len ) {
1066                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
1067                 AST_RWLIST_UNLOCK(&uri_redirects);
1068                 return;
1069         }
1070
1071         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
1072                 if ( AST_RWLIST_NEXT(cur, entry) 
1073                         && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len ) {
1074                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
1075                         AST_RWLIST_UNLOCK(&uri_redirects); 
1076                         return;
1077                 }
1078         }
1079
1080         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
1081
1082         AST_RWLIST_UNLOCK(&uri_redirects);
1083 }
1084
1085 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
1086 {
1087         if (post_map->from)
1088                 ast_free(post_map->from);
1089         if (post_map->to)
1090                 ast_free(post_map->to);
1091         ast_free(post_map);
1092 }
1093
1094 static void destroy_post_mappings(void)
1095 {
1096         struct ast_http_post_mapping *post_map;
1097
1098         AST_RWLIST_WRLOCK(&post_mappings);
1099         while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry)))
1100                 destroy_post_mapping(post_map);
1101         AST_RWLIST_UNLOCK(&post_mappings);
1102 }
1103
1104 static void add_post_mapping(const char *from, const char *to)
1105 {
1106         struct ast_http_post_mapping *post_map;
1107
1108         if (!(post_map = ast_calloc(1, sizeof(*post_map))))
1109                 return;
1110
1111         if (!(post_map->from = ast_strdup(from))) {
1112                 destroy_post_mapping(post_map);
1113                 return;
1114         }
1115
1116         if (!(post_map->to = ast_strdup(to))) {
1117                 destroy_post_mapping(post_map);
1118                 return;
1119         }
1120
1121         AST_RWLIST_WRLOCK(&post_mappings);
1122         AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
1123         AST_RWLIST_UNLOCK(&post_mappings);
1124 }
1125
1126 static int __ast_http_load(int reload)
1127 {
1128         struct ast_config *cfg;
1129         struct ast_variable *v;
1130         int enabled=0;
1131         int newenablestatic=0;
1132         struct hostent *hp;
1133         struct ast_hostent ahp;
1134         char newprefix[MAX_PREFIX];
1135         int have_sslbindaddr = 0;
1136         struct http_uri_redirect *redirect;
1137
1138         /* default values */
1139         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
1140         http_desc.sin.sin_port = htons(8088);
1141
1142         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
1143         https_desc.sin.sin_port = htons(8089);
1144
1145         strcpy(newprefix, DEFAULT_PREFIX);
1146
1147         http_tls_cfg.enabled = 0;
1148         if (http_tls_cfg.certfile)
1149                 ast_free(http_tls_cfg.certfile);
1150         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
1151         if (http_tls_cfg.cipher)
1152                 ast_free(http_tls_cfg.cipher);
1153         http_tls_cfg.cipher = ast_strdup("");
1154
1155         AST_RWLIST_WRLOCK(&uri_redirects);
1156         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry)))
1157                 ast_free(redirect);
1158         AST_RWLIST_UNLOCK(&uri_redirects);
1159
1160         destroy_post_mappings();
1161
1162         cfg = ast_config_load("http.conf");
1163         if (cfg) {
1164                 v = ast_variable_browse(cfg, "general");
1165                 for (; v; v = v->next) {
1166                         if (!strcasecmp(v->name, "enabled"))
1167                                 enabled = ast_true(v->value);
1168                         else if (!strcasecmp(v->name, "sslenable"))
1169                                 http_tls_cfg.enabled = ast_true(v->value);
1170                         else if (!strcasecmp(v->name, "sslbindport"))
1171                                 https_desc.sin.sin_port = htons(atoi(v->value));
1172                         else if (!strcasecmp(v->name, "sslcert")) {
1173                                 ast_free(http_tls_cfg.certfile);
1174                                 http_tls_cfg.certfile = ast_strdup(v->value);
1175                         } else if (!strcasecmp(v->name, "sslcipher")) {
1176                                 ast_free(http_tls_cfg.cipher);
1177                                 http_tls_cfg.cipher = ast_strdup(v->value);
1178                         }
1179                         else if (!strcasecmp(v->name, "enablestatic"))
1180                                 newenablestatic = ast_true(v->value);
1181                         else if (!strcasecmp(v->name, "bindport"))
1182                                 http_desc.sin.sin_port = htons(atoi(v->value));
1183                         else if (!strcasecmp(v->name, "sslbindaddr")) {
1184                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1185                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
1186                                         have_sslbindaddr = 1;
1187                                 } else {
1188                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1189                                 }
1190                         } else if (!strcasecmp(v->name, "bindaddr")) {
1191                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1192                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
1193                                 } else {
1194                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1195                                 }
1196                         } else if (!strcasecmp(v->name, "prefix")) {
1197                                 if (!ast_strlen_zero(v->value)) {
1198                                         newprefix[0] = '/';
1199                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1200                                 } else {
1201                                         newprefix[0] = '\0';
1202                                 }
1203                         } else if (!strcasecmp(v->name, "redirect")) {
1204                                 add_redirect(v->value);
1205                         } else {
1206                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1207                         }
1208                 }
1209
1210                 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
1211                         add_post_mapping(v->name, v->value);
1212
1213                 ast_config_destroy(cfg);
1214         }
1215         if (!have_sslbindaddr)
1216                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
1217         if (enabled)
1218                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
1219         if (strcmp(prefix, newprefix))
1220                 ast_copy_string(prefix, newprefix, sizeof(prefix));
1221         enablestatic = newenablestatic;
1222         server_start(&http_desc);
1223         if (ssl_setup(https_desc.tls_cfg))
1224                 server_start(&https_desc);
1225
1226         return 0;
1227 }
1228
1229 static int handle_show_http(int fd, int argc, char *argv[])
1230 {
1231         struct ast_http_uri *urih;
1232         struct http_uri_redirect *redirect;
1233         struct ast_http_post_mapping *post_map;
1234
1235         if (argc != 3)
1236                 return RESULT_SHOWUSAGE;
1237
1238         ast_cli(fd, "HTTP Server Status:\n");
1239         ast_cli(fd, "Prefix: %s\n", prefix);
1240         if (!http_desc.oldsin.sin_family)
1241                 ast_cli(fd, "Server Disabled\n\n");
1242         else {
1243                 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
1244                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
1245                         ntohs(http_desc.oldsin.sin_port));
1246                 if (http_tls_cfg.enabled)
1247                         ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1248                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
1249                                 ntohs(https_desc.oldsin.sin_port));
1250         }
1251
1252         ast_cli(fd, "Enabled URI's:\n");
1253         AST_RWLIST_RDLOCK(&uris);
1254         if (AST_RWLIST_EMPTY(&uris)) {
1255                 ast_cli(fd, "None.\n");
1256         } else {
1257                 AST_RWLIST_TRAVERSE(&uris, urih, entry)
1258                         ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
1259         }
1260         AST_RWLIST_UNLOCK(&uris);
1261
1262         ast_cli(fd, "\nEnabled Redirects:\n");
1263         AST_RWLIST_RDLOCK(&uri_redirects);
1264         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
1265                 ast_cli(fd, "  %s => %s\n", redirect->target, redirect->dest);
1266         if (AST_RWLIST_EMPTY(&uri_redirects))
1267                 ast_cli(fd, "  None.\n");
1268         AST_RWLIST_UNLOCK(&uri_redirects);
1269
1270
1271         ast_cli(fd, "\nPOST mappings:\n");
1272         AST_RWLIST_RDLOCK(&post_mappings);
1273         AST_LIST_TRAVERSE(&post_mappings, post_map, entry)
1274                 ast_cli(fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
1275         ast_cli(fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
1276         AST_RWLIST_UNLOCK(&post_mappings);
1277
1278         return RESULT_SUCCESS;
1279 }
1280
1281 int ast_http_reload(void)
1282 {
1283         return __ast_http_load(1);
1284 }
1285
1286 static char show_http_help[] =
1287 "Usage: http show status\n"
1288 "       Lists status of internal HTTP engine\n";
1289
1290 static struct ast_cli_entry cli_http[] = {
1291         { { "http", "show", "status", NULL },
1292         handle_show_http, "Display HTTP server status",
1293         show_http_help },
1294 };
1295
1296 int ast_http_init(void)
1297 {
1298         mm_library_init();
1299         mm_codec_registerdefaultcodecs();
1300
1301         ast_http_uri_link(&statusuri);
1302         ast_http_uri_link(&staticuri);
1303         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1304
1305         return __ast_http_load(0);
1306 }