Asterisk - The Open Source Telephony Project  21.4.1
curl_utils.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2023, Sangoma Technologies Corporation
5  *
6  * George Joseph <gjoseph@sangoma.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 #include <curl/curl.h>
20 
21 #include "asterisk.h"
22 #include "asterisk/config.h"
23 
24 #include "curl_utils.h"
25 
26 void curl_header_data_free(void *obj)
27 {
28  struct curl_header_data *cb_data = obj;
29  if (!cb_data) {
30  return;
31  }
33  if (cb_data->debug_info) {
34  ast_free(cb_data->debug_info);
35  }
36  ast_free(cb_data);
37 }
38 
39 size_t curl_header_cb(char *data, size_t size,
40  size_t nitems, void *client_data)
41 {
42  struct curl_header_data *cb_data = client_data;
43  size_t realsize = size * nitems;
44  size_t adjusted_size = realsize;
45  char *debug_info = S_OR(cb_data->debug_info, "");
46  char *start = data;
47  char *colon = NULL;
48  struct ast_variable *h;
49  char *header;
50  char *value;
51  SCOPE_ENTER(5, "'%s': Header received with %zu bytes\n",
52  debug_info, realsize);
53 
54  if (cb_data->max_header_len == 0) {
55  cb_data->max_header_len = AST_CURL_DEFAULT_MAX_HEADER_LEN;
56  }
57 
58  if (realsize > cb_data->max_header_len) {
59  /*
60  * Silently ignore any header over the length limit.
61  */
62  SCOPE_EXIT_RTN_VALUE(realsize, "oversize header: %zu > %zu\n",
63  realsize, cb_data->max_header_len);
64  }
65 
66  /* Per CURL: buffer may not be NULL terminated. */
67 
68  /* Skip blanks */
69  while (*start && ((unsigned char) *start) < 33 && start < data + realsize) {
70  start++;
71  adjusted_size--;
72  }
73 
74  if (adjusted_size < strlen("HTTP/") + 1) {
75  /* this is probably the \r\n\r\n sequence that ends the headers */
76  cb_data->_capture = 0;
77  SCOPE_EXIT_RTN_VALUE(realsize, "undersized header. probably end-of-headers marker: %zu\n",
78  adjusted_size);
79  }
80 
81  /*
82  * We only want headers from a 2XX response
83  * so don't start capturing until we see
84  * the 2XX.
85  */
86  if (ast_begins_with(start, "HTTP/")) {
87  int code;
88  /*
89  * HTTP/1.1 200 OK
90  * We want there to be a version after the HTTP/
91  * and reason text after the code but we don't care
92  * what they are.
93  */
94  int rc = sscanf(start, "HTTP/%*s %d %*s", &code);
95  if (rc == 1) {
96  if (code / 100 == 2) {
97  cb_data->_capture = 1;
98  }
99  }
100  SCOPE_EXIT_RTN_VALUE(realsize, "HTTP response code: %d\n",
101  code);
102  }
103 
104  if (!cb_data->_capture) {
105  SCOPE_EXIT_RTN_VALUE(realsize, "not capturing\n");
106  }
107 
108  header = ast_alloca(adjusted_size + 1);
109  ast_copy_string(header, start, adjusted_size + 1);
110 
111  /* We have a NULL terminated string now */
112 
113  colon = strchr(header, ':');
114  if (!colon) {
115  SCOPE_EXIT_RTN_VALUE(realsize, "No colon in the header. Weird\n");
116  }
117 
118  *colon++ = '\0';
119  value = colon;
120  value = ast_skip_blanks(ast_trim_blanks(value));
121 
122  h = ast_variable_new(header, value, __FILE__);
123  if (!h) {
124  SCOPE_EXIT_LOG_RTN_VALUE(CURL_WRITEFUNC_ERROR, LOG_WARNING,
125  "'%s': Unable to allocate memory for header '%s'\n",
126  debug_info, header);
127  }
128  ast_variable_list_append(&cb_data->headers, h);
129 
130  SCOPE_EXIT_RTN_VALUE(realsize, "header: <%s> value: <%s>",
131  header, value);
132 }
133 
134 void curl_write_data_free(void *obj)
135 {
136  struct curl_write_data *cb_data = obj;
137  if (!cb_data) {
138  return;
139  }
140  if (cb_data->output) {
141  fclose(cb_data->output);
142  }
143  if (cb_data->debug_info) {
144  ast_free(cb_data->debug_info);
145  }
146  ast_std_free(cb_data->stream_buffer);
147  ast_free(cb_data);
148 }
149 
150 size_t curl_write_cb(char *data, size_t size,
151  size_t nmemb, void *client_data)
152 {
153  struct curl_write_data *cb_data = client_data;
154  size_t realsize = size * nmemb;
155  size_t bytes_written = 0;
156  char *debug_info = S_OR(cb_data->debug_info, "");
157  SCOPE_ENTER(5, "'%s': Writing data chunk of %zu bytes\n",
158  debug_info, realsize);
159 
160  if (!cb_data->output) {
161  cb_data->output = open_memstream(
162  &cb_data->stream_buffer,
163  &cb_data->stream_bytes_downloaded);
164  if (!cb_data->output) {
165  SCOPE_EXIT_LOG_RTN_VALUE(CURL_WRITEFUNC_ERROR, LOG_WARNING,
166  "'%s': Xfer failed. "
167  "open_memstream failed: %s\n", debug_info, strerror(errno));
168  }
169  cb_data->_internal_memstream = 1;
170  }
171 
172  if (cb_data->max_download_bytes > 0 &&
173  cb_data->stream_bytes_downloaded + realsize >
174  cb_data->max_download_bytes) {
175  SCOPE_EXIT_LOG_RTN_VALUE(CURL_WRITEFUNC_ERROR, LOG_WARNING,
176  "'%s': Xfer failed. "
177  "Exceeded maximum %zu bytes transferred\n", debug_info,
178  cb_data->max_download_bytes);
179  }
180 
181  bytes_written = fwrite(data, 1, realsize, cb_data->output);
182  cb_data->bytes_downloaded += bytes_written;
183  if (bytes_written != realsize) {
184  SCOPE_EXIT_LOG_RTN_VALUE(CURL_WRITEFUNC_ERROR, LOG_WARNING,
185  "'%s': Xfer failed. "
186  "Expected to write %zu bytes but wrote %zu\n",
187  debug_info, realsize, bytes_written);
188  }
189 
190  SCOPE_EXIT_RTN_VALUE(realsize, "Wrote %zu bytes\n", bytes_written);
191 }
192 
193 void curl_open_socket_data_free(void *obj)
194 {
195  struct curl_open_socket_data *cb_data = obj;
196  if (!cb_data) {
197  return;
198  }
199  if (cb_data->debug_info) {
200  ast_free(cb_data->debug_info);
201  }
202  ast_free(cb_data);
203 }
204 
205 curl_socket_t curl_open_socket_cb(void *client_data,
206  curlsocktype purpose, struct curl_sockaddr *address)
207 {
208  struct curl_open_socket_data *cb_data = client_data;
209  char *debug_info = S_OR(cb_data->debug_info, "");
210  SCOPE_ENTER(5, "'%s': Opening socket\n", debug_info);
211 
212  if (!ast_acl_list_is_empty((struct ast_acl_list *)cb_data->acl)) {
213  struct ast_sockaddr ast_address = { {0,} };
214 
215  ast_sockaddr_copy_sockaddr(&ast_address, &address->addr, address->addrlen);
216 
217  if (ast_apply_acl((struct ast_acl_list *)cb_data->acl, &ast_address, NULL) != AST_SENSE_ALLOW) {
218  SCOPE_EXIT_LOG_RTN_VALUE(CURL_SOCKET_BAD, LOG_WARNING,
219  "'%s': Unable to apply acl\n", debug_info);
220  }
221  }
222 
223  cb_data->sockfd = socket(address->family, address->socktype, address->protocol);
224  if (cb_data->sockfd < 0) {
225  SCOPE_EXIT_LOG_RTN_VALUE(CURL_SOCKET_BAD, LOG_WARNING,
226  "'%s': Failed to open socket: %s\n", debug_info, strerror(errno));
227  }
228 
229  SCOPE_EXIT_RTN_VALUE(cb_data->sockfd, "Success");
230 }
231 
232 long curler(const char *url, int request_timeout,
233  struct curl_write_data *write_data,
235  struct curl_open_socket_data *open_socket_data)
236 {
237  RAII_VAR(CURL *, curl, NULL, curl_easy_cleanup);
238  long http_code = 0;
239  CURLcode rc;
240 
241  SCOPE_ENTER(1, "'%s': Retrieving\n", url);
242 
243  if (ast_strlen_zero(url)) {
244  SCOPE_EXIT_LOG_RTN_VALUE(500, LOG_ERROR, "'missing': url is missing\n");
245  }
246 
247  if (!write_data) {
248  SCOPE_EXIT_LOG_RTN_VALUE(500, LOG_ERROR, "'%s': Either wite_cb and write_data are missing\n", url);
249  }
250 
251  curl = curl_easy_init();
252  if (!curl) {
253  SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "'%s': Failed to set up CURL instance\n", url);
254  }
255 
256  curl_easy_setopt(curl, CURLOPT_URL, url);
257  if (request_timeout) {
258  curl_easy_setopt(curl, CURLOPT_TIMEOUT, request_timeout);
259  }
260  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
261  curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_data);
262 
263  if (header_data) {
264  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_cb);
265  curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_data);
266  }
267 
268  curl_easy_setopt(curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
269 
270  if (open_socket_data) {
271  curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, curl_open_socket_cb);
272  curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, open_socket_data);
273  }
274 
275  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
276  /*
277  * ATIS-1000074 specifically says to NOT follow redirections.
278  */
279  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0);
280 
281  rc = curl_easy_perform(curl);
282  if (rc != CURLE_OK) {
283  char *err = ast_strdupa(curl_easy_strerror(rc));
284  SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "'%s': %s\n", url, err);
285  }
286 
287  fflush(write_data->output);
288  if (write_data->_internal_memstream) {
289  fclose(write_data->output);
290  write_data->output = NULL;
291  }
292 
293  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
294  curl_easy_cleanup(curl);
295  curl = NULL;
296 
297  SCOPE_EXIT_RTN_VALUE(http_code, "'%s': Done: %ld\n", url, http_code);
298 }
299 
300 long curl_download_to_memory(const char *url, size_t *returned_length,
301  char **returned_data, struct ast_variable **headers)
302 {
303  struct curl_write_data data = {
304  .debug_info = ast_strdupa(url),
305  };
306  struct curl_header_data hdata = {
307  .debug_info = ast_strdupa(url),
308  };
309 
310  long rc = curler(url, 0, &data, headers ? &hdata : NULL, NULL);
311 
312  *returned_length = data.stream_bytes_downloaded;
313  *returned_data = data.stream_buffer;
314  if (headers) {
315  *headers = hdata.headers;
316  }
317 
318  return rc;
319 }
320 
321 long curl_download_to_file(const char *url, char *filename)
322 {
323  FILE *fp = NULL;
324  long rc = 0;
325  struct curl_write_data data = {
326  .debug_info = ast_strdup(url),
327  };
328 
329  if (ast_strlen_zero(url) || ast_strlen_zero(filename)) {
330  ast_log(LOG_ERROR,"url or filename was NULL\n");
331  return -1;
332  }
333  data.output = fopen(filename, "w");
334  if (!fp) {
335  ast_log(LOG_ERROR,"Unable to open file '%s': %s\n", filename,
336  strerror(errno));
337  return -1;
338  }
339  rc = curler(url, 0, &data, NULL, NULL);
340  fclose(data.output);
341  ast_free(data.debug_info);
342  return rc;
343 }
344 
Asterisk main include file. File version handling, generic pbx functions.
size_t curl_write_cb(char *data, size_t size, size_t nmemb, void *client_data)
A default implementation of a write data callback.
Definition: curl_utils.c:150
size_t curl_header_cb(char *data, size_t size, size_t nitems, void *client_data)
A default implementation of a header callback.
Definition: curl_utils.c:39
Context structure passed to ast_curl_open_socket_default_cb.
Definition: curl_utils.h:341
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
const struct ast_acl_list * acl
Definition: curl_utils.h:346
size_t max_header_len
Definition: curl_utils.h:173
Structure for variables, used for configurations and for channel variables.
Data structure used for ast_sip_push_task_wait_serializer.
char * stream_buffer
Definition: curl_utils.h:276
Wrapper for an ast_acl linked list.
Definition: acl.h:76
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
Socket address structure.
Definition: netsock2.h:97
Configuration File Parser.
Context structure passed to ast_curl_write_default_cb.
Definition: curl_utils.h:245
static void ast_sockaddr_copy_sockaddr(struct ast_sockaddr *dst, struct sockaddr *src, socklen_t len)
Copies the data from a sockaddr to an ast_sockaddr.
Definition: netsock2.h:151
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
Context structure passed to ast_curl_header_default_cb.
Definition: curl_utils.h:163
enum ast_acl_sense ast_apply_acl(struct ast_acl_list *acl_list, const struct ast_sockaddr *addr, const char *purpose)
Apply a set of rules to a given IP address.
Definition: acl.c:799
long curler(const char *url, int request_timeout, struct curl_write_data *write_data, struct curl_header_data *header_data, struct curl_open_socket_data *open_socket_data)
Perform a curl request.
Definition: curl_utils.c:232
int ast_acl_list_is_empty(struct ast_acl_list *acl_list)
Determines if an ACL is empty or if it contains entries.
Definition: acl.c:540
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
size_t max_download_bytes
Definition: curl_utils.h:250
char * ast_skip_blanks(const char *str)
Gets a pointer to the first non-whitespace character in a string.
Definition: strings.h:161
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
Definition: strings.h:186
curl_socket_t curl_open_socket_cb(void *client_data, curlsocktype purpose, struct curl_sockaddr *address)
A default implementation of an open socket callback.
Definition: curl_utils.c:205
size_t bytes_downloaded
Definition: curl_utils.h:267
long curl_download_to_file(const char *url, char *filename)
Really simple document retrieval to file.
Definition: curl_utils.c:321
long curl_download_to_memory(const char *url, size_t *returned_length, char **returned_data, struct ast_variable **headers)
Really simple document retrieval to memory.
Definition: curl_utils.c:300
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Checks whether a string begins with another.
Definition: strings.h:97
char * debug_info
Definition: curl_utils.h:260
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
struct ast_variable * headers
Definition: curl_utils.h:182
size_t stream_bytes_downloaded
Definition: curl_utils.h:281