Wed Aug 7 17:15:44 2019

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

Generated on 7 Aug 2019 for Asterisk - The Open Source Telephony Project by  doxygen 1.6.1