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