in the helper thread, separate the FILE * creation from the actual
[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
828         /* default values */
829         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
830         http_desc.sin.sin_port = htons(8088);
831
832         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
833         https_desc.sin.sin_port = htons(8089);
834         strcpy(newprefix, DEFAULT_PREFIX);
835         cfg = ast_config_load("http.conf");
836
837         do_ssl = 0;
838         if (certfile)
839                 free(certfile);
840         certfile = ast_strdup(AST_CERTFILE);
841         if (cipher)
842                 free(cipher);
843         cipher = ast_strdup("");
844
845         if (cfg) {
846                 v = ast_variable_browse(cfg, "general");
847                 while(v) {
848                         if (!strcasecmp(v->name, "enabled"))
849                                 enabled = ast_true(v->value);
850                         else if (!strcasecmp(v->name, "sslenable"))
851                                 do_ssl = ast_true(v->value);
852                         else if (!strcasecmp(v->name, "sslbindport"))
853                                 https_desc.sin.sin_port = htons(atoi(v->value));
854                         else if (!strcasecmp(v->name, "sslcert")) {
855                                 free(certfile);
856                                 certfile = ast_strdup(v->value);
857                         } else if (!strcasecmp(v->name, "sslcipher")) {
858                                 free(cipher);
859                                 cipher = ast_strdup(v->value);
860                         }
861                         else if (!strcasecmp(v->name, "enablestatic"))
862                                 newenablestatic = ast_true(v->value);
863                         else if (!strcasecmp(v->name, "bindport"))
864                                 http_desc.sin.sin_port = htons(atoi(v->value));
865                         else if (!strcasecmp(v->name, "bindaddr")) {
866                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
867                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
868                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
869                                 } else {
870                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
871                                 }
872                         } else if (!strcasecmp(v->name, "prefix")) {
873                                 if (!ast_strlen_zero(v->value)) {
874                                         newprefix[0] = '/';
875                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
876                                 } else {
877                                         newprefix[0] = '\0';
878                                 }
879                                         
880                         }
881                         v = v->next;
882                 }
883                 ast_config_destroy(cfg);
884         }
885         if (enabled)
886                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
887         if (strcmp(prefix, newprefix))
888                 ast_copy_string(prefix, newprefix, sizeof(prefix));
889         enablestatic = newenablestatic;
890         server_start(&http_desc);
891         if (ssl_setup())
892                 server_start(&https_desc);
893         return 0;
894 }
895
896 static int handle_show_http(int fd, int argc, char *argv[])
897 {
898         struct ast_http_uri *urih;
899         if (argc != 3)
900                 return RESULT_SHOWUSAGE;
901         ast_cli(fd, "HTTP Server Status:\n");
902         ast_cli(fd, "Prefix: %s\n", prefix);
903         if (!http_desc.oldsin.sin_family)
904                 ast_cli(fd, "Server Disabled\n\n");
905         else {
906                 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
907                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
908                         ntohs(http_desc.oldsin.sin_port));
909                 if (do_ssl)
910                         ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
911                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
912                                 ntohs(https_desc.oldsin.sin_port));
913         }
914         ast_cli(fd, "Enabled URI's:\n");
915         urih = uris;
916         while(urih){
917                 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
918                 urih = urih->next;
919         }
920         if (!uris)
921                 ast_cli(fd, "None.\n");
922         return RESULT_SUCCESS;
923 }
924
925 int ast_http_reload(void)
926 {
927         return __ast_http_load(1);
928 }
929
930 static char show_http_help[] =
931 "Usage: http show status\n"
932 "       Lists status of internal HTTP engine\n";
933
934 static struct ast_cli_entry cli_http[] = {
935         { { "http", "show", "status", NULL },
936         handle_show_http, "Display HTTP server status",
937         show_http_help },
938 };
939
940 int ast_http_init(void)
941 {
942         ast_http_uri_link(&statusuri);
943         ast_http_uri_link(&staticuri);
944         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
945         return __ast_http_load(0);
946 }