Simplify the definition of http_uri_redirect such that only one allocation is
[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         target = strsep(&dest, "=");
821
822         if (!dest) {
823                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
824                 return;
825         }
826
827         target_len = strlen(target) + 1;
828         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
829
830         if (!(redirect = ast_calloc(1, total_len)))
831                 return;
832
833         redirect->dest = redirect->target + target_len;
834         strcpy(redirect->target, target);
835         strcpy(redirect->dest, dest);
836
837         AST_LIST_LOCK(&uri_redirects);
838
839         target_len--; /* So we can compare directly with strlen() */
840         if ( AST_LIST_EMPTY(&uri_redirects) 
841                 || strlen(AST_LIST_FIRST(&uri_redirects)->target) <= target_len ) {
842                 AST_LIST_INSERT_HEAD(&uri_redirects, redirect, entry);
843                 AST_LIST_UNLOCK(&uri_redirects);
844                 return;
845         }
846
847         AST_LIST_TRAVERSE(&uri_redirects, cur, entry) {
848                 if ( AST_LIST_NEXT(cur, entry) 
849                         && strlen(AST_LIST_NEXT(cur, entry)->target) <= target_len ) {
850                         AST_LIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
851                         AST_LIST_UNLOCK(&uri_redirects); 
852                         return;
853                 }
854         }
855
856         AST_LIST_INSERT_TAIL(&uri_redirects, redirect, entry);
857
858         AST_LIST_UNLOCK(&uri_redirects);
859 }
860
861 static int __ast_http_load(int reload)
862 {
863         struct ast_config *cfg;
864         struct ast_variable *v;
865         int enabled=0;
866         int newenablestatic=0;
867         struct hostent *hp;
868         struct ast_hostent ahp;
869         char newprefix[MAX_PREFIX];
870         int have_sslbindaddr = 0;
871         struct http_uri_redirect *redirect;
872
873         /* default values */
874         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
875         http_desc.sin.sin_port = htons(8088);
876
877         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
878         https_desc.sin.sin_port = htons(8089);
879         strcpy(newprefix, DEFAULT_PREFIX);
880
881         http_tls_cfg.enabled = 0;
882         if (http_tls_cfg.certfile)
883                 free(http_tls_cfg.certfile);
884         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
885         if (http_tls_cfg.cipher)
886                 free(http_tls_cfg.cipher);
887         http_tls_cfg.cipher = ast_strdup("");
888
889         AST_LIST_LOCK(&uri_redirects);
890         while ((redirect = AST_LIST_REMOVE_HEAD(&uri_redirects, entry)))
891                 free(redirect);
892         AST_LIST_UNLOCK(&uri_redirects);
893
894         cfg = ast_config_load("http.conf");
895         if (cfg) {
896                 v = ast_variable_browse(cfg, "general");
897                 for (; v; v = v->next) {
898                         if (!strcasecmp(v->name, "enabled"))
899                                 enabled = ast_true(v->value);
900                         else if (!strcasecmp(v->name, "sslenable"))
901                                 http_tls_cfg.enabled = ast_true(v->value);
902                         else if (!strcasecmp(v->name, "sslbindport"))
903                                 https_desc.sin.sin_port = htons(atoi(v->value));
904                         else if (!strcasecmp(v->name, "sslcert")) {
905                                 free(http_tls_cfg.certfile);
906                                 http_tls_cfg.certfile = ast_strdup(v->value);
907                         } else if (!strcasecmp(v->name, "sslcipher")) {
908                                 free(http_tls_cfg.cipher);
909                                 http_tls_cfg.cipher = ast_strdup(v->value);
910                         }
911                         else if (!strcasecmp(v->name, "enablestatic"))
912                                 newenablestatic = ast_true(v->value);
913                         else if (!strcasecmp(v->name, "bindport"))
914                                 http_desc.sin.sin_port = htons(atoi(v->value));
915                         else if (!strcasecmp(v->name, "sslbindaddr")) {
916                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
917                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
918                                         have_sslbindaddr = 1;
919                                 } else {
920                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
921                                 }
922                         } else if (!strcasecmp(v->name, "bindaddr")) {
923                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
924                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
925                                 } else {
926                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
927                                 }
928                         } else if (!strcasecmp(v->name, "prefix")) {
929                                 if (!ast_strlen_zero(v->value)) {
930                                         newprefix[0] = '/';
931                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
932                                 } else {
933                                         newprefix[0] = '\0';
934                                 }
935                         } else if (!strcasecmp(v->name, "redirect")) {
936                                 add_redirect(v->value);
937                         } else {
938                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
939                         }
940                 }
941                 ast_config_destroy(cfg);
942         }
943         if (!have_sslbindaddr)
944                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
945         if (enabled)
946                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
947         if (strcmp(prefix, newprefix))
948                 ast_copy_string(prefix, newprefix, sizeof(prefix));
949         enablestatic = newenablestatic;
950         server_start(&http_desc);
951         if (ssl_setup(https_desc.tls_cfg))
952                 server_start(&https_desc);
953         return 0;
954 }
955
956 static int handle_show_http(int fd, int argc, char *argv[])
957 {
958         struct ast_http_uri *urih;
959         struct http_uri_redirect *redirect;
960
961         if (argc != 3)
962                 return RESULT_SHOWUSAGE;
963
964         ast_cli(fd, "HTTP Server Status:\n");
965         ast_cli(fd, "Prefix: %s\n", prefix);
966         if (!http_desc.oldsin.sin_family)
967                 ast_cli(fd, "Server Disabled\n\n");
968         else {
969                 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
970                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
971                         ntohs(http_desc.oldsin.sin_port));
972                 if (http_tls_cfg.enabled)
973                         ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
974                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
975                                 ntohs(https_desc.oldsin.sin_port));
976         }
977
978         ast_cli(fd, "Enabled URI's:\n");
979         AST_LIST_LOCK(&uris);
980         AST_LIST_TRAVERSE(&uris, urih, entry)
981                 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
982         if (AST_LIST_EMPTY(&uris))
983                 ast_cli(fd, "None.\n");
984         AST_LIST_UNLOCK(&uris);
985
986         ast_cli(fd, "\nEnabled Redirects:\n");
987         AST_LIST_LOCK(&uri_redirects);
988         AST_LIST_TRAVERSE(&uri_redirects, redirect, entry)
989                 ast_cli(fd, "  %s => %s\n", redirect->target, redirect->dest);
990         if (AST_LIST_EMPTY(&uri_redirects))
991                 ast_cli(fd, "  None.\n");
992         AST_LIST_UNLOCK(&uri_redirects);
993
994         return RESULT_SUCCESS;
995 }
996
997 int ast_http_reload(void)
998 {
999         return __ast_http_load(1);
1000 }
1001
1002 static char show_http_help[] =
1003 "Usage: http show status\n"
1004 "       Lists status of internal HTTP engine\n";
1005
1006 static struct ast_cli_entry cli_http[] = {
1007         { { "http", "show", "status", NULL },
1008         handle_show_http, "Display HTTP server status",
1009         show_http_help },
1010 };
1011
1012 int ast_http_init(void)
1013 {
1014         ast_http_uri_link(&statusuri);
1015         ast_http_uri_link(&staticuri);
1016         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1017         return __ast_http_load(0);
1018 }