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