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