Asterisk - The Open Source Telephony Project  18.5.0
curl.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2020, Sangoma Technologies Corporation
5  *
6  * Ben Ford <[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 #include "asterisk.h"
20 
21 #include "asterisk/utils.h"
22 #include "asterisk/logger.h"
23 #include "curl.h"
24 #include "general.h"
25 #include "stir_shaken.h"
26 
27 #include <curl/curl.h>
28 #include <sys/stat.h>
29 
30 /* Used to check CURL headers */
31 #define MAX_HEADER_LENGTH 1023
32 
33 /* Used for CURL requests */
34 #define GLOBAL_USERAGENT "asterisk-libcurl-agent/1.0"
35 
36 /* CURL callback data to avoid storing useless info in AstDB */
37 struct curl_cb_data {
39  char *expires;
40 };
41 
43 {
44  struct curl_cb_data *data;
45 
46  data = ast_calloc(1, sizeof(*data));
47 
48  return data;
49 }
50 
51 void curl_cb_data_free(struct curl_cb_data *data)
52 {
53  if (!data) {
54  return;
55  }
56 
57  ast_free(data->cache_control);
58  ast_free(data->expires);
59 
60  ast_free(data);
61 }
62 
64 {
65  if (!data) {
66  return NULL;
67  }
68 
69  return data->cache_control;
70 }
71 
72 char *curl_cb_data_get_expires(const struct curl_cb_data *data)
73 {
74  if (!data) {
75  return NULL;
76  }
77 
78  return data->expires;
79 }
80 
81 /*!
82  * \brief Called when a CURL request completes
83  *
84  * \param data The curl_cb_data structure to store expiration info
85  */
86 static size_t curl_header_callback(char *buffer, size_t size, size_t nitems, void *data)
87 {
88  struct curl_cb_data *cb_data = data;
89  size_t realsize;
90  char *header;
91  char *value;
92 
93  realsize = size * nitems;
94 
95  if (realsize > MAX_HEADER_LENGTH) {
96  ast_log(LOG_WARNING, "CURL header length is too large (size: '%zu' | max: '%d')\n",
97  realsize, MAX_HEADER_LENGTH);
98  return 0;
99  }
100 
101  header = ast_alloca(realsize + 1);
102  memcpy(header, buffer, realsize);
103  header[realsize] = '\0';
104  value = strchr(header, ':');
105  if (!value) {
106  return realsize;
107  }
108  *value++ = '\0';
109  value = ast_trim_blanks(ast_skip_blanks(value));
110 
111  if (!strcasecmp(header, "Cache-Control")) {
112  cb_data->cache_control = ast_strdup(value);
113  } else if (!strcasecmp(header, "Expires")) {
114  cb_data->expires = ast_strdup(value);
115  }
116 
117  return realsize;
118 }
119 
120 /*!
121  * \brief Prepare a CURL instance to use
122  *
123  * \param data The CURL callback data
124  *
125  * \retval NULL on failure
126  * \retval CURL instance on success
127  */
128 static CURL *get_curl_instance(struct curl_cb_data *data)
129 {
130  CURL *curl;
131  struct stir_shaken_general *cfg;
132  unsigned int curl_timeout;
133 
134  cfg = stir_shaken_general_get();
135  curl_timeout = ast_stir_shaken_curl_timeout(cfg);
136  ao2_cleanup(cfg);
137 
138  curl = curl_easy_init();
139  if (!curl) {
140  return NULL;
141  }
142 
143  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
144  curl_easy_setopt(curl, CURLOPT_TIMEOUT, curl_timeout);
145  curl_easy_setopt(curl, CURLOPT_USERAGENT, GLOBAL_USERAGENT);
146  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
147  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
148  curl_easy_setopt(curl, CURLOPT_HEADERDATA, data);
149 
150  return curl;
151 }
152 
153 /*!
154  * \brief Create a temporary file located at path
155  *
156  * \note This function assumes path does not end with a '/'
157  *
158  * \param path The directory path to create the file in
159  * \param filename Function allocates memory and stores full filename (including path) here
160  *
161  * \retval -1 on failure
162  * \retval file descriptor on success
163  */
164 static int create_temp_file(const char *path, char **filename)
165 {
166  const char *template_name = "certXXXXXX";
167  int fd;
168 
169  if (ast_asprintf(filename, "%s/%s", path, template_name) < 0) {
170  ast_log(LOG_ERROR, "Failed to set up temporary file path for CURL\n");
171  return -1;
172  }
173 
174  ast_mkdir(path, 0644);
175 
176  if ((fd = mkstemp(*filename)) < 0) {
177  ast_log(LOG_NOTICE, "Failed to create temporary file for CURL\n");
178  ast_free(*filename);
179  return -1;
180  }
181 
182  return fd;
183 }
184 
185 char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data)
186 {
187  FILE *public_key_file;
188  RAII_VAR(char *, tmp_filename, NULL, ast_free);
189  char *filename;
190  char *serial;
191  int fd;
192  long http_code;
193  CURL *curl;
194  char curl_errbuf[CURL_ERROR_SIZE + 1];
195 
196  curl_errbuf[CURL_ERROR_SIZE] = '\0';
197 
198  /* For now, it's fine to pass in path as is - it shouldn't end with a '/'. However,
199  * if we decide to change how certificates are stored in the future (configurable paths),
200  * then we will need to check to see if path ends with '/', copy everything up to the '/',
201  * and use this new variable for create_temp_file as well as for ast_asprintf below.
202  */
203  fd = create_temp_file(path, &tmp_filename);
204  if (fd == -1) {
205  ast_log(LOG_ERROR, "Failed to get temporary file descriptor for CURL\n");
206  return NULL;
207  }
208 
209  public_key_file = fdopen(fd, "wb");
210  if (!public_key_file) {
211  ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n",
212  tmp_filename, public_cert_url, strerror(errno), errno);
213  close(fd);
214  remove(tmp_filename);
215  return NULL;
216  }
217 
218  curl = get_curl_instance(data);
219  if (!curl) {
220  ast_log(LOG_ERROR, "Failed to set up CURL isntance for '%s'\n", public_cert_url);
221  fclose(public_key_file);
222  remove(tmp_filename);
223  return NULL;
224  }
225 
226  curl_easy_setopt(curl, CURLOPT_URL, public_cert_url);
227  curl_easy_setopt(curl, CURLOPT_WRITEDATA, public_key_file);
228  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
229 
230  if (curl_easy_perform(curl)) {
231  ast_log(LOG_ERROR, "%s\n", curl_errbuf);
232  curl_easy_cleanup(curl);
233  fclose(public_key_file);
234  remove(tmp_filename);
235  return NULL;
236  }
237 
238  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
239 
240  curl_easy_cleanup(curl);
241  fclose(public_key_file);
242 
243  if (http_code / 100 != 2) {
244  ast_log(LOG_ERROR, "Failed to retrieve URL '%s': code %ld\n", public_cert_url, http_code);
245  remove(tmp_filename);
246  return NULL;
247  }
248 
249  serial = stir_shaken_get_serial_number_x509(tmp_filename);
250  if (!serial) {
251  ast_log(LOG_ERROR, "Failed to get serial from cert %s\n", tmp_filename);
252  remove(tmp_filename);
253  return NULL;
254  }
255 
256  if (ast_asprintf(&filename, "%s/%s.pem", path, serial) < 0) {
257  ast_log(LOG_ERROR, "Failed to allocate memory for new filename for temporary "
258  "file %s after CURL\n", tmp_filename);
259  ast_free(serial);
260  remove(tmp_filename);
261  return NULL;
262  }
263 
264  ast_free(serial);
265 
266  if (rename(tmp_filename, filename)) {
267  ast_log(LOG_ERROR, "Failed to rename temporary file %s to %s after CURL\n", tmp_filename, filename);
268  ast_free(filename);
269  remove(tmp_filename);
270  return NULL;
271  }
272 
273  return filename;
274 }
struct stir_shaken_general * stir_shaken_general_get()
Retrieve the stir/shaken &#39;general&#39; configuration object.
Definition: general.c:54
char * stir_shaken_get_serial_number_x509(const char *path)
Gets the serial number in hex form from the X509 certificate at path.
Definition: stir_shaken.c:140
Asterisk main include file. File version handling, generic pbx functions.
void curl_cb_data_free(struct curl_cb_data *data)
Free a curl_cb_data struct.
Definition: curl.c:51
#define MAX_HEADER_LENGTH
Definition: curl.c:31
#define LOG_WARNING
Definition: logger.h:274
static size_t curl_header_callback(char *buffer, size_t size, size_t nitems, void *data)
Called when a CURL request completes.
Definition: curl.c:86
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:243
#define NULL
Definition: resample.c:96
int value
Definition: syslog.c:37
Utility functions.
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:269
#define ast_log
Definition: astobj2.c:42
char * curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data)
CURL the public key from the provided URL to the specified path.
Definition: curl.c:185
#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:911
#define GLOBAL_USERAGENT
Definition: curl.c:34
unsigned int curl_timeout
Definition: general.c:47
static CURL * get_curl_instance(struct curl_cb_data *data)
Prepare a CURL instance to use.
Definition: curl.c:128
char * curl_cb_data_get_expires(const struct curl_cb_data *data)
Get the expires field from a curl_cb_data struct.
Definition: curl.c:72
char * curl_cb_data_get_cache_control(const struct curl_cb_data *data)
Get the cache_control field from a curl_cb_data struct.
Definition: curl.c:63
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:290
#define LOG_ERROR
Definition: logger.h:285
int errno
char * ast_skip_blanks(const char *str)
Gets a pointer to the first non-whitespace character in a string.
Definition: strings.h:157
#define LOG_NOTICE
Definition: logger.h:263
char * ast_trim_blanks(char *str)
Trims trailing whitespace characters from a string.
Definition: strings.h:182
#define ast_free(a)
Definition: astmm.h:182
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
char * expires
Definition: curl.c:39
Support for logging to various files, console and syslog Configuration in file logger.conf.
#define ao2_cleanup(obj)
Definition: astobj2.h:1958
struct curl_cb_data * curl_cb_data_create(void)
Allocate memory for a curl_cb_data struct.
Definition: curl.c:42
char * cache_control
Definition: curl.c:38
static int create_temp_file(const char *path, char **filename)
Create a temporary file located at path.
Definition: curl.c:164
int ast_mkdir(const char *path, int mode)
Recursively create directory path.
Definition: main/utils.c:2231
unsigned int ast_stir_shaken_curl_timeout(const struct stir_shaken_general *cfg)
Retrieve the &#39;curl_timeout&#39; general configuration option value.
Definition: general.c:87