2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
21 * \brief http server for AMI access
23 * \author Mark Spencer <markster@digium.com>
25 * This program implements a tiny http server
26 * and was inspired by micro-httpd by Jef Poskanzer
28 * \ref AstHTTP - AMI over the http protocol
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35 #include <sys/types.h>
41 #include <netinet/in.h>
43 #include <sys/socket.h>
45 #include <sys/signal.h>
46 #include <arpa/inet.h>
51 #include "minimime/mm.h"
53 #include "asterisk/cli.h"
54 #include "asterisk/http.h"
55 #include "asterisk/utils.h"
56 #include "asterisk/strings.h"
57 #include "asterisk/options.h"
58 #include "asterisk/config.h"
59 #include "asterisk/stringfields.h"
60 #include "asterisk/version.h"
61 #include "asterisk/manager.h"
64 #define DEFAULT_PREFIX "/asterisk"
66 /* See http.h for more information about the SSL implementation */
67 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
68 #define DO_SSL /* comment in/out if you want to support ssl */
71 static struct tls_config http_tls_cfg;
73 static void *httpd_helper_thread(void *arg);
76 * we have up to two accepting threads, one for http, one for https
78 static struct server_args http_desc = {
80 .master = AST_PTHREADT_NULL,
83 .name = "http server",
84 .accept_fn = server_root,
85 .worker_fn = httpd_helper_thread,
88 static struct server_args https_desc = {
90 .master = AST_PTHREADT_NULL,
91 .tls_cfg = &http_tls_cfg,
93 .name = "https server",
94 .accept_fn = server_root,
95 .worker_fn = httpd_helper_thread,
98 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri); /*!< list of supported handlers */
100 struct ast_http_post_mapping {
101 AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
106 static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
108 /* all valid URIs must be prepended by the string in prefix. */
109 static char prefix[MAX_PREFIX];
110 static int enablestatic;
112 /*! \brief Limit the kinds of files we're willing to serve up */
117 { "png", "image/png" },
118 { "jpg", "image/jpeg" },
119 { "js", "application/x-javascript" },
120 { "wav", "audio/x-wav" },
121 { "mp3", "audio/mpeg" },
122 { "svg", "image/svg+xml" },
123 { "svgz", "image/svg+xml" },
124 { "gif", "image/gif" },
127 struct http_uri_redirect {
128 AST_LIST_ENTRY(http_uri_redirect) entry;
133 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
135 static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
139 for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
140 if (!strcasecmp(ftype, mimetypes[x].ext))
141 return mimetypes[x].mtype;
144 snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
148 static struct ast_str *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
150 struct ast_str *result;
159 /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
160 substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
161 if (!enablestatic || ast_strlen_zero(uri))
163 /* Disallow any funny filenames at all */
164 if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
166 if (strstr(uri, "/.."))
169 if ((ftype = strrchr(uri, '.')))
171 mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
173 /* Cap maximum length */
174 len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
179 sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
182 if (S_ISDIR(st.st_mode))
184 fd = open(path, O_RDONLY);
188 len = st.st_size + strlen(mtype) + 40;
189 result = ast_str_create(len);
190 if (result == NULL) /* XXX not really but... */
193 ast_str_append(&result, 0, "Content-type: %s\r\n\r\n", mtype);
194 *contentlength = read(fd, result->str + result->used, st.st_size);
195 if (*contentlength < 0) {
200 result->used += *contentlength;
206 *title = ast_strdup("Not Found");
207 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
211 *title = ast_strdup("Access Denied");
212 return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
216 static struct ast_str *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
218 struct ast_str *out = ast_str_create(512);
219 struct ast_variable *v;
224 ast_str_append(&out, 0,
226 "<title>Asterisk HTTP Status</title>\r\n"
227 "<body bgcolor=\"#ffffff\">\r\n"
228 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
229 "<h2> Asterisk™ HTTP Status</h2></td></tr>\r\n");
231 ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
232 ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
233 ast_inet_ntoa(http_desc.oldsin.sin_addr));
234 ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
235 ntohs(http_desc.oldsin.sin_port));
236 if (http_tls_cfg.enabled)
237 ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
238 ntohs(https_desc.oldsin.sin_port));
239 ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
240 for (v = vars; v; v = v->next) {
241 if (strncasecmp(v->name, "cookie_", 7))
242 ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
244 ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
245 for (v = vars; v; v = v->next) {
246 if (!strncasecmp(v->name, "cookie_", 7))
247 ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
249 ast_str_append(&out, 0, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
253 static struct ast_http_uri statusuri = {
254 .callback = httpstatus_callback,
255 .description = "Asterisk HTTP General Status",
260 static struct ast_http_uri staticuri = {
261 .callback = static_callback,
262 .description = "Asterisk HTTP Static Delivery",
268 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
270 struct ast_str *out = ast_str_create(512);
274 "Content-type: text/html\r\n"
277 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
279 "<title>%d %s</title>\r\n"
284 "<address>Asterisk Server</address>\r\n"
285 "</body></html>\r\n",
286 (extra_header ? extra_header : ""), status, title, title, text);
291 * Link the new uri into the list.
293 * They are sorted by length of
294 * the string, not alphabetically. Duplicate entries are not replaced,
295 * but the insertion order (using <= and not just <) makes sure that
296 * more recent insertions hide older ones.
297 * On a lookup, we just scan the list and stop at the first matching entry.
299 int ast_http_uri_link(struct ast_http_uri *urih)
301 struct ast_http_uri *uri;
302 int len = strlen(urih->uri);
304 AST_RWLIST_WRLOCK(&uris);
306 if ( AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len ) {
307 AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
308 AST_RWLIST_UNLOCK(&uris);
312 AST_RWLIST_TRAVERSE(&uris, uri, entry) {
313 if ( AST_RWLIST_NEXT(uri, entry)
314 && strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len ) {
315 AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
316 AST_RWLIST_UNLOCK(&uris);
321 AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
323 AST_RWLIST_UNLOCK(&uris);
328 void ast_http_uri_unlink(struct ast_http_uri *urih)
330 AST_RWLIST_WRLOCK(&uris);
331 AST_RWLIST_REMOVE(&uris, urih, entry);
332 AST_RWLIST_UNLOCK(&uris);
335 /*! \note This assumes that the post_mappings list is locked */
336 static struct ast_http_post_mapping *find_post_mapping(const char *uri)
338 struct ast_http_post_mapping *post_map;
340 if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
341 ast_log(LOG_DEBUG, "URI %s does not have prefix %s\n", uri, prefix);
345 uri += strlen(prefix);
349 AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
350 if (!strcmp(uri, post_map->from))
357 static int get_filename(struct mm_mimepart *part, char *fn, size_t fn_len)
359 const char *filename;
361 filename = mm_content_getdispositionparambyname(part->type, "filename");
363 if (ast_strlen_zero(filename))
366 ast_copy_string(fn, filename, fn_len);
371 static void post_raw(struct mm_mimepart *part, const char *post_dir, const char *fn)
373 char filename[PATH_MAX];
378 snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
381 ast_log(LOG_DEBUG, "Posting raw data to %s\n", filename);
383 if (!(f = fopen(filename, "w"))) {
384 ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
388 if (!(body = mm_mimepart_getbody(part, 0))) {
390 ast_log(LOG_DEBUG, "Couldn't get the mimepart body\n");
394 body_len = mm_mimepart_getlength(part);
397 ast_log(LOG_DEBUG, "Body length is %ld\n", (long int)body_len);
399 fwrite(body, 1, body_len, f);
404 static struct ast_str *handle_post(struct server_instance *ser, char *uri,
405 int *status, char **title, int *contentlength, struct ast_variable *headers,
406 struct ast_variable *cookies)
411 struct ast_variable *var;
415 struct ast_http_post_mapping *post_map;
416 const char *post_dir;
417 unsigned long ident = 0;
419 for (var = cookies; var; var = var->next) {
420 if (strcasecmp(var->name, "mansession_id"))
423 if (sscanf(var->value, "%lx", &ident) != 1) {
425 *title = ast_strdup("Bad Request");
426 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
429 if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
431 *title = ast_strdup("Unauthorized");
432 return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
439 *title = ast_strdup("Unauthorized");
440 return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
443 if (!(f = tmpfile()))
446 for (var = headers; var; var = var->next) {
447 if (!strcasecmp(var->name, "Content-Length")) {
448 if ((sscanf(var->value, "%u", &content_len)) != 1) {
449 ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
454 ast_log(LOG_DEBUG, "Got a Content-Length of %d\n", content_len);
455 } else if (!strcasecmp(var->name, "Content-Type"))
456 fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
459 while ((res = fread(&buf, 1, 1, ser->f))) {
460 fwrite(&buf, 1, 1, f);
466 if (fseek(f, SEEK_SET, 0)) {
468 ast_log(LOG_DEBUG, "Failed to seek temp file back to beginning.\n");
473 AST_RWLIST_RDLOCK(&post_mappings);
474 if (!(post_map = find_post_mapping(uri))) {
476 ast_log(LOG_DEBUG, "%s is not a valid URI for POST\n", uri);
477 AST_RWLIST_UNLOCK(&post_mappings);
480 *title = ast_strdup("Not Found");
481 return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
483 post_dir = ast_strdupa(post_map->to);
485 AST_RWLIST_UNLOCK(&post_mappings);
488 ast_log(LOG_DEBUG, "Going to post files to dir %s\n", post_dir);
490 if (!(ctx = mm_context_new())) {
495 mm_res = mm_parse_fileptr(ctx, f, MM_PARSE_LOOSE, 0);
498 ast_log(LOG_ERROR, "Error parsing MIME data\n");
499 mm_context_free(ctx);
501 *title = ast_strdup("Bad Request");
502 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
505 mm_res = mm_context_countparts(ctx);
507 ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
508 mm_context_free(ctx);
510 *title = ast_strdup("Bad Request");
511 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
515 if (mm_context_iscomposite(ctx))
516 ast_log(LOG_DEBUG, "Found %d MIME parts\n", mm_res - 1);
518 ast_log(LOG_DEBUG, "We have a flat (not multi-part) message\n");
521 for (i = 1; i < mm_res; i++) {
522 struct mm_mimepart *part;
525 if (!(part = mm_context_getpart(ctx, i))) {
527 ast_log(LOG_DEBUG, "Failed to get mime part num %d\n", i);
531 if (get_filename(part, fn, sizeof(fn))) {
533 ast_log(LOG_DEBUG, "Failed to retrieve a filename for part num %d\n", i);
539 ast_log(LOG_DEBUG, "This part has no content struct?\n");
543 /* XXX This assumes the MIME part body is not encoded! */
544 post_raw(part, post_dir, fn);
547 mm_context_free(ctx);
550 *title = ast_strdup("OK");
551 return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
554 static struct ast_str *handle_uri(struct sockaddr_in *sin, char *uri, int *status,
555 char **title, int *contentlength, struct ast_variable **cookies,
556 unsigned int *static_content)
559 struct ast_str *out = NULL;
561 struct ast_http_uri *urih=NULL;
563 struct ast_variable *vars=NULL, *v, *prev = NULL;
564 struct http_uri_redirect *redirect;
566 strsep(¶ms, "?");
567 /* Extract arguments from the request and store them in variables. */
571 while ((val = strsep(¶ms, "&"))) {
572 var = strsep(&val, "=");
578 if ((v = ast_variable_new(var, val))) {
588 * Append the cookies to the variables (the only reason to have them
589 * at the end is to avoid another pass of the cookies list to find
593 prev->next = *cookies;
599 AST_RWLIST_RDLOCK(&uri_redirects);
600 AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
601 if (!strcasecmp(uri, redirect->target)) {
603 snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
604 out = ast_http_error(302, "Moved Temporarily", buf,
605 "There is no spoon...");
607 *title = ast_strdup("Moved Temporarily");
611 AST_RWLIST_UNLOCK(&uri_redirects);
615 /* We want requests to start with the prefix and '/' */
617 if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
619 /* scan registered uris to see if we match one. */
620 AST_RWLIST_RDLOCK(&uris);
621 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
622 l = strlen(urih->uri);
623 c = uri + l; /* candidate */
624 if (strncasecmp(urih->uri, uri, l) /* no match */
625 || (*c && *c != '/')) /* substring */
629 if (!*c || urih->has_subtree) {
635 AST_RWLIST_UNLOCK(&uris);
638 if (urih->static_content)
640 out = urih->callback(sin, uri, vars, status, title, contentlength);
641 AST_RWLIST_UNLOCK(&uris);
643 out = ast_http_error(404, "Not Found", NULL,
644 "The requested URL was not found on this server.");
646 *title = ast_strdup("Not Found");
650 ast_variables_destroy(vars);
655 #if defined(HAVE_FUNOPEN)
659 #define HOOK_T ssize_t
663 * replacement read/write functions for SSL support.
664 * We use wrappers rather than SSL_read/SSL_write directly so
665 * we can put in some debugging.
667 static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
669 int i = SSL_read(cookie, buf, len-1);
673 ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
678 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
681 char *s = alloca(len+1);
682 strncpy(s, buf, len);
684 ast_verbose("ssl write size %d <%s>\n", (int)len, s);
686 return SSL_write(cookie, buf, len);
689 static int ssl_close(void *cookie)
691 close(SSL_get_fd(cookie));
692 SSL_shutdown(cookie);
699 * creates a FILE * from the fd passed by the accept thread.
700 * This operation is potentially expensive (certificate verification),
701 * so we do it in the child thread context.
703 static void *make_file_from_fd(void *data)
705 struct server_instance *ser = data;
708 * open a FILE * as appropriate.
710 if (!ser->parent->tls_cfg)
711 ser->f = fdopen(ser->fd, "w+");
713 else if ( (ser->ssl = SSL_new(ser->parent->tls_cfg->ssl_ctx)) ) {
714 SSL_set_fd(ser->ssl, ser->fd);
715 if (SSL_accept(ser->ssl) == 0)
716 ast_verbose(" error setting up ssl connection");
718 #if defined(HAVE_FUNOPEN) /* the BSD interface */
719 ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close);
721 #elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */
722 static const cookie_io_functions_t cookie_funcs = {
723 ssl_read, ssl_write, NULL, ssl_close
725 ser->f = fopencookie(ser->ssl, "w+", cookie_funcs);
727 /* could add other methods here */
730 if (!ser->f) /* no success opening descriptor stacking */
737 ast_log(LOG_WARNING, "FILE * open failed!\n");
741 return ser->parent->worker_fn(ser);
744 static void *httpd_helper_thread(void *data)
748 struct server_instance *ser = data;
749 struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
750 char *uri, *title=NULL;
751 int status = 200, contentlength = 0;
752 struct ast_str *out = NULL;
753 unsigned int static_content = 0;
755 if (!fgets(buf, sizeof(buf), ser->f))
758 uri = ast_skip_nonblanks(buf); /* Skip method */
762 uri = ast_skip_blanks(uri); /* Skip white space */
764 if (*uri) { /* terminate at the first blank */
765 char *c = ast_skip_nonblanks(uri);
770 /* process "Cookie: " lines */
771 while (fgets(cookie, sizeof(cookie), ser->f)) {
775 /* Trim trailing characters */
776 ast_trim_blanks(cookie);
777 if (ast_strlen_zero(cookie))
779 if (strncasecmp(cookie, "Cookie: ", 8)) {
782 value = ast_strdupa(cookie);
783 name = strsep(&value, ":");
786 value = ast_skip_blanks(value);
787 if (ast_strlen_zero(value))
789 var = ast_variable_new(name, value);
797 /* TODO - The cookie parsing code below seems to work
798 in IE6 and FireFox 1.5. However, it is not entirely
799 correct, and therefore may not work in all
801 For more details see RFC 2109 and RFC 2965 */
803 /* FireFox cookie strings look like:
804 Cookie: mansession_id="********"
805 InternetExplorer's look like:
806 Cookie: $Version="1"; mansession_id="********" */
808 /* If we got a FireFox cookie string, the name's right
810 vname = ast_skip_blanks(cookie + 8);
812 /* If we got an IE cookie string, we need to skip to
813 past the version to get to the name */
816 if (!vname) /* no name ? */
818 vname = ast_skip_blanks(vname);
820 vval = strchr(vname, '=');
823 /* Ditch the = and the quotes */
827 if ( (l = strlen(vval)) )
828 vval[l - 1] = '\0'; /* trim trailing quote */
829 var = ast_variable_new(vname, vval);
840 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
841 else if (!strcasecmp(buf, "post"))
842 out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
843 else if (strcasecmp(buf, "get"))
844 out = ast_http_error(501, "Not Implemented", NULL,
845 "Attempt to use unimplemented / unsupported method");
846 else /* try to serve it */
847 out = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars, &static_content);
849 /* If they aren't mopped up already, clean up the cookies */
851 ast_variables_destroy(vars);
854 out = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
856 time_t t = time(NULL);
859 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
860 fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
861 "Server: Asterisk/%s\r\n"
863 "Connection: close\r\n"
865 status, title ? title : "OK", ASTERISK_VERSION, timebuf,
866 static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
867 if (!contentlength) { /* opaque body ? just dump it hoping it is properly formatted */
868 fprintf(ser->f, "%s", out->str);
870 char *tmp = strstr(out->str, "\r\n\r\n");
873 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
874 /* first write the header, then the body */
875 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
876 fwrite(tmp + 4, 1, contentlength, ser->f);
890 void *server_root(void *data)
892 struct server_args *desc = data;
894 struct sockaddr_in sin;
896 struct server_instance *ser;
902 if (desc->periodic_fn)
903 desc->periodic_fn(desc);
904 i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout);
907 sinlen = sizeof(sin);
908 fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen);
910 if ((errno != EAGAIN) && (errno != EINTR))
911 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
914 ser = ast_calloc(1, sizeof(*ser));
916 ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
920 flags = fcntl(fd, F_GETFL);
921 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
924 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
926 if (ast_pthread_create_detached_background(&launched, NULL, make_file_from_fd, ser)) {
927 ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
936 int ssl_setup(struct tls_config *cfg)
944 SSL_load_error_strings();
945 SSLeay_add_ssl_algorithms();
946 cfg->ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
947 if (!ast_strlen_zero(cfg->certfile)) {
948 if (SSL_CTX_use_certificate_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
949 SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
950 SSL_CTX_check_private_key(cfg->ssl_ctx) == 0 ) {
951 ast_verbose("ssl cert error <%s>", cfg->certfile);
957 if (!ast_strlen_zero(cfg->cipher)) {
958 if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) {
959 ast_verbose("ssl cipher error <%s>", cfg->cipher);
965 ast_verbose("ssl cert ok");
971 * This is a generic (re)start routine for a TCP server,
972 * which does the socket/bind/listen and starts a thread for handling
975 void server_start(struct server_args *desc)
980 /* Do nothing if nothing has changed */
981 if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
983 ast_log(LOG_DEBUG, "Nothing changed in %s\n", desc->name);
987 desc->oldsin = desc->sin;
989 /* Shutdown a running server if there is one */
990 if (desc->master != AST_PTHREADT_NULL) {
991 pthread_cancel(desc->master);
992 pthread_kill(desc->master, SIGURG);
993 pthread_join(desc->master, NULL);
996 if (desc->accept_fd != -1)
997 close(desc->accept_fd);
999 /* If there's no new server, stop here */
1000 if (desc->sin.sin_family == 0)
1003 desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
1004 if (desc->accept_fd < 0) {
1005 ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n",
1006 desc->name, strerror(errno));
1010 setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
1011 if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {
1012 ast_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n",
1014 ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
1018 if (listen(desc->accept_fd, 10)) {
1019 ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name);
1022 flags = fcntl(desc->accept_fd, F_GETFL);
1023 fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
1024 if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {
1025 ast_log(LOG_NOTICE, "Unable to launch %s on %s:%d: %s\n",
1027 ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
1034 close(desc->accept_fd);
1035 desc->accept_fd = -1;
1039 * \brief Add a new URI redirect
1040 * The entries in the redirect list are sorted by length, just like the list
1043 static void add_redirect(const char *value)
1045 char *target, *dest;
1046 struct http_uri_redirect *redirect, *cur;
1047 unsigned int target_len;
1048 unsigned int total_len;
1050 dest = ast_strdupa(value);
1051 dest = ast_skip_blanks(dest);
1052 target = strsep(&dest, " ");
1053 target = ast_skip_blanks(target);
1054 target = strsep(&target, " "); /* trim trailing whitespace */
1057 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
1061 target_len = strlen(target) + 1;
1062 total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
1064 if (!(redirect = ast_calloc(1, total_len)))
1067 redirect->dest = redirect->target + target_len;
1068 strcpy(redirect->target, target);
1069 strcpy(redirect->dest, dest);
1071 AST_RWLIST_WRLOCK(&uri_redirects);
1073 target_len--; /* So we can compare directly with strlen() */
1074 if ( AST_RWLIST_EMPTY(&uri_redirects)
1075 || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len ) {
1076 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
1077 AST_RWLIST_UNLOCK(&uri_redirects);
1081 AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
1082 if ( AST_RWLIST_NEXT(cur, entry)
1083 && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len ) {
1084 AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
1085 AST_RWLIST_UNLOCK(&uri_redirects);
1090 AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
1092 AST_RWLIST_UNLOCK(&uri_redirects);
1095 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
1098 ast_free(post_map->from);
1100 ast_free(post_map->to);
1104 static void destroy_post_mappings(void)
1106 struct ast_http_post_mapping *post_map;
1108 AST_RWLIST_WRLOCK(&post_mappings);
1109 while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry)))
1110 destroy_post_mapping(post_map);
1111 AST_RWLIST_UNLOCK(&post_mappings);
1114 static void add_post_mapping(const char *from, const char *to)
1116 struct ast_http_post_mapping *post_map;
1118 if (!(post_map = ast_calloc(1, sizeof(*post_map))))
1121 if (!(post_map->from = ast_strdup(from))) {
1122 destroy_post_mapping(post_map);
1126 if (!(post_map->to = ast_strdup(to))) {
1127 destroy_post_mapping(post_map);
1131 AST_RWLIST_WRLOCK(&post_mappings);
1132 AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
1133 AST_RWLIST_UNLOCK(&post_mappings);
1136 static int __ast_http_load(int reload)
1138 struct ast_config *cfg;
1139 struct ast_variable *v;
1141 int newenablestatic=0;
1143 struct ast_hostent ahp;
1144 char newprefix[MAX_PREFIX];
1145 int have_sslbindaddr = 0;
1146 struct http_uri_redirect *redirect;
1148 /* default values */
1149 memset(&http_desc.sin, 0, sizeof(http_desc.sin));
1150 http_desc.sin.sin_port = htons(8088);
1152 memset(&https_desc.sin, 0, sizeof(https_desc.sin));
1153 https_desc.sin.sin_port = htons(8089);
1155 strcpy(newprefix, DEFAULT_PREFIX);
1157 http_tls_cfg.enabled = 0;
1158 if (http_tls_cfg.certfile)
1159 ast_free(http_tls_cfg.certfile);
1160 http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
1161 if (http_tls_cfg.cipher)
1162 ast_free(http_tls_cfg.cipher);
1163 http_tls_cfg.cipher = ast_strdup("");
1165 AST_RWLIST_WRLOCK(&uri_redirects);
1166 while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry)))
1168 AST_RWLIST_UNLOCK(&uri_redirects);
1170 destroy_post_mappings();
1172 cfg = ast_config_load("http.conf");
1174 v = ast_variable_browse(cfg, "general");
1175 for (; v; v = v->next) {
1176 if (!strcasecmp(v->name, "enabled"))
1177 enabled = ast_true(v->value);
1178 else if (!strcasecmp(v->name, "sslenable"))
1179 http_tls_cfg.enabled = ast_true(v->value);
1180 else if (!strcasecmp(v->name, "sslbindport"))
1181 https_desc.sin.sin_port = htons(atoi(v->value));
1182 else if (!strcasecmp(v->name, "sslcert")) {
1183 ast_free(http_tls_cfg.certfile);
1184 http_tls_cfg.certfile = ast_strdup(v->value);
1185 } else if (!strcasecmp(v->name, "sslcipher")) {
1186 ast_free(http_tls_cfg.cipher);
1187 http_tls_cfg.cipher = ast_strdup(v->value);
1189 else if (!strcasecmp(v->name, "enablestatic"))
1190 newenablestatic = ast_true(v->value);
1191 else if (!strcasecmp(v->name, "bindport"))
1192 http_desc.sin.sin_port = htons(atoi(v->value));
1193 else if (!strcasecmp(v->name, "sslbindaddr")) {
1194 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1195 memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
1196 have_sslbindaddr = 1;
1198 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1200 } else if (!strcasecmp(v->name, "bindaddr")) {
1201 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1202 memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
1204 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1206 } else if (!strcasecmp(v->name, "prefix")) {
1207 if (!ast_strlen_zero(v->value)) {
1209 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1211 newprefix[0] = '\0';
1213 } else if (!strcasecmp(v->name, "redirect")) {
1214 add_redirect(v->value);
1216 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1220 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
1221 add_post_mapping(v->name, v->value);
1223 ast_config_destroy(cfg);
1225 if (!have_sslbindaddr)
1226 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
1228 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
1229 if (strcmp(prefix, newprefix))
1230 ast_copy_string(prefix, newprefix, sizeof(prefix));
1231 enablestatic = newenablestatic;
1232 server_start(&http_desc);
1233 if (ssl_setup(https_desc.tls_cfg))
1234 server_start(&https_desc);
1239 static int handle_show_http(int fd, int argc, char *argv[])
1241 struct ast_http_uri *urih;
1242 struct http_uri_redirect *redirect;
1243 struct ast_http_post_mapping *post_map;
1246 return RESULT_SHOWUSAGE;
1248 ast_cli(fd, "HTTP Server Status:\n");
1249 ast_cli(fd, "Prefix: %s\n", prefix);
1250 if (!http_desc.oldsin.sin_family)
1251 ast_cli(fd, "Server Disabled\n\n");
1253 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
1254 ast_inet_ntoa(http_desc.oldsin.sin_addr),
1255 ntohs(http_desc.oldsin.sin_port));
1256 if (http_tls_cfg.enabled)
1257 ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1258 ast_inet_ntoa(https_desc.oldsin.sin_addr),
1259 ntohs(https_desc.oldsin.sin_port));
1262 ast_cli(fd, "Enabled URI's:\n");
1263 AST_RWLIST_RDLOCK(&uris);
1264 if (AST_RWLIST_EMPTY(&uris)) {
1265 ast_cli(fd, "None.\n");
1267 AST_RWLIST_TRAVERSE(&uris, urih, entry)
1268 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
1270 AST_RWLIST_UNLOCK(&uris);
1272 ast_cli(fd, "\nEnabled Redirects:\n");
1273 AST_RWLIST_RDLOCK(&uri_redirects);
1274 AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
1275 ast_cli(fd, " %s => %s\n", redirect->target, redirect->dest);
1276 if (AST_RWLIST_EMPTY(&uri_redirects))
1277 ast_cli(fd, " None.\n");
1278 AST_RWLIST_UNLOCK(&uri_redirects);
1281 ast_cli(fd, "\nPOST mappings:\n");
1282 AST_RWLIST_RDLOCK(&post_mappings);
1283 AST_LIST_TRAVERSE(&post_mappings, post_map, entry)
1284 ast_cli(fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
1285 ast_cli(fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
1286 AST_RWLIST_UNLOCK(&post_mappings);
1288 return RESULT_SUCCESS;
1291 int ast_http_reload(void)
1293 return __ast_http_load(1);
1296 static char show_http_help[] =
1297 "Usage: http show status\n"
1298 " Lists status of internal HTTP engine\n";
1300 static struct ast_cli_entry cli_http[] = {
1301 { { "http", "show", "status", NULL },
1302 handle_show_http, "Display HTTP server status",
1306 int ast_http_init(void)
1309 mm_codec_registerdefaultcodecs();
1311 ast_http_uri_link(&statusuri);
1312 ast_http_uri_link(&staticuri);
1313 ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1315 return __ast_http_load(0);