res_http_post.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 1999 - 2006, Digium, Inc.
  5. *
  6. * Mark Spencer <markster@digium.com>
  7. *
  8. * See http://www.asterisk.org for more information about
  9. * the Asterisk project. Please do not directly contact
  10. * any of the maintainers of this project for assistance;
  11. * the project provides a web site, mailing lists and IRC
  12. * channels for your use.
  13. *
  14. * This program is free software, distributed under the terms of
  15. * the GNU General Public License Version 2. See the LICENSE file
  16. * at the top of the source tree.
  17. */
  18. /*!
  19. * \file
  20. * \brief HTTP POST upload support for Asterisk HTTP server
  21. *
  22. * \author Terry Wilson <twilson@digium.com
  23. *
  24. * \ref AstHTTP - AMI over the http protocol
  25. */
  26. /*** MODULEINFO
  27. <depend>gmime</depend>
  28. <support_level>core</support_level>
  29. ***/
  30. #include "asterisk.h"
  31. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  32. #include <sys/stat.h>
  33. #include <fcntl.h>
  34. #include <gmime/gmime.h>
  35. #if defined (__OpenBSD__) || defined(__FreeBSD__) || defined(__Darwin__)
  36. #include <libgen.h>
  37. #endif
  38. #include "asterisk/linkedlists.h"
  39. #include "asterisk/http.h"
  40. #include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */
  41. #include "asterisk/tcptls.h"
  42. #include "asterisk/manager.h"
  43. #include "asterisk/cli.h"
  44. #include "asterisk/module.h"
  45. #include "asterisk/ast_version.h"
  46. #define MAX_PREFIX 80
  47. /* gmime 2.4 provides a newer interface. */
  48. #ifdef GMIME_TYPE_CONTENT_TYPE
  49. #define AST_GMIME_VER_24
  50. #endif
  51. /* just a little structure to hold callback info for gmime */
  52. struct mime_cbinfo {
  53. int count;
  54. const char *post_dir;
  55. };
  56. /* all valid URIs must be prepended by the string in prefix. */
  57. static char prefix[MAX_PREFIX];
  58. static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
  59. {
  60. char filename[PATH_MAX];
  61. GMimeDataWrapper *content;
  62. GMimeStream *stream;
  63. int fd;
  64. snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
  65. ast_debug(1, "Posting raw data to %s\n", filename);
  66. if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666)) == -1) {
  67. ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
  68. return;
  69. }
  70. stream = g_mime_stream_fs_new(fd);
  71. content = g_mime_part_get_content_object(part);
  72. g_mime_data_wrapper_write_to_stream(content, stream);
  73. g_mime_stream_flush(stream);
  74. #ifndef AST_GMIME_VER_24
  75. g_object_unref(content);
  76. #endif
  77. g_object_unref(stream);
  78. }
  79. static GMimeMessage *parse_message(FILE *f)
  80. {
  81. GMimeMessage *message;
  82. GMimeParser *parser;
  83. GMimeStream *stream;
  84. stream = g_mime_stream_file_new(f);
  85. parser = g_mime_parser_new_with_stream(stream);
  86. g_mime_parser_set_respect_content_length(parser, 1);
  87. g_object_unref(stream);
  88. message = g_mime_parser_construct_message(parser);
  89. g_object_unref(parser);
  90. return message;
  91. }
  92. #ifdef AST_GMIME_VER_24
  93. static void process_message_callback(GMimeObject *parent, GMimeObject *part, gpointer user_data)
  94. #else
  95. static void process_message_callback(GMimeObject *part, gpointer user_data)
  96. #endif
  97. {
  98. struct mime_cbinfo *cbinfo = user_data;
  99. cbinfo->count++;
  100. /* We strip off the headers before we get here, so should only see GMIME_IS_PART */
  101. if (GMIME_IS_MESSAGE_PART(part)) {
  102. ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PART\n");
  103. return;
  104. } else if (GMIME_IS_MESSAGE_PARTIAL(part)) {
  105. ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
  106. return;
  107. } else if (GMIME_IS_MULTIPART(part)) {
  108. #ifndef AST_GMIME_VER_24
  109. GList *l;
  110. ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MULTIPART, trying to process subparts\n");
  111. l = GMIME_MULTIPART(part)->subparts;
  112. while (l) {
  113. process_message_callback(l->data, cbinfo);
  114. l = l->next;
  115. }
  116. #else
  117. ast_log(LOG_WARNING, "Got unexpected MIME subpart.\n");
  118. #endif
  119. } else if (GMIME_IS_PART(part)) {
  120. const char *filename;
  121. if (ast_strlen_zero(filename = g_mime_part_get_filename(GMIME_PART(part)))) {
  122. ast_debug(1, "Skipping part with no filename\n");
  123. return;
  124. }
  125. post_raw(GMIME_PART(part), cbinfo->post_dir, filename);
  126. } else {
  127. ast_log(LOG_ERROR, "Encountered unknown MIME part. This should never happen!\n");
  128. }
  129. }
  130. static int process_message(GMimeMessage *message, const char *post_dir)
  131. {
  132. struct mime_cbinfo cbinfo = {
  133. .count = 0,
  134. .post_dir = post_dir,
  135. };
  136. #ifdef AST_GMIME_VER_24
  137. g_mime_message_foreach(message, process_message_callback, &cbinfo);
  138. #else
  139. g_mime_message_foreach_part(message, process_message_callback, &cbinfo);
  140. #endif
  141. return cbinfo.count;
  142. }
  143. /* Find a sequence of bytes within a binary array. */
  144. static int find_sequence(char * inbuf, int inlen, char * matchbuf, int matchlen)
  145. {
  146. int current;
  147. int comp;
  148. int found = 0;
  149. for (current = 0; current < inlen-matchlen; current++, inbuf++) {
  150. if (*inbuf == *matchbuf) {
  151. found=1;
  152. for (comp = 1; comp < matchlen; comp++) {
  153. if (inbuf[comp] != matchbuf[comp]) {
  154. found = 0;
  155. break;
  156. }
  157. }
  158. if (found) {
  159. break;
  160. }
  161. }
  162. }
  163. if (found) {
  164. return current;
  165. } else {
  166. return -1;
  167. }
  168. }
  169. /*
  170. * The following is a work around to deal with how IE7 embeds the local file name
  171. * within the Mime header using full WINDOWS file path with backslash directory delimiters.
  172. * This section of code attempts to isolate the directory path and remove it
  173. * from what is written into the output file. In addition, it changes
  174. * esc chars (i.e. backslashes) to forward slashes.
  175. * This function has two modes. The first to find a boundary marker. The
  176. * second is to find the filename immediately after the boundary.
  177. */
  178. static int readmimefile(FILE *fin, FILE *fout, char *boundary, int contentlen)
  179. {
  180. int find_filename = 0;
  181. char buf[4096];
  182. int marker;
  183. int x;
  184. int char_in_buf = 0;
  185. int num_to_read;
  186. int boundary_len;
  187. char * path_end, * path_start, * filespec;
  188. if (NULL == fin || NULL == fout || NULL == boundary || 0 >= contentlen) {
  189. return -1;
  190. }
  191. boundary_len = strlen(boundary);
  192. while (0 < contentlen || 0 < char_in_buf) {
  193. /* determine how much I will read into the buffer */
  194. if (contentlen > sizeof(buf) - char_in_buf) {
  195. num_to_read = sizeof(buf)- char_in_buf;
  196. } else {
  197. num_to_read = contentlen;
  198. }
  199. if (0 < num_to_read) {
  200. if (fread(&(buf[char_in_buf]), 1, num_to_read, fin) < num_to_read) {
  201. ast_log(LOG_WARNING, "fread() failed: %s\n", strerror(errno));
  202. num_to_read = 0;
  203. }
  204. contentlen -= num_to_read;
  205. char_in_buf += num_to_read;
  206. }
  207. /* If I am looking for the filename spec */
  208. if (find_filename) {
  209. path_end = filespec = NULL;
  210. x = strlen("filename=\"");
  211. marker = find_sequence(buf, char_in_buf, "filename=\"", x );
  212. if (0 <= marker) {
  213. marker += x; /* Index beyond the filename marker */
  214. path_start = &buf[marker];
  215. for (path_end = path_start, x = 0; x < char_in_buf-marker; x++, path_end++) {
  216. if ('\\' == *path_end) { /* convert backslashses to forward slashes */
  217. *path_end = '/';
  218. }
  219. if ('\"' == *path_end) { /* If at the end of the file name spec */
  220. *path_end = '\0'; /* temporarily null terminate the file spec for basename */
  221. filespec = basename(path_start);
  222. *path_end = '\"';
  223. break;
  224. }
  225. }
  226. }
  227. if (filespec) { /* If the file name path was found in the header */
  228. if (fwrite(buf, 1, marker, fout) != marker) {
  229. ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
  230. }
  231. x = (int)(path_end+1 - filespec);
  232. if (fwrite(filespec, 1, x, fout) != x) {
  233. ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
  234. }
  235. x = (int)(path_end+1 - buf);
  236. memmove(buf, &(buf[x]), char_in_buf-x);
  237. char_in_buf -= x;
  238. }
  239. find_filename = 0;
  240. } else { /* I am looking for the boundary marker */
  241. marker = find_sequence(buf, char_in_buf, boundary, boundary_len);
  242. if (0 > marker) {
  243. if (char_in_buf < (boundary_len)) {
  244. /*no possibility to find the boundary, write all you have */
  245. if (fwrite(buf, 1, char_in_buf, fout) != char_in_buf) {
  246. ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
  247. }
  248. char_in_buf = 0;
  249. } else {
  250. /* write all except for area where the boundary marker could be */
  251. if (fwrite(buf, 1, char_in_buf -(boundary_len -1), fout) != char_in_buf - (boundary_len - 1)) {
  252. ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
  253. }
  254. x = char_in_buf -(boundary_len -1);
  255. memmove(buf, &(buf[x]), char_in_buf-x);
  256. char_in_buf = (boundary_len -1);
  257. }
  258. } else {
  259. /* write up through the boundary, then look for filename in the rest */
  260. if (fwrite(buf, 1, marker + boundary_len, fout) != marker + boundary_len) {
  261. ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
  262. }
  263. x = marker + boundary_len;
  264. memmove(buf, &(buf[x]), char_in_buf-x);
  265. char_in_buf -= marker + boundary_len;
  266. find_filename =1;
  267. }
  268. }
  269. }
  270. return 0;
  271. }
  272. 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)
  273. {
  274. struct ast_variable *var;
  275. uint32_t ident;
  276. FILE *f;
  277. int content_len = 0;
  278. struct ast_str *post_dir;
  279. GMimeMessage *message;
  280. char *boundary_marker = NULL;
  281. if (method != AST_HTTP_POST) {
  282. ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
  283. return 0;
  284. }
  285. if (!urih) {
  286. ast_http_error(ser, 400, "Missing URI handle", "There was an error parsing the request");
  287. return 0;
  288. }
  289. ident = ast_http_manid_from_vars(headers);
  290. if (!ident || !astman_is_authed(ident)) {
  291. ast_http_request_close_on_completion(ser);
  292. ast_http_error(ser, 403, "Access Denied", "Sorry, I cannot let you do that, Dave.");
  293. return 0;
  294. }
  295. if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
  296. ast_http_request_close_on_completion(ser);
  297. ast_http_error(ser, 401, "Unauthorized", "You are not authorized to make this request.");
  298. return 0;
  299. }
  300. if (!(f = tmpfile())) {
  301. ast_log(LOG_ERROR, "Could not create temp file.\n");
  302. ast_http_error(ser, 500, "Internal server error", "Could not create temp file.");
  303. return 0;
  304. }
  305. for (var = headers; var; var = var->next) {
  306. fprintf(f, "%s: %s\r\n", var->name, var->value);
  307. if (!strcasecmp(var->name, "Content-Length")) {
  308. if ((sscanf(var->value, "%30u", &content_len)) != 1) {
  309. ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
  310. fclose(f);
  311. ast_http_request_close_on_completion(ser);
  312. ast_http_error(ser, 400, "Bad Request", "Invalid Content-Length in POST request!");
  313. return 0;
  314. }
  315. ast_debug(1, "Got a Content-Length of %d\n", content_len);
  316. } else if (!strcasecmp(var->name, "Content-Type")) {
  317. boundary_marker = strstr(var->value, "boundary=");
  318. if (boundary_marker) {
  319. boundary_marker += strlen("boundary=");
  320. }
  321. }
  322. }
  323. fprintf(f, "\r\n");
  324. /*
  325. * Always mark the body read as failed.
  326. *
  327. * XXX Should change readmimefile() to always be sure to read
  328. * the entire body so we can update the read status and
  329. * potentially keep the connection open.
  330. */
  331. ast_http_body_read_status(ser, 0);
  332. if (0 > readmimefile(ser->f, f, boundary_marker, content_len)) {
  333. ast_debug(1, "Cannot find boundary marker in POST request.\n");
  334. fclose(f);
  335. ast_http_error(ser, 400, "Bad Request", "Cannot find boundary marker in POST request.");
  336. return 0;
  337. }
  338. if (fseek(f, SEEK_SET, 0)) {
  339. ast_log(LOG_ERROR, "Failed to seek temp file back to beginning.\n");
  340. fclose(f);
  341. ast_http_error(ser, 500, "Internal server error", "Failed to seek temp file back to beginning.");
  342. return 0;
  343. }
  344. post_dir = urih->data;
  345. message = parse_message(f); /* Takes ownership and will close f */
  346. if (!message) {
  347. ast_log(LOG_ERROR, "Error parsing MIME data\n");
  348. ast_http_error(ser, 400, "Bad Request", "There was an error parsing the request.");
  349. return 0;
  350. }
  351. if (!process_message(message, ast_str_buffer(post_dir))) {
  352. ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
  353. g_object_unref(message);
  354. ast_http_error(ser, 400, "Bad Request", "There was an error parsing the request.");
  355. return 0;
  356. }
  357. g_object_unref(message);
  358. /* XXX Passing 200 to the error response routine? */
  359. ast_http_error(ser, 200, "OK", "File successfully uploaded.");
  360. return 0;
  361. }
  362. static int __ast_http_post_load(int reload)
  363. {
  364. struct ast_config *cfg;
  365. struct ast_variable *v;
  366. struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
  367. cfg = ast_config_load2("http.conf", "http", config_flags);
  368. if (!cfg || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
  369. return 0;
  370. }
  371. if (reload) {
  372. ast_http_uri_unlink_all_with_key(__FILE__);
  373. }
  374. for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
  375. if (!strcasecmp(v->name, "prefix")) {
  376. ast_copy_string(prefix, v->value, sizeof(prefix));
  377. if (prefix[strlen(prefix)] == '/') {
  378. prefix[strlen(prefix)] = '\0';
  379. }
  380. }
  381. }
  382. for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) {
  383. struct ast_http_uri *urih;
  384. struct ast_str *ds;
  385. if (!(urih = ast_calloc(sizeof(*urih), 1))) {
  386. ast_config_destroy(cfg);
  387. return -1;
  388. }
  389. if (!(ds = ast_str_create(32))) {
  390. ast_free(urih);
  391. ast_config_destroy(cfg);
  392. return -1;
  393. }
  394. urih->description = ast_strdup("HTTP POST mapping");
  395. urih->uri = ast_strdup(v->name);
  396. ast_str_set(&ds, 0, "%s", v->value);
  397. urih->data = ds;
  398. urih->has_subtree = 0;
  399. urih->callback = http_post_callback;
  400. urih->key = __FILE__;
  401. urih->mallocd = urih->dmallocd = 1;
  402. ast_http_uri_link(urih);
  403. }
  404. ast_config_destroy(cfg);
  405. return 0;
  406. }
  407. static int unload_module(void)
  408. {
  409. ast_http_uri_unlink_all_with_key(__FILE__);
  410. return 0;
  411. }
  412. static int reload(void)
  413. {
  414. __ast_http_post_load(1);
  415. return AST_MODULE_LOAD_SUCCESS;
  416. }
  417. static int load_module(void)
  418. {
  419. g_mime_init(0);
  420. __ast_http_post_load(0);
  421. return AST_MODULE_LOAD_SUCCESS;
  422. }
  423. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP POST support",
  424. .support_level = AST_MODULE_SUPPORT_CORE,
  425. .load = load_module,
  426. .unload = unload_module,
  427. .reload = reload,
  428. );