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