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