14293c5d0fac79afb0364bab02c5b79ed7865182
[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  * This program implements a tiny http server supporting the "get" method
25  * only and was inspired by micro-httpd by Jef Poskanzer 
26  * 
27  * \ref AstHTTP - AMI over the http protocol
28  */
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include <sys/types.h>
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <time.h>
39 #include <string.h>
40 #include <netinet/in.h>
41 #include <sys/time.h>
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 #include <sys/signal.h>
45 #include <arpa/inet.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <pthread.h>
49
50 #include "asterisk/cli.h"
51 #include "asterisk/http.h"
52 #include "asterisk/utils.h"
53 #include "asterisk/strings.h"
54 #include "asterisk/options.h"
55 #include "asterisk/config.h"
56 #include "asterisk/stringfields.h"
57
58 #define MAX_PREFIX 80
59 #define DEFAULT_PREFIX "/asterisk"
60
61 /*!
62  * In order to have TLS/SSL support, we need the openssl libraries.
63  * Still we can decide whether or not to use them by commenting
64  * in or out the DO_SSL macro.
65  * TLS/SSL support is basically implemented by reading from a config file
66  * (currently http.conf) the names of the certificate and cipher to use,
67  * and then run ssl_setup() to create an appropriate SSL_CTX (ssl_ctx)
68  * If we support multiple domains, presumably we need to read multiple
69  * certificates.
70  * When we are requested to open a TLS socket, we run make_file_from_fd()
71  * on the socket, to do the necessary setup. At the moment the context's name
72  * is hardwired in the function, but we can certainly make it into an extra
73  * parameter to the function.
74  *
75  * We declare most of ssl support variables unconditionally,
76  * because their number is small and this simplifies the code.
77  */
78
79 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
80 #define DO_SSL  /* comment in/out if you want to support ssl */
81 #endif
82
83 static struct tls_config http_tls_cfg;
84
85 static void *httpd_helper_thread(void *arg);
86
87 /*!
88  * we have up to two accepting threads, one for http, one for https
89  */
90 static struct server_args http_desc = {
91         .accept_fd = -1,
92         .master = AST_PTHREADT_NULL,
93         .tls_cfg = NULL,
94         .poll_timeout = -1,
95         .name = "http server",
96         .accept_fn = server_root,
97         .worker_fn = httpd_helper_thread,
98 };
99
100 static struct server_args https_desc = {
101         .accept_fd = -1,
102         .master = AST_PTHREADT_NULL,
103         .tls_cfg = &http_tls_cfg,
104         .poll_timeout = -1,
105         .name = "https server",
106         .accept_fn = server_root,
107         .worker_fn = httpd_helper_thread,
108 };
109
110 static AST_LIST_HEAD_STATIC(uris, ast_http_uri);        /*!< list of supported handlers */
111
112 /* all valid URIs must be prepended by the string in prefix. */
113 static char prefix[MAX_PREFIX];
114 static int enablestatic=0;
115
116 /*! \brief Limit the kinds of files we're willing to serve up */
117 static struct {
118         char *ext;
119         char *mtype;
120 } mimetypes[] = {
121         { "png", "image/png" },
122         { "jpg", "image/jpeg" },
123         { "js", "application/x-javascript" },
124         { "wav", "audio/x-wav" },
125         { "mp3", "audio/mpeg" },
126 };
127
128 struct http_uri_redirect {
129         AST_LIST_ENTRY(http_uri_redirect) entry;
130         char *dest;
131         char target[0];
132 };
133
134 static AST_LIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
135
136 static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
137 {
138         int x;
139         if (ftype) {
140                 for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
141                         if (!strcasecmp(ftype, mimetypes[x].ext))
142                                 return mimetypes[x].mtype;
143                 }
144         }
145         snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
146         return wkspace;
147 }
148
149 /* like ast_uri_decode, but replace '+' with ' ' */
150 static char *uri_decode(char *buf)
151 {
152         char *c;
153         ast_uri_decode(buf);
154         for (c = buf; *c; c++) {
155                 if (*c == '+')
156                         *c = ' ';
157         }
158         return buf;
159 }
160 static struct ast_str *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
161 {
162         struct ast_str *result;
163         char *path;
164         char *ftype, *mtype;
165         char wkspace[80];
166         struct stat st;
167         int len;
168         int fd;
169
170         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
171            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
172         if (!enablestatic || ast_strlen_zero(uri))
173                 goto out403;
174         /* Disallow any funny filenames at all */
175         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
176                 goto out403;
177         if (strstr(uri, "/.."))
178                 goto out403;
179                 
180         if ((ftype = strrchr(uri, '.')))
181                 ftype++;
182         mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
183         
184         /* Cap maximum length */
185         len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
186         if (len > 1024)
187                 goto out403;
188                 
189         path = alloca(len);
190         sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
191         if (stat(path, &st))
192                 goto out404;
193         if (S_ISDIR(st.st_mode))
194                 goto out404;
195         fd = open(path, O_RDONLY);
196         if (fd < 0)
197                 goto out403;
198
199         len = st.st_size + strlen(mtype) + 40;
200         result = ast_str_create(len);
201         if (result == NULL)     /* XXX not really but... */
202                 goto out403;
203
204         ast_str_append(&result, 0, "Content-type: %s\r\n\r\n", mtype);
205         *contentlength = read(fd, result->str + result->used, st.st_size);
206         if (*contentlength < 0) {
207                 close(fd);
208                 free(result);
209                 goto out403;
210         }
211         result->used += *contentlength;
212         close(fd);
213         return result;
214
215 out404:
216         *status = 404;
217         *title = strdup("Not Found");
218         return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
219
220 out403:
221         *status = 403;
222         *title = strdup("Access Denied");
223         return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
224 }
225
226
227 static struct ast_str *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
228 {
229         struct ast_str *out = ast_str_create(512);
230         struct ast_variable *v;
231
232         if (out == NULL)
233                 return out;
234
235         ast_str_append(&out, 0,
236                 "\r\n"
237                 "<title>Asterisk HTTP Status</title>\r\n"
238                 "<body bgcolor=\"#ffffff\">\r\n"
239                 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
240                 "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
241
242         ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
243         ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
244                         ast_inet_ntoa(http_desc.oldsin.sin_addr));
245         ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
246                         ntohs(http_desc.oldsin.sin_port));
247         if (http_tls_cfg.enabled)
248                 ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
249                         ntohs(https_desc.oldsin.sin_port));
250         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
251         for (v = vars; v; v = v->next) {
252                 if (strncasecmp(v->name, "cookie_", 7))
253                         ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
254         }
255         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
256         for (v = vars; v; v = v->next) {
257                 if (!strncasecmp(v->name, "cookie_", 7))
258                         ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
259         }
260         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");
261         return out;
262 }
263
264 static struct ast_http_uri statusuri = {
265         .callback = httpstatus_callback,
266         .description = "Asterisk HTTP General Status",
267         .uri = "httpstatus",
268         .has_subtree = 0,
269 };
270         
271 static struct ast_http_uri staticuri = {
272         .callback = static_callback,
273         .description = "Asterisk HTTP Static Delivery",
274         .uri = "static",
275         .has_subtree = 1,
276 };
277         
278 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
279 {
280         struct ast_str *out = ast_str_create(512);
281         if (out == NULL)
282                 return out;
283         ast_str_set(&out, 0,
284                 "Content-type: text/html\r\n"
285                 "%s"
286                 "\r\n"
287                 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
288                 "<html><head>\r\n"
289                 "<title>%d %s</title>\r\n"
290                 "</head><body>\r\n"
291                 "<h1>%s</h1>\r\n"
292                 "<p>%s</p>\r\n"
293                 "<hr />\r\n"
294                 "<address>Asterisk Server</address>\r\n"
295                 "</body></html>\r\n",
296                         (extra_header ? extra_header : ""), status, title, title, text);
297         return out;
298 }
299
300 /*! \brief 
301  * Link the new uri into the list. They are sorted by length of
302  * the string, not alphabetically. Duplicate entries are not replaced,
303  * but the insertion order (using <= and not just <) makes sure that
304  * more recent insertions hide older ones.
305  * On a lookup, we just scan the list and stop at the first matching entry.
306  */
307 int ast_http_uri_link(struct ast_http_uri *urih)
308 {
309         struct ast_http_uri *uri;
310         int len = strlen(urih->uri);
311
312         AST_LIST_LOCK(&uris);
313
314         if ( AST_LIST_EMPTY(&uris) || strlen(AST_LIST_FIRST(&uris)->uri) <= len ) {
315                 AST_LIST_INSERT_HEAD(&uris, urih, entry);
316                 AST_LIST_UNLOCK(&uris);
317                 return 0;
318         }
319
320         AST_LIST_TRAVERSE(&uris, uri, entry) {
321                 if ( AST_LIST_NEXT(uri, entry) 
322                         && strlen(AST_LIST_NEXT(uri, entry)->uri) <= len ) {
323                         AST_LIST_INSERT_AFTER(&uris, uri, urih, entry);
324                         AST_LIST_UNLOCK(&uris); 
325                         return 0;
326                 }
327         }
328
329         AST_LIST_INSERT_TAIL(&uris, urih, entry);
330
331         AST_LIST_UNLOCK(&uris);
332         
333         return 0;
334 }       
335
336 void ast_http_uri_unlink(struct ast_http_uri *urih)
337 {
338         AST_LIST_LOCK(&uris);
339         AST_LIST_REMOVE(&uris, urih, entry);
340         AST_LIST_UNLOCK(&uris);
341 }
342
343 static struct ast_str *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
344 {
345         char *c;
346         struct ast_str *out = NULL;
347         char *params = uri;
348         struct ast_http_uri *urih=NULL;
349         int l;
350         struct ast_variable *vars=NULL, *v, *prev = NULL;
351         struct http_uri_redirect *redirect;
352
353         strsep(&params, "?");
354         /* Extract arguments from the request and store them in variables. */
355         if (params) {
356                 char *var, *val;
357
358                 while ((val = strsep(&params, "&"))) {
359                         var = strsep(&val, "=");
360                         if (val)
361                                 uri_decode(val);
362                         else 
363                                 val = "";
364                         ast_uri_decode(var);
365                         if ((v = ast_variable_new(var, val))) {
366                                 if (vars)
367                                         prev->next = v;
368                                 else
369                                         vars = v;
370                                 prev = v;
371                         }
372                 }
373         }
374         /*
375          * Append the cookies to the variables (the only reason to have them
376          * at the end is to avoid another pass of the cookies list to find
377          * the tail).
378          */
379         if (prev)
380                 prev->next = *cookies;
381         else
382                 vars = *cookies;
383         *cookies = NULL;
384         ast_uri_decode(uri);
385
386         AST_LIST_LOCK(&uri_redirects);
387         AST_LIST_TRAVERSE(&uri_redirects, redirect, entry) {
388                 if (!strcasecmp(uri, redirect->target)) {
389                         char buf[512];
390                         snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
391                         out = ast_http_error(302, "Moved Temporarily", buf,
392                                 "There is no spoon...");
393                         *status = 302;
394                         *title = strdup("Moved Temporarily");
395                         break;
396                 }
397         }
398         AST_LIST_UNLOCK(&uri_redirects);
399         if (redirect)
400                 goto cleanup;
401
402         /* We want requests to start with the prefix and '/' */
403         l = strlen(prefix);
404         if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
405                 uri += l + 1;
406                 /* scan registered uris to see if we match one. */
407                 AST_LIST_LOCK(&uris);
408                 AST_LIST_TRAVERSE(&uris, urih, entry) {
409                         l = strlen(urih->uri);
410                         c = uri + l;    /* candidate */
411                         if (strncasecmp(urih->uri, uri, l) /* no match */
412                             || (*c && *c != '/')) /* substring */
413                                 continue;
414                         if (*c == '/')
415                                 c++;
416                         if (!*c || urih->has_subtree) {
417                                 uri = c;
418                                 break;
419                         }
420                 }
421                 if (!urih)
422                         AST_LIST_UNLOCK(&uris);
423         }
424         if (urih) {
425                 out = urih->callback(sin, uri, vars, status, title, contentlength);
426                 AST_LIST_UNLOCK(&uris);
427         } else {
428                 out = ast_http_error(404, "Not Found", NULL,
429                         "The requested URL was not found on this server.");
430                 *status = 404;
431                 *title = strdup("Not Found");
432         }
433
434 cleanup:
435         ast_variables_destroy(vars);
436         return out;
437 }
438
439 #ifdef DO_SSL
440 #if defined(HAVE_FUNOPEN)
441 #define HOOK_T int
442 #define LEN_T int
443 #else
444 #define HOOK_T ssize_t
445 #define LEN_T size_t
446 #endif
447 /*!
448  * replacement read/write functions for SSL support.
449  * We use wrappers rather than SSL_read/SSL_write directly so
450  * we can put in some debugging.
451  */
452 static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
453 {
454         int i = SSL_read(cookie, buf, len-1);
455 #if 0
456         if (i >= 0)
457                 buf[i] = '\0';
458         ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
459 #endif
460         return i;
461 }
462
463 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
464 {
465 #if 0
466         char *s = alloca(len+1);
467         strncpy(s, buf, len);
468         s[len] = '\0';
469         ast_verbose("ssl write size %d <%s>\n", (int)len, s);
470 #endif
471         return SSL_write(cookie, buf, len);
472 }
473
474 static int ssl_close(void *cookie)
475 {
476         close(SSL_get_fd(cookie));
477         SSL_shutdown(cookie);
478         SSL_free(cookie);
479         return 0;
480 }
481 #endif  /* DO_SSL */
482
483 /*!
484  * creates a FILE * from the fd passed by the accept thread.
485  * This operation is potentially expensive (certificate verification),
486  * so we do it in the child thread context.
487  */
488 static void *make_file_from_fd(void *data)
489 {
490         struct server_instance *ser = data;
491
492         /*
493          * open a FILE * as appropriate.
494          */
495         if (!ser->parent->tls_cfg)
496                 ser->f = fdopen(ser->fd, "w+");
497 #ifdef DO_SSL
498         else if ( (ser->ssl = SSL_new(ser->parent->tls_cfg->ssl_ctx)) ) {
499                 SSL_set_fd(ser->ssl, ser->fd);
500                 if (SSL_accept(ser->ssl) == 0)
501                         ast_verbose(" error setting up ssl connection");
502                 else {
503 #if defined(HAVE_FUNOPEN)       /* the BSD interface */
504                         ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close);
505
506 #elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */
507                         static const cookie_io_functions_t cookie_funcs = {
508                                 ssl_read, ssl_write, NULL, ssl_close
509                         };
510                         ser->f = fopencookie(ser->ssl, "w+", cookie_funcs);
511 #else
512                         /* could add other methods here */
513 #endif
514                 }
515                 if (!ser->f)    /* no success opening descriptor stacking */
516                         SSL_free(ser->ssl);
517         }
518 #endif /* DO_SSL */
519
520         if (!ser->f) {
521                 close(ser->fd);
522                 ast_log(LOG_WARNING, "FILE * open failed!\n");
523                 free(ser);
524                 return NULL;
525         }
526         return ser->parent->worker_fn(ser);
527 }
528
529 static void *httpd_helper_thread(void *data)
530 {
531         char buf[4096];
532         char cookie[4096];
533         struct server_instance *ser = data;
534         struct ast_variable *var, *prev=NULL, *vars=NULL;
535         char *uri, *title=NULL;
536         int status = 200, contentlength = 0;
537         struct ast_str *out = NULL;
538
539         if (!fgets(buf, sizeof(buf), ser->f))
540                 goto done;
541
542         uri = ast_skip_nonblanks(buf);  /* Skip method */
543         if (*uri)
544                 *uri++ = '\0';
545
546         uri = ast_skip_blanks(uri);     /* Skip white space */
547
548         if (*uri) {                     /* terminate at the first blank */
549                 char *c = ast_skip_nonblanks(uri);
550                 if (*c)
551                         *c = '\0';
552         }
553
554         /* process "Cookie: " lines */
555         while (fgets(cookie, sizeof(cookie), ser->f)) {
556                 char *vname, *vval;
557                 int l;
558
559                 /* Trim trailing characters */
560                 ast_trim_blanks(cookie);
561                 if (ast_strlen_zero(cookie))
562                         break;
563                 if (strncasecmp(cookie, "Cookie: ", 8))
564                         continue;
565
566                 /* TODO - The cookie parsing code below seems to work   
567                    in IE6 and FireFox 1.5.  However, it is not entirely 
568                    correct, and therefore may not work in all           
569                    circumstances.                                       
570                       For more details see RFC 2109 and RFC 2965        */
571         
572                 /* FireFox cookie strings look like:                    
573                      Cookie: mansession_id="********"                   
574                    InternetExplorer's look like:                        
575                      Cookie: $Version="1"; mansession_id="********"     */
576                 
577                 /* If we got a FireFox cookie string, the name's right  
578                     after "Cookie: "                                    */
579                 vname = ast_skip_blanks(cookie + 8);
580                         
581                 /* If we got an IE cookie string, we need to skip to    
582                     past the version to get to the name                 */
583                 if (*vname == '$') {
584                         strsep(&vname, ";");
585                         if (!vname)     /* no name ? */
586                                 continue;
587                         vname = ast_skip_blanks(vname);
588                 }
589                 vval = strchr(vname, '=');
590                 if (!vval)
591                         continue;
592                 /* Ditch the = and the quotes */
593                 *vval++ = '\0';
594                 if (*vval)
595                         vval++;
596                 if ( (l = strlen(vval)) )
597                         vval[l - 1] = '\0';     /* trim trailing quote */
598                 var = ast_variable_new(vname, vval);
599                 if (var) {
600                         if (prev)
601                                 prev->next = var;
602                         else
603                                 vars = var;
604                         prev = var;
605                 }
606         }
607
608         if (!*uri)
609                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
610         else if (strcasecmp(buf, "get")) 
611                 out = ast_http_error(501, "Not Implemented", NULL,
612                         "Attempt to use unimplemented / unsupported method");
613         else    /* try to serve it */
614                 out = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
615
616         /* If they aren't mopped up already, clean up the cookies */
617         if (vars)
618                 ast_variables_destroy(vars);
619
620         if (out == NULL)
621                 out = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
622         if (out) {
623                 time_t t = time(NULL);
624                 char timebuf[256];
625
626                 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
627                 fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
628                                 "Server: Asterisk\r\n"
629                                 "Date: %s\r\n"
630                                 "Connection: close\r\n",
631                         status, title ? title : "OK", timebuf);
632                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
633                         fprintf(ser->f, "%s", out->str);
634                 } else {
635                         char *tmp = strstr(out->str, "\r\n\r\n");
636
637                         if (tmp) {
638                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
639                                 /* first write the header, then the body */
640                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
641                                 fwrite(tmp + 4, 1, contentlength, ser->f);
642                         }
643                 }
644                 free(out);
645         }
646         if (title)
647                 free(title);
648
649 done:
650         if (ser->f)
651                 fclose(ser->f);
652         free(ser);
653         return NULL;
654 }
655
656 void *server_root(void *data)
657 {
658         struct server_args *desc = data;
659         int fd;
660         struct sockaddr_in sin;
661         socklen_t sinlen;
662         struct server_instance *ser;
663         pthread_t launched;
664         pthread_attr_t attr;
665         
666         for (;;) {
667                 int i, flags;
668
669                 if (desc->periodic_fn)
670                         desc->periodic_fn(desc);
671                 i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout);
672                 if (i <= 0)
673                         continue;
674                 sinlen = sizeof(sin);
675                 fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen);
676                 if (fd < 0) {
677                         if ((errno != EAGAIN) && (errno != EINTR))
678                                 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
679                         continue;
680                 }
681                 ser = ast_calloc(1, sizeof(*ser));
682                 if (!ser) {
683                         ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
684                         close(fd);
685                         continue;
686                 }
687                 flags = fcntl(fd, F_GETFL);
688                 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
689                 ser->fd = fd;
690                 ser->parent = desc;
691                 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
692
693                 pthread_attr_init(&attr);
694                 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
695                         
696                 if (ast_pthread_create_background(&launched, &attr, make_file_from_fd, ser)) {
697                         ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
698                         close(ser->fd);
699                         free(ser);
700                 }
701         }
702         return NULL;
703 }
704
705 int ssl_setup(struct tls_config *cfg)
706 {
707 #ifndef DO_SSL
708         cfg->enabled = 0;
709         return 0;
710 #else
711         if (!cfg->enabled)
712                 return 0;
713         SSL_load_error_strings();
714         SSLeay_add_ssl_algorithms();
715         cfg->ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
716         if (!ast_strlen_zero(cfg->certfile)) {
717                 if (SSL_CTX_use_certificate_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
718                     SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
719                     SSL_CTX_check_private_key(cfg->ssl_ctx) == 0 ) {
720                         ast_verbose("ssl cert error <%s>", cfg->certfile);
721                         sleep(2);
722                         cfg->enabled = 0;
723                         return 0;
724                 }
725         }
726         if (!ast_strlen_zero(cfg->cipher)) {
727                 if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) {
728                         ast_verbose("ssl cipher error <%s>", cfg->cipher);
729                         sleep(2);
730                         cfg->enabled = 0;
731                         return 0;
732                 }
733         }
734         ast_verbose("ssl cert ok");
735         return 1;
736 #endif
737 }
738
739 /*!
740  * This is a generic (re)start routine for a TCP server,
741  * which does the socket/bind/listen and starts a thread for handling
742  * accept().
743  */
744 void server_start(struct server_args *desc)
745 {
746         int flags;
747         int x = 1;
748         
749         /* Do nothing if nothing has changed */
750         if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
751                 if (option_debug)
752                         ast_log(LOG_DEBUG, "Nothing changed in %s\n", desc->name);
753                 return;
754         }
755         
756         desc->oldsin = desc->sin;
757         
758         /* Shutdown a running server if there is one */
759         if (desc->master != AST_PTHREADT_NULL) {
760                 pthread_cancel(desc->master);
761                 pthread_kill(desc->master, SIGURG);
762                 pthread_join(desc->master, NULL);
763         }
764         
765         if (desc->accept_fd != -1)
766                 close(desc->accept_fd);
767
768         /* If there's no new server, stop here */
769         if (desc->sin.sin_family == 0)
770                 return;
771
772         desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
773         if (desc->accept_fd < 0) {
774                 ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n",
775                         desc->name, strerror(errno));
776                 return;
777         }
778         
779         setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
780         if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {
781                 ast_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n",
782                         desc->name,
783                         ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
784                         strerror(errno));
785                 goto error;
786         }
787         if (listen(desc->accept_fd, 10)) {
788                 ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name);
789                 goto error;
790         }
791         flags = fcntl(desc->accept_fd, F_GETFL);
792         fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
793         if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {
794                 ast_log(LOG_NOTICE, "Unable to launch %s on %s:%d: %s\n",
795                         desc->name,
796                         ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
797                         strerror(errno));
798                 goto error;
799         }
800         return;
801
802 error:
803         close(desc->accept_fd);
804         desc->accept_fd = -1;
805 }
806
807 /*!
808  * \brief Add a new URI redirect
809  * The entries in the redirect list are sorted by length, just like the list
810  * of URI handlers.
811  */
812 static void add_redirect(const char *value)
813 {
814         char *target, *dest;
815         struct http_uri_redirect *redirect, *cur;
816         unsigned int target_len;
817         unsigned int total_len;
818
819         dest = ast_strdupa(value);
820         dest = ast_skip_blanks(dest);
821         target = strsep(&dest, " ");
822         target = ast_skip_blanks(target);
823         target = strsep(&target, " "); /* trim trailing whitespace */
824
825         if (!dest) {
826                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
827                 return;
828         }
829
830         target_len = strlen(target) + 1;
831         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
832
833         if (!(redirect = ast_calloc(1, total_len)))
834                 return;
835
836         redirect->dest = redirect->target + target_len;
837         strcpy(redirect->target, target);
838         strcpy(redirect->dest, dest);
839
840         AST_LIST_LOCK(&uri_redirects);
841
842         target_len--; /* So we can compare directly with strlen() */
843         if ( AST_LIST_EMPTY(&uri_redirects) 
844                 || strlen(AST_LIST_FIRST(&uri_redirects)->target) <= target_len ) {
845                 AST_LIST_INSERT_HEAD(&uri_redirects, redirect, entry);
846                 AST_LIST_UNLOCK(&uri_redirects);
847                 return;
848         }
849
850         AST_LIST_TRAVERSE(&uri_redirects, cur, entry) {
851                 if ( AST_LIST_NEXT(cur, entry) 
852                         && strlen(AST_LIST_NEXT(cur, entry)->target) <= target_len ) {
853                         AST_LIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
854                         AST_LIST_UNLOCK(&uri_redirects); 
855                         return;
856                 }
857         }
858
859         AST_LIST_INSERT_TAIL(&uri_redirects, redirect, entry);
860
861         AST_LIST_UNLOCK(&uri_redirects);
862 }
863
864 static int __ast_http_load(int reload)
865 {
866         struct ast_config *cfg;
867         struct ast_variable *v;
868         int enabled=0;
869         int newenablestatic=0;
870         struct hostent *hp;
871         struct ast_hostent ahp;
872         char newprefix[MAX_PREFIX];
873         int have_sslbindaddr = 0;
874         struct http_uri_redirect *redirect;
875
876         /* default values */
877         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
878         http_desc.sin.sin_port = htons(8088);
879
880         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
881         https_desc.sin.sin_port = htons(8089);
882         strcpy(newprefix, DEFAULT_PREFIX);
883
884         http_tls_cfg.enabled = 0;
885         if (http_tls_cfg.certfile)
886                 free(http_tls_cfg.certfile);
887         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
888         if (http_tls_cfg.cipher)
889                 free(http_tls_cfg.cipher);
890         http_tls_cfg.cipher = ast_strdup("");
891
892         AST_LIST_LOCK(&uri_redirects);
893         while ((redirect = AST_LIST_REMOVE_HEAD(&uri_redirects, entry)))
894                 free(redirect);
895         AST_LIST_UNLOCK(&uri_redirects);
896
897         cfg = ast_config_load("http.conf");
898         if (cfg) {
899                 v = ast_variable_browse(cfg, "general");
900                 for (; v; v = v->next) {
901                         if (!strcasecmp(v->name, "enabled"))
902                                 enabled = ast_true(v->value);
903                         else if (!strcasecmp(v->name, "sslenable"))
904                                 http_tls_cfg.enabled = ast_true(v->value);
905                         else if (!strcasecmp(v->name, "sslbindport"))
906                                 https_desc.sin.sin_port = htons(atoi(v->value));
907                         else if (!strcasecmp(v->name, "sslcert")) {
908                                 free(http_tls_cfg.certfile);
909                                 http_tls_cfg.certfile = ast_strdup(v->value);
910                         } else if (!strcasecmp(v->name, "sslcipher")) {
911                                 free(http_tls_cfg.cipher);
912                                 http_tls_cfg.cipher = ast_strdup(v->value);
913                         }
914                         else if (!strcasecmp(v->name, "enablestatic"))
915                                 newenablestatic = ast_true(v->value);
916                         else if (!strcasecmp(v->name, "bindport"))
917                                 http_desc.sin.sin_port = htons(atoi(v->value));
918                         else if (!strcasecmp(v->name, "sslbindaddr")) {
919                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
920                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
921                                         have_sslbindaddr = 1;
922                                 } else {
923                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
924                                 }
925                         } else if (!strcasecmp(v->name, "bindaddr")) {
926                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
927                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
928                                 } else {
929                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
930                                 }
931                         } else if (!strcasecmp(v->name, "prefix")) {
932                                 if (!ast_strlen_zero(v->value)) {
933                                         newprefix[0] = '/';
934                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
935                                 } else {
936                                         newprefix[0] = '\0';
937                                 }
938                         } else if (!strcasecmp(v->name, "redirect")) {
939                                 add_redirect(v->value);
940                         } else {
941                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
942                         }
943                 }
944                 ast_config_destroy(cfg);
945         }
946         if (!have_sslbindaddr)
947                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
948         if (enabled)
949                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
950         if (strcmp(prefix, newprefix))
951                 ast_copy_string(prefix, newprefix, sizeof(prefix));
952         enablestatic = newenablestatic;
953         server_start(&http_desc);
954         if (ssl_setup(https_desc.tls_cfg))
955                 server_start(&https_desc);
956         return 0;
957 }
958
959 static int handle_show_http(int fd, int argc, char *argv[])
960 {
961         struct ast_http_uri *urih;
962         struct http_uri_redirect *redirect;
963
964         if (argc != 3)
965                 return RESULT_SHOWUSAGE;
966
967         ast_cli(fd, "HTTP Server Status:\n");
968         ast_cli(fd, "Prefix: %s\n", prefix);
969         if (!http_desc.oldsin.sin_family)
970                 ast_cli(fd, "Server Disabled\n\n");
971         else {
972                 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
973                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
974                         ntohs(http_desc.oldsin.sin_port));
975                 if (http_tls_cfg.enabled)
976                         ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
977                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
978                                 ntohs(https_desc.oldsin.sin_port));
979         }
980
981         ast_cli(fd, "Enabled URI's:\n");
982         AST_LIST_LOCK(&uris);
983         AST_LIST_TRAVERSE(&uris, urih, entry)
984                 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
985         if (AST_LIST_EMPTY(&uris))
986                 ast_cli(fd, "None.\n");
987         AST_LIST_UNLOCK(&uris);
988
989         ast_cli(fd, "\nEnabled Redirects:\n");
990         AST_LIST_LOCK(&uri_redirects);
991         AST_LIST_TRAVERSE(&uri_redirects, redirect, entry)
992                 ast_cli(fd, "  %s => %s\n", redirect->target, redirect->dest);
993         if (AST_LIST_EMPTY(&uri_redirects))
994                 ast_cli(fd, "  None.\n");
995         AST_LIST_UNLOCK(&uri_redirects);
996
997         return RESULT_SUCCESS;
998 }
999
1000 int ast_http_reload(void)
1001 {
1002         return __ast_http_load(1);
1003 }
1004
1005 static char show_http_help[] =
1006 "Usage: http show status\n"
1007 "       Lists status of internal HTTP engine\n";
1008
1009 static struct ast_cli_entry cli_http[] = {
1010         { { "http", "show", "status", NULL },
1011         handle_show_http, "Display HTTP server status",
1012         show_http_help },
1013 };
1014
1015 int ast_http_init(void)
1016 {
1017         ast_http_uri_link(&statusuri);
1018         ast_http_uri_link(&staticuri);
1019         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1020         return __ast_http_load(0);
1021 }