Asterisk - The Open Source Telephony Project  18.5.0
res_http_post.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <[email protected]>
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 <[email protected]
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)) {
346  ast_http_error(ser, 403, "Access Denied", "Sorry, I cannot let you do that, Dave.");
347  return 0;
348  }
349 
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);
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  */
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 
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) {
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;
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 {
487 
488  return 0;
489 }
490 
491 static int reload(void)
492 {
494 
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 
507 
509 }
510 
512  .support_level = AST_MODULE_SUPPORT_CORE,
513  .load = load_module,
514  .unload = unload_module,
515  .reload = reload,
516  .requires = "http",
517 );
struct ast_variable * next
Asterisk main include file. File version handling, generic pbx functions.
static void process_message_callback(GMimeObject *part, gpointer user_data)
void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text)
Send HTTP error message and close socket.
Definition: http.c:648
ast_http_callback callback
Definition: http.h:105
Asterisk version information.
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1216
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
#define LOG_WARNING
Definition: logger.h:274
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition: http.c:673
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:714
#define CONFIG_STATUS_FILEINVALID
static int load_module(void)
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)
struct ast_config * ast_config_load2(const char *filename, const char *who_asked, struct ast_flags flags)
Load a config file.
Definition: main/config.c:3154
Structure for variables, used for configurations and for channel variables.
#define var
Definition: ast_expr2f.c:614
const char * key
Definition: http.h:116
uint32_t ast_http_manid_from_vars(struct ast_variable *headers) attribute_pure
Return manager id, if exist, from request headers.
Definition: http.c:217
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:243
unsigned int has_subtree
Definition: http.h:106
#define NULL
Definition: resample.c:96
static int inbuf(struct baseio *bio, FILE *fi)
utility used by inchar(), for base_encode()
Generic support for tcp/tls servers in Asterisk.
static char prefix[MAX_PREFIX]
Definition: res_http_post.c:69
#define ast_strlen_zero(foo)
Definition: strings.h:52
unsigned int mallocd
Definition: http.h:108
ssize_t ast_iostream_read(struct ast_iostream *stream, void *buffer, size_t count)
Read data from an iostream.
Definition: iostream.c:273
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1065
Support for Private Asterisk HTTP Servers.
static int readmimefile(struct ast_iostream *in, FILE *fout, char *boundary, int contentlen)
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
int astman_is_authed(uint32_t ident)
Determinie if a manager session ident is authenticated.
Definition: manager.c:7551
static int reload(void)
static int process_message(GMimeMessage *message, const char *post_dir)
FILE * in
Definition: utils/frame.c:33
Asterisk file paths, configured in asterisk.conf.
static int __ast_http_post_load(int reload)
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: extconf.c:1290
const char * method
Definition: res_pjsip.c:4335
A set of macros to manage forward-linked lists.
describes a server instance
Definition: tcptls.h:149
#define CONFIG_STATUS_FILEUNCHANGED
The AMI - Asterisk Manager Interface - is a TCP protocol created to manage Asterisk with third-party ...
#define LOG_ERROR
Definition: logger.h:285
#define EVENT_FLAG_CONFIG
Definition: manager.h:78
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
int errno
const char * description
Definition: http.h:102
void ast_http_body_read_status(struct ast_tcptls_session_instance *ser, int read_success)
Update the body read success status.
Definition: http.c:899
#define ast_free(a)
Definition: astmm.h:182
int astman_verify_session_writepermissions(uint32_t ident, int perm)
Verify a session&#39;s write permissions against a permission mask.
Definition: manager.c:7600
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
static int unload_module(void)
const char * post_dir
Definition: res_http_post.c:65
Structure used to handle boolean flags.
Definition: utils.h:199
struct ast_iostream * stream
Definition: tcptls.h:160
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
#define MAX_PREFIX
Definition: res_http_post.c:52
static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
Definition: res_http_post.c:71
Standard Command Line Interface.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
Definition of a URI handler.
Definition: http.h:100
static GMimeMessage * parse_message(FILE *f)
void ast_http_uri_unlink_all_with_key(const char *key)
Unregister all handlers with matching key.
Definition: http.c:712
unsigned int dmallocd
Definition: http.h:110
#define PATH_MAX
Definition: asterisk.h:40
const char * uri
Definition: http.h:103
ast_http_method
HTTP Request methods known by Asterisk.
Definition: http.h:56
static int find_sequence(char *inbuf, int inlen, char *matchbuf, int matchlen)
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
void ast_http_request_close_on_completion(struct ast_tcptls_session_instance *ser)
Request the HTTP connection be closed after this HTTP request.
Definition: http.c:836
void * data
Definition: http.h:114
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620