the default port number was erroneously stored in host order,
[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                 ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
473                 ast_cli(ser->fd, "Server: Asterisk\r\n");
474                 ast_cli(ser->fd, "Date: %s\r\n", timebuf);
475                 ast_cli(ser->fd, "Connection: close\r\n");
476                 if (contentlength) {
477                         char *tmp = strstr(c, "\r\n\r\n");
478
479                         if (tmp) {
480                                 ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
481                                 /* first write the header, then the body */
482                                 write(ser->fd, c, (tmp + 4 - c));
483                                 write(ser->fd, tmp + 4, contentlength);
484                         }
485                 } else
486                         ast_cli(ser->fd, "%s", c);
487                 free(c);
488         }
489         if (title)
490                 free(title);
491
492 done:
493         fclose(ser->f);
494         free(ser);
495         return NULL;
496 }
497
498 static void *http_root(void *data)
499 {
500         int fd;
501         struct sockaddr_in sin;
502         socklen_t sinlen;
503         struct ast_http_server_instance *ser;
504         pthread_t launched;
505         pthread_attr_t attr;
506         
507         for (;;) {
508                 int flags;
509
510                 ast_wait_for_input(httpfd, -1);
511                 sinlen = sizeof(sin);
512                 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
513                 if (fd < 0) {
514                         if ((errno != EAGAIN) && (errno != EINTR))
515                                 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
516                         continue;
517                 }
518                 ser = ast_calloc(1, sizeof(*ser));
519                 if (!ser) {
520                         ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
521                         close(fd);
522                         continue;
523                 }
524                 flags = fcntl(fd, F_GETFL);
525                 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
526                 ser->fd = fd;
527                 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
528                 if ((ser->f = fdopen(ser->fd, "w+"))) {
529                         pthread_attr_init(&attr);
530                         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
531                         
532                         if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
533                                 ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
534                                 fclose(ser->f);
535                                 free(ser);
536                         }
537                 } else {
538                         ast_log(LOG_WARNING, "fdopen failed!\n");
539                         close(ser->fd);
540                         free(ser);
541                 }
542         }
543         return NULL;
544 }
545
546 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
547 {
548         char *c;
549         c = buf;
550         ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
551         if (expires)
552                 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
553         ast_build_string(&c, &buflen, "\r\n");
554         return buf;
555 }
556
557
558 static void http_server_start(struct sockaddr_in *sin)
559 {
560         int flags;
561         int x = 1;
562         
563         /* Do nothing if nothing has changed */
564         if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
565                 if (option_debug)
566                         ast_log(LOG_DEBUG, "Nothing changed in http\n");
567                 return;
568         }
569         
570         memcpy(&oldsin, sin, sizeof(oldsin));
571         
572         /* Shutdown a running server if there is one */
573         if (master != AST_PTHREADT_NULL) {
574                 pthread_cancel(master);
575                 pthread_kill(master, SIGURG);
576                 pthread_join(master, NULL);
577         }
578         
579         if (httpfd != -1)
580                 close(httpfd);
581
582         /* If there's no new server, stop here */
583         if (!sin->sin_family)
584                 return;
585         
586         
587         httpfd = socket(AF_INET, SOCK_STREAM, 0);
588         if (httpfd < 0) {
589                 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
590                 return;
591         }
592         
593         setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
594         if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
595                 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
596                         ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
597                         strerror(errno));
598                 close(httpfd);
599                 httpfd = -1;
600                 return;
601         }
602         if (listen(httpfd, 10)) {
603                 ast_log(LOG_NOTICE, "Unable to listen!\n");
604                 close(httpfd);
605                 httpfd = -1;
606                 return;
607         }
608         flags = fcntl(httpfd, F_GETFL);
609         fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
610         if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
611                 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
612                                 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
613                                 strerror(errno));
614                 close(httpfd);
615                 httpfd = -1;
616         }
617 }
618
619 static int __ast_http_load(int reload)
620 {
621         struct ast_config *cfg;
622         struct ast_variable *v;
623         int enabled=0;
624         int newenablestatic=0;
625         struct sockaddr_in sin;
626         struct hostent *hp;
627         struct ast_hostent ahp;
628         char newprefix[MAX_PREFIX];
629
630         memset(&sin, 0, sizeof(sin));
631         sin.sin_port = htons(8088);
632         strcpy(newprefix, DEFAULT_PREFIX);
633         cfg = ast_config_load("http.conf");
634         if (cfg) {
635                 v = ast_variable_browse(cfg, "general");
636                 while(v) {
637                         if (!strcasecmp(v->name, "enabled"))
638                                 enabled = ast_true(v->value);
639                         else if (!strcasecmp(v->name, "enablestatic"))
640                                 newenablestatic = ast_true(v->value);
641                         else if (!strcasecmp(v->name, "bindport"))
642                                 sin.sin_port = htons(atoi(v->value));
643                         else if (!strcasecmp(v->name, "bindaddr")) {
644                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
645                                         memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
646                                 } else {
647                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
648                                 }
649                         } else if (!strcasecmp(v->name, "prefix")) {
650                                 if (!ast_strlen_zero(v->value)) {
651                                         newprefix[0] = '/';
652                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
653                                 } else {
654                                         newprefix[0] = '\0';
655                                 }
656                                         
657                         }
658                         v = v->next;
659                 }
660                 ast_config_destroy(cfg);
661         }
662         if (enabled)
663                 sin.sin_family = AF_INET;
664         if (strcmp(prefix, newprefix))
665                 ast_copy_string(prefix, newprefix, sizeof(prefix));
666         enablestatic = newenablestatic;
667         http_server_start(&sin);
668         return 0;
669 }
670
671 static int handle_show_http(int fd, int argc, char *argv[])
672 {
673         struct ast_http_uri *urih;
674         if (argc != 3)
675                 return RESULT_SHOWUSAGE;
676         ast_cli(fd, "HTTP Server Status:\n");
677         ast_cli(fd, "Prefix: %s\n", prefix);
678         if (oldsin.sin_family)
679                 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
680                         ast_inet_ntoa(oldsin.sin_addr),
681                         ntohs(oldsin.sin_port));
682         else
683                 ast_cli(fd, "Server Disabled\n\n");
684         ast_cli(fd, "Enabled URI's:\n");
685         urih = uris;
686         while(urih){
687                 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
688                 urih = urih->next;
689         }
690         if (!uris)
691                 ast_cli(fd, "None.\n");
692         return RESULT_SUCCESS;
693 }
694
695 int ast_http_reload(void)
696 {
697         return __ast_http_load(1);
698 }
699
700 static char show_http_help[] =
701 "Usage: http list status\n"
702 "       Lists status of internal HTTP engine\n";
703
704 static struct ast_cli_entry cli_http[] = {
705         { { "http", "list", "status", NULL },
706         handle_show_http, "Display HTTP server status",
707         show_http_help },
708 };
709
710 int ast_http_init(void)
711 {
712         ast_http_uri_link(&statusuri);
713         ast_http_uri_link(&staticuri);
714         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
715         return __ast_http_load(0);
716 }