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