unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
url_curl.c (10420B)
1 /* 2 * Unreal Internet Relay Chat Daemon, src/url.c 3 * (C) 2003 Dominick Meglio and the UnrealIRCd Team 4 * (C) 2004-2021 Bram Matthys <syzop@vulnscan.org> 5 * (C) 2012 William Pitcock <nenolod@dereferenced.org> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 1, or (at your option) 10 * any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 20 */ 21 22 #include "unrealircd.h" 23 #include "dns.h" 24 25 extern char *TLSKeyPasswd; 26 27 /* Stores information about the async transfer. 28 * Used to maintain information about the transfer 29 * to trigger the callback upon completion. 30 */ 31 typedef struct Download Download; 32 33 struct Download 34 { 35 Download *prev, *next; 36 vFP callback; 37 void *callback_data; 38 FILE *file_fd; /**< File open for writing (otherwise NULL) */ 39 char filename[PATH_MAX]; 40 char *url; /*< must be free()d by url_do_transfers_async() */ 41 char errorbuf[CURL_ERROR_SIZE]; 42 time_t cachetime; 43 }; 44 45 CURLM *multihandle = NULL; 46 47 Download *downloads = NULL; 48 49 void url_free_handle(Download *handle) 50 { 51 DelListItem(handle, downloads); 52 if (handle->file_fd) 53 fclose(handle->file_fd); 54 safe_free(handle->url); 55 safe_free(handle); 56 } 57 58 void url_cancel_handle_by_callback_data(void *ptr) 59 { 60 Download *d, *d_next; 61 62 for (d = downloads; d; d = d_next) 63 { 64 d_next = d->next; 65 if (d->callback_data == ptr) 66 { 67 d->callback = NULL; 68 d->callback_data = NULL; 69 } 70 } 71 } 72 73 /* 74 * Sets up all of the SSL options necessary to support HTTPS/FTPS 75 * transfers. 76 */ 77 static void set_curl_tls_options(CURL *curl) 78 { 79 char buf[512]; 80 81 #if 0 82 /* This would only be necessary if you use client certificates over HTTPS and such. 83 * But this information is not known yet since the configuration file has not been 84 * parsed yet at this point. 85 */ 86 curl_easy_setopt(curl, CURLOPT_SSLCERT, iConf.tls_options->certificate_file); 87 if (TLSKeyPasswd) 88 curl_easy_setopt(curl, CURLOPT_SSLKEYPASSWD, TLSKeyPasswd); 89 curl_easy_setopt(curl, CURLOPT_SSLKEY, iConf.tls_options->key_file); 90 #endif 91 92 snprintf(buf, sizeof(buf), "%s/tls/curl-ca-bundle.crt", CONFDIR); 93 curl_easy_setopt(curl, CURLOPT_CAINFO, buf); 94 } 95 96 /* 97 * Used by CURLOPT_WRITEFUNCTION to actually write the data to 98 * a stream. 99 */ 100 static size_t do_download(void *ptr, size_t size, size_t nmemb, void *stream) 101 { 102 return fwrite(ptr, size, nmemb, (FILE *)stream); 103 } 104 105 /* 106 * Interface for new-style evented I/O. 107 * 108 * url_socket_pollcb is the callback from our eventing system into 109 * cURL. 110 * 111 * The other callbacks are for cURL notifying our event system what 112 * it wants to do. 113 */ 114 static void url_check_multi_handles(void) 115 { 116 CURLMsg *msg; 117 int msgs_left; 118 119 while ((msg = curl_multi_info_read(multihandle, &msgs_left)) != NULL) 120 { 121 if (msg->msg == CURLMSG_DONE) 122 { 123 Download *handle; 124 long code; 125 long last_mod; 126 CURL *easyhand = msg->easy_handle; 127 128 curl_easy_getinfo(easyhand, CURLINFO_RESPONSE_CODE, &code); 129 curl_easy_getinfo(easyhand, CURLINFO_PRIVATE, (char **) &handle); 130 curl_easy_getinfo(easyhand, CURLINFO_FILETIME, &last_mod); 131 fclose(handle->file_fd); 132 handle->file_fd = NULL; 133 134 if (handle->callback == NULL) 135 { 136 /* Request is already canceled, we don't care about the result, just clean up */ 137 remove(handle->filename); 138 } else 139 if (msg->data.result == CURLE_OK) 140 { 141 if (code == 304 || (last_mod != -1 && last_mod <= handle->cachetime)) 142 { 143 handle->callback(handle->url, NULL, NULL, 1, handle->callback_data); 144 remove(handle->filename); 145 } 146 else 147 { 148 if (last_mod != -1) 149 unreal_setfilemodtime(handle->filename, last_mod); 150 151 handle->callback(handle->url, handle->filename, NULL, 0, handle->callback_data); 152 remove(handle->filename); 153 } 154 } 155 else 156 { 157 handle->callback(handle->url, NULL, handle->errorbuf, 0, handle->callback_data); 158 remove(handle->filename); 159 } 160 161 url_free_handle(handle); 162 curl_multi_remove_handle(multihandle, easyhand); 163 164 /* NOTE: after curl_multi_remove_handle() you cannot use 165 * 'msg' anymore because it has freed by curl (as of v7.11.0), 166 * therefore 'easyhand' is used... fun! -- Syzop 167 */ 168 curl_easy_cleanup(easyhand); 169 } 170 } 171 } 172 173 static void url_socket_pollcb(int fd, int revents, void *data) 174 { 175 int flags = 0; 176 int dummy; 177 178 if (revents & FD_SELECT_READ) 179 flags |= CURL_CSELECT_IN; 180 if (revents & FD_SELECT_WRITE) 181 flags |= CURL_CSELECT_OUT; 182 183 curl_multi_socket_action(multihandle, fd, flags, &dummy); 184 url_check_multi_handles(); 185 } 186 187 static int url_socket_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) 188 { 189 if (what == CURL_POLL_REMOVE) 190 { 191 fd_close(s); 192 } 193 else 194 { 195 FDEntry *fde = &fd_table[s]; 196 int flags = 0; 197 198 if (!fde->is_open) 199 { 200 /* NOTE: We use FDCLOSE_NONE here because cURL will take 201 * care of the closing of the socket. So *WE* must never 202 * close the socket ourselves. 203 */ 204 fd_open(s, "CURL transfer", FDCLOSE_NONE); 205 } 206 207 if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) 208 flags |= FD_SELECT_READ; 209 210 if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) 211 flags |= FD_SELECT_WRITE; 212 213 fd_setselect(s, flags, url_socket_pollcb, NULL); 214 } 215 216 return 0; 217 } 218 219 /* Handle timeouts. */ 220 EVENT(url_socket_timeout) 221 { 222 int dummy; 223 224 curl_multi_socket_action(multihandle, CURL_SOCKET_TIMEOUT, 0, &dummy); 225 url_check_multi_handles(); 226 } 227 228 static Event *url_socket_timeout_hdl = NULL; 229 230 /* 231 * Initializes the URL system 232 */ 233 void url_init(void) 234 { 235 curl_global_init(CURL_GLOBAL_ALL); 236 multihandle = curl_multi_init(); 237 238 curl_multi_setopt(multihandle, CURLMOPT_SOCKETFUNCTION, url_socket_cb); 239 url_socket_timeout_hdl = EventAdd(NULL, "url_socket_timeout", url_socket_timeout, NULL, 500, 0); 240 } 241 242 /* 243 * Handles asynchronous downloading of a file. This function allows 244 * a download to be made transparently without the caller having any 245 * knowledge of how libcurl works. The specified callback function is 246 * called when the download completes, or the download fails. The 247 * callback function is defined as: 248 * 249 * void callback(const char *url, const char *filename, char *errorbuf, int cached, void *data); 250 * - url will contain the original URL used to download the file. 251 * - filename will contain the name of the file (if successful, NULL on error or if cached). 252 * This file will be cleaned up after the callback returns, so save a copy to support caching. 253 * - errorbuf will contain the error message (if failed, NULL otherwise). 254 * - cached 1 if the specified cachetime is >= the current file on the server, 255 * if so, errorbuf will be NULL, filename will contain the path to the file. 256 * - data will be the value of callback_data, allowing you to figure 257 * out how to use the data contained in the downloaded file ;-). 258 * Make sure that if you access the contents of this pointer, you 259 * know that this pointer will persist. A download could take more 260 * than 10 seconds to happen and the config file can be rehashed 261 * multiple times during that time. 262 */ 263 void download_file_async(const char *url, time_t cachetime, vFP callback, void *callback_data, char *original_url, int maxredirects) 264 { 265 static char errorbuf[CURL_ERROR_SIZE]; 266 char user_agent[256]; 267 CURL *curl; 268 char *file; 269 const char *filename; 270 char *tmp; 271 Download *handle; 272 273 curl = curl_easy_init(); 274 if (!curl) 275 { 276 unreal_log(ULOG_ERROR, "main", "CURL_INTERNAL_FAILURE", NULL, 277 "Could not initialize curl handle. Maybe out of memory/resources?"); 278 snprintf(errorbuf, sizeof(errorbuf), "Could not initialize curl handle"); 279 return; 280 } 281 282 file = url_getfilename(url); 283 filename = unreal_getfilename(file); 284 tmp = unreal_mktemp(TMPDIR, filename ? filename : "download.conf"); 285 286 handle = safe_alloc(sizeof(Download)); 287 handle->file_fd = fopen(tmp, "wb"); 288 if (!handle->file_fd) 289 { 290 snprintf(errorbuf, sizeof(errorbuf), "Cannot create '%s': %s", tmp, strerror(ERRNO)); 291 callback(url, NULL, errorbuf, 0, callback_data); 292 safe_free(file); 293 safe_free(handle); 294 return; 295 } 296 AddListItem(handle, downloads); 297 298 handle->callback = callback; 299 handle->callback_data = callback_data; 300 handle->cachetime = cachetime; 301 safe_strdup(handle->url, url); 302 strlcpy(handle->filename, tmp, sizeof(handle->filename)); 303 safe_free(file); 304 305 curl_easy_setopt(curl, CURLOPT_URL, url); 306 snprintf(user_agent, sizeof(user_agent), "UnrealIRCd %s", VERSIONONLY); 307 curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent); 308 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, do_download); 309 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)handle->file_fd); 310 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); 311 set_curl_tls_options(curl); 312 memset(handle->errorbuf, 0, CURL_ERROR_SIZE); 313 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, handle->errorbuf); 314 curl_easy_setopt(curl, CURLOPT_PRIVATE, (char *)handle); 315 curl_easy_setopt(curl, CURLOPT_FILETIME, 1); 316 /* We need to set CURLOPT_FORBID_REUSE because otherwise libcurl does not 317 * notify us (or not in time) about FD close/opens, thus we end up closing and 318 * screwing up another innocent FD, like a listener (BAD!). In my view a bug, but 319 * mailing list archives seem to indicate curl devs have a different opinion 320 * on these matters... 321 * Actually I don't know for sure if this option alone fixes 100% of the cases 322 * but at least I can't crash my server anymore. 323 * As a side-effect we also fix useless CLOSE_WAIT connections. 324 */ 325 curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1); 326 if (cachetime) 327 { 328 curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); 329 curl_easy_setopt(curl, CURLOPT_TIMEVALUE, cachetime); 330 } 331 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); 332 curl_easy_setopt(curl, CURLOPT_TIMEOUT, DOWNLOAD_TRANSFER_TIMEOUT); 333 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, DOWNLOAD_CONNECT_TIMEOUT); 334 #if LIBCURL_VERSION_NUM >= 0x070f01 335 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); 336 curl_easy_setopt(curl, CURLOPT_MAXREDIRS, maxredirects); 337 #endif 338 339 curl_multi_add_handle(multihandle, curl); 340 }