Thu Apr 28 2011 17:13:34

Asterisk developer's documentation


http.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2006, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*!
00020  * \file 
00021  * \brief http server for AMI access
00022  *
00023  * \author Mark Spencer <markster@digium.com>
00024  *
00025  * This program implements a tiny http server
00026  * and was inspired by micro-httpd by Jef Poskanzer 
00027  * 
00028  * \ref AstHTTP - AMI over the http protocol
00029  */
00030 
00031 #include "asterisk.h"
00032 
00033 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 315201 $")
00034 
00035 #include <time.h>
00036 #include <sys/time.h>
00037 #include <sys/stat.h>
00038 #include <sys/signal.h>
00039 #include <fcntl.h>
00040 
00041 #include "asterisk/paths.h"   /* use ast_config_AST_DATA_DIR */
00042 #include "asterisk/network.h"
00043 #include "asterisk/cli.h"
00044 #include "asterisk/tcptls.h"
00045 #include "asterisk/http.h"
00046 #include "asterisk/utils.h"
00047 #include "asterisk/strings.h"
00048 #include "asterisk/config.h"
00049 #include "asterisk/stringfields.h"
00050 #include "asterisk/ast_version.h"
00051 #include "asterisk/manager.h"
00052 #include "asterisk/_private.h"
00053 #include "asterisk/astobj2.h"
00054 
00055 #define MAX_PREFIX 80
00056 #define DEFAULT_SESSION_LIMIT 100
00057 
00058 /* See http.h for more information about the SSL implementation */
00059 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
00060 #define  DO_SSL   /* comment in/out if you want to support ssl */
00061 #endif
00062 
00063 static int session_limit = DEFAULT_SESSION_LIMIT;
00064 static int session_count = 0;
00065 
00066 static struct ast_tls_config http_tls_cfg;
00067 
00068 static void *httpd_helper_thread(void *arg);
00069 
00070 /*!
00071  * we have up to two accepting threads, one for http, one for https
00072  */
00073 static struct ast_tcptls_session_args http_desc = {
00074    .accept_fd = -1,
00075    .master = AST_PTHREADT_NULL,
00076    .tls_cfg = NULL,
00077    .poll_timeout = -1,
00078    .name = "http server",
00079    .accept_fn = ast_tcptls_server_root,
00080    .worker_fn = httpd_helper_thread,
00081 };
00082 
00083 static struct ast_tcptls_session_args https_desc = {
00084    .accept_fd = -1,
00085    .master = AST_PTHREADT_NULL,
00086    .tls_cfg = &http_tls_cfg,
00087    .poll_timeout = -1,
00088    .name = "https server",
00089    .accept_fn = ast_tcptls_server_root,
00090    .worker_fn = httpd_helper_thread,
00091 };
00092 
00093 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri); /*!< list of supported handlers */
00094 
00095 /* all valid URIs must be prepended by the string in prefix. */
00096 static char prefix[MAX_PREFIX];
00097 static int enablestatic;
00098 
00099 /*! \brief Limit the kinds of files we're willing to serve up */
00100 static struct {
00101    const char *ext;
00102    const char *mtype;
00103 } mimetypes[] = {
00104    { "png", "image/png" },
00105    { "xml", "text/xml" },
00106    { "jpg", "image/jpeg" },
00107    { "js", "application/x-javascript" },
00108    { "wav", "audio/x-wav" },
00109    { "mp3", "audio/mpeg" },
00110    { "svg", "image/svg+xml" },
00111    { "svgz", "image/svg+xml" },
00112    { "gif", "image/gif" },
00113    { "html", "text/html" },
00114    { "htm", "text/html" },
00115    { "css", "text/css" },
00116    { "cnf", "text/plain" },
00117    { "cfg", "text/plain" },
00118    { "bin", "application/octet-stream" },
00119    { "sbn", "application/octet-stream" },
00120    { "ld", "application/octet-stream" },
00121 };
00122 
00123 struct http_uri_redirect {
00124    AST_LIST_ENTRY(http_uri_redirect) entry;
00125    char *dest;
00126    char target[0];
00127 };
00128 
00129 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
00130 
00131 static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
00132 {
00133    int x;
00134 
00135    if (ftype) {
00136       for (x = 0; x < ARRAY_LEN(mimetypes); x++) {
00137          if (!strcasecmp(ftype, mimetypes[x].ext)) {
00138             return mimetypes[x].mtype;
00139          }
00140       }
00141    }
00142 
00143    snprintf(wkspace, wkspacelen, "text/%s", S_OR(ftype, "plain"));
00144 
00145    return wkspace;
00146 }
00147 
00148 static uint32_t manid_from_vars(struct ast_variable *sid) {
00149    uint32_t mngid;
00150 
00151    while (sid && strcmp(sid->name, "mansession_id"))
00152       sid = sid->next;
00153 
00154    if (!sid || sscanf(sid->value, "%30x", &mngid) != 1)
00155       return 0;
00156 
00157    return mngid;
00158 }
00159 
00160 void ast_http_prefix(char *buf, int len)
00161 {
00162    if (buf) {
00163       ast_copy_string(buf, prefix, len);
00164    }
00165 }
00166 
00167 static struct ast_str *static_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *vars, struct ast_variable *headers, int *status, char **title, int *contentlength)
00168 {
00169    char *path;
00170    char *ftype;
00171    const char *mtype;
00172    char wkspace[80];
00173    struct stat st;
00174    int len;
00175    int fd;
00176    struct timeval now = ast_tvnow();
00177    char buf[256];
00178    struct ast_tm tm;
00179 
00180    /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
00181       substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
00182    if (!enablestatic || ast_strlen_zero(uri)) {
00183       goto out403;
00184    }
00185 
00186    /* Disallow any funny filenames at all */
00187    if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0])) {
00188       goto out403;
00189    }
00190 
00191    if (strstr(uri, "/..")) {
00192       goto out403;
00193    }
00194       
00195    if ((ftype = strrchr(uri, '.'))) {
00196       ftype++;
00197    }
00198 
00199    mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
00200    
00201    /* Cap maximum length */
00202    if ((len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5) > 1024) {
00203       goto out403;
00204    }
00205       
00206    path = alloca(len);
00207    sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
00208    if (stat(path, &st)) {
00209       goto out404;
00210    }
00211 
00212    if (S_ISDIR(st.st_mode)) {
00213       goto out404;
00214    }  
00215 
00216    if ((fd = open(path, O_RDONLY)) < 0) {
00217       goto out403;
00218    }
00219 
00220    if (strstr(path, "/private/") && !astman_is_authed(manid_from_vars(vars))) {
00221       goto out403;
00222    }
00223 
00224    ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&now, &tm, "GMT"));
00225    fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
00226       "Server: Asterisk/%s\r\n"
00227       "Date: %s\r\n"
00228       "Connection: close\r\n"
00229       "Cache-Control: private\r\n"
00230       "Content-Length: %d\r\n"
00231       "Content-type: %s\r\n\r\n",
00232       ast_get_version(), buf, (int) st.st_size, mtype);
00233 
00234    while ((len = read(fd, buf, sizeof(buf))) > 0) {
00235       if (fwrite(buf, 1, len, ser->f) != len) {
00236          ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00237       }
00238    }
00239 
00240    close(fd);
00241 
00242    return NULL;
00243 
00244 out404:
00245    return ast_http_error((*status = 404),
00246                (*title = ast_strdup("Not Found")),
00247                 NULL, "The requested URL was not found on this server.");
00248 
00249 out403:
00250    return ast_http_error((*status = 403),
00251                (*title = ast_strdup("Access Denied")),
00252                NULL, "You do not have permission to access the requested URL.");
00253 }
00254 
00255 
00256 static struct ast_str *httpstatus_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *vars, struct ast_variable *headers, int *status, char **title, int *contentlength)
00257 {
00258    struct ast_str *out = ast_str_create(512);
00259    struct ast_variable *v;
00260 
00261    if (out == NULL) {
00262       return out;
00263    }
00264 
00265    ast_str_append(&out, 0,
00266              "\r\n"
00267              "<title>Asterisk HTTP Status</title>\r\n"
00268              "<body bgcolor=\"#ffffff\">\r\n"
00269              "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
00270              "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
00271    ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
00272    ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
00273              ast_inet_ntoa(http_desc.old_address.sin_addr));
00274    ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
00275              ntohs(http_desc.old_address.sin_port));
00276 
00277    if (http_tls_cfg.enabled) {
00278       ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
00279                 ntohs(https_desc.old_address.sin_port));
00280    }
00281 
00282    ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
00283 
00284    for (v = vars; v; v = v->next) {
00285       if (strncasecmp(v->name, "cookie_", 7)) {
00286          ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
00287       }
00288    }
00289 
00290    ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
00291 
00292    for (v = vars; v; v = v->next) {
00293       if (!strncasecmp(v->name, "cookie_", 7)) {
00294          ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
00295       }
00296    }
00297 
00298    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");
00299    return out;
00300 }
00301 
00302 static struct ast_http_uri statusuri = {
00303    .callback = httpstatus_callback,
00304    .description = "Asterisk HTTP General Status",
00305    .uri = "httpstatus",
00306    .supports_get = 1,
00307    .data = NULL,
00308    .key = __FILE__,
00309 };
00310    
00311 static struct ast_http_uri staticuri = {
00312    .callback = static_callback,
00313    .description = "Asterisk HTTP Static Delivery",
00314    .uri = "static",
00315    .has_subtree = 1,
00316    .static_content = 1,
00317    .supports_get = 1,
00318    .data = NULL,
00319    .key= __FILE__,
00320 };
00321    
00322 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
00323 {
00324    struct ast_str *out = ast_str_create(512);
00325 
00326    if (out == NULL) {
00327       return out;
00328    }
00329 
00330    ast_str_set(&out, 0,
00331           "Content-type: text/html\r\n"
00332           "%s"
00333           "\r\n"
00334           "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
00335           "<html><head>\r\n"
00336           "<title>%d %s</title>\r\n"
00337           "</head><body>\r\n"
00338           "<h1>%s</h1>\r\n"
00339           "<p>%s</p>\r\n"
00340           "<hr />\r\n"
00341           "<address>Asterisk Server</address>\r\n"
00342           "</body></html>\r\n",
00343           (extra_header ? extra_header : ""), status, title, title, text);
00344 
00345    return out;
00346 }
00347 
00348 /*! \brief 
00349  * Link the new uri into the list. 
00350  *
00351  * They are sorted by length of
00352  * the string, not alphabetically. Duplicate entries are not replaced,
00353  * but the insertion order (using <= and not just <) makes sure that
00354  * more recent insertions hide older ones.
00355  * On a lookup, we just scan the list and stop at the first matching entry.
00356  */
00357 int ast_http_uri_link(struct ast_http_uri *urih)
00358 {
00359    struct ast_http_uri *uri;
00360    int len = strlen(urih->uri);
00361 
00362    if (!(urih->supports_get || urih->supports_post)) {
00363       ast_log(LOG_WARNING, "URI handler does not provide either GET or POST method: %s (%s)\n", urih->uri, urih->description);
00364       return -1;
00365    }
00366 
00367    AST_RWLIST_WRLOCK(&uris);
00368 
00369    if (AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len) {
00370       AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
00371       AST_RWLIST_UNLOCK(&uris);
00372 
00373       return 0;
00374    }
00375 
00376    AST_RWLIST_TRAVERSE(&uris, uri, entry) {
00377       if (AST_RWLIST_NEXT(uri, entry) &&
00378           strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len) {
00379          AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
00380          AST_RWLIST_UNLOCK(&uris); 
00381 
00382          return 0;
00383       }
00384    }
00385 
00386    AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
00387 
00388    AST_RWLIST_UNLOCK(&uris);
00389    
00390    return 0;
00391 }  
00392 
00393 void ast_http_uri_unlink(struct ast_http_uri *urih)
00394 {
00395    AST_RWLIST_WRLOCK(&uris);
00396    AST_RWLIST_REMOVE(&uris, urih, entry);
00397    AST_RWLIST_UNLOCK(&uris);
00398 }
00399 
00400 void ast_http_uri_unlink_all_with_key(const char *key)
00401 {
00402    struct ast_http_uri *urih;
00403    AST_RWLIST_WRLOCK(&uris);
00404    AST_RWLIST_TRAVERSE_SAFE_BEGIN(&uris, urih, entry) {
00405       if (!strcmp(urih->key, key)) {
00406          AST_RWLIST_REMOVE_CURRENT(entry);
00407       }
00408       if (urih->dmallocd) {
00409          ast_free(urih->data);
00410       }
00411       if (urih->mallocd) {
00412          ast_free(urih);
00413       }
00414    }
00415    AST_RWLIST_TRAVERSE_SAFE_END;
00416    AST_RWLIST_UNLOCK(&uris);
00417 }
00418 
00419 /*
00420  * Decode special characters in http uri.
00421  * We have ast_uri_decode to handle %XX sequences, but spaces
00422  * are encoded as a '+' so we need to replace them beforehand.
00423  */
00424 static void http_decode(char *s)
00425 {
00426    char *t;
00427    
00428    for (t = s; *t; t++) {
00429       if (*t == '+')
00430          *t = ' ';
00431    }
00432 
00433    ast_uri_decode(s);
00434 }
00435 
00436 static struct ast_str *handle_uri(struct ast_tcptls_session_instance *ser, char *uri, enum ast_http_method method,
00437               int *status, char **title, int *contentlength, struct ast_variable **cookies, struct ast_variable *headers, 
00438               unsigned int *static_content)
00439 {
00440    char *c;
00441    struct ast_str *out = NULL;
00442    char *params = uri;
00443    struct ast_http_uri *urih = NULL;
00444    int l;
00445    struct ast_variable *vars = NULL, *v, *prev = NULL;
00446    struct http_uri_redirect *redirect;
00447    int saw_method = 0;
00448 
00449    ast_debug(2, "HTTP Request URI is %s \n", uri);
00450 
00451    /* preserve previous behavior of only support URI parameters on GET requests */
00452    if (method == AST_HTTP_GET) {
00453       strsep(&params, "?");
00454       
00455       /* Extract arguments from the request and store them in variables.
00456        * Note that a request can have multiple arguments with the same
00457        * name, and we store them all in the list of variables.
00458        * It is up to the application to handle multiple values.
00459        */
00460       if (params) {
00461          char *var, *val;
00462          
00463          while ((val = strsep(&params, "&"))) {
00464             var = strsep(&val, "=");
00465             if (val) {
00466                http_decode(val);
00467             } else {
00468                val = "";
00469             }
00470             http_decode(var);
00471             if ((v = ast_variable_new(var, val, ""))) {
00472                if (vars) {
00473                   prev->next = v;
00474                } else {
00475                   vars = v;
00476                }
00477                prev = v;
00478             }
00479          }
00480       }
00481    }
00482 
00483    /*
00484     * Append the cookies to the list of variables.
00485     * This saves a pass in the cookies list, but has the side effect
00486     * that a variable might mask a cookie with the same name if the
00487     * application stops at the first match.
00488     * Note that this is the same behaviour as $_REQUEST variables in PHP.
00489     */
00490    if (prev) {
00491       prev->next = *cookies;
00492    } else {
00493       vars = *cookies;
00494    }
00495    *cookies = NULL;
00496 
00497    http_decode(uri);
00498 
00499    AST_RWLIST_RDLOCK(&uri_redirects);
00500    AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
00501       if (!strcasecmp(uri, redirect->target)) {
00502          char buf[512];
00503 
00504          snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
00505          out = ast_http_error((*status = 302),
00506                     (*title = ast_strdup("Moved Temporarily")),
00507                     buf, "Redirecting...");
00508 
00509          break;
00510       }
00511    }
00512    AST_RWLIST_UNLOCK(&uri_redirects);
00513 
00514    if (redirect) {
00515       goto cleanup;
00516    }
00517 
00518    /* We want requests to start with the (optional) prefix and '/' */
00519    l = strlen(prefix);
00520    if (!strncasecmp(uri, prefix, l) && uri[l] == '/') {
00521       uri += l + 1;
00522       /* scan registered uris to see if we match one. */
00523       AST_RWLIST_RDLOCK(&uris);
00524       AST_RWLIST_TRAVERSE(&uris, urih, entry) {
00525          ast_debug(2, "match request [%s] with handler [%s] len %d\n", uri, urih->uri, l);
00526          if (!saw_method) {
00527             switch (method) {
00528             case AST_HTTP_GET:
00529                if (urih->supports_get) {
00530                   saw_method = 1;
00531                }
00532                break;
00533             case AST_HTTP_POST:
00534                if (urih->supports_post) {
00535                   saw_method = 1;
00536                }
00537                break;
00538             }
00539          }
00540 
00541          l = strlen(urih->uri);
00542          c = uri + l;   /* candidate */
00543 
00544          if (strncasecmp(urih->uri, uri, l) || /* no match */
00545              (*c && *c != '/')) { /* substring */
00546             continue;
00547          }
00548 
00549          if (*c == '/') {
00550             c++;
00551          }
00552 
00553          if (!*c || urih->has_subtree) {
00554             if (((method == AST_HTTP_GET) && urih->supports_get) ||
00555                 ((method == AST_HTTP_POST) && urih->supports_post)) {
00556                uri = c;
00557 
00558                break;
00559             }
00560          }
00561       }
00562 
00563       if (!urih) {
00564          AST_RWLIST_UNLOCK(&uris);
00565       }
00566    }
00567 
00568    if (method == AST_HTTP_POST && !astman_is_authed(manid_from_vars(vars))) {
00569       out = ast_http_error((*status = 403),
00570                (*title = ast_strdup("Access Denied")),
00571                NULL, "You do not have permission to access the requested URL.");
00572    } else if (urih) {
00573       *static_content = urih->static_content;
00574       out = urih->callback(ser, urih, uri, method, vars, headers, status, title, contentlength);
00575       AST_RWLIST_UNLOCK(&uris);
00576    } else if (saw_method) {
00577       out = ast_http_error((*status = 404),
00578                  (*title = ast_strdup("Not Found")), NULL,
00579                  "The requested URL was not found on this server.");
00580    } else {
00581       out = ast_http_error((*status = 501),
00582                  (*title = ast_strdup("Not Implemented")), NULL,
00583                  "Attempt to use unimplemented / unsupported method");
00584    }
00585 
00586 cleanup:
00587    ast_variables_destroy(vars);
00588 
00589    return out;
00590 }
00591 
00592 #ifdef DO_SSL
00593 #if defined(HAVE_FUNOPEN)
00594 #define HOOK_T int
00595 #define LEN_T int
00596 #else
00597 #define HOOK_T ssize_t
00598 #define LEN_T size_t
00599 #endif
00600 
00601 /*!
00602  * replacement read/write functions for SSL support.
00603  * We use wrappers rather than SSL_read/SSL_write directly so
00604  * we can put in some debugging.
00605  */
00606 /*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
00607 {
00608    int i = SSL_read(cookie, buf, len-1);
00609 #if 0
00610    if (i >= 0)
00611       buf[i] = '\0';
00612    ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
00613 #endif
00614    return i;
00615 }
00616 
00617 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
00618 {
00619 #if 0
00620    char *s = alloca(len+1);
00621    strncpy(s, buf, len);
00622    s[len] = '\0';
00623    ast_verbose("ssl write size %d <%s>\n", (int)len, s);
00624 #endif
00625    return SSL_write(cookie, buf, len);
00626 }
00627 
00628 static int ssl_close(void *cookie)
00629 {
00630    close(SSL_get_fd(cookie));
00631    SSL_shutdown(cookie);
00632    SSL_free(cookie);
00633    return 0;
00634 }*/
00635 #endif   /* DO_SSL */
00636 
00637 static struct ast_variable *parse_cookies(char *cookies)
00638 {
00639    char *cur;
00640    struct ast_variable *vars = NULL, *var;
00641 
00642    /* Skip Cookie: */
00643    cookies += 8;
00644 
00645    while ((cur = strsep(&cookies, ";"))) {
00646       char *name, *val;
00647       
00648       name = val = cur;
00649       strsep(&val, "=");
00650 
00651       if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
00652          continue;
00653       }
00654 
00655       name = ast_strip(name);
00656       val = ast_strip_quoted(val, "\"", "\"");
00657 
00658       if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
00659          continue;
00660       }
00661 
00662       ast_debug(1, "HTTP Cookie, Name: '%s'  Value: '%s'\n", name, val);
00663 
00664       var = ast_variable_new(name, val, __FILE__);
00665       var->next = vars;
00666       vars = var;
00667    }
00668 
00669    return vars;
00670 }
00671 
00672 static void *httpd_helper_thread(void *data)
00673 {
00674    char buf[4096];
00675    char cookie[4096];
00676    struct ast_tcptls_session_instance *ser = data;
00677    struct ast_variable *vars=NULL, *headers = NULL;
00678    char *uri, *title=NULL;
00679    int status = 200, contentlength = 0;
00680    struct ast_str *out = NULL;
00681    unsigned int static_content = 0;
00682    struct ast_variable *tail = headers;
00683 
00684    if (ast_atomic_fetchadd_int(&session_count, +1) >= session_limit) {
00685       goto done;
00686    }
00687 
00688    if (!fgets(buf, sizeof(buf), ser->f)) {
00689       goto done;
00690    }
00691 
00692    uri = ast_skip_nonblanks(buf);   /* Skip method */
00693    if (*uri) {
00694       *uri++ = '\0';
00695    }
00696 
00697    uri = ast_skip_blanks(uri);   /* Skip white space */
00698 
00699    if (*uri) {       /* terminate at the first blank */
00700       char *c = ast_skip_nonblanks(uri);
00701 
00702       if (*c) {
00703          *c = '\0';
00704       }
00705    }
00706 
00707    /* process "Cookie: " lines */
00708    while (fgets(cookie, sizeof(cookie), ser->f)) {
00709       /* Trim trailing characters */
00710       ast_trim_blanks(cookie);
00711       if (ast_strlen_zero(cookie)) {
00712          break;
00713       }
00714       if (!strncasecmp(cookie, "Cookie: ", 8)) {
00715          vars = parse_cookies(cookie);
00716       } else {
00717          char *name, *val;
00718 
00719          val = cookie;
00720          name = strsep(&val, ":");
00721          if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
00722             continue;
00723          }
00724          ast_trim_blanks(name);
00725          val = ast_skip_blanks(val);
00726 
00727          if (!headers) {
00728             headers = ast_variable_new(name, val, __FILE__);
00729             tail = headers;
00730          } else {
00731             tail->next = ast_variable_new(name, val, __FILE__);
00732             tail = tail->next;
00733          }
00734       }
00735    }
00736 
00737    if (!*uri) {
00738       out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
00739    } else if (strcasecmp(buf, "post") && strcasecmp(buf, "get")) {
00740       out = ast_http_error(501, "Not Implemented", NULL,
00741                  "Attempt to use unimplemented / unsupported method");
00742    } else { /* try to serve it */
00743       out = handle_uri(ser, uri, (!strcasecmp(buf, "get")) ? AST_HTTP_GET : AST_HTTP_POST,
00744              &status, &title, &contentlength, &vars, headers, &static_content);
00745    }
00746 
00747    /* If they aren't mopped up already, clean up the cookies */
00748    if (vars) {
00749       ast_variables_destroy(vars);
00750    }
00751    /* Clean up all the header information pulled as well */
00752    if (headers) {
00753       ast_variables_destroy(headers);
00754    }
00755 
00756    if (out) {
00757       struct timeval now = ast_tvnow();
00758       char timebuf[256];
00759       struct ast_tm tm;
00760 
00761       ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&now, &tm, "GMT"));
00762       fprintf(ser->f,
00763          "HTTP/1.1 %d %s\r\n"
00764          "Server: Asterisk/%s\r\n"
00765          "Date: %s\r\n"
00766          "Connection: close\r\n"
00767          "%s",
00768          status, title ? title : "OK", ast_get_version(), timebuf,
00769          static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
00770          /* We set the no-cache headers only for dynamic content.
00771          * If you want to make sure the static file you requested is not from cache,
00772          * append a random variable to your GET request.  Ex: 'something.html?r=109987734'
00773          */
00774       if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
00775          fprintf(ser->f, "%s", ast_str_buffer(out));
00776       } else {
00777          char *tmp = strstr(ast_str_buffer(out), "\r\n\r\n");
00778 
00779          if (tmp) {
00780             fprintf(ser->f, "Content-length: %d\r\n", contentlength);
00781             /* first write the header, then the body */
00782             if (fwrite(ast_str_buffer(out), 1, (tmp + 4 - ast_str_buffer(out)), ser->f) != tmp + 4 - ast_str_buffer(out)) {
00783                ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00784             }
00785             if (fwrite(tmp + 4, 1, contentlength, ser->f) != contentlength ) {
00786                ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00787             }
00788          }
00789       }
00790       ast_free(out);
00791    }
00792 
00793    if (title) {
00794       ast_free(title);
00795    }
00796 
00797 done:
00798    ast_atomic_fetchadd_int(&session_count, -1);
00799    fclose(ser->f);
00800    ao2_ref(ser, -1);
00801    ser = NULL;
00802 
00803    return NULL;
00804 }
00805 
00806 /*!
00807  * \brief Add a new URI redirect
00808  * The entries in the redirect list are sorted by length, just like the list
00809  * of URI handlers.
00810  */
00811 static void add_redirect(const char *value)
00812 {
00813    char *target, *dest;
00814    struct http_uri_redirect *redirect, *cur;
00815    unsigned int target_len;
00816    unsigned int total_len;
00817 
00818    dest = ast_strdupa(value);
00819    dest = ast_skip_blanks(dest);
00820    target = strsep(&dest, " ");
00821    target = ast_skip_blanks(target);
00822    target = strsep(&target, " "); /* trim trailing whitespace */
00823 
00824    if (!dest) {
00825       ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
00826       return;
00827    }
00828 
00829    target_len = strlen(target) + 1;
00830    total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
00831 
00832    if (!(redirect = ast_calloc(1, total_len))) {
00833       return;
00834    }
00835 
00836    redirect->dest = redirect->target + target_len;
00837    strcpy(redirect->target, target);
00838    strcpy(redirect->dest, dest);
00839 
00840    AST_RWLIST_WRLOCK(&uri_redirects);
00841 
00842    target_len--; /* So we can compare directly with strlen() */
00843    if (AST_RWLIST_EMPTY(&uri_redirects) 
00844        || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len) {
00845       AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
00846       AST_RWLIST_UNLOCK(&uri_redirects);
00847 
00848       return;
00849    }
00850 
00851    AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
00852       if (AST_RWLIST_NEXT(cur, entry) 
00853           && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len) {
00854          AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
00855          AST_RWLIST_UNLOCK(&uri_redirects); 
00856 
00857          return;
00858       }
00859    }
00860 
00861    AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
00862 
00863    AST_RWLIST_UNLOCK(&uri_redirects);
00864 }
00865 
00866 static int __ast_http_load(int reload)
00867 {
00868    struct ast_config *cfg;
00869    struct ast_variable *v;
00870    int enabled=0;
00871    int newenablestatic=0;
00872    struct hostent *hp;
00873    struct ast_hostent ahp;
00874    char newprefix[MAX_PREFIX] = "";
00875    int have_sslbindaddr = 0;
00876    struct http_uri_redirect *redirect;
00877    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00878 
00879    cfg = ast_config_load2("http.conf", "http", config_flags);
00880    if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
00881       return 0;
00882    }
00883 
00884    /* default values */
00885    memset(&http_desc.local_address, 0, sizeof(http_desc.local_address));
00886    http_desc.local_address.sin_port = htons(8088);
00887 
00888    memset(&https_desc.local_address, 0, sizeof(https_desc.local_address));
00889    https_desc.local_address.sin_port = htons(8089);
00890 
00891    http_tls_cfg.enabled = 0;
00892    if (http_tls_cfg.certfile) {
00893       ast_free(http_tls_cfg.certfile);
00894    }
00895    http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
00896    if (http_tls_cfg.cipher) {
00897       ast_free(http_tls_cfg.cipher);
00898    }
00899    http_tls_cfg.cipher = ast_strdup("");
00900 
00901    AST_RWLIST_WRLOCK(&uri_redirects);
00902    while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
00903       ast_free(redirect);
00904    }
00905    AST_RWLIST_UNLOCK(&uri_redirects);
00906 
00907    if (cfg) {
00908       v = ast_variable_browse(cfg, "general");
00909       for (; v; v = v->next) {
00910          if (!strcasecmp(v->name, "enabled")) {
00911             enabled = ast_true(v->value);
00912          } else if (!strcasecmp(v->name, "sslenable")) {
00913             http_tls_cfg.enabled = ast_true(v->value);
00914          } else if (!strcasecmp(v->name, "sslbindport")) {
00915             https_desc.local_address.sin_port = htons(atoi(v->value));
00916          } else if (!strcasecmp(v->name, "sslcert")) {
00917             ast_free(http_tls_cfg.certfile);
00918             http_tls_cfg.certfile = ast_strdup(v->value);
00919          } else if (!strcasecmp(v->name, "sslcipher")) {
00920             ast_free(http_tls_cfg.cipher);
00921             http_tls_cfg.cipher = ast_strdup(v->value);
00922          } else if (!strcasecmp(v->name, "enablestatic")) {
00923             newenablestatic = ast_true(v->value);
00924          } else if (!strcasecmp(v->name, "bindport")) {
00925             http_desc.local_address.sin_port = htons(atoi(v->value));
00926          } else if (!strcasecmp(v->name, "sslbindaddr")) {
00927             if ((hp = ast_gethostbyname(v->value, &ahp))) {
00928                memcpy(&https_desc.local_address.sin_addr, hp->h_addr, sizeof(https_desc.local_address.sin_addr));
00929                have_sslbindaddr = 1;
00930             } else {
00931                ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
00932             }
00933          } else if (!strcasecmp(v->name, "bindaddr")) {
00934             if ((hp = ast_gethostbyname(v->value, &ahp))) {
00935                memcpy(&http_desc.local_address.sin_addr, hp->h_addr, sizeof(http_desc.local_address.sin_addr));
00936             } else {
00937                ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
00938             }
00939          } else if (!strcasecmp(v->name, "prefix")) {
00940             if (!ast_strlen_zero(v->value)) {
00941                newprefix[0] = '/';
00942                ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
00943             } else {
00944                newprefix[0] = '\0';
00945             }
00946          } else if (!strcasecmp(v->name, "redirect")) {
00947             add_redirect(v->value);
00948          } else if (!strcasecmp(v->name, "sessionlimit")) {
00949             if (ast_parse_arg(v->value, PARSE_INT32|PARSE_DEFAULT|PARSE_IN_RANGE,
00950                      &session_limit, DEFAULT_SESSION_LIMIT, 1, INT_MAX)) {
00951                ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
00952                      v->name, v->value, v->lineno);
00953             }
00954          } else {
00955             ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
00956          }
00957       }
00958 
00959       ast_config_destroy(cfg);
00960    }
00961 
00962    if (!have_sslbindaddr) {
00963       https_desc.local_address.sin_addr = http_desc.local_address.sin_addr;
00964    }
00965    if (enabled) {
00966       http_desc.local_address.sin_family = https_desc.local_address.sin_family = AF_INET;
00967    }
00968    if (strcmp(prefix, newprefix)) {
00969       ast_copy_string(prefix, newprefix, sizeof(prefix));
00970    }
00971    enablestatic = newenablestatic;
00972    ast_tcptls_server_start(&http_desc);
00973    if (ast_ssl_setup(https_desc.tls_cfg)) {
00974       ast_tcptls_server_start(&https_desc);
00975    }
00976 
00977    return 0;
00978 }
00979 
00980 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00981 {
00982    struct ast_http_uri *urih;
00983    struct http_uri_redirect *redirect;
00984 
00985    switch (cmd) {
00986    case CLI_INIT:
00987       e->command = "http show status";
00988       e->usage = 
00989          "Usage: http show status\n"
00990          "       Lists status of internal HTTP engine\n";
00991       return NULL;
00992    case CLI_GENERATE:
00993       return NULL;
00994    }
00995    
00996    if (a->argc != 3) {
00997       return CLI_SHOWUSAGE;
00998    }
00999    ast_cli(a->fd, "HTTP Server Status:\n");
01000    ast_cli(a->fd, "Prefix: %s\n", prefix);
01001    if (!http_desc.old_address.sin_family) {
01002       ast_cli(a->fd, "Server Disabled\n\n");
01003    } else {
01004       ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
01005          ast_inet_ntoa(http_desc.old_address.sin_addr),
01006          ntohs(http_desc.old_address.sin_port));
01007       if (http_tls_cfg.enabled) {
01008          ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
01009             ast_inet_ntoa(https_desc.old_address.sin_addr),
01010             ntohs(https_desc.old_address.sin_port));
01011       }
01012    }
01013 
01014    ast_cli(a->fd, "Enabled URI's:\n");
01015    AST_RWLIST_RDLOCK(&uris);
01016    if (AST_RWLIST_EMPTY(&uris)) {
01017       ast_cli(a->fd, "None.\n");
01018    } else {
01019       AST_RWLIST_TRAVERSE(&uris, urih, entry) {
01020          ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : ""), urih->description);
01021       }
01022    }
01023    AST_RWLIST_UNLOCK(&uris);
01024 
01025    ast_cli(a->fd, "\nEnabled Redirects:\n");
01026    AST_RWLIST_RDLOCK(&uri_redirects);
01027    AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
01028       ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
01029    }
01030    if (AST_RWLIST_EMPTY(&uri_redirects)) {
01031       ast_cli(a->fd, "  None.\n");
01032    }
01033    AST_RWLIST_UNLOCK(&uri_redirects);
01034 
01035    return CLI_SUCCESS;
01036 }
01037 
01038 int ast_http_reload(void)
01039 {
01040    return __ast_http_load(1);
01041 }
01042 
01043 static struct ast_cli_entry cli_http[] = {
01044    AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
01045 };
01046 
01047 int ast_http_init(void)
01048 {
01049    ast_http_uri_link(&statusuri);
01050    ast_http_uri_link(&staticuri);
01051    ast_cli_register_multiple(cli_http, ARRAY_LEN(cli_http));
01052 
01053    return __ast_http_load(0);
01054 }