ab29e3d833d9f46e2e59213b5253260a2ecfd46d
[asterisk/asterisk.git] / res / res_stir_shaken / curl.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2020, Sangoma Technologies Corporation
5  *
6  * Ben Ford <bford@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 "asterisk.h"
20
21 #include "asterisk/utils.h"
22 #include "asterisk/logger.h"
23 #include "curl.h"
24 #include "general.h"
25
26 #include <curl/curl.h>
27
28 /* Used to check CURL headers */
29 #define MAX_HEADER_LENGTH 1023
30
31 /* Used for CURL requests */
32 #define GLOBAL_USERAGENT "asterisk-libcurl-agent/1.0"
33
34 /* CURL callback data to avoid storing useless info in AstDB */
35 struct curl_cb_data {
36         char *cache_control;
37         char *expires;
38 };
39
40 struct curl_cb_data *curl_cb_data_create(void)
41 {
42         struct curl_cb_data *data;
43
44         data = ast_calloc(1, sizeof(*data));
45
46         return data;
47 }
48
49 void curl_cb_data_free(struct curl_cb_data *data)
50 {
51         if (!data) {
52                 return;
53         }
54
55         ast_free(data->cache_control);
56         ast_free(data->expires);
57
58         ast_free(data);
59 }
60
61 char *curl_cb_data_get_cache_control(const struct curl_cb_data *data)
62 {
63         if (!data) {
64                 return NULL;
65         }
66
67         return data->cache_control;
68 }
69
70 char *curl_cb_data_get_expires(const struct curl_cb_data *data)
71 {
72         if (!data) {
73                 return NULL;
74         }
75
76         return data->expires;
77 }
78
79 /*!
80  * \brief Called when a CURL request completes
81  *
82  * \param data The curl_cb_data structure to store expiration info
83  */
84 static size_t curl_header_callback(char *buffer, size_t size, size_t nitems, void *data)
85 {
86         struct curl_cb_data *cb_data = data;
87         size_t realsize;
88         char *header;
89         char *value;
90
91         realsize = size * nitems;
92
93         if (realsize > MAX_HEADER_LENGTH) {
94                 ast_log(LOG_WARNING, "CURL header length is too large (size: '%zu' | max: '%d')\n",
95                         realsize, MAX_HEADER_LENGTH);
96                 return 0;
97         }
98
99         header = ast_alloca(realsize + 1);
100         memcpy(header, buffer, realsize);
101         header[realsize] = '\0';
102         value = strchr(header, ':');
103         if (!value) {
104                 return realsize;
105         }
106         *value++ = '\0';
107         value = ast_trim_blanks(ast_skip_blanks(value));
108
109         if (!strcasecmp(header, "Cache-Control")) {
110                 cb_data->cache_control = ast_strdup(value);
111         } else if (!strcasecmp(header, "Expires")) {
112                 cb_data->expires = ast_strdup(value);
113         }
114
115         return realsize;
116 }
117
118 /*!
119  * \brief Prepare a CURL instance to use
120  *
121  * \param data The CURL callback data
122  *
123  * \retval NULL on failure
124  * \retval CURL instance on success
125  */
126 static CURL *get_curl_instance(struct curl_cb_data *data)
127 {
128         CURL *curl;
129         struct stir_shaken_general *cfg;
130         unsigned int curl_timeout;
131
132         cfg = stir_shaken_general_get();
133         curl_timeout = ast_stir_shaken_curl_timeout(cfg);
134         ao2_cleanup(cfg);
135
136         curl = curl_easy_init();
137         if (!curl) {
138                 return NULL;
139         }
140
141         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
142         curl_easy_setopt(curl, CURLOPT_TIMEOUT, curl_timeout);
143         curl_easy_setopt(curl, CURLOPT_USERAGENT, GLOBAL_USERAGENT);
144         curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
145         curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
146         curl_easy_setopt(curl, CURLOPT_HEADERDATA, data);
147
148         return curl;
149 }
150
151 int curl_public_key(const char *public_key_url, const char *path, struct curl_cb_data *data)
152 {
153         FILE *public_key_file;
154         long http_code;
155         CURL *curl;
156         char curl_errbuf[CURL_ERROR_SIZE + 1];
157         char hash[41];
158
159         ast_sha1_hash(hash, public_key_url);
160
161         curl_errbuf[CURL_ERROR_SIZE] = '\0';
162
163         public_key_file = fopen(path, "wb");
164         if (!public_key_file) {
165                 ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n",
166                         path, public_key_url, strerror(errno), errno);
167                 return -1;
168         }
169
170         curl = get_curl_instance(data);
171         if (!curl) {
172                 ast_log(LOG_ERROR, "Failed to set up CURL isntance for '%s'\n", public_key_url);
173                 fclose(public_key_file);
174                 return -1;
175         }
176
177         curl_easy_setopt(curl, CURLOPT_URL, public_key_url);
178         curl_easy_setopt(curl, CURLOPT_WRITEDATA, public_key_file);
179         curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
180
181         if (curl_easy_perform(curl)) {
182                 ast_log(LOG_ERROR, "%s\n", curl_errbuf);
183                 curl_easy_cleanup(curl);
184                 fclose(public_key_file);
185                 return -1;
186         }
187
188         curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
189
190         curl_easy_cleanup(curl);
191         fclose(public_key_file);
192
193         if (http_code / 100 != 2) {
194                 ast_log(LOG_ERROR, "Failed to retrieve URL '%s': code %ld\n", public_key_url, http_code);
195                 return -1;
196         }
197
198         return 0;
199 }