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