ab70df5885e4da47882045badae2eee0dd8bad24
[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 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         ast_http_callback callback;
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 static void *httpd_helper_thread(void *data)
488 {
489         char buf[4096];
490         char cookie[4096];
491         struct server_instance *ser = data;
492         struct ast_variable *var, *prev=NULL, *vars=NULL;
493         char *uri, *c, *title=NULL;
494         int status = 200, contentlength = 0;
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                 goto done;
528         }
529
530         if (!fgets(buf, sizeof(buf), ser->f))
531                 goto done;
532
533         uri = ast_skip_nonblanks(buf);  /* Skip method */
534         if (*uri)
535                 *uri++ = '\0';
536
537         uri = ast_skip_blanks(uri);     /* Skip white space */
538
539         if (*uri) {                     /* terminate at the first blank */
540                 c = ast_skip_nonblanks(uri);
541                 if (*c)
542                         *c = '\0';
543         }
544
545         /* process "Cookie: " lines */
546         while (fgets(cookie, sizeof(cookie), ser->f)) {
547                 char *vname, *vval;
548                 int l;
549
550                 /* Trim trailing characters */
551                 ast_trim_blanks(cookie);
552                 if (ast_strlen_zero(cookie))
553                         break;
554                 if (strncasecmp(cookie, "Cookie: ", 8))
555                         continue;
556
557                 /* TODO - The cookie parsing code below seems to work   
558                    in IE6 and FireFox 1.5.  However, it is not entirely 
559                    correct, and therefore may not work in all           
560                    circumstances.                                       
561                       For more details see RFC 2109 and RFC 2965        */
562         
563                 /* FireFox cookie strings look like:                    
564                      Cookie: mansession_id="********"                   
565                    InternetExplorer's look like:                        
566                      Cookie: $Version="1"; mansession_id="********"     */
567                 
568                 /* If we got a FireFox cookie string, the name's right  
569                     after "Cookie: "                                    */
570                 vname = ast_skip_blanks(cookie + 8);
571                         
572                 /* If we got an IE cookie string, we need to skip to    
573                     past the version to get to the name                 */
574                 if (*vname == '$') {
575                         strsep(&vname, ";");
576                         if (!vname)     /* no name ? */
577                                 continue;
578                         vname = ast_skip_blanks(vname);
579                 }
580                 vval = strchr(vname, '=');
581                 if (!vval)
582                         continue;
583                 /* Ditch the = and the quotes */
584                 *vval++ = '\0';
585                 if (*vval)
586                         vval++;
587                 if ( (l = strlen(vval)) )
588                         vval[l - 1] = '\0';     /* trim trailing quote */
589                 var = ast_variable_new(vname, vval);
590                 if (var) {
591                         if (prev)
592                                 prev->next = var;
593                         else
594                                 vars = var;
595                         prev = var;
596                 }
597         }
598
599         if (!*uri)
600                 c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
601         else if (strcasecmp(buf, "get")) 
602                 c = ast_http_error(501, "Not Implemented", NULL,
603                         "Attempt to use unimplemented / unsupported method");
604         else    /* try to serve it */
605                 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
606
607         /* If they aren't mopped up already, clean up the cookies */
608         if (vars)
609                 ast_variables_destroy(vars);
610
611         if (!c)
612                 c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
613         if (c) {
614                 time_t t = time(NULL);
615                 char timebuf[256];
616
617                 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
618                 fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
619                                 "Server: Asterisk\r\n"
620                                 "Date: %s\r\n"
621                                 "Connection: close\r\n",
622                         status, title ? title : "OK", timebuf);
623                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
624                         fprintf(ser->f, "%s", c);
625                 } else {
626                         char *tmp = strstr(c, "\r\n\r\n");
627
628                         if (tmp) {
629                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
630                                 /* first write the header, then the body */
631                                 fwrite(c, 1, (tmp + 4 - c), ser->f);
632                                 fwrite(tmp + 4, 1, contentlength, ser->f);
633                         }
634                 }
635                 free(c);
636         }
637         if (title)
638                 free(title);
639
640 done:
641         if (ser->f)
642                 fclose(ser->f);
643         free(ser);
644         return NULL;
645 }
646
647 static void *http_root(void *data)
648 {
649         struct server_args *desc = data;
650         int fd;
651         struct sockaddr_in sin;
652         socklen_t sinlen;
653         struct server_instance *ser;
654         pthread_t launched;
655         pthread_attr_t attr;
656         
657         for (;;) {
658                 int flags;
659
660                 ast_wait_for_input(desc->accept_fd, -1);
661                 sinlen = sizeof(sin);
662                 fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen);
663                 if (fd < 0) {
664                         if ((errno != EAGAIN) && (errno != EINTR))
665                                 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
666                         continue;
667                 }
668                 ser = ast_calloc(1, sizeof(*ser));
669                 if (!ser) {
670                         ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
671                         close(fd);
672                         continue;
673                 }
674                 flags = fcntl(fd, F_GETFL);
675                 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
676                 ser->fd = fd;
677                 ser->is_ssl = desc->is_ssl;
678                 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
679
680                 pthread_attr_init(&attr);
681                 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
682                         
683                 if (ast_pthread_create_background(&launched, &attr, desc->worker_fn, ser)) {
684                         ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
685                         close(ser->fd);
686                         free(ser);
687                 }
688         }
689         return NULL;
690 }
691
692 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
693 {
694         char *c;
695         c = buf;
696         ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
697         if (expires)
698                 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
699         ast_build_string(&c, &buflen, "\r\n");
700         return buf;
701 }
702
703 static int ssl_setup(void)
704 {
705 #ifndef DO_SSL
706         do_ssl = 0;
707         return 0;
708 #else
709         if (!do_ssl)
710                 return 0;
711         SSL_load_error_strings();
712         SSLeay_add_ssl_algorithms();
713         ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
714         if (!ast_strlen_zero(certfile)) {
715                 if (SSL_CTX_use_certificate_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) == 0 ||
716                     SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) == 0 ||
717                     SSL_CTX_check_private_key(ssl_ctx) == 0 ) {
718                         ast_verbose("ssl cert error <%s>", certfile);
719                         sleep(2);
720                         do_ssl = 0;
721                         return 0;
722                 }
723         }
724         if (!ast_strlen_zero(cipher)) {
725                 if (SSL_CTX_set_cipher_list(ssl_ctx, cipher) == 0 ) {
726                         ast_verbose("ssl cipher error <%s>", cipher);
727                         sleep(2);
728                         do_ssl = 0;
729                         return 0;
730                 }
731         }
732         ast_verbose("ssl cert ok");
733         return 1;
734 #endif
735 }
736
737 /*!
738  * This is a generic (re)start routine for a TCP server,
739  * which does the socket/bind/listen and starts a thread for handling
740  * accept().
741  */
742 static void server_start(struct server_args *desc)
743 {
744         int flags;
745         int x = 1;
746         
747         /* Do nothing if nothing has changed */
748         if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
749                 if (option_debug)
750                         ast_log(LOG_DEBUG, "Nothing changed in %s\n", desc->name);
751                 return;
752         }
753         
754         desc->oldsin = desc->sin;
755         
756         /* Shutdown a running server if there is one */
757         if (desc->master != AST_PTHREADT_NULL) {
758                 pthread_cancel(desc->master);
759                 pthread_kill(desc->master, SIGURG);
760                 pthread_join(desc->master, NULL);
761         }
762         
763         if (desc->accept_fd != -1)
764                 close(desc->accept_fd);
765
766         /* If there's no new server, stop here */
767         if (desc->sin.sin_family == 0)
768                 return;
769
770         desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
771         if (desc->accept_fd < 0) {
772                 ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n",
773                         desc->name, strerror(errno));
774                 return;
775         }
776         
777         setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
778         if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {
779                 ast_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n",
780                         desc->name,
781                         ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
782                         strerror(errno));
783                 goto error;
784         }
785         if (listen(desc->accept_fd, 10)) {
786                 ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name);
787                 goto error;
788         }
789         flags = fcntl(desc->accept_fd, F_GETFL);
790         fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
791         if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {
792                 ast_log(LOG_NOTICE, "Unable to launch %s on %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         return;
799
800 error:
801         close(desc->accept_fd);
802         desc->accept_fd = -1;
803 }
804
805 static int __ast_http_load(int reload)
806 {
807         struct ast_config *cfg;
808         struct ast_variable *v;
809         int enabled=0;
810         int newenablestatic=0;
811         struct hostent *hp;
812         struct ast_hostent ahp;
813         char newprefix[MAX_PREFIX];
814
815         /* default values */
816         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
817         http_desc.sin.sin_port = htons(8088);
818
819         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
820         https_desc.sin.sin_port = htons(8089);
821         strcpy(newprefix, DEFAULT_PREFIX);
822         cfg = ast_config_load("http.conf");
823
824         do_ssl = 0;
825         if (certfile)
826                 free(certfile);
827         certfile = ast_strdup(AST_CERTFILE);
828         if (cipher)
829                 free(cipher);
830         cipher = ast_strdup("");
831
832         if (cfg) {
833                 v = ast_variable_browse(cfg, "general");
834                 while(v) {
835                         if (!strcasecmp(v->name, "enabled"))
836                                 enabled = ast_true(v->value);
837                         else if (!strcasecmp(v->name, "sslenable"))
838                                 do_ssl = ast_true(v->value);
839                         else if (!strcasecmp(v->name, "sslbindport"))
840                                 https_desc.sin.sin_port = htons(atoi(v->value));
841                         else if (!strcasecmp(v->name, "sslcert")) {
842                                 free(certfile);
843                                 certfile = ast_strdup(v->value);
844                         } else if (!strcasecmp(v->name, "sslcipher")) {
845                                 free(cipher);
846                                 cipher = ast_strdup(v->value);
847                         }
848                         else if (!strcasecmp(v->name, "enablestatic"))
849                                 newenablestatic = ast_true(v->value);
850                         else if (!strcasecmp(v->name, "bindport"))
851                                 http_desc.sin.sin_port = htons(atoi(v->value));
852                         else if (!strcasecmp(v->name, "bindaddr")) {
853                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
854                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
855                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
856                                 } else {
857                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
858                                 }
859                         } else if (!strcasecmp(v->name, "prefix")) {
860                                 if (!ast_strlen_zero(v->value)) {
861                                         newprefix[0] = '/';
862                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
863                                 } else {
864                                         newprefix[0] = '\0';
865                                 }
866                                         
867                         }
868                         v = v->next;
869                 }
870                 ast_config_destroy(cfg);
871         }
872         if (enabled)
873                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
874         if (strcmp(prefix, newprefix))
875                 ast_copy_string(prefix, newprefix, sizeof(prefix));
876         enablestatic = newenablestatic;
877         server_start(&http_desc);
878         if (ssl_setup())
879                 server_start(&https_desc);
880         return 0;
881 }
882
883 static int handle_show_http(int fd, int argc, char *argv[])
884 {
885         struct ast_http_uri *urih;
886         if (argc != 3)
887                 return RESULT_SHOWUSAGE;
888         ast_cli(fd, "HTTP Server Status:\n");
889         ast_cli(fd, "Prefix: %s\n", prefix);
890         if (!http_desc.oldsin.sin_family)
891                 ast_cli(fd, "Server Disabled\n\n");
892         else {
893                 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
894                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
895                         ntohs(http_desc.oldsin.sin_port));
896                 if (do_ssl)
897                         ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
898                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
899                                 ntohs(https_desc.oldsin.sin_port));
900         }
901         ast_cli(fd, "Enabled URI's:\n");
902         urih = uris;
903         while(urih){
904                 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
905                 urih = urih->next;
906         }
907         if (!uris)
908                 ast_cli(fd, "None.\n");
909         return RESULT_SUCCESS;
910 }
911
912 int ast_http_reload(void)
913 {
914         return __ast_http_load(1);
915 }
916
917 static char show_http_help[] =
918 "Usage: http show status\n"
919 "       Lists status of internal HTTP engine\n";
920
921 static struct ast_cli_entry cli_http[] = {
922         { { "http", "show", "status", NULL },
923         handle_show_http, "Display HTTP server status",
924         show_http_help },
925 };
926
927 int ast_http_init(void)
928 {
929         ast_http_uri_link(&statusuri);
930         ast_http_uri_link(&staticuri);
931         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
932         return __ast_http_load(0);
933 }