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