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