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