Rename version.h to ast_version.h. Next, I will be re-adding version.h as an
[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  *
25  * This program implements a tiny http server
26  * and was inspired by micro-httpd by Jef Poskanzer 
27  * 
28  * \ref AstHTTP - AMI over the http protocol
29  */
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include "asterisk/paths.h"     /* use ast_config_AST_DATA_DIR */
36 #include "asterisk/network.h"
37 #include <time.h>
38 #include <sys/time.h>
39 #include <sys/stat.h>
40 #include <sys/signal.h>
41 #include <fcntl.h>
42
43 #include "minimime/mm.h"
44
45 #include "asterisk/cli.h"
46 #include "asterisk/tcptls.h"
47 #include "asterisk/http.h"
48 #include "asterisk/utils.h"
49 #include "asterisk/strings.h"
50 #include "asterisk/config.h"
51 #include "asterisk/stringfields.h"
52 #include "asterisk/ast_version.h"
53 #include "asterisk/manager.h"
54
55 #define MAX_PREFIX 80
56 #define DEFAULT_PREFIX "/asterisk"
57
58 /* See http.h for more information about the SSL implementation */
59 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
60 #define DO_SSL  /* comment in/out if you want to support ssl */
61 #endif
62
63 static struct ast_tls_config http_tls_cfg;
64
65 static void *httpd_helper_thread(void *arg);
66
67 /*!
68  * we have up to two accepting threads, one for http, one for https
69  */
70 static struct server_args http_desc = {
71         .accept_fd = -1,
72         .master = AST_PTHREADT_NULL,
73         .tls_cfg = NULL,
74         .poll_timeout = -1,
75         .name = "http server",
76         .accept_fn = server_root,
77         .worker_fn = httpd_helper_thread,
78 };
79
80 static struct server_args https_desc = {
81         .accept_fd = -1,
82         .master = AST_PTHREADT_NULL,
83         .tls_cfg = &http_tls_cfg,
84         .poll_timeout = -1,
85         .name = "https server",
86         .accept_fn = server_root,
87         .worker_fn = httpd_helper_thread,
88 };
89
90 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri);      /*!< list of supported handlers */
91
92 struct ast_http_post_mapping {
93         AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
94         char *from;
95         char *to;
96 };
97
98 static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
99
100 /* all valid URIs must be prepended by the string in prefix. */
101 static char prefix[MAX_PREFIX];
102 static int enablestatic;
103
104 /*! \brief Limit the kinds of files we're willing to serve up */
105 static struct {
106         const char *ext;
107         const char *mtype;
108 } mimetypes[] = {
109         { "png", "image/png" },
110         { "jpg", "image/jpeg" },
111         { "js", "application/x-javascript" },
112         { "wav", "audio/x-wav" },
113         { "mp3", "audio/mpeg" },
114         { "svg", "image/svg+xml" },
115         { "svgz", "image/svg+xml" },
116         { "gif", "image/gif" },
117 };
118
119 struct http_uri_redirect {
120         AST_LIST_ENTRY(http_uri_redirect) entry;
121         char *dest;
122         char target[0];
123 };
124
125 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
126
127 static const 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 static struct ast_str *static_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
141 {
142         char *path;
143         char *ftype;
144         const char *mtype;
145         char wkspace[80];
146         struct stat st;
147         int len;
148         int fd;
149         struct timeval tv = ast_tvnow();
150         char buf[256];
151         struct ast_tm tm;
152
153         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
154            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
155         if (!enablestatic || ast_strlen_zero(uri))
156                 goto out403;
157         /* Disallow any funny filenames at all */
158         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
159                 goto out403;
160         if (strstr(uri, "/.."))
161                 goto out403;
162                 
163         if ((ftype = strrchr(uri, '.')))
164                 ftype++;
165         mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
166         
167         /* Cap maximum length */
168         len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
169         if (len > 1024)
170                 goto out403;
171                 
172         path = alloca(len);
173         sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
174         if (stat(path, &st))
175                 goto out404;
176         if (S_ISDIR(st.st_mode))
177                 goto out404;
178         fd = open(path, O_RDONLY);
179         if (fd < 0)
180                 goto out403;
181
182         ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
183         fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
184                 "Server: Asterisk/%s\r\n"
185                 "Date: %s\r\n"
186                 "Connection: close\r\n"
187                 "Cache-Control: no-cache, no-store\r\n"
188                 "Content-Length: %d\r\n"
189                 "Content-type: %s\r\n\r\n",
190                 ast_get_version(), buf, (int) st.st_size, mtype);
191
192         while ((len = read(fd, buf, sizeof(buf))) > 0)
193                 fwrite(buf, 1, len, ser->f);
194
195         close(fd);
196         return NULL;
197
198 out404:
199         *status = 404;
200         *title = ast_strdup("Not Found");
201         return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
202
203 out403:
204         *status = 403;
205         *title = ast_strdup("Access Denied");
206         return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
207 }
208
209
210 static struct ast_str *httpstatus_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
211 {
212         struct ast_str *out = ast_str_create(512);
213         struct ast_variable *v;
214
215         if (out == NULL)
216                 return out;
217
218         ast_str_append(&out, 0,
219                 "\r\n"
220                 "<title>Asterisk HTTP Status</title>\r\n"
221                 "<body bgcolor=\"#ffffff\">\r\n"
222                 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
223                 "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
224
225         ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
226         ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
227                         ast_inet_ntoa(http_desc.oldsin.sin_addr));
228         ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
229                         ntohs(http_desc.oldsin.sin_port));
230         if (http_tls_cfg.enabled)
231                 ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
232                         ntohs(https_desc.oldsin.sin_port));
233         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
234         for (v = vars; v; v = v->next) {
235                 if (strncasecmp(v->name, "cookie_", 7))
236                         ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
237         }
238         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
239         for (v = vars; v; v = v->next) {
240                 if (!strncasecmp(v->name, "cookie_", 7))
241                         ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
242         }
243         ast_str_append(&out, 0, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
244         return out;
245 }
246
247 static struct ast_http_uri statusuri = {
248         .callback = httpstatus_callback,
249         .description = "Asterisk HTTP General Status",
250         .uri = "httpstatus",
251         .has_subtree = 0,
252 };
253         
254 static struct ast_http_uri staticuri = {
255         .callback = static_callback,
256         .description = "Asterisk HTTP Static Delivery",
257         .uri = "static",
258         .has_subtree = 1,
259         .static_content = 1,
260 };
261         
262 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
263 {
264         struct ast_str *out = ast_str_create(512);
265         if (out == NULL)
266                 return out;
267         ast_str_set(&out, 0,
268                 "Content-type: text/html\r\n"
269                 "%s"
270                 "\r\n"
271                 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
272                 "<html><head>\r\n"
273                 "<title>%d %s</title>\r\n"
274                 "</head><body>\r\n"
275                 "<h1>%s</h1>\r\n"
276                 "<p>%s</p>\r\n"
277                 "<hr />\r\n"
278                 "<address>Asterisk Server</address>\r\n"
279                 "</body></html>\r\n",
280                         (extra_header ? extra_header : ""), status, title, title, text);
281         return out;
282 }
283
284 /*! \brief 
285  * Link the new uri into the list. 
286  *
287  * They are sorted by length of
288  * the string, not alphabetically. Duplicate entries are not replaced,
289  * but the insertion order (using <= and not just <) makes sure that
290  * more recent insertions hide older ones.
291  * On a lookup, we just scan the list and stop at the first matching entry.
292  */
293 int ast_http_uri_link(struct ast_http_uri *urih)
294 {
295         struct ast_http_uri *uri;
296         int len = strlen(urih->uri);
297
298         AST_RWLIST_WRLOCK(&uris);
299
300         if ( AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len ) {
301                 AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
302                 AST_RWLIST_UNLOCK(&uris);
303                 return 0;
304         }
305
306         AST_RWLIST_TRAVERSE(&uris, uri, entry) {
307                 if ( AST_RWLIST_NEXT(uri, entry) 
308                         && strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len ) {
309                         AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
310                         AST_RWLIST_UNLOCK(&uris); 
311                         return 0;
312                 }
313         }
314
315         AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
316
317         AST_RWLIST_UNLOCK(&uris);
318         
319         return 0;
320 }       
321
322 void ast_http_uri_unlink(struct ast_http_uri *urih)
323 {
324         AST_RWLIST_WRLOCK(&uris);
325         AST_RWLIST_REMOVE(&uris, urih, entry);
326         AST_RWLIST_UNLOCK(&uris);
327 }
328
329 /*! \note This assumes that the post_mappings list is locked */
330 static struct ast_http_post_mapping *find_post_mapping(const char *uri)
331 {
332         struct ast_http_post_mapping *post_map;
333
334         if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
335                 ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
336                 return NULL;
337         }
338
339         uri += strlen(prefix);
340         if (*uri == '/')
341                 uri++;
342         
343         AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
344                 if (!strcmp(uri, post_map->from))
345                         return post_map;
346         }
347
348         return NULL;
349 }
350
351 static int get_filename(struct mm_mimepart *part, char *fn, size_t fn_len)
352 {
353         const char *filename;
354
355         filename = mm_content_getdispositionparambyname(part->type, "filename");
356
357         if (ast_strlen_zero(filename))
358                 return -1;
359
360         ast_copy_string(fn, filename, fn_len);
361
362         return 0;
363 }
364
365 static void post_raw(struct mm_mimepart *part, const char *post_dir, const char *fn)
366 {
367         char filename[PATH_MAX];
368         FILE *f;
369         const char *body;
370         size_t body_len;
371
372         snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
373
374         ast_debug(1, "Posting raw data to %s\n", filename);
375
376         if (!(f = fopen(filename, "w"))) {
377                 ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
378                 return;
379         }
380
381         if (!(body = mm_mimepart_getbody(part, 0))) {
382                 ast_debug(1, "Couldn't get the mimepart body\n");
383                 fclose(f);
384                 return;
385         }
386         body_len = mm_mimepart_getlength(part);
387
388         ast_debug(1, "Body length is %ld\n", (long int)body_len);
389
390         fwrite(body, 1, body_len, f);
391
392         fclose(f);
393 }
394
395 static struct ast_str *handle_post(struct server_instance *ser, char *uri, 
396         int *status, char **title, int *contentlength, struct ast_variable *headers,
397         struct ast_variable *cookies)
398 {
399         char buf;
400         FILE *f;
401         size_t res;
402         struct ast_variable *var;
403         int content_len = 0;
404         MM_CTX *ctx;
405         int mm_res, i;
406         struct ast_http_post_mapping *post_map;
407         const char *post_dir;
408         unsigned long ident = 0;
409
410         for (var = cookies; var; var = var->next) {
411                 if (strcasecmp(var->name, "mansession_id"))
412                         continue;
413
414                 if (sscanf(var->value, "%lx", &ident) != 1) {
415                         *status = 400;
416                         *title = ast_strdup("Bad Request");
417                         return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
418                 }
419
420                 if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
421                         *status = 401;
422                         *title = ast_strdup("Unauthorized");
423                         return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
424                 }
425
426                 break;
427         }
428         if (!var) {
429                 *status = 401;
430                 *title = ast_strdup("Unauthorized");
431                 return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
432         }
433
434         if (!(f = tmpfile()))
435                 return NULL;
436
437         for (var = headers; var; var = var->next) {
438                 if (!strcasecmp(var->name, "Content-Length")) {
439                         if ((sscanf(var->value, "%u", &content_len)) != 1) {
440                                 ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
441                                 fclose(f);
442                                 return NULL;
443                         }
444                         ast_debug(1, "Got a Content-Length of %d\n", content_len);
445                 } else if (!strcasecmp(var->name, "Content-Type"))
446                         fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
447         }
448
449         while ((res = fread(&buf, 1, 1, ser->f))) {
450                 fwrite(&buf, 1, 1, f);
451                 content_len--;
452                 if (!content_len)
453                         break;
454         }
455
456         if (fseek(f, SEEK_SET, 0)) {
457                 ast_debug(1, "Failed to seek temp file back to beginning.\n");
458                 fclose(f);
459                 return NULL;
460         }
461
462         AST_RWLIST_RDLOCK(&post_mappings);
463         if (!(post_map = find_post_mapping(uri))) {
464                 ast_debug(1, "%s is not a valid URI for POST\n", uri);
465                 AST_RWLIST_UNLOCK(&post_mappings);
466                 fclose(f);
467                 *status = 404;
468                 *title = ast_strdup("Not Found");
469                 return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
470         }
471         post_dir = ast_strdupa(post_map->to);
472         post_map = NULL;
473         AST_RWLIST_UNLOCK(&post_mappings);
474
475         ast_debug(1, "Going to post files to dir %s\n", post_dir);
476
477         if (!(ctx = mm_context_new())) {
478                 fclose(f);
479                 return NULL;
480         }
481
482         mm_res = mm_parse_fileptr(ctx, f, MM_PARSE_LOOSE, 0);
483         fclose(f);
484         if (mm_res == -1) {
485                 ast_log(LOG_ERROR, "Error parsing MIME data\n");
486                 mm_context_free(ctx);
487                 *status = 400;
488                 *title = ast_strdup("Bad Request");
489                 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
490         }
491
492         mm_res = mm_context_countparts(ctx);
493         if (!mm_res) {
494                 ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
495                 mm_context_free(ctx);
496                 *status = 400;
497                 *title = ast_strdup("Bad Request");
498                 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
499         }
500
501         if (option_debug) {
502                 if (mm_context_iscomposite(ctx))
503                         ast_debug(1, "Found %d MIME parts\n", mm_res - 1);
504                 else
505                         ast_debug(1, "We have a flat (not multi-part) message\n");
506         }
507
508         for (i = 1; i < mm_res; i++) {
509                 struct mm_mimepart *part;
510                 char fn[PATH_MAX];
511
512                 if (!(part = mm_context_getpart(ctx, i))) {
513                         ast_debug(1, "Failed to get mime part num %d\n", i);
514                         continue;
515                 }
516
517                 if (get_filename(part, fn, sizeof(fn))) {
518                         ast_debug(1, "Failed to retrieve a filename for part num %d\n", i);
519                         continue;
520                 }
521         
522                 if (!part->type) {
523                         ast_debug(1, "This part has no content struct?\n");
524                         continue;
525                 }
526
527                 /* XXX This assumes the MIME part body is not encoded! */
528                 post_raw(part, post_dir, fn);
529         }
530
531         mm_context_free(ctx);
532
533         *status = 200;
534         *title = ast_strdup("OK");
535         return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
536 }
537
538 static struct ast_str *handle_uri(struct server_instance *ser, char *uri, int *status, 
539         char **title, int *contentlength, struct ast_variable **cookies, 
540         unsigned int *static_content)
541 {
542         char *c;
543         struct ast_str *out = NULL;
544         char *params = uri;
545         struct ast_http_uri *urih=NULL;
546         int l;
547         struct ast_variable *vars=NULL, *v, *prev = NULL;
548         struct http_uri_redirect *redirect;
549
550         strsep(&params, "?");
551         /* Extract arguments from the request and store them in variables. */
552         if (params) {
553                 char *var, *val;
554
555                 while ((val = strsep(&params, "&"))) {
556                         var = strsep(&val, "=");
557                         if (val)
558                                 ast_uri_decode(val);
559                         else 
560                                 val = "";
561                         ast_uri_decode(var);
562                         if ((v = ast_variable_new(var, val, ""))) {
563                                 if (vars)
564                                         prev->next = v;
565                                 else
566                                         vars = v;
567                                 prev = v;
568                         }
569                 }
570         }
571         /*
572          * Append the cookies to the variables (the only reason to have them
573          * at the end is to avoid another pass of the cookies list to find
574          * the tail).
575          */
576         if (prev)
577                 prev->next = *cookies;
578         else
579                 vars = *cookies;
580         *cookies = NULL;
581         ast_uri_decode(uri);
582
583         AST_RWLIST_RDLOCK(&uri_redirects);
584         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
585                 if (!strcasecmp(uri, redirect->target)) {
586                         char buf[512];
587                         snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
588                         out = ast_http_error(302, "Moved Temporarily", buf,
589                                 "There is no spoon...");
590                         *status = 302;
591                         *title = ast_strdup("Moved Temporarily");
592                         break;
593                 }
594         }
595         AST_RWLIST_UNLOCK(&uri_redirects);
596         if (redirect)
597                 goto cleanup;
598
599         /* We want requests to start with the prefix and '/' */
600         l = strlen(prefix);
601         if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
602                 uri += l + 1;
603                 /* scan registered uris to see if we match one. */
604                 AST_RWLIST_RDLOCK(&uris);
605                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
606                         l = strlen(urih->uri);
607                         c = uri + l;    /* candidate */
608                         if (strncasecmp(urih->uri, uri, l) /* no match */
609                             || (*c && *c != '/')) /* substring */
610                                 continue;
611                         if (*c == '/')
612                                 c++;
613                         if (!*c || urih->has_subtree) {
614                                 uri = c;
615                                 break;
616                         }
617                 }
618                 if (!urih)
619                         AST_RWLIST_UNLOCK(&uris);
620         }
621         if (urih) {
622                 if (urih->static_content)
623                         *static_content = 1;
624                 out = urih->callback(ser, uri, vars, status, title, contentlength);
625                 AST_RWLIST_UNLOCK(&uris);
626         } else {
627                 out = ast_http_error(404, "Not Found", NULL,
628                         "The requested URL was not found on this server.");
629                 *status = 404;
630                 *title = ast_strdup("Not Found");
631         }
632
633 cleanup:
634         ast_variables_destroy(vars);
635         return out;
636 }
637
638 #ifdef DO_SSL
639 #if defined(HAVE_FUNOPEN)
640 #define HOOK_T int
641 #define LEN_T int
642 #else
643 #define HOOK_T ssize_t
644 #define LEN_T size_t
645 #endif
646 /*!
647  * replacement read/write functions for SSL support.
648  * We use wrappers rather than SSL_read/SSL_write directly so
649  * we can put in some debugging.
650  */
651 /*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
652 {
653         int i = SSL_read(cookie, buf, len-1);
654 #if 0
655         if (i >= 0)
656                 buf[i] = '\0';
657         ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
658 #endif
659         return i;
660 }
661
662 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
663 {
664 #if 0
665         char *s = alloca(len+1);
666         strncpy(s, buf, len);
667         s[len] = '\0';
668         ast_verbose("ssl write size %d <%s>\n", (int)len, s);
669 #endif
670         return SSL_write(cookie, buf, len);
671 }
672
673 static int ssl_close(void *cookie)
674 {
675         close(SSL_get_fd(cookie));
676         SSL_shutdown(cookie);
677         SSL_free(cookie);
678         return 0;
679 }*/
680 #endif  /* DO_SSL */
681
682 static void *httpd_helper_thread(void *data)
683 {
684         char buf[4096];
685         char cookie[4096];
686         struct server_instance *ser = data;
687         struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
688         char *uri, *title=NULL;
689         int status = 200, contentlength = 0;
690         struct ast_str *out = NULL;
691         unsigned int static_content = 0;
692
693         if (!fgets(buf, sizeof(buf), ser->f))
694                 goto done;
695
696         uri = ast_skip_nonblanks(buf);  /* Skip method */
697         if (*uri)
698                 *uri++ = '\0';
699
700         uri = ast_skip_blanks(uri);     /* Skip white space */
701
702         if (*uri) {                     /* terminate at the first blank */
703                 char *c = ast_skip_nonblanks(uri);
704                 if (*c)
705                         *c = '\0';
706         }
707
708         /* process "Cookie: " lines */
709         while (fgets(cookie, sizeof(cookie), ser->f)) {
710                 char *vname, *vval;
711                 int l;
712
713                 /* Trim trailing characters */
714                 ast_trim_blanks(cookie);
715                 if (ast_strlen_zero(cookie))
716                         break;
717                 if (strncasecmp(cookie, "Cookie: ", 8)) {
718                         char *name, *value;
719
720                         value = ast_strdupa(cookie);
721                         name = strsep(&value, ":");
722                         if (!value)
723                                 continue;
724                         value = ast_skip_blanks(value);
725                         if (ast_strlen_zero(value))
726                                 continue;
727                         var = ast_variable_new(name, value, "");
728                         if (!var)
729                                 continue;
730                         var->next = headers;
731                         headers = var;
732                         continue;
733                 }
734
735                 /* TODO - The cookie parsing code below seems to work   
736                    in IE6 and FireFox 1.5.  However, it is not entirely 
737                    correct, and therefore may not work in all           
738                    circumstances.                                       
739                       For more details see RFC 2109 and RFC 2965        */
740         
741                 /* FireFox cookie strings look like:                    
742                      Cookie: mansession_id="********"                   
743                    InternetExplorer's look like:                        
744                      Cookie: $Version="1"; mansession_id="********"     */
745                 
746                 /* If we got a FireFox cookie string, the name's right  
747                     after "Cookie: "                                    */
748                 vname = ast_skip_blanks(cookie + 8);
749                         
750                 /* If we got an IE cookie string, we need to skip to    
751                     past the version to get to the name                 */
752                 if (*vname == '$') {
753                         strsep(&vname, ";");
754                         if (!vname)     /* no name ? */
755                                 continue;
756                         vname = ast_skip_blanks(vname);
757                 }
758                 vval = strchr(vname, '=');
759                 if (!vval)
760                         continue;
761                 /* Ditch the = and the quotes */
762                 *vval++ = '\0';
763                 if (*vval)
764                         vval++;
765                 if ( (l = strlen(vval)) )
766                         vval[l - 1] = '\0';     /* trim trailing quote */
767                 var = ast_variable_new(vname, vval, "");
768                 if (var) {
769                         if (prev)
770                                 prev->next = var;
771                         else
772                                 vars = var;
773                         prev = var;
774                 }
775         }
776
777         if (!*uri)
778                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
779         else if (!strcasecmp(buf, "post")) 
780                 out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
781         else if (strcasecmp(buf, "get")) 
782                 out = ast_http_error(501, "Not Implemented", NULL,
783                         "Attempt to use unimplemented / unsupported method");
784         else    /* try to serve it */
785                 out = handle_uri(ser, uri, &status, &title, &contentlength, &vars, &static_content);
786
787         /* If they aren't mopped up already, clean up the cookies */
788         if (vars)
789                 ast_variables_destroy(vars);
790         /* Clean up all the header information pulled as well */
791         if (headers)
792                 ast_variables_destroy(headers);
793
794         if (out) {
795                 struct timeval tv = ast_tvnow();
796                 char timebuf[256];
797                 struct ast_tm tm;
798
799                 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
800                 fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
801                                 "Server: Asterisk/%s\r\n"
802                                 "Date: %s\r\n"
803                                 "Connection: close\r\n"
804                                 "%s",
805                         status, title ? title : "OK", ast_get_version(), timebuf,
806                         static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
807                         /* We set the no-cache headers only for dynamic content.
808                         * If you want to make sure the static file you requested is not from cache,
809                         * append a random variable to your GET request.  Ex: 'something.html?r=109987734'
810                         */
811                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
812                         fprintf(ser->f, "%s", out->str);
813                 } else {
814                         char *tmp = strstr(out->str, "\r\n\r\n");
815
816                         if (tmp) {
817                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
818                                 /* first write the header, then the body */
819                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
820                                 fwrite(tmp + 4, 1, contentlength, ser->f);
821                         }
822                 }
823                 ast_free(out);
824         }
825         if (title)
826                 ast_free(title);
827
828 done:
829         fclose(ser->f);
830         ast_free(ser);
831         return NULL;
832 }
833
834 /*!
835  * \brief Add a new URI redirect
836  * The entries in the redirect list are sorted by length, just like the list
837  * of URI handlers.
838  */
839 static void add_redirect(const char *value)
840 {
841         char *target, *dest;
842         struct http_uri_redirect *redirect, *cur;
843         unsigned int target_len;
844         unsigned int total_len;
845
846         dest = ast_strdupa(value);
847         dest = ast_skip_blanks(dest);
848         target = strsep(&dest, " ");
849         target = ast_skip_blanks(target);
850         target = strsep(&target, " "); /* trim trailing whitespace */
851
852         if (!dest) {
853                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
854                 return;
855         }
856
857         target_len = strlen(target) + 1;
858         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
859
860         if (!(redirect = ast_calloc(1, total_len)))
861                 return;
862
863         redirect->dest = redirect->target + target_len;
864         strcpy(redirect->target, target);
865         strcpy(redirect->dest, dest);
866
867         AST_RWLIST_WRLOCK(&uri_redirects);
868
869         target_len--; /* So we can compare directly with strlen() */
870         if ( AST_RWLIST_EMPTY(&uri_redirects) 
871                 || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len ) {
872                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
873                 AST_RWLIST_UNLOCK(&uri_redirects);
874                 return;
875         }
876
877         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
878                 if ( AST_RWLIST_NEXT(cur, entry) 
879                         && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len ) {
880                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
881                         AST_RWLIST_UNLOCK(&uri_redirects); 
882                         return;
883                 }
884         }
885
886         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
887
888         AST_RWLIST_UNLOCK(&uri_redirects);
889 }
890
891 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
892 {
893         if (post_map->from)
894                 ast_free(post_map->from);
895         if (post_map->to)
896                 ast_free(post_map->to);
897         ast_free(post_map);
898 }
899
900 static void destroy_post_mappings(void)
901 {
902         struct ast_http_post_mapping *post_map;
903
904         AST_RWLIST_WRLOCK(&post_mappings);
905         while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry)))
906                 destroy_post_mapping(post_map);
907         AST_RWLIST_UNLOCK(&post_mappings);
908 }
909
910 static void add_post_mapping(const char *from, const char *to)
911 {
912         struct ast_http_post_mapping *post_map;
913
914         if (!(post_map = ast_calloc(1, sizeof(*post_map))))
915                 return;
916
917         if (!(post_map->from = ast_strdup(from))) {
918                 destroy_post_mapping(post_map);
919                 return;
920         }
921
922         if (!(post_map->to = ast_strdup(to))) {
923                 destroy_post_mapping(post_map);
924                 return;
925         }
926
927         AST_RWLIST_WRLOCK(&post_mappings);
928         AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
929         AST_RWLIST_UNLOCK(&post_mappings);
930 }
931
932 static int __ast_http_load(int reload)
933 {
934         struct ast_config *cfg;
935         struct ast_variable *v;
936         int enabled=0;
937         int newenablestatic=0;
938         struct hostent *hp;
939         struct ast_hostent ahp;
940         char newprefix[MAX_PREFIX];
941         int have_sslbindaddr = 0;
942         struct http_uri_redirect *redirect;
943         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
944
945         if ((cfg = ast_config_load("http.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
946                 return 0;
947
948         /* default values */
949         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
950         http_desc.sin.sin_port = htons(8088);
951
952         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
953         https_desc.sin.sin_port = htons(8089);
954
955         strcpy(newprefix, DEFAULT_PREFIX);
956
957         http_tls_cfg.enabled = 0;
958         if (http_tls_cfg.certfile)
959                 ast_free(http_tls_cfg.certfile);
960         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
961         if (http_tls_cfg.cipher)
962                 ast_free(http_tls_cfg.cipher);
963         http_tls_cfg.cipher = ast_strdup("");
964
965         AST_RWLIST_WRLOCK(&uri_redirects);
966         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry)))
967                 ast_free(redirect);
968         AST_RWLIST_UNLOCK(&uri_redirects);
969
970         destroy_post_mappings();
971
972         if (cfg) {
973                 v = ast_variable_browse(cfg, "general");
974                 for (; v; v = v->next) {
975                         if (!strcasecmp(v->name, "enabled"))
976                                 enabled = ast_true(v->value);
977                         else if (!strcasecmp(v->name, "sslenable"))
978                                 http_tls_cfg.enabled = ast_true(v->value);
979                         else if (!strcasecmp(v->name, "sslbindport"))
980                                 https_desc.sin.sin_port = htons(atoi(v->value));
981                         else if (!strcasecmp(v->name, "sslcert")) {
982                                 ast_free(http_tls_cfg.certfile);
983                                 http_tls_cfg.certfile = ast_strdup(v->value);
984                         } else if (!strcasecmp(v->name, "sslcipher")) {
985                                 ast_free(http_tls_cfg.cipher);
986                                 http_tls_cfg.cipher = ast_strdup(v->value);
987                         }
988                         else if (!strcasecmp(v->name, "enablestatic"))
989                                 newenablestatic = ast_true(v->value);
990                         else if (!strcasecmp(v->name, "bindport"))
991                                 http_desc.sin.sin_port = htons(atoi(v->value));
992                         else if (!strcasecmp(v->name, "sslbindaddr")) {
993                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
994                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
995                                         have_sslbindaddr = 1;
996                                 } else {
997                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
998                                 }
999                         } else if (!strcasecmp(v->name, "bindaddr")) {
1000                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1001                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
1002                                 } else {
1003                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1004                                 }
1005                         } else if (!strcasecmp(v->name, "prefix")) {
1006                                 if (!ast_strlen_zero(v->value)) {
1007                                         newprefix[0] = '/';
1008                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1009                                 } else {
1010                                         newprefix[0] = '\0';
1011                                 }
1012                         } else if (!strcasecmp(v->name, "redirect")) {
1013                                 add_redirect(v->value);
1014                         } else {
1015                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1016                         }
1017                 }
1018
1019                 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
1020                         add_post_mapping(v->name, v->value);
1021
1022                 ast_config_destroy(cfg);
1023         }
1024         if (!have_sslbindaddr)
1025                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
1026         if (enabled)
1027                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
1028         if (strcmp(prefix, newprefix))
1029                 ast_copy_string(prefix, newprefix, sizeof(prefix));
1030         enablestatic = newenablestatic;
1031         server_start(&http_desc);
1032         if (ssl_setup(https_desc.tls_cfg))
1033                 server_start(&https_desc);
1034
1035         return 0;
1036 }
1037
1038 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1039 {
1040         struct ast_http_uri *urih;
1041         struct http_uri_redirect *redirect;
1042         struct ast_http_post_mapping *post_map;
1043         switch (cmd) {
1044         case CLI_INIT:
1045                 e->command = "http show status";
1046                 e->usage = 
1047                         "Usage: http show status\n"
1048                         "       Lists status of internal HTTP engine\n";
1049                 return NULL;
1050         case CLI_GENERATE:
1051                 return NULL;
1052         }
1053         
1054         if (a->argc != 3)
1055                 return CLI_SHOWUSAGE;
1056         ast_cli(a->fd, "HTTP Server Status:\n");
1057         ast_cli(a->fd, "Prefix: %s\n", prefix);
1058         if (!http_desc.oldsin.sin_family)
1059                 ast_cli(a->fd, "Server Disabled\n\n");
1060         else {
1061                 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
1062                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
1063                         ntohs(http_desc.oldsin.sin_port));
1064                 if (http_tls_cfg.enabled)
1065                         ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1066                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
1067                                 ntohs(https_desc.oldsin.sin_port));
1068         }
1069
1070         ast_cli(a->fd, "Enabled URI's:\n");
1071         AST_RWLIST_RDLOCK(&uris);
1072         if (AST_RWLIST_EMPTY(&uris)) {
1073                 ast_cli(a->fd, "None.\n");
1074         } else {
1075                 AST_RWLIST_TRAVERSE(&uris, urih, entry)
1076                         ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
1077         }
1078         AST_RWLIST_UNLOCK(&uris);
1079
1080         ast_cli(a->fd, "\nEnabled Redirects:\n");
1081         AST_RWLIST_RDLOCK(&uri_redirects);
1082         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
1083                 ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
1084         if (AST_RWLIST_EMPTY(&uri_redirects))
1085                 ast_cli(a->fd, "  None.\n");
1086         AST_RWLIST_UNLOCK(&uri_redirects);
1087
1088
1089         ast_cli(a->fd, "\nPOST mappings:\n");
1090         AST_RWLIST_RDLOCK(&post_mappings);
1091         AST_LIST_TRAVERSE(&post_mappings, post_map, entry)
1092                 ast_cli(a->fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
1093         ast_cli(a->fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
1094         AST_RWLIST_UNLOCK(&post_mappings);
1095
1096         return CLI_SUCCESS;
1097 }
1098
1099 int ast_http_reload(void)
1100 {
1101         return __ast_http_load(1);
1102 }
1103
1104 static struct ast_cli_entry cli_http[] = {
1105         AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
1106 };
1107
1108 int ast_http_init(void)
1109 {
1110         mm_library_init();
1111         mm_codec_registerdefaultcodecs();
1112
1113         ast_http_uri_link(&statusuri);
1114         ast_http_uri_link(&staticuri);
1115         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1116
1117         return __ast_http_load(0);
1118 }