Wed Oct 28 11:45:38 2009

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: 211551 $")
00034 
00035 #include "asterisk/paths.h"   /* use ast_config_AST_DATA_DIR */
00036 #include "asterisk/network.h"
00037 #include <time.h>
00038 #include <sys/time.h>
00039 #include <sys/stat.h>
00040 #include <sys/signal.h>
00041 #include <fcntl.h>
00042 
00043 #ifdef ENABLE_UPLOADS
00044 #include <gmime/gmime.h>
00045 #endif /* ENABLE_UPLOADS */
00046 
00047 #include "asterisk/cli.h"
00048 #include "asterisk/tcptls.h"
00049 #include "asterisk/http.h"
00050 #include "asterisk/utils.h"
00051 #include "asterisk/strings.h"
00052 #include "asterisk/config.h"
00053 #include "asterisk/stringfields.h"
00054 #include "asterisk/ast_version.h"
00055 #include "asterisk/manager.h"
00056 
00057 #define MAX_PREFIX 80
00058 
00059 /* See http.h for more information about the SSL implementation */
00060 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
00061 #define  DO_SSL   /* comment in/out if you want to support ssl */
00062 #endif
00063 
00064 static struct ast_tls_config http_tls_cfg;
00065 
00066 static void *httpd_helper_thread(void *arg);
00067 
00068 /*!
00069  * we have up to two accepting threads, one for http, one for https
00070  */
00071 static struct server_args http_desc = {
00072    .accept_fd = -1,
00073    .master = AST_PTHREADT_NULL,
00074    .tls_cfg = NULL,
00075    .poll_timeout = -1,
00076    .name = "http server",
00077    .accept_fn = ast_tcptls_server_root,
00078    .worker_fn = httpd_helper_thread,
00079 };
00080 
00081 static struct server_args https_desc = {
00082    .accept_fd = -1,
00083    .master = AST_PTHREADT_NULL,
00084    .tls_cfg = &http_tls_cfg,
00085    .poll_timeout = -1,
00086    .name = "https server",
00087    .accept_fn = ast_tcptls_server_root,
00088    .worker_fn = httpd_helper_thread,
00089 };
00090 
00091 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri); /*!< list of supported handlers */
00092 
00093 #ifdef ENABLE_UPLOADS
00094 struct ast_http_post_mapping {
00095    AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
00096    char *from;
00097    char *to;
00098 };
00099 
00100 static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
00101 
00102 struct mime_cbinfo {
00103    int count;
00104    const char *post_dir;
00105 };
00106 #endif /* ENABLE_UPLOADS */
00107 
00108 /* all valid URIs must be prepended by the string in prefix. */
00109 static char prefix[MAX_PREFIX];
00110 static int enablestatic;
00111 
00112 /*! \brief Limit the kinds of files we're willing to serve up */
00113 static struct {
00114    const char *ext;
00115    const char *mtype;
00116 } mimetypes[] = {
00117    { "png", "image/png" },
00118    { "jpg", "image/jpeg" },
00119    { "js", "application/x-javascript" },
00120    { "wav", "audio/x-wav" },
00121    { "mp3", "audio/mpeg" },
00122    { "svg", "image/svg+xml" },
00123    { "svgz", "image/svg+xml" },
00124    { "gif", "image/gif" },
00125 };
00126 
00127 struct http_uri_redirect {
00128    AST_LIST_ENTRY(http_uri_redirect) entry;
00129    char *dest;
00130    char target[0];
00131 };
00132 
00133 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
00134 
00135 static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
00136 {
00137    int x;
00138    if (ftype) {
00139       for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
00140          if (!strcasecmp(ftype, mimetypes[x].ext))
00141             return mimetypes[x].mtype;
00142       }
00143    }
00144    snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
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 static struct ast_str *static_callback(struct ast_tcptls_session_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
00161 {
00162    char *path;
00163    char *ftype;
00164    const char *mtype;
00165    char wkspace[80];
00166    struct stat st;
00167    int len;
00168    int fd;
00169    struct timeval tv = ast_tvnow();
00170    char buf[256];
00171    struct ast_tm tm;
00172 
00173    /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
00174       substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
00175    if (!enablestatic || ast_strlen_zero(uri))
00176       goto out403;
00177    /* Disallow any funny filenames at all */
00178    if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
00179       goto out403;
00180    if (strstr(uri, "/.."))
00181       goto out403;
00182       
00183    if ((ftype = strrchr(uri, '.')))
00184       ftype++;
00185    mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
00186    
00187    /* Cap maximum length */
00188    len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
00189    if (len > 1024)
00190       goto out403;
00191       
00192    path = alloca(len);
00193    sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
00194    if (stat(path, &st))
00195       goto out404;
00196    if (S_ISDIR(st.st_mode))
00197       goto out404;
00198    fd = open(path, O_RDONLY);
00199    if (fd < 0)
00200       goto out403;
00201 
00202    if (strstr(path, "/private/") && !astman_is_authed(manid_from_vars(vars))) {
00203       goto out403;
00204    }
00205 
00206    ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
00207    fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
00208       "Server: Asterisk/%s\r\n"
00209       "Date: %s\r\n"
00210       "Connection: close\r\n"
00211       "Cache-Control: private\r\n"
00212       "Content-Length: %d\r\n"
00213       "Content-type: %s\r\n\r\n",
00214       ast_get_version(), buf, (int) st.st_size, mtype);
00215 
00216    while ((len = read(fd, buf, sizeof(buf))) > 0)
00217       if (fwrite(buf, 1, len, ser->f) != len) {
00218          ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00219       }
00220 
00221    close(fd);
00222    return NULL;
00223 
00224 out404:
00225    return ast_http_error((*status = 404),
00226                (*title = ast_strdup("Not Found")),
00227                 NULL, "The requested URL was not found on this server.");
00228 
00229 out403:
00230    return ast_http_error((*status = 403),
00231                (*title = ast_strdup("Access Denied")),
00232                NULL, "You do not have permission to access the requested URL.");
00233 }
00234 
00235 
00236 static struct ast_str *httpstatus_callback(struct ast_tcptls_session_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
00237 {
00238    struct ast_str *out = ast_str_create(512);
00239    struct ast_variable *v;
00240 
00241    if (out == NULL)
00242       return out;
00243 
00244    ast_str_append(&out, 0,
00245       "\r\n"
00246       "<title>Asterisk HTTP Status</title>\r\n"
00247       "<body bgcolor=\"#ffffff\">\r\n"
00248       "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
00249       "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
00250 
00251    ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
00252    ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
00253          ast_inet_ntoa(http_desc.oldsin.sin_addr));
00254    ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
00255          ntohs(http_desc.oldsin.sin_port));
00256    if (http_tls_cfg.enabled)
00257       ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
00258          ntohs(https_desc.oldsin.sin_port));
00259    ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
00260    for (v = vars; v; v = v->next) {
00261       if (strncasecmp(v->name, "cookie_", 7))
00262          ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
00263    }
00264    ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
00265    for (v = vars; v; v = v->next) {
00266       if (!strncasecmp(v->name, "cookie_", 7))
00267          ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
00268    }
00269    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");
00270    return out;
00271 }
00272 
00273 static struct ast_http_uri statusuri = {
00274    .callback = httpstatus_callback,
00275    .description = "Asterisk HTTP General Status",
00276    .uri = "httpstatus",
00277    .has_subtree = 0,
00278 };
00279    
00280 static struct ast_http_uri staticuri = {
00281    .callback = static_callback,
00282    .description = "Asterisk HTTP Static Delivery",
00283    .uri = "static",
00284    .has_subtree = 1,
00285    .static_content = 1,
00286 };
00287    
00288 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
00289 {
00290    struct ast_str *out = ast_str_create(512);
00291    if (out == NULL)
00292       return out;
00293    ast_str_set(&out, 0,
00294       "Content-type: text/html\r\n"
00295       "%s"
00296       "\r\n"
00297       "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
00298       "<html><head>\r\n"
00299       "<title>%d %s</title>\r\n"
00300       "</head><body>\r\n"
00301       "<h1>%s</h1>\r\n"
00302       "<p>%s</p>\r\n"
00303       "<hr />\r\n"
00304       "<address>Asterisk Server</address>\r\n"
00305       "</body></html>\r\n",
00306          (extra_header ? extra_header : ""), status, title, title, text);
00307    return out;
00308 }
00309 
00310 /*! \brief 
00311  * Link the new uri into the list. 
00312  *
00313  * They are sorted by length of
00314  * the string, not alphabetically. Duplicate entries are not replaced,
00315  * but the insertion order (using <= and not just <) makes sure that
00316  * more recent insertions hide older ones.
00317  * On a lookup, we just scan the list and stop at the first matching entry.
00318  */
00319 int ast_http_uri_link(struct ast_http_uri *urih)
00320 {
00321    struct ast_http_uri *uri;
00322    int len = strlen(urih->uri);
00323 
00324    AST_RWLIST_WRLOCK(&uris);
00325 
00326    if ( AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len ) {
00327       AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
00328       AST_RWLIST_UNLOCK(&uris);
00329       return 0;
00330    }
00331 
00332    AST_RWLIST_TRAVERSE(&uris, uri, entry) {
00333       if ( AST_RWLIST_NEXT(uri, entry) 
00334          && strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len ) {
00335          AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
00336          AST_RWLIST_UNLOCK(&uris); 
00337          return 0;
00338       }
00339    }
00340 
00341    AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
00342 
00343    AST_RWLIST_UNLOCK(&uris);
00344    
00345    return 0;
00346 }  
00347 
00348 void ast_http_uri_unlink(struct ast_http_uri *urih)
00349 {
00350    AST_RWLIST_WRLOCK(&uris);
00351    AST_RWLIST_REMOVE(&uris, urih, entry);
00352    AST_RWLIST_UNLOCK(&uris);
00353 }
00354 
00355 #ifdef ENABLE_UPLOADS
00356 /*! \note This assumes that the post_mappings list is locked */
00357 static struct ast_http_post_mapping *find_post_mapping(const char *uri)
00358 {
00359    struct ast_http_post_mapping *post_map;
00360 
00361    if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
00362       ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
00363       return NULL;
00364    }
00365 
00366    uri += strlen(prefix);
00367    if (*uri == '/')
00368       uri++;
00369    
00370    AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
00371       if (!strcmp(uri, post_map->from))
00372          return post_map;
00373    }
00374 
00375    return NULL;
00376 }
00377 
00378 static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
00379 {
00380    char filename[PATH_MAX];
00381    GMimeDataWrapper *content;
00382    GMimeStream *stream;
00383    int fd;
00384 
00385    snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
00386 
00387    ast_debug(1, "Posting raw data to %s\n", filename);
00388 
00389    if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666)) == -1) {
00390       ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
00391       return;
00392    }
00393 
00394    stream = g_mime_stream_fs_new(fd);
00395 
00396    content = g_mime_part_get_content_object(part);
00397    g_mime_data_wrapper_write_to_stream(content, stream);
00398    g_mime_stream_flush(stream);
00399 
00400    g_object_unref(content);
00401    g_object_unref(stream);
00402 }
00403 
00404 static GMimeMessage *parse_message(FILE *f)
00405 {
00406    GMimeMessage *message;
00407    GMimeParser *parser;
00408    GMimeStream *stream;
00409 
00410    stream = g_mime_stream_file_new(f);
00411 
00412    parser = g_mime_parser_new_with_stream(stream);
00413    g_mime_parser_set_respect_content_length(parser, 1);
00414    
00415    g_object_unref(stream);
00416 
00417    message = g_mime_parser_construct_message(parser);
00418 
00419    g_object_unref(parser);
00420 
00421    return message;
00422 }
00423 
00424 static void process_message_callback(GMimeObject *part, gpointer user_data)
00425 {
00426    struct mime_cbinfo *cbinfo = user_data;
00427 
00428    cbinfo->count++;
00429 
00430    /* We strip off the headers before we get here, so should only see GMIME_IS_PART */
00431    if (GMIME_IS_MESSAGE_PART(part)) {
00432       ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PART\n");
00433       return;
00434    } else if (GMIME_IS_MESSAGE_PARTIAL(part)) {
00435       ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
00436       return;
00437    } else if (GMIME_IS_MULTIPART(part)) {
00438       GList *l;
00439       
00440       ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MULTIPART, trying to process subparts\n");
00441       l = GMIME_MULTIPART (part)->subparts;
00442       while (l) {
00443          process_message_callback(l->data, cbinfo);
00444          l = l->next;
00445       }
00446    } else if (GMIME_IS_PART(part)) {
00447       const char *filename;
00448 
00449       ast_debug(3, "Got mime part\n");
00450       if (ast_strlen_zero(filename = g_mime_part_get_filename(GMIME_PART(part)))) {
00451          ast_debug(1, "Skipping part with no filename\n");
00452          return;
00453       }
00454 
00455       post_raw(GMIME_PART(part), cbinfo->post_dir, filename);
00456    } else {
00457       ast_log(LOG_ERROR, "Encountered unknown MIME part. This should never happen!\n");
00458    }
00459 }
00460 
00461 static int process_message(GMimeMessage *message, const char *post_dir)
00462 {
00463    struct mime_cbinfo cbinfo = {
00464       .count = 0,
00465       .post_dir = post_dir,
00466    };
00467 
00468    g_mime_message_foreach_part(message, process_message_callback, &cbinfo);
00469 
00470    return cbinfo.count;
00471 }
00472 
00473 static struct ast_str *handle_post(struct ast_tcptls_session_instance *ser, char *uri, 
00474    int *status, char **title, int *contentlength, struct ast_variable *headers,
00475    struct ast_variable *cookies)
00476 {
00477    char buf[4096];
00478    FILE *f;
00479    size_t res;
00480    struct ast_variable *var;
00481    int content_len = 0;
00482    struct ast_http_post_mapping *post_map;
00483    const char *post_dir;
00484    unsigned long ident = 0;
00485    GMimeMessage *message;
00486    int message_count = 0;
00487 
00488    for (var = cookies; var; var = var->next) {
00489       if (strcasecmp(var->name, "mansession_id"))
00490          continue;
00491 
00492       if (sscanf(var->value, "%30lx", &ident) != 1) {
00493          *status = 400;
00494          *title = ast_strdup("Bad Request");
00495          return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
00496       }
00497 
00498       if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
00499          *status = 401;
00500          *title = ast_strdup("Unauthorized");
00501          return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
00502       }
00503 
00504       break;
00505    }
00506    if (!var) {
00507       *status = 401;
00508       *title = ast_strdup("Unauthorized");
00509       return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
00510    }
00511 
00512    if (!(f = tmpfile()))
00513       return NULL;
00514 
00515    for (var = headers; var; var = var->next) {
00516       if (!strcasecmp(var->name, "Content-Length")) {
00517          if ((sscanf(var->value, "%30u", &content_len)) != 1) {
00518             ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
00519             fclose(f);
00520             return NULL;
00521          }
00522          ast_debug(1, "Got a Content-Length of %d\n", content_len);
00523       } else if (!strcasecmp(var->name, "Content-Type"))
00524          fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
00525    }
00526 
00527    for(res = sizeof(buf);content_len;content_len -= res) {
00528       if (content_len < res)
00529          res = content_len;
00530       if (fread(buf, 1, res, ser->f) != res) {
00531          ast_log(LOG_WARNING, "fread() failed: %s\n", strerror(errno));
00532       }
00533       if (fwrite(buf, 1, res, f) != res) {
00534          ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00535       }
00536    }
00537 
00538    if (fseek(f, SEEK_SET, 0)) {
00539       ast_debug(1, "Failed to seek temp file back to beginning.\n");
00540       fclose(f);
00541       return NULL;
00542    }
00543 
00544    AST_RWLIST_RDLOCK(&post_mappings);
00545    if (!(post_map = find_post_mapping(uri))) {
00546       ast_debug(1, "%s is not a valid URI for POST\n", uri);
00547       AST_RWLIST_UNLOCK(&post_mappings);
00548       *status = 404;
00549       *title = ast_strdup("Not Found");
00550       return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
00551    }
00552    post_dir = ast_strdupa(post_map->to);
00553    post_map = NULL;
00554    AST_RWLIST_UNLOCK(&post_mappings);
00555 
00556    ast_debug(1, "Going to post files to dir %s\n", post_dir);
00557 
00558    message = parse_message(f); /* Takes ownership and will close f */
00559 
00560    if (!message) {
00561       ast_log(LOG_ERROR, "Error parsing MIME data\n");
00562       *status = 400;
00563       *title = ast_strdup("Bad Request");
00564       return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
00565    }
00566 
00567    if (!(message_count = process_message(message, post_dir))) {
00568       ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
00569       *status = 400;
00570       *title = ast_strdup("Bad Request");
00571       return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
00572    }
00573 
00574    *status = 200;
00575    *title = ast_strdup("OK");
00576    return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
00577 }
00578 #endif /* ENABLE_UPLOADS */
00579 
00580 /*
00581  * Decode special characters in http uri.
00582  * We have ast_uri_decode to handle %XX sequences, but spaces
00583  * are encoded as a '+' so we need to replace them beforehand.
00584  */
00585 static void http_decode(char *s)
00586 {
00587    char *t;
00588    
00589    for (t = s; *t; t++) {
00590       if (*t == '+')
00591          *t = ' ';
00592    }
00593 
00594    ast_uri_decode(s);
00595 }
00596 
00597 static struct ast_str *handle_uri(struct ast_tcptls_session_instance *ser, char *uri, int *status, 
00598    char **title, int *contentlength, struct ast_variable **cookies, 
00599    unsigned int *static_content)
00600 {
00601    char *c;
00602    struct ast_str *out = NULL;
00603    char *params = uri;
00604    struct ast_http_uri *urih=NULL;
00605    int l;
00606    struct ast_variable *vars=NULL, *v, *prev = NULL;
00607    struct http_uri_redirect *redirect;
00608 
00609    strsep(&params, "?");
00610    /* Extract arguments from the request and store them in variables. */
00611    if (params) {
00612       char *var, *val;
00613 
00614       while ((val = strsep(&params, "&"))) {
00615          var = strsep(&val, "=");
00616          if (val)
00617             http_decode(val);
00618          else 
00619             val = "";
00620          http_decode(var);
00621          if ((v = ast_variable_new(var, val, ""))) {
00622             if (vars)
00623                prev->next = v;
00624             else
00625                vars = v;
00626             prev = v;
00627          }
00628       }
00629    }
00630    /*
00631     * Append the cookies to the variables (the only reason to have them
00632     * at the end is to avoid another pass of the cookies list to find
00633     * the tail).
00634     */
00635    if (prev)
00636       prev->next = *cookies;
00637    else
00638       vars = *cookies;
00639    *cookies = NULL;
00640    http_decode(uri);
00641 
00642    AST_RWLIST_RDLOCK(&uri_redirects);
00643    AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
00644       if (!strcasecmp(uri, redirect->target)) {
00645          char buf[512];
00646          snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
00647          out = ast_http_error((*status = 302),
00648                     (*title = ast_strdup("Moved Temporarily")),
00649                     buf, "Redirecting...");
00650 
00651          break;
00652       }
00653    }
00654    AST_RWLIST_UNLOCK(&uri_redirects);
00655    if (redirect)
00656       goto cleanup;
00657 
00658    /* We want requests to start with the (optional) prefix and '/' */
00659    l = strlen(prefix);
00660    if (!strncasecmp(uri, prefix, l) && uri[l] == '/') {
00661       uri += l + 1;
00662       /* scan registered uris to see if we match one. */
00663       AST_RWLIST_RDLOCK(&uris);
00664       AST_RWLIST_TRAVERSE(&uris, urih, entry) {
00665          l = strlen(urih->uri);
00666          c = uri + l;   /* candidate */
00667          if (strncasecmp(urih->uri, uri, l) /* no match */
00668              || (*c && *c != '/')) /* substring */
00669             continue;
00670          if (*c == '/')
00671             c++;
00672          if (!*c || urih->has_subtree) {
00673             uri = c;
00674             break;
00675          }
00676       }
00677       if (!urih)
00678          AST_RWLIST_UNLOCK(&uris);
00679    }
00680    if (urih) {
00681       if (urih->static_content)
00682          *static_content = 1;
00683       out = urih->callback(ser, uri, vars, status, title, contentlength);
00684       AST_RWLIST_UNLOCK(&uris);
00685    } else {
00686       out = ast_http_error(404, "Not Found", NULL,
00687          "The requested URL was not found on this server.");
00688       *status = 404;
00689       *title = ast_strdup("Not Found");
00690    }
00691 
00692 cleanup:
00693    ast_variables_destroy(vars);
00694    return out;
00695 }
00696 
00697 #ifdef DO_SSL
00698 #if defined(HAVE_FUNOPEN)
00699 #define HOOK_T int
00700 #define LEN_T int
00701 #else
00702 #define HOOK_T ssize_t
00703 #define LEN_T size_t
00704 #endif
00705 /*!
00706  * replacement read/write functions for SSL support.
00707  * We use wrappers rather than SSL_read/SSL_write directly so
00708  * we can put in some debugging.
00709  */
00710 /*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
00711 {
00712    int i = SSL_read(cookie, buf, len-1);
00713 #if 0
00714    if (i >= 0)
00715       buf[i] = '\0';
00716    ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
00717 #endif
00718    return i;
00719 }
00720 
00721 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
00722 {
00723 #if 0
00724    char *s = alloca(len+1);
00725    strncpy(s, buf, len);
00726    s[len] = '\0';
00727    ast_verbose("ssl write size %d <%s>\n", (int)len, s);
00728 #endif
00729    return SSL_write(cookie, buf, len);
00730 }
00731 
00732 static int ssl_close(void *cookie)
00733 {
00734    close(SSL_get_fd(cookie));
00735    SSL_shutdown(cookie);
00736    SSL_free(cookie);
00737    return 0;
00738 }*/
00739 #endif   /* DO_SSL */
00740 
00741 static struct ast_variable *parse_cookies(char *cookies)
00742 {
00743    char *cur;
00744    struct ast_variable *vars = NULL, *var;
00745 
00746    /* Skip Cookie: */
00747    cookies += 8;
00748 
00749    while ((cur = strsep(&cookies, ";"))) {
00750       char *name, *val;
00751       
00752       name = val = cur;
00753       strsep(&val, "=");
00754 
00755       if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
00756          continue;
00757       }
00758 
00759       name = ast_strip(name);
00760       val = ast_strip_quoted(val, "\"", "\"");
00761 
00762       if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
00763          continue;
00764       }
00765 
00766       if (option_debug) {
00767          ast_log(LOG_DEBUG, "mmm ... cookie!  Name: '%s'  Value: '%s'\n", name, val);
00768       }
00769 
00770       var = ast_variable_new(name, val, __FILE__);
00771       var->next = vars;
00772       vars = var;
00773    }
00774 
00775    return vars;
00776 }
00777 
00778 static void *httpd_helper_thread(void *data)
00779 {
00780    char buf[4096];
00781    char cookie[4096];
00782    struct ast_tcptls_session_instance *ser = data;
00783    struct ast_variable *vars=NULL, *headers = NULL;
00784    char *uri, *title=NULL;
00785    int status = 200, contentlength = 0;
00786    struct ast_str *out = NULL;
00787    unsigned int static_content = 0;
00788 
00789    if (!fgets(buf, sizeof(buf), ser->f))
00790       goto done;
00791 
00792    uri = ast_skip_nonblanks(buf);   /* Skip method */
00793    if (*uri)
00794       *uri++ = '\0';
00795 
00796    uri = ast_skip_blanks(uri);   /* Skip white space */
00797 
00798    if (*uri) {       /* terminate at the first blank */
00799       char *c = ast_skip_nonblanks(uri);
00800       if (*c)
00801          *c = '\0';
00802    }
00803 
00804    /* process "Cookie: " lines */
00805    while (fgets(cookie, sizeof(cookie), ser->f)) {
00806       /* Trim trailing characters */
00807       ast_trim_blanks(cookie);
00808       if (ast_strlen_zero(cookie))
00809          break;
00810       if (strncasecmp(cookie, "Cookie: ", 8)) {
00811          char *name, *value;
00812          struct ast_variable *var;
00813 
00814          value = ast_strdupa(cookie);
00815          name = strsep(&value, ":");
00816          if (!value)
00817             continue;
00818          value = ast_skip_blanks(value);
00819          if (ast_strlen_zero(value))
00820             continue;
00821          var = ast_variable_new(name, value, "");
00822          if (!var)
00823             continue;
00824          var->next = headers;
00825          headers = var;
00826          continue;
00827       }
00828 
00829       if (vars) {
00830          ast_variables_destroy(vars);
00831       }
00832       vars = parse_cookies(cookie);
00833    }
00834 
00835    if (!*uri) {
00836       out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
00837    } else if (!strcasecmp(buf, "post")) {
00838 #ifdef ENABLE_UPLOADS
00839       out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
00840 #else
00841       out = ast_http_error(501, "Not Implemented", NULL,
00842          "Attempt to use unimplemented / unsupported method");
00843 #endif /* ENABLE_UPLOADS */
00844    } else if (strcasecmp(buf, "get")) {
00845       out = ast_http_error(501, "Not Implemented", NULL,
00846          "Attempt to use unimplemented / unsupported method");
00847    } else { /* try to serve it */
00848       out = handle_uri(ser, uri, &status, &title, &contentlength, &vars, &static_content);
00849    }
00850 
00851    /* If they aren't mopped up already, clean up the cookies */
00852    if (vars)
00853       ast_variables_destroy(vars);
00854    /* Clean up all the header information pulled as well */
00855    if (headers)
00856       ast_variables_destroy(headers);
00857 
00858    if (out) {
00859       struct timeval tv = ast_tvnow();
00860       char timebuf[256];
00861       struct ast_tm tm;
00862 
00863       ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
00864       fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
00865             "Server: Asterisk/%s\r\n"
00866             "Date: %s\r\n"
00867             "Connection: close\r\n"
00868             "%s",
00869          status, title ? title : "OK", ast_get_version(), timebuf,
00870          static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
00871          /* We set the no-cache headers only for dynamic content.
00872          * If you want to make sure the static file you requested is not from cache,
00873          * append a random variable to your GET request.  Ex: 'something.html?r=109987734'
00874          */
00875       if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
00876          fprintf(ser->f, "%s", out->str);
00877       } else {
00878          char *tmp = strstr(out->str, "\r\n\r\n");
00879 
00880          if (tmp) {
00881             fprintf(ser->f, "Content-length: %d\r\n", contentlength);
00882             /* first write the header, then the body */
00883             if (fwrite(out->str, 1, (tmp + 4 - out->str), ser->f) != tmp + 4 - out->str) {
00884                ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00885             }
00886             if (fwrite(tmp + 4, 1, contentlength, ser->f) != contentlength ) {
00887                ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00888             }
00889          }
00890       }
00891       ast_free(out);
00892    }
00893    if (title)
00894       ast_free(title);
00895 
00896 done:
00897    fclose(ser->f);
00898    ao2_ref(ser, -1);
00899    ser = NULL;
00900    return NULL;
00901 }
00902 
00903 /*!
00904  * \brief Add a new URI redirect
00905  * The entries in the redirect list are sorted by length, just like the list
00906  * of URI handlers.
00907  */
00908 static void add_redirect(const char *value)
00909 {
00910    char *target, *dest;
00911    struct http_uri_redirect *redirect, *cur;
00912    unsigned int target_len;
00913    unsigned int total_len;
00914 
00915    dest = ast_strdupa(value);
00916    dest = ast_skip_blanks(dest);
00917    target = strsep(&dest, " ");
00918    target = ast_skip_blanks(target);
00919    target = strsep(&target, " "); /* trim trailing whitespace */
00920 
00921    if (!dest) {
00922       ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
00923       return;
00924    }
00925 
00926    target_len = strlen(target) + 1;
00927    total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
00928 
00929    if (!(redirect = ast_calloc(1, total_len)))
00930       return;
00931 
00932    redirect->dest = redirect->target + target_len;
00933    strcpy(redirect->target, target);
00934    strcpy(redirect->dest, dest);
00935 
00936    AST_RWLIST_WRLOCK(&uri_redirects);
00937 
00938    target_len--; /* So we can compare directly with strlen() */
00939    if ( AST_RWLIST_EMPTY(&uri_redirects) 
00940       || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len ) {
00941       AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
00942       AST_RWLIST_UNLOCK(&uri_redirects);
00943       return;
00944    }
00945 
00946    AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
00947       if ( AST_RWLIST_NEXT(cur, entry) 
00948          && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len ) {
00949          AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
00950          AST_RWLIST_UNLOCK(&uri_redirects); 
00951          return;
00952       }
00953    }
00954 
00955    AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
00956 
00957    AST_RWLIST_UNLOCK(&uri_redirects);
00958 }
00959 
00960 #ifdef ENABLE_UPLOADS
00961 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
00962 {
00963    if (post_map->from)
00964       ast_free(post_map->from);
00965    if (post_map->to)
00966       ast_free(post_map->to);
00967    ast_free(post_map);
00968 }
00969 
00970 static void destroy_post_mappings(void)
00971 {
00972    struct ast_http_post_mapping *post_map;
00973 
00974    AST_RWLIST_WRLOCK(&post_mappings);
00975    while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry)))
00976       destroy_post_mapping(post_map);
00977    AST_RWLIST_UNLOCK(&post_mappings);
00978 }
00979 
00980 static void add_post_mapping(const char *from, const char *to)
00981 {
00982    struct ast_http_post_mapping *post_map;
00983 
00984    if (!(post_map = ast_calloc(1, sizeof(*post_map))))
00985       return;
00986 
00987    if (!(post_map->from = ast_strdup(from))) {
00988       destroy_post_mapping(post_map);
00989       return;
00990    }
00991 
00992    if (!(post_map->to = ast_strdup(to))) {
00993       destroy_post_mapping(post_map);
00994       return;
00995    }
00996 
00997    AST_RWLIST_WRLOCK(&post_mappings);
00998    AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
00999    AST_RWLIST_UNLOCK(&post_mappings);
01000 }
01001 #endif /* ENABLE_UPLOADS */
01002 
01003 static int __ast_http_load(int reload)
01004 {
01005    struct ast_config *cfg;
01006    struct ast_variable *v;
01007    int enabled=0;
01008    int newenablestatic=0;
01009    struct hostent *hp;
01010    struct ast_hostent ahp;
01011    char newprefix[MAX_PREFIX] = "";
01012    int have_sslbindaddr = 0;
01013    struct http_uri_redirect *redirect;
01014    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
01015 
01016    if ((cfg = ast_config_load("http.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
01017       return 0;
01018 
01019    /* default values */
01020    memset(&http_desc.sin, 0, sizeof(http_desc.sin));
01021    http_desc.sin.sin_port = htons(8088);
01022 
01023    memset(&https_desc.sin, 0, sizeof(https_desc.sin));
01024    https_desc.sin.sin_port = htons(8089);
01025 
01026    http_tls_cfg.enabled = 0;
01027    if (http_tls_cfg.certfile)
01028       ast_free(http_tls_cfg.certfile);
01029    http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
01030    if (http_tls_cfg.cipher)
01031       ast_free(http_tls_cfg.cipher);
01032    http_tls_cfg.cipher = ast_strdup("");
01033 
01034    AST_RWLIST_WRLOCK(&uri_redirects);
01035    while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry)))
01036       ast_free(redirect);
01037    AST_RWLIST_UNLOCK(&uri_redirects);
01038 
01039 #ifdef ENABLE_UPLOADS
01040    destroy_post_mappings();
01041 #endif /* ENABLE_UPLOADS */
01042 
01043    if (cfg) {
01044       v = ast_variable_browse(cfg, "general");
01045       for (; v; v = v->next) {
01046          if (!strcasecmp(v->name, "enabled"))
01047             enabled = ast_true(v->value);
01048          else if (!strcasecmp(v->name, "sslenable"))
01049             http_tls_cfg.enabled = ast_true(v->value);
01050          else if (!strcasecmp(v->name, "sslbindport"))
01051             https_desc.sin.sin_port = htons(atoi(v->value));
01052          else if (!strcasecmp(v->name, "sslcert")) {
01053             ast_free(http_tls_cfg.certfile);
01054             http_tls_cfg.certfile = ast_strdup(v->value);
01055          } else if (!strcasecmp(v->name, "sslcipher")) {
01056             ast_free(http_tls_cfg.cipher);
01057             http_tls_cfg.cipher = ast_strdup(v->value);
01058          }
01059          else if (!strcasecmp(v->name, "enablestatic"))
01060             newenablestatic = ast_true(v->value);
01061          else if (!strcasecmp(v->name, "bindport"))
01062             http_desc.sin.sin_port = htons(atoi(v->value));
01063          else if (!strcasecmp(v->name, "sslbindaddr")) {
01064             if ((hp = ast_gethostbyname(v->value, &ahp))) {
01065                memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
01066                have_sslbindaddr = 1;
01067             } else {
01068                ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
01069             }
01070          } else if (!strcasecmp(v->name, "bindaddr")) {
01071             if ((hp = ast_gethostbyname(v->value, &ahp))) {
01072                memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
01073             } else {
01074                ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
01075             }
01076          } else if (!strcasecmp(v->name, "prefix")) {
01077             if (!ast_strlen_zero(v->value)) {
01078                newprefix[0] = '/';
01079                ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
01080             } else {
01081                newprefix[0] = '\0';
01082             }
01083          } else if (!strcasecmp(v->name, "redirect")) {
01084             add_redirect(v->value);
01085          } else {
01086             ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
01087          }
01088       }
01089 
01090 #ifdef ENABLE_UPLOADS
01091       for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
01092          add_post_mapping(v->name, v->value);
01093 #endif /* ENABLE_UPLOADS */
01094 
01095       ast_config_destroy(cfg);
01096    }
01097    if (!have_sslbindaddr)
01098       https_desc.sin.sin_addr = http_desc.sin.sin_addr;
01099    if (enabled)
01100       http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
01101    if (strcmp(prefix, newprefix))
01102       ast_copy_string(prefix, newprefix, sizeof(prefix));
01103    enablestatic = newenablestatic;
01104    ast_tcptls_server_start(&http_desc);
01105    if (ast_ssl_setup(https_desc.tls_cfg))
01106       ast_tcptls_server_start(&https_desc);
01107 
01108    return 0;
01109 }
01110 
01111 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01112 {
01113    struct ast_http_uri *urih;
01114    struct http_uri_redirect *redirect;
01115 
01116 #ifdef ENABLE_UPLOADS
01117    struct ast_http_post_mapping *post_map;
01118 #endif /* ENABLE_UPLOADS */
01119 
01120    switch (cmd) {
01121    case CLI_INIT:
01122       e->command = "http show status";
01123       e->usage = 
01124          "Usage: http show status\n"
01125          "       Lists status of internal HTTP engine\n";
01126       return NULL;
01127    case CLI_GENERATE:
01128       return NULL;
01129    }
01130    
01131    if (a->argc != 3)
01132       return CLI_SHOWUSAGE;
01133    ast_cli(a->fd, "HTTP Server Status:\n");
01134    ast_cli(a->fd, "Prefix: %s\n", prefix);
01135    if (!http_desc.oldsin.sin_family)
01136       ast_cli(a->fd, "Server Disabled\n\n");
01137    else {
01138       ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
01139          ast_inet_ntoa(http_desc.oldsin.sin_addr),
01140          ntohs(http_desc.oldsin.sin_port));
01141       if (http_tls_cfg.enabled)
01142          ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
01143             ast_inet_ntoa(https_desc.oldsin.sin_addr),
01144             ntohs(https_desc.oldsin.sin_port));
01145    }
01146 
01147    ast_cli(a->fd, "Enabled URI's:\n");
01148    AST_RWLIST_RDLOCK(&uris);
01149    if (AST_RWLIST_EMPTY(&uris)) {
01150       ast_cli(a->fd, "None.\n");
01151    } else {
01152       AST_RWLIST_TRAVERSE(&uris, urih, entry)
01153          ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
01154    }
01155    AST_RWLIST_UNLOCK(&uris);
01156 
01157    ast_cli(a->fd, "\nEnabled Redirects:\n");
01158    AST_RWLIST_RDLOCK(&uri_redirects);
01159    AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
01160       ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
01161    if (AST_RWLIST_EMPTY(&uri_redirects))
01162       ast_cli(a->fd, "  None.\n");
01163    AST_RWLIST_UNLOCK(&uri_redirects);
01164 
01165 
01166 #ifdef ENABLE_UPLOADS
01167    ast_cli(a->fd, "\nPOST mappings:\n");
01168    AST_RWLIST_RDLOCK(&post_mappings);
01169    AST_LIST_TRAVERSE(&post_mappings, post_map, entry) {
01170       ast_cli(a->fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
01171    }
01172    ast_cli(a->fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
01173    AST_RWLIST_UNLOCK(&post_mappings);
01174 #endif /* ENABLE_UPLOADS */
01175 
01176    return CLI_SUCCESS;
01177 }
01178 
01179 int ast_http_reload(void)
01180 {
01181    return __ast_http_load(1);
01182 }
01183 
01184 static struct ast_cli_entry cli_http[] = {
01185    AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
01186 };
01187 
01188 int ast_http_init(void)
01189 {
01190 #ifdef ENABLE_UPLOADS
01191    g_mime_init(0);
01192 #endif /* ENABLE_UPLOADS */
01193 
01194    ast_http_uri_link(&statusuri);
01195    ast_http_uri_link(&staticuri);
01196    ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
01197 
01198    return __ast_http_load(0);
01199 }

Generated on Wed Oct 28 11:45:38 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6