Wed Jan 8 2020 09:49:50

Asterisk developer's documentation


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