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