res_http_post.c 14 KB

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