Wed Apr 6 11:29:47 2011

Asterisk developer's documentation


res_http_post.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 POST upload support for Asterisk HTTP server
00022  *
00023  * \author Terry Wilson <twilson@digium.com
00024  *
00025  * \ref AstHTTP - AMI over the http protocol
00026  */
00027 
00028 /*** MODULEINFO
00029    <depend>gmime</depend>
00030  ***/
00031 
00032 
00033 #include "asterisk.h"
00034 
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 226099 $")
00036 
00037 #include <sys/stat.h>
00038 #include <fcntl.h>
00039 #include <gmime/gmime.h>
00040 #if defined (__OpenBSD__) || defined(__FreeBSD__)
00041 #include <libgen.h>
00042 #endif
00043 
00044 #include "asterisk/linkedlists.h"
00045 #include "asterisk/http.h"
00046 #include "asterisk/paths.h"   /* use ast_config_AST_DATA_DIR */
00047 #include "asterisk/tcptls.h"
00048 #include "asterisk/manager.h"
00049 #include "asterisk/cli.h"
00050 #include "asterisk/module.h"
00051 #include "asterisk/ast_version.h"
00052 
00053 #define MAX_PREFIX 80
00054 
00055 /* just a little structure to hold callback info for gmime */
00056 struct mime_cbinfo {
00057    int count;
00058    const char *post_dir;
00059 };
00060 
00061 /* all valid URIs must be prepended by the string in prefix. */
00062 static char prefix[MAX_PREFIX];
00063 
00064 static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
00065 {
00066    char filename[PATH_MAX];
00067    GMimeDataWrapper *content;
00068    GMimeStream *stream;
00069    int fd;
00070 
00071    snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
00072 
00073    ast_debug(1, "Posting raw data to %s\n", filename);
00074 
00075    if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666)) == -1) {
00076       ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
00077 
00078       return;
00079    }
00080 
00081    stream = g_mime_stream_fs_new(fd);
00082 
00083    content = g_mime_part_get_content_object(part);
00084    g_mime_data_wrapper_write_to_stream(content, stream);
00085    g_mime_stream_flush(stream);
00086 
00087    g_object_unref(content);
00088    g_object_unref(stream);
00089 }
00090 
00091 static GMimeMessage *parse_message(FILE *f)
00092 {
00093    GMimeMessage *message;
00094    GMimeParser *parser;
00095    GMimeStream *stream;
00096 
00097    stream = g_mime_stream_file_new(f);
00098 
00099    parser = g_mime_parser_new_with_stream(stream);
00100    g_mime_parser_set_respect_content_length(parser, 1);
00101    
00102    g_object_unref(stream);
00103 
00104    message = g_mime_parser_construct_message(parser);
00105 
00106    g_object_unref(parser);
00107 
00108    return message;
00109 }
00110 
00111 static void process_message_callback(GMimeObject *part, gpointer user_data)
00112 {
00113    struct mime_cbinfo *cbinfo = user_data;
00114 
00115    cbinfo->count++;
00116 
00117    /* We strip off the headers before we get here, so should only see GMIME_IS_PART */
00118    if (GMIME_IS_MESSAGE_PART(part)) {
00119       ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PART\n");
00120       return;
00121    } else if (GMIME_IS_MESSAGE_PARTIAL(part)) {
00122       ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
00123       return;
00124    } else if (GMIME_IS_MULTIPART(part)) {
00125       GList *l;
00126       
00127       ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MULTIPART, trying to process subparts\n");
00128       l = GMIME_MULTIPART(part)->subparts;
00129       while (l) {
00130          process_message_callback(l->data, cbinfo);
00131          l = l->next;
00132       }
00133    } else if (GMIME_IS_PART(part)) {
00134       const char *filename;
00135 
00136       if (ast_strlen_zero(filename = g_mime_part_get_filename(GMIME_PART(part)))) {
00137          ast_debug(1, "Skipping part with no filename\n");
00138          return;
00139       }
00140 
00141       post_raw(GMIME_PART(part), cbinfo->post_dir, filename);
00142    } else {
00143       ast_log(LOG_ERROR, "Encountered unknown MIME part. This should never happen!\n");
00144    }
00145 }
00146 
00147 static int process_message(GMimeMessage *message, const char *post_dir)
00148 {
00149    struct mime_cbinfo cbinfo = {
00150       .count = 0,
00151       .post_dir = post_dir,
00152    };
00153 
00154    g_mime_message_foreach_part(message, process_message_callback, &cbinfo);
00155 
00156    return cbinfo.count;
00157 }
00158 
00159 /* Find a sequence of bytes within a binary array. */
00160 static int find_sequence(char * inbuf, int inlen, char * matchbuf, int matchlen)
00161 {
00162    int current;
00163    int comp;
00164    int found = 0;
00165 
00166    for (current = 0; current < inlen-matchlen; current++, inbuf++) {
00167       if (*inbuf == *matchbuf) {
00168          found=1;
00169          for (comp = 1; comp < matchlen; comp++) {
00170             if (inbuf[comp] != matchbuf[comp]) {
00171                found = 0;
00172                break;
00173             }
00174          }
00175          if (found) {
00176             break;
00177          }
00178       }
00179    }
00180    if (found) {
00181       return current;
00182    } else {
00183       return -1;
00184    }
00185 }
00186 
00187 /*
00188 * The following is a work around to deal with how IE7 embeds the local file name
00189 * within the Mime header using full WINDOWS file path with backslash directory delimiters.
00190 * This section of code attempts to isolate the directory path and remove it
00191 * from what is written into the output file.  In addition, it changes
00192 * esc chars (i.e. backslashes) to forward slashes.
00193 * This function has two modes.  The first to find a boundary marker.  The
00194 * second is to find the filename immediately after the boundary.
00195 */
00196 static int readmimefile(FILE * fin, FILE * fout, char * boundary, int contentlen)
00197 {
00198    int find_filename = 0;
00199    char buf[4096];
00200    int marker;
00201    int x;
00202    int char_in_buf = 0;
00203    int num_to_read;
00204    int boundary_len;
00205    char * path_end, * path_start, * filespec;
00206 
00207    if (NULL == fin || NULL == fout || NULL == boundary || 0 >= contentlen) {
00208       return -1;
00209    }
00210 
00211    boundary_len = strlen(boundary);
00212    while (0 < contentlen || 0 < char_in_buf) {
00213       /* determine how much I will read into the buffer */
00214       if (contentlen > sizeof(buf) - char_in_buf) {
00215          num_to_read = sizeof(buf)- char_in_buf;
00216       } else {
00217          num_to_read = contentlen;
00218       }
00219 
00220       if (0 < num_to_read) {
00221          if (fread(&(buf[char_in_buf]), 1, num_to_read, fin) < num_to_read) {
00222             ast_log(LOG_WARNING, "fread() failed: %s\n", strerror(errno));
00223             num_to_read = 0;
00224          }
00225          contentlen -= num_to_read;
00226          char_in_buf += num_to_read;
00227       }
00228       /* If I am looking for the filename spec */
00229       if (find_filename) {
00230          path_end = filespec = NULL;
00231          x = strlen("filename=\"");
00232          marker = find_sequence(buf, char_in_buf, "filename=\"", x );
00233          if (0 <= marker) {
00234             marker += x;  /* Index beyond the filename marker */
00235             path_start = &buf[marker];
00236             for (path_end = path_start, x = 0; x < char_in_buf-marker; x++, path_end++) {
00237                if ('\\' == *path_end) {   /* convert backslashses to forward slashes */
00238                   *path_end = '/';
00239                }
00240                if ('\"' == *path_end) {   /* If at the end of the file name spec */
00241                   *path_end = '\0';    /* temporarily null terminate the file spec for basename */
00242                   filespec = basename(path_start);
00243                   *path_end = '\"';
00244                   break;
00245                }
00246             }
00247          }
00248          if (filespec) {   /* If the file name path was found in the header */
00249             if (fwrite(buf, 1, marker, fout) != marker) {
00250                ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00251             }
00252             x = (int)(path_end+1 - filespec);
00253             if (fwrite(filespec, 1, x, fout) != x) {
00254                ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00255             }
00256             x = (int)(path_end+1 - buf);
00257             memmove(buf, &(buf[x]), char_in_buf-x);
00258             char_in_buf -= x;
00259          }
00260          find_filename = 0;
00261       } else { /* I am looking for the boundary marker */
00262          marker = find_sequence(buf, char_in_buf, boundary, boundary_len);
00263          if (0 > marker) {
00264             if (char_in_buf < (boundary_len)) {
00265                /*no possibility to find the boundary, write all you have */
00266                if (fwrite(buf, 1, char_in_buf, fout) != char_in_buf) {
00267                   ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00268                }
00269                char_in_buf = 0;
00270             } else {
00271                /* write all except for area where the boundary marker could be */
00272                if (fwrite(buf, 1, char_in_buf -(boundary_len -1), fout) != char_in_buf - (boundary_len - 1)) {
00273                   ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00274                }
00275                x = char_in_buf -(boundary_len -1);
00276                memmove(buf, &(buf[x]), char_in_buf-x);
00277                char_in_buf = (boundary_len -1);
00278             }
00279          } else {
00280             /* write up through the boundary, then look for filename in the rest */
00281             if (fwrite(buf, 1, marker + boundary_len, fout) != marker + boundary_len) {
00282                ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
00283             }
00284             x = marker + boundary_len;
00285             memmove(buf, &(buf[x]), char_in_buf-x);
00286             char_in_buf -= marker + boundary_len;
00287             find_filename =1;
00288          }
00289       }
00290    }
00291    return 0;
00292 }
00293 
00294 static int http_post_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers)
00295 {
00296    struct ast_variable *var, *cookies;
00297    unsigned long ident = 0;
00298    FILE *f;
00299    int content_len = 0;
00300    struct ast_str *post_dir;
00301    GMimeMessage *message;
00302    int message_count = 0;
00303    char * boundary_marker = NULL;
00304 
00305    if (method != AST_HTTP_POST) {
00306       ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
00307       return -1;
00308    }
00309 
00310    if (!astman_is_authed(ast_http_manid_from_vars(headers))) {
00311       ast_http_error(ser, 403, "Access Denied", "Sorry, I cannot let you do that, Dave.");
00312       return -1;
00313    }
00314 
00315    if (!urih) {
00316       ast_http_error(ser, 400, "Missing URI handle", "There was an error parsing the request");
00317            return -1;
00318    }
00319 
00320    cookies = ast_http_get_cookies(headers);
00321    for (var = cookies; var; var = var->next) {
00322       if (!strcasecmp(var->name, "mansession_id")) {
00323          sscanf(var->value, "%30lx", &ident);
00324          break;
00325       }
00326    }
00327    if (cookies) {
00328       ast_variables_destroy(cookies);
00329    }
00330 
00331    if (ident == 0) {
00332       ast_http_error(ser, 401, "Unauthorized", "You are not authorized to make this request.");
00333       return -1;
00334    }
00335    if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
00336       ast_http_error(ser, 401, "Unauthorized", "You are not authorized to make this request.");
00337       return -1;
00338    }
00339 
00340    if (!(f = tmpfile())) {
00341       ast_log(LOG_ERROR, "Could not create temp file.\n");
00342       ast_http_error(ser, 500, "Internal server error", "Could not create temp file.");
00343       return -1;
00344    }
00345 
00346    for (var = headers; var; var = var->next) {
00347       fprintf(f, "%s: %s\r\n", var->name, var->value);
00348 
00349       if (!strcasecmp(var->name, "Content-Length")) {
00350          if ((sscanf(var->value, "%30u", &content_len)) != 1) {
00351             ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
00352             fclose(f);
00353             ast_http_error(ser, 500, "Internal server error", "Invalid Content-Length in POST request!");
00354             return -1;
00355          }
00356          ast_debug(1, "Got a Content-Length of %d\n", content_len);
00357       } else if (!strcasecmp(var->name, "Content-Type")) {
00358          boundary_marker = strstr(var->value, "boundary=");
00359          if (boundary_marker) {
00360             boundary_marker += strlen("boundary=");
00361          }
00362       }
00363    }
00364 
00365    fprintf(f, "\r\n");
00366 
00367    if (0 > readmimefile(ser->f, f, boundary_marker, content_len)) {
00368       if (option_debug) {
00369          ast_log(LOG_DEBUG, "Cannot find boundary marker in POST request.\n");
00370       }
00371       fclose(f);
00372 
00373       return -1;
00374    }
00375 
00376    if (fseek(f, SEEK_SET, 0)) {
00377       ast_log(LOG_ERROR, "Failed to seek temp file back to beginning.\n");
00378       fclose(f);
00379       ast_http_error(ser, 500, "Internal server error", "Failed to seek temp file back to beginning.");
00380       return -1;
00381    }
00382 
00383    post_dir = urih->data;
00384 
00385    message = parse_message(f); /* Takes ownership and will close f */
00386 
00387    if (!message) {
00388       ast_log(LOG_ERROR, "Error parsing MIME data\n");
00389 
00390       ast_http_error(ser, 400, "Bad Request", "The was an error parsing the request.");
00391       return -1;
00392    }
00393 
00394    if (!(message_count = process_message(message, ast_str_buffer(post_dir)))) {
00395       ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
00396       g_object_unref(message);
00397       ast_http_error(ser, 400, "Bad Request", "The was an error parsing the request.");
00398       return -1;
00399    }
00400    g_object_unref(message);
00401 
00402    ast_http_error(ser, 200, "OK", "File successfully uploaded.");
00403    return 0;
00404 }
00405 
00406 static int __ast_http_post_load(int reload)
00407 {
00408    struct ast_config *cfg;
00409    struct ast_variable *v;
00410    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00411 
00412    cfg = ast_config_load2("http.conf", "http", config_flags);
00413    if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
00414       return 0;
00415    }
00416 
00417    if (reload) {
00418       ast_http_uri_unlink_all_with_key(__FILE__);
00419    }
00420 
00421    if (cfg) {
00422       for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
00423          if (!strcasecmp(v->name, "prefix")) {
00424             ast_copy_string(prefix, v->value, sizeof(prefix));
00425             if (prefix[strlen(prefix)] == '/') {
00426                prefix[strlen(prefix)] = '\0';
00427             }
00428          }
00429       }
00430 
00431       for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) {
00432          struct ast_http_uri *urih;
00433          struct ast_str *ds;
00434 
00435          if (!(urih = ast_calloc(sizeof(*urih), 1))) {
00436             ast_config_destroy(cfg);
00437             return -1;
00438          }
00439 
00440          if (!(ds = ast_str_create(32))) {
00441             ast_free(urih);
00442             ast_config_destroy(cfg);
00443             return -1;
00444          }
00445 
00446          urih->description = ast_strdup("HTTP POST mapping");
00447          urih->uri = ast_strdup(v->name);
00448          ast_str_set(&ds, 0, "%s", v->value);
00449          urih->data = ds;
00450          urih->has_subtree = 0;
00451          urih->callback = http_post_callback;
00452          urih->key = __FILE__;
00453          urih->mallocd = urih->dmallocd = 1;
00454 
00455          ast_http_uri_link(urih);
00456       }
00457 
00458       ast_config_destroy(cfg);
00459    }
00460    return 0;
00461 }
00462 
00463 static int unload_module(void)
00464 {
00465    ast_http_uri_unlink_all_with_key(__FILE__);
00466 
00467    return 0;
00468 }
00469 
00470 static int reload(void)
00471 {
00472    __ast_http_post_load(1);
00473 
00474    return AST_MODULE_LOAD_SUCCESS;
00475 }
00476 
00477 static int load_module(void)
00478 {
00479    g_mime_init(0);
00480 
00481    __ast_http_post_load(0);
00482 
00483    return AST_MODULE_LOAD_SUCCESS;
00484 }
00485 
00486 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP POST support",
00487    .load = load_module,
00488    .unload = unload_module,
00489    .reload = reload,
00490 );

Generated on Wed Apr 6 11:29:47 2011 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7