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 }