/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * SPDX-License-Identifier: curl * ***************************************************************************/ #include "curl_setup.h" #include "urldata.h" #include "multiif.h" #include "curl_threads.h" #include "curl_share.h" #include "vtls/vtls.h" #include "vtls/vtls_scache.h" #include "hsts.h" #include "url.h" static void share_destroy(struct Curl_share *share) { if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) { Curl_cpool_destroy(&share->cpool); } Curl_dnscache_destroy(&share->dnscache); #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) Curl_cookie_cleanup(share->cookies); #endif #ifndef CURL_DISABLE_HSTS Curl_hsts_cleanup(&share->hsts); #endif #ifdef USE_SSL if(share->ssl_scache) { Curl_ssl_scache_destroy(share->ssl_scache); share->ssl_scache = NULL; } #endif Curl_psl_destroy(&share->psl); Curl_close(&share->admin); #ifdef USE_MUTEX Curl_mutex_destroy(&share->lock); #endif share->magic = 0; curlx_free(share); } CURLSH *curl_share_init(void) { struct Curl_share *share = curlx_calloc(1, sizeof(struct Curl_share)); if(share) { share->magic = CURL_GOOD_SHARE; share->specifier |= (1 << CURL_LOCK_DATA_SHARE); #ifdef USE_MUTEX Curl_mutex_init(&share->lock); #endif share->ref_count = 1; Curl_dnscache_init(&share->dnscache, 23); share->admin = curl_easy_init(); if(!share->admin) { share_destroy(share); return NULL; } /* admin handles have mid 0 */ share->admin->mid = 0; share->admin->state.internal = TRUE; #ifdef DEBUGBUILD if(getenv("CURL_DEBUG")) share->admin->set.verbose = TRUE; #endif } return share; } static uint32_t share_ref_inc(struct Curl_share *share) { uint32_t n; #ifdef USE_MUTEX Curl_mutex_acquire(&share->lock); n = ++(share->ref_count); share->has_been_shared = TRUE; Curl_mutex_release(&share->lock); #else n = ++(share->ref_count); share->has_been_shared = TRUE; #endif return n; } static uint32_t share_ref_dec(struct Curl_share *share) { uint32_t n; #ifdef USE_MUTEX Curl_mutex_acquire(&share->lock); DEBUGASSERT(share->ref_count); n = --(share->ref_count); Curl_mutex_release(&share->lock); #else n = --(share->ref_count); #endif return n; } static bool share_has_been_shared(struct Curl_share *share) { bool was_shared; #ifdef USE_MUTEX Curl_mutex_acquire(&share->lock); was_shared = share->has_been_shared; Curl_mutex_release(&share->lock); #else was_shared = share->has_been_shared; #endif return was_shared; } static bool share_lock_acquire(struct Curl_share *share, struct Curl_easy *data) { if(share->lockfunc && share->unlockfunc && (data || share_has_been_shared(share))) { share->lockfunc(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE, share->clientdata); return TRUE; } return FALSE; } static void share_lock_release(struct Curl_share *share, struct Curl_easy *data, bool locked) { if(locked) { DEBUGASSERT(share->unlockfunc); if(share->unlockfunc) share->unlockfunc(data, CURL_LOCK_DATA_SHARE, share->clientdata); } } static bool share_in_use(struct Curl_share *share) { bool in_use; #ifdef USE_MUTEX Curl_mutex_acquire(&share->lock); in_use = (share->ref_count > 1); Curl_mutex_release(&share->lock); #else bool locked = share_lock_acquire(share, NULL); in_use = (share->ref_count > 1); share_lock_release(share, NULL, locked); #endif return in_use; } static void share_unlink(struct Curl_share **pshare, struct Curl_easy *data, bool locked) { struct Curl_share *share = *pshare; uint32_t n; *pshare = NULL; n = share_ref_dec(share); if(locked) share_lock_release(share, data, locked); if(!n) /* last reference gone */ share_destroy(share); } #undef curl_share_setopt CURLSHcode curl_share_setopt(CURLSH *sh, CURLSHoption option, ...) { va_list param; int type; curl_lock_function lockfunc; curl_unlock_function unlockfunc; void *ptr; CURLSHcode res = CURLSHE_OK; struct Curl_share *share = sh; if(!GOOD_SHARE_HANDLE(share)) return CURLSHE_INVALID; if(share_in_use(share)) { /* do not allow setting options while one or more handles are already using this share */ return CURLSHE_IN_USE; } va_start(param, option); switch(option) { case CURLSHOPT_SHARE: /* this is a type this share will share */ type = va_arg(param, int); switch(type) { case CURL_LOCK_DATA_DNS: break; case CURL_LOCK_DATA_COOKIE: #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) if(!share->cookies) { share->cookies = Curl_cookie_init(); if(!share->cookies) res = CURLSHE_NOMEM; } #else /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */ res = CURLSHE_NOT_BUILT_IN; #endif break; case CURL_LOCK_DATA_HSTS: #ifndef CURL_DISABLE_HSTS if(!share->hsts) { share->hsts = Curl_hsts_init(); if(!share->hsts) res = CURLSHE_NOMEM; } #else /* CURL_DISABLE_HSTS */ res = CURLSHE_NOT_BUILT_IN; #endif break; case CURL_LOCK_DATA_SSL_SESSION: #ifdef USE_SSL if(!share->ssl_scache) { /* There is no way (yet) for the application to configure the * session cache size, shared between many transfers. As for curl * itself, a high session count will impact startup time. Also, the * scache is not optimized for several hundreds of peers. * Keep it at a reasonable level. */ if(Curl_ssl_scache_create(25, 2, &share->ssl_scache)) res = CURLSHE_NOMEM; } #else res = CURLSHE_NOT_BUILT_IN; #endif break; case CURL_LOCK_DATA_CONNECT: /* It is safe to set this option several times on a share. */ if(!share->cpool.initialised) { Curl_cpool_init(&share->cpool, share->admin, share, 103); } break; case CURL_LOCK_DATA_PSL: #ifndef USE_LIBPSL res = CURLSHE_NOT_BUILT_IN; #endif break; default: res = CURLSHE_BAD_OPTION; } if(!res) share->specifier |= (unsigned int)(1 << type); break; case CURLSHOPT_UNSHARE: /* this is a type this share will no longer share */ type = va_arg(param, int); switch(type) { case CURL_LOCK_DATA_DNS: break; case CURL_LOCK_DATA_COOKIE: #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) if(share->cookies) { Curl_cookie_cleanup(share->cookies); share->cookies = NULL; } #else /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */ res = CURLSHE_NOT_BUILT_IN; #endif break; case CURL_LOCK_DATA_HSTS: #ifndef CURL_DISABLE_HSTS if(share->hsts) { Curl_hsts_cleanup(&share->hsts); } #else /* CURL_DISABLE_HSTS */ res = CURLSHE_NOT_BUILT_IN; #endif break; case CURL_LOCK_DATA_SSL_SESSION: #ifdef USE_SSL if(share->ssl_scache) { Curl_ssl_scache_destroy(share->ssl_scache); share->ssl_scache = NULL; } #else res = CURLSHE_NOT_BUILT_IN; #endif break; case CURL_LOCK_DATA_CONNECT: break; default: res = CURLSHE_BAD_OPTION; break; } if(!res) share->specifier &= ~(unsigned int)(1 << type); break; case CURLSHOPT_LOCKFUNC: lockfunc = va_arg(param, curl_lock_function); share->lockfunc = lockfunc; break; case CURLSHOPT_UNLOCKFUNC: unlockfunc = va_arg(param, curl_unlock_function); share->unlockfunc = unlockfunc; break; case CURLSHOPT_USERDATA: ptr = va_arg(param, void *); share->clientdata = ptr; break; default: res = CURLSHE_BAD_OPTION; break; } va_end(param); return res; } CURLSHcode curl_share_cleanup(CURLSH *sh) { struct Curl_share *share = sh; bool locked; if(!GOOD_SHARE_HANDLE(share)) return CURLSHE_INVALID; if(share_in_use(share)) return CURLSHE_IN_USE; locked = share_lock_acquire(share, NULL); share_unlink(&share, NULL, locked); return CURLSHE_OK; } CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type, curl_lock_access accesstype) { struct Curl_share *share = data->share; if(!share) return CURLSHE_INVALID; if(share->specifier & (unsigned int)(1 << type)) { if(share->lockfunc) /* only call this if set! */ share->lockfunc(data, type, accesstype, share->clientdata); } /* else if we do not share this, pretend successful lock */ return CURLSHE_OK; } CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) { struct Curl_share *share = data->share; if(!share) return CURLSHE_INVALID; if(share->specifier & (unsigned int)(1 << type)) { if(share->unlockfunc) /* only call this if set! */ share->unlockfunc(data, type, share->clientdata); } return CURLSHE_OK; } CURLcode Curl_share_easy_unlink(struct Curl_easy *data) { struct Curl_share *share = data->share; if(share) { bool locked = share_lock_acquire(share, data); /* If data has a connection from this share, detach it. */ if(data->conn && (share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) Curl_detach_connection(data); #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) if(share->cookies == data->cookies) data->cookies = NULL; #endif #ifndef CURL_DISABLE_HSTS if(share->hsts == data->hsts) data->hsts = NULL; #endif #ifdef USE_LIBPSL if(&share->psl == data->psl) data->psl = data->multi ? &data->multi->psl : NULL; #endif share_unlink(&data->share, data, locked); } return CURLE_OK; } CURLcode Curl_share_easy_link(struct Curl_easy *data, struct Curl_share *share) { if(data->share) { DEBUGASSERT(0); return CURLE_FAILED_INIT; } if(share) { bool locked = share_lock_acquire(share, data); share_ref_inc(share); data->share = share; #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) if(share->cookies) { /* use shared cookie list, first free own one if any */ Curl_cookie_cleanup(data->cookies); /* enable cookies since we now use a share that uses cookies! */ data->cookies = share->cookies; } #endif /* CURL_DISABLE_HTTP */ #ifndef CURL_DISABLE_HSTS if(share->hsts) { /* first free the private one if any */ Curl_hsts_cleanup(&data->hsts); data->hsts = share->hsts; } #endif #ifdef USE_LIBPSL if(share->specifier & (1 << CURL_LOCK_DATA_PSL)) data->psl = &share->psl; #endif /* check for host cache not needed, * it will be done by curl_easy_perform */ share_lock_release(share, data, locked); } return CURLE_OK; }