Properly handle empty prefix
[asterisk/asterisk.git] / 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
22  *
23  * This program implements a tiny http server supporting the "get" method
24  * only and was inspired by micro-httpd by Jef Poskanzer 
25  */
26
27 #include <sys/types.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <time.h>
32 #include <string.h>
33 #include <netinet/in.h>
34 #include <sys/time.h>
35 #include <sys/socket.h>
36 #include <sys/stat.h>
37 #include <sys/signal.h>
38 #include <arpa/inet.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <pthread.h>
42
43 #include "asterisk.h"
44 #include "asterisk/cli.h"
45 #include "asterisk/http.h"
46 #include "asterisk/utils.h"
47 #include "asterisk/strings.h"
48 #include "asterisk/options.h"
49 #include "asterisk/config.h"
50
51 #define MAX_PREFIX 80
52 #define DEFAULT_PREFIX "asterisk"
53
54 struct ast_http_server_instance {
55         FILE *f;
56         int fd;
57         struct sockaddr_in requestor;
58         ast_http_callback callback;
59 };
60
61 static struct ast_http_uri *uris;
62
63 static int httpfd = -1;
64 static pthread_t master = AST_PTHREADT_NULL;
65 static char prefix[MAX_PREFIX];
66 static int prefix_len = 0;
67 static struct sockaddr_in oldsin;
68 static int enablestatic=0;
69
70 /* Limit the kinds of files we're willing to serve up */
71 static struct {
72         char *ext;
73         char *mtype;
74 } mimetypes[] = {
75         { "png", "image/png" },
76         { "jpg", "image/jpeg" },
77         { "js", "application/x-javascript" },
78         { "wav", "audio/x-wav" },
79         { "mp3", "audio/mpeg" },
80 };
81
82 static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
83 {
84         int x;
85         if (ftype) {
86                 for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
87                         if (!strcasecmp(ftype, mimetypes[x].ext))
88                                 return mimetypes[x].mtype;
89                 }
90         }
91         snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
92         return wkspace;
93 }
94
95 static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
96 {
97         char result[4096];
98         char *c=result;
99         char *path;
100         char *ftype, *mtype;
101         char wkspace[80];
102         struct stat st;
103         int len;
104         int fd;
105         void *blob;
106
107         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
108            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
109         if (!enablestatic || ast_strlen_zero(uri))
110                 goto out403;
111         /* Disallow any funny filenames at all */
112         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
113                 goto out403;
114         if (strstr(uri, "/.."))
115                 goto out403;
116                 
117         if ((ftype = strrchr(uri, '.')))
118                 ftype++;
119         mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
120         
121         /* Cap maximum length */
122         len = strlen(uri) + strlen(ast_config_AST_VAR_DIR) + strlen("/static-http/") + 5;
123         if (len > 1024)
124                 goto out403;
125                 
126         path = alloca(len);
127         sprintf(path, "%s/static-http/%s", ast_config_AST_VAR_DIR, uri);
128         if (stat(path, &st))
129                 goto out404;
130         if (S_ISDIR(st.st_mode))
131                 goto out404;
132         fd = open(path, O_RDONLY);
133         if (fd < 0)
134                 goto out403;
135         
136         len = st.st_size + strlen(mtype) + 40;
137         
138         blob = malloc(len);
139         if (blob) {
140                 c = blob;
141                 sprintf(c, "Content-type: %s\r\n\r\n", mtype);
142                 c += strlen(c);
143                 *contentlength = read(fd, c, st.st_size);
144                 if (*contentlength < 0) {
145                         close(fd);
146                         free(blob);
147                         goto out403;
148                 }
149         }
150         return blob;
151
152 out404:
153         *status = 404;
154         *title = strdup("Not Found");
155         return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
156
157 out403:
158         *status = 403;
159         *title = strdup("Access Denied");
160         return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
161 }
162
163
164 static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
165 {
166         char result[4096];
167         size_t reslen = sizeof(result);
168         char *c=result;
169         struct ast_variable *v;
170         char iabuf[INET_ADDRSTRLEN];
171
172         ast_build_string(&c, &reslen,
173                 "\r\n"
174                 "<title>Asterisk HTTP Status</title>\r\n"
175                 "<body bgcolor=\"#ffffff\">\r\n"
176                 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
177                 "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
178
179         ast_build_string(&c, &reslen, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
180         ast_build_string(&c, &reslen, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
181                         ast_inet_ntoa(iabuf, sizeof(iabuf), oldsin.sin_addr));
182         ast_build_string(&c, &reslen, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
183                         ntohs(oldsin.sin_port));
184         ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
185         v = vars;
186         while(v) {
187                 if (strncasecmp(v->name, "cookie_", 7))
188                         ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
189                 v = v->next;
190         }
191         ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
192         v = vars;
193         while(v) {
194                 if (!strncasecmp(v->name, "cookie_", 7))
195                         ast_build_string(&c, &reslen, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
196                 v = v->next;
197         }
198         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");
199         return strdup(result);
200 }
201
202 static struct ast_http_uri statusuri = {
203         .callback = httpstatus_callback,
204         .description = "Asterisk HTTP General Status",
205         .uri = "httpstatus",
206         .has_subtree = 0,
207 };
208         
209 static struct ast_http_uri staticuri = {
210         .callback = static_callback,
211         .description = "Asterisk HTTP Static Delivery",
212         .uri = "static",
213         .has_subtree = 1,
214 };
215         
216 char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
217 {
218         char *c = NULL;
219         asprintf(&c,
220                 "Content-type: text/html\r\n"
221                 "%s"
222                 "\r\n"
223                 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
224                 "<html><head>\r\n"
225                 "<title>%d %s</title>\r\n"
226                 "</head><body>\r\n"
227                 "<h1>%s</h1>\r\n"
228                 "<p>%s</p>\r\n"
229                 "<hr />\r\n"
230                 "<address>Asterisk Server</address>\r\n"
231                 "</body></html>\r\n",
232                         (extra_header ? extra_header : ""), status, title, title, text);
233         return c;
234 }
235
236 int ast_http_uri_link(struct ast_http_uri *urih)
237 {
238         struct ast_http_uri *prev=uris;
239         if (!uris || strlen(uris->uri) <= strlen(urih->uri)) {
240                 urih->next = uris;
241                 uris = urih;
242         } else {
243                 while (prev->next && (strlen(prev->next->uri) > strlen(urih->uri)))
244                         prev = prev->next;
245                 /* Insert it here */
246                 urih->next = prev->next;
247                 prev->next = urih;
248         }
249         return 0;
250 }       
251
252 void ast_http_uri_unlink(struct ast_http_uri *urih)
253 {
254         struct ast_http_uri *prev = uris;
255         if (!uris)
256                 return;
257         if (uris == urih) {
258                 uris = uris->next;
259         }
260         while(prev->next) {
261                 if (prev->next == urih) {
262                         prev->next = urih->next;
263                         break;
264                 }
265                 prev = prev->next;
266         }
267 }
268
269 static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
270 {
271         char *c;
272         char *turi;
273         char *params;
274         char *var;
275         char *val;
276         struct ast_http_uri *urih=NULL;
277         int len;
278         struct ast_variable *vars=NULL, *v, *prev = NULL;
279         
280         
281         params = strchr(uri, '?');
282         if (params) {
283                 *params = '\0';
284                 params++;
285                 while ((var = strsep(&params, "&"))) {
286                         val = strchr(var, '=');
287                         if (val) {
288                                 *val = '\0';
289                                 val++;
290                                 ast_uri_decode(val);
291                         } else 
292                                 val = "";
293                         ast_uri_decode(var);
294                         if ((v = ast_variable_new(var, val))) {
295                                 if (vars)
296                                         prev->next = v;
297                                 else
298                                         vars = v;
299                                 prev = v;
300                         }
301                 }
302         }
303         if (prev)
304                 prev->next = *cookies;
305         else
306                 vars = *cookies;
307         *cookies = NULL;
308         ast_uri_decode(uri);
309         if (!strncasecmp(uri, prefix, prefix_len)) {
310                 uri += prefix_len;
311                 if (!*uri || (*uri == '/')) {
312                         if (*uri == '/')
313                                 uri++;
314                         urih = uris;
315                         while(urih) {
316                                 len = strlen(urih->uri);
317                                 if (!strncasecmp(urih->uri, uri, len)) {
318                                         if (!uri[len] || uri[len] == '/') {
319                                                 turi = uri + len;
320                                                 if (*turi == '/')
321                                                         turi++;
322                                                 if (!*turi || urih->has_subtree) {
323                                                         uri = turi;
324                                                         break;
325                                                 }
326                                         }
327                                 }
328                                 urih = urih->next;
329                         }
330                 }
331         }
332         if (urih) {
333                 c = urih->callback(sin, uri, vars, status, title, contentlength);
334                 ast_variables_destroy(vars);
335         } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) {
336                 /* Special case: If no prefix, and no URI, send to /static/index.html */
337                 c = ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
338                 *status = 302;
339                 *title = strdup("Moved Temporarily");
340         } else {
341                 c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this serer.");
342                 *status = 404;
343                 *title = strdup("Not Found");
344         }
345         return c;
346 }
347
348 static void *ast_httpd_helper_thread(void *data)
349 {
350         char buf[4096];
351         char cookie[4096];
352         char timebuf[256];
353         struct ast_http_server_instance *ser = data;
354         struct ast_variable *var, *prev=NULL, *vars=NULL;
355         char *uri, *c, *title=NULL;
356         char *vname, *vval;
357         int status = 200, contentlength = 0;
358         time_t t;
359
360         if (fgets(buf, sizeof(buf), ser->f)) {
361                 /* Skip method */
362                 uri = buf;
363                 while(*uri && (*uri > 32)) uri++;
364                 if (*uri) {
365                         *uri = '\0';
366                         uri++;
367                 }
368
369                 /* Skip white space */
370                 while (*uri && (*uri < 33)) uri++;
371
372                 if (*uri) {
373                         c = uri;
374                         while (*c && (*c > 32)) c++;
375                         if (*c) {
376                                 *c = '\0';
377                         }
378                 }
379
380                 while (fgets(cookie, sizeof(cookie), ser->f)) {
381                         /* Trim trailing characters */
382                         while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) {
383                                 cookie[strlen(cookie) - 1] = '\0';
384                         }
385                         if (ast_strlen_zero(cookie))
386                                 break;
387                         if (!strncasecmp(cookie, "Cookie: ", 8)) {
388                                 vname = cookie + 8;
389                                 vval = strchr(vname, '=');
390                                 if (vval) {
391                                         /* Ditch the = and the quotes */
392                                         *vval = '\0';
393                                         vval++;
394                                         if (*vval)
395                                                 vval++;
396                                         if (strlen(vval))
397                                                 vval[strlen(vval) - 1] = '\0';
398                                         var = ast_variable_new(vname, vval);
399                                         if (var) {
400                                                 if (prev)
401                                                         prev->next = var;
402                                                 else
403                                                         vars = var;
404                                                 prev = var;
405                                         }
406                                 }
407                         }
408                 }
409
410                 if (*uri) {
411                         if (!strcasecmp(buf, "get")) 
412                                 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
413                         else 
414                                 c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
415                 } else 
416                         c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
417
418                 /* If they aren't mopped up already, clean up the cookies */
419                 if (vars)
420                         ast_variables_destroy(vars);
421
422                 if (!c)
423                         c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
424                 if (c) {
425                         time(&t);
426                         strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
427                         ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
428                         ast_cli(ser->fd, "Server: Asterisk\r\n");
429                         ast_cli(ser->fd, "Date: %s\r\n", timebuf);
430                         ast_cli(ser->fd, "Connection: close\r\n");
431                         if (contentlength) {
432                                 char *tmp;
433                                 tmp = strstr(c, "\r\n\r\n");
434                                 if (tmp) {
435                                         ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
436                                         write(ser->fd, c, (tmp + 4 - c));
437                                         write(ser->fd, tmp + 4, contentlength);
438                                 }
439                         } else
440                                 ast_cli(ser->fd, "%s", c);
441                         free(c);
442                 }
443                 if (title)
444                         free(title);
445         }
446         fclose(ser->f);
447         free(ser);
448         return NULL;
449 }
450
451 static void *http_root(void *data)
452 {
453         int fd;
454         struct sockaddr_in sin;
455         int sinlen;
456         struct ast_http_server_instance *ser;
457         pthread_t launched;
458         for (;;) {
459                 ast_wait_for_input(httpfd, -1);
460                 sinlen = sizeof(sin);
461                 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
462                 if (fd < 0) {
463                         if ((errno != EAGAIN) && (errno != EINTR))
464                                 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
465                         continue;
466                 }
467                 ser = ast_calloc(1, sizeof(*ser));
468                 if (ser) {
469                         ser->fd = fd;
470                         memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
471                         if ((ser->f = fdopen(ser->fd, "w+"))) {
472                                 if (ast_pthread_create(&launched, NULL, ast_httpd_helper_thread, ser)) {
473                                         ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
474                                         fclose(ser->f);
475                                         free(ser);
476                                 }
477                         } else {
478                                 ast_log(LOG_WARNING, "fdopen failed!\n");
479                                 close(ser->fd);
480                                 free(ser);
481                         }
482                 } else {
483                         close(ser->fd);
484                         free(ser);
485                 }
486         }
487         return NULL;
488 }
489
490 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, int buflen)
491 {
492         char *c;
493         c = buf;
494         ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
495         if (expires)
496                 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
497         ast_build_string(&c, &buflen, "\r\n");
498         return buf;
499 }
500
501
502 static void http_server_start(struct sockaddr_in *sin)
503 {
504         char iabuf[INET_ADDRSTRLEN];
505         int flags;
506         int x = 1;
507         
508         /* Do nothing if nothing has changed */
509         if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
510                 ast_log(LOG_DEBUG, "Nothing changed in http\n");
511                 return;
512         }
513         
514         memcpy(&oldsin, sin, sizeof(oldsin));
515         
516         /* Shutdown a running server if there is one */
517         if (master != AST_PTHREADT_NULL) {
518                 pthread_cancel(master);
519                 pthread_kill(master, SIGURG);
520                 pthread_join(master, NULL);
521         }
522         
523         if (httpfd != -1)
524                 close(httpfd);
525
526         /* If there's no new server, stop here */
527         if (!sin->sin_family)
528                 return;
529         
530         
531         httpfd = socket(AF_INET, SOCK_STREAM, 0);
532         if (httpfd < 0) {
533                 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
534                 return;
535         }
536         
537         setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
538         if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
539                 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
540                         ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), ntohs(sin->sin_port),
541                         strerror(errno));
542                 close(httpfd);
543                 httpfd = -1;
544                 return;
545         }
546         if (listen(httpfd, 10)) {
547                 ast_log(LOG_NOTICE, "Unable to listen!\n");
548                 close(httpfd);
549                 httpfd = -1;
550                 return;
551         }
552         flags = fcntl(httpfd, F_GETFL);
553         fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
554         if (ast_pthread_create(&master, NULL, http_root, NULL)) {
555                 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
556                                 ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), ntohs(sin->sin_port),
557                                 strerror(errno));
558                 close(httpfd);
559                 httpfd = -1;
560         }
561 }
562
563 static int __ast_http_load(int reload)
564 {
565         struct ast_config *cfg;
566         struct ast_variable *v;
567         int enabled=0;
568         int newenablestatic=0;
569         struct sockaddr_in sin;
570         struct hostent *hp;
571         struct ast_hostent ahp;
572         char newprefix[MAX_PREFIX];
573         memset(&sin, 0, sizeof(sin));
574         sin.sin_port = 8088;
575         strcpy(newprefix, DEFAULT_PREFIX);
576         cfg = ast_config_load("http.conf");
577         if (cfg) {
578                 v = ast_variable_browse(cfg, "general");
579                 while(v) {
580                         if (!strcasecmp(v->name, "enabled"))
581                                 enabled = ast_true(v->value);
582                         else if (!strcasecmp(v->name, "enablestatic"))
583                                 newenablestatic = ast_true(v->value);
584                         else if (!strcasecmp(v->name, "bindport"))
585                                 sin.sin_port = ntohs(atoi(v->value));
586                         else if (!strcasecmp(v->name, "bindaddr")) {
587                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
588                                         memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
589                                 } else {
590                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
591                                 }
592                         } else if (!strcasecmp(v->name, "prefix")) {
593                                 if (!ast_strlen_zero(v->value)) {
594                                         newprefix[0] = '/';
595                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
596                                 } else {
597                                         newprefix[0] = '\0';
598                                 }
599                                         
600                         }
601                         v = v->next;
602                 }
603                 ast_config_destroy(cfg);
604         }
605         if (enabled)
606                 sin.sin_family = AF_INET;
607         if (strcmp(prefix, newprefix)) {
608                 ast_copy_string(prefix, newprefix, sizeof(prefix));
609                 prefix_len = strlen(prefix);
610         }
611         enablestatic = newenablestatic;
612         http_server_start(&sin);
613         return 0;
614 }
615
616 static int handle_show_http(int fd, int argc, char *argv[])
617 {
618         char iabuf[INET_ADDRSTRLEN];
619         struct ast_http_uri *urih;
620         if (argc != 2)
621                 return RESULT_SHOWUSAGE;
622         ast_cli(fd, "HTTP Server Status:\n");
623         ast_cli(fd, "Prefix: %s\n", prefix);
624         if (oldsin.sin_family)
625                 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
626                         ast_inet_ntoa(iabuf, sizeof(iabuf), oldsin.sin_addr),
627                         ntohs(oldsin.sin_port));
628         else
629                 ast_cli(fd, "Server Disabled\n\n");
630         ast_cli(fd, "Enabled URI's:\n");
631         urih = uris;
632         while(urih){
633                 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
634                 urih = urih->next;
635         }
636         if (!uris)
637                 ast_cli(fd, "None.\n");
638         return RESULT_SUCCESS;
639 }
640
641 int ast_http_reload(void)
642 {
643         return __ast_http_load(1);
644 }
645
646 static char show_http_help[] =
647 "Usage: show http\n"
648 "       Shows status of internal HTTP engine\n";
649
650 static struct ast_cli_entry http_cli[] = {
651         { { "show", "http", NULL }, handle_show_http,
652           "Display HTTP status", show_http_help },
653 };
654
655 int ast_http_init(void)
656 {
657         ast_http_uri_link(&statusuri);
658         ast_http_uri_link(&staticuri);
659         ast_cli_register_multiple(http_cli, sizeof(http_cli) / sizeof(http_cli[0]));
660         return __ast_http_load(0);
661 }