/*************************************************************************** * _ _ ____ _ * 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 * ***************************************************************************/ /* This file is for implementing all "generic" SSL functions that all libcurl internals should use. It is then responsible for calling the proper "backend" function. SSL-functions in libcurl should call functions in this source file, and not to any specific SSL-layer. Curl_ssl_ - prefix for generic ones Note that this source code uses the functions of the configured SSL backend via the global Curl_ssl instance. "SSL/TLS Strong Encryption: An Introduction" https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html */ #include "curl_setup.h" #ifdef USE_SSL #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #include "urldata.h" #include "cfilters.h" #include "vtls.h" /* generic SSL protos etc */ #include "vtls_int.h" #include "vtls_scache.h" #include "strcase.h" #include "url.h" #include "llist.h" #include "share.h" #include "curl_trc.h" #include "curl_sha256.h" #include "warnless.h" #include "curl_printf.h" #include "strdup.h" /* The last #include files should be: */ #include "curl_memory.h" #include "memdebug.h" /* a peer+tls-config we cache sessions for */ struct Curl_ssl_scache_peer { char *ssl_peer_key; /* id for peer + relevant TLS configuration */ char *clientcert; char *srp_username; char *srp_password; struct Curl_llist sessions; void *sobj; /* object instance or NULL */ Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */ unsigned char key_salt[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */ unsigned char key_hmac[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */ size_t max_sessions; long age; /* just a number, the higher the more recent */ BIT(hmac_set); /* if key_salt and key_hmac are present */ }; struct Curl_ssl_scache { struct Curl_ssl_scache_peer *peers; size_t peer_count; int default_lifetime_secs; long age; }; static void cf_ssl_scache_clear_session(struct Curl_ssl_session *s) { if(s->sdata) { free((void *)s->sdata); s->sdata = NULL; } s->sdata_len = 0; s->ietf_tls_id = 0; s->time_received = 0; s->lifetime_secs = 0; Curl_safefree(s->alpn); } static void cf_ssl_scache_sesssion_ldestroy(void *udata, void *s) { (void)udata; cf_ssl_scache_clear_session(s); free(s); } CURLcode Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len, int ietf_tls_id, const char *alpn, curl_off_t time_received, long lifetime_secs, struct Curl_ssl_session **psession) { struct Curl_ssl_session *s; if(!sdata || !sdata_len) { free(sdata); return CURLE_BAD_FUNCTION_ARGUMENT; } *psession = NULL; s = calloc(1, sizeof(*s)); if(!s) { free(sdata); return CURLE_OUT_OF_MEMORY; } s->ietf_tls_id = ietf_tls_id; s->time_received = time_received; if(lifetime_secs < 0) lifetime_secs = -1; /* unknown */ else if((s->ietf_tls_id == CURL_IETF_PROTO_TLS1_3) && (lifetime_secs > CURL_SCACHE_MAX_13_LIFETIME_SEC)) lifetime_secs = CURL_SCACHE_MAX_13_LIFETIME_SEC; else if(lifetime_secs > CURL_SCACHE_MAX_12_LIFETIME_SEC) lifetime_secs = CURL_SCACHE_MAX_12_LIFETIME_SEC; s->lifetime_secs = (int)lifetime_secs; s->sdata = sdata; s->sdata_len = sdata_len; if(alpn) { s->alpn = strdup(alpn); if(!s->alpn) { cf_ssl_scache_sesssion_ldestroy(NULL, s); return CURLE_OUT_OF_MEMORY; } } *psession = s; return CURLE_OK; } void Curl_ssl_session_destroy(struct Curl_ssl_session *s) { if(s) { /* if in the list, the list destructor takes care of it */ if(Curl_node_llist(&s->list)) Curl_node_remove(&s->list); else { cf_ssl_scache_sesssion_ldestroy(NULL, s); } } } static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer) { Curl_llist_destroy(&peer->sessions, NULL); if(peer->sobj) { DEBUGASSERT(peer->sobj_free); if(peer->sobj_free) peer->sobj_free(peer->sobj); peer->sobj = NULL; } peer->sobj_free = NULL; Curl_safefree(peer->clientcert); #ifdef USE_TLS_SRP Curl_safefree(peer->srp_username); Curl_safefree(peer->srp_password); #endif Curl_safefree(peer->ssl_peer_key); peer->age = 0; peer->hmac_set = FALSE; } static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer, void *sobj, Curl_ssl_scache_obj_dtor *sobj_free) { DEBUGASSERT(peer); if(peer->sobj_free) { peer->sobj_free(peer->sobj); } peer->sobj = sobj; peer->sobj_free = sobj_free; } static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, const char *ssl_peer_key, const char *clientcert, const char *srp_username, const char *srp_password) { CURLcode result = CURLE_OUT_OF_MEMORY; DEBUGASSERT(!peer->ssl_peer_key); peer->ssl_peer_key = strdup(ssl_peer_key); if(!peer->ssl_peer_key) goto out; if(clientcert) { peer->clientcert = strdup(clientcert); if(!peer->clientcert) goto out; } if(srp_username) { peer->srp_username = strdup(srp_username); if(!peer->srp_username) goto out; } if(srp_password) { peer->srp_password = strdup(srp_password); if(!peer->srp_password) goto out; } result = CURLE_OK; out: if(result) cf_ssl_scache_clear_peer(peer); return result; } static void cf_scache_session_remove(struct Curl_ssl_scache_peer *peer, struct Curl_ssl_session *s) { (void)peer; DEBUGASSERT(Curl_node_llist(&s->list) == &peer->sessions); Curl_ssl_session_destroy(s); } static bool cf_scache_session_expired(struct Curl_ssl_session *s, curl_off_t now) { return (s->lifetime_secs > 0 && (s->time_received + s->lifetime_secs) < now); } static void cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer *peer, curl_off_t now) { struct Curl_llist_node *n = Curl_llist_head(&peer->sessions); while(n) { struct Curl_ssl_session *s = Curl_node_elem(n); n = Curl_node_next(n); if(cf_scache_session_expired(s, now)) cf_scache_session_remove(peer, s); } } static void cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer *peer) { struct Curl_llist_node *n = Curl_llist_head(&peer->sessions); while(n) { struct Curl_ssl_session *s = Curl_node_elem(n); n = Curl_node_next(n); if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) cf_scache_session_remove(peer, s); } } CURLcode Curl_ssl_scache_create(size_t max_peers, size_t max_sessions_per_peer, struct Curl_ssl_scache **pscache) { struct Curl_ssl_scache *scache; struct Curl_ssl_scache_peer *peers; size_t i; *pscache = NULL; peers = calloc(max_peers, sizeof(*peers)); if(!peers) return CURLE_OUT_OF_MEMORY; scache = calloc(1, sizeof(*scache)); if(!scache) { free(peers); return CURLE_OUT_OF_MEMORY; } scache->default_lifetime_secs = (24*60*60); /* 1 day */ scache->peer_count = max_peers; scache->peers = peers; scache->age = 1; for(i = 0; i < scache->peer_count; ++i) { scache->peers[i].max_sessions = max_sessions_per_peer; Curl_llist_init(&scache->peers[i].sessions, cf_ssl_scache_sesssion_ldestroy); } *pscache = scache; return CURLE_OK; } void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache) { if(scache) { size_t i; for(i = 0; i < scache->peer_count; ++i) { cf_ssl_scache_clear_peer(&scache->peers[i]); } free(scache->peers); free(scache); } } /* Lock shared SSL session data */ void Curl_ssl_scache_lock(struct Curl_easy *data) { if(CURL_SHARE_ssl_scache(data)) Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); } /* Unlock shared SSL session data */ void Curl_ssl_scache_unlock(struct Curl_easy *data) { if(CURL_SHARE_ssl_scache(data)) Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); } static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf, const char *name, char *path) { if(path && path[0]) { /* We try to add absolute paths, so that the session key can stay * valid when used in another process with different CWD. However, * when a path does not exist, this does not work. Then, we add * the path as is. */ #ifdef _WIN32 char abspath[_MAX_PATH]; if(_fullpath(abspath, path, _MAX_PATH)) return Curl_dyn_addf(buf, ":%s-%s", name, abspath); #else if(path[0] != '/') { char *abspath = realpath(path, NULL); if(abspath) { CURLcode r = Curl_dyn_addf(buf, ":%s-%s", name, abspath); (free)(abspath); /* allocated by libc, free without memdebug */ return r; } } #endif return Curl_dyn_addf(buf, ":%s-%s", name, path); } return CURLE_OK; } static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf, const char *name, struct curl_blob *blob) { CURLcode r = CURLE_OK; if(blob && blob->len) { unsigned char hash[CURL_SHA256_DIGEST_LENGTH]; size_t i; r = Curl_dyn_addf(buf, ":%s-", name); if(r) goto out; r = Curl_sha256it(hash, blob->data, blob->len); if(r) goto out; for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) { r = Curl_dyn_addf(buf, "%02x", hash[i]); if(r) goto out; } } out: return r; } CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, const struct ssl_peer *peer, const char *tls_id, char **ppeer_key) { struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf); struct dynbuf buf; size_t key_len; CURLcode r; *ppeer_key = NULL; Curl_dyn_init(&buf, 10 * 1024); r = Curl_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port); if(r) goto out; switch(peer->transport) { case TRNSPRT_TCP: break; case TRNSPRT_UDP: r = Curl_dyn_add(&buf, ":UDP"); break; case TRNSPRT_QUIC: r = Curl_dyn_add(&buf, ":QUIC"); break; case TRNSPRT_UNIX: r = Curl_dyn_add(&buf, ":UNIX"); break; default: r = Curl_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport); break; } if(r) goto out; if(!ssl->verifypeer) { r = Curl_dyn_add(&buf, ":NO-VRFY-PEER"); if(r) goto out; } if(!ssl->verifyhost) { r = Curl_dyn_add(&buf, ":NO-VRFY-HOST"); if(r) goto out; } if(ssl->verifystatus) { r = Curl_dyn_add(&buf, ":VRFY-STATUS"); if(r) goto out; } if(!ssl->verifypeer || !ssl->verifyhost) { if(cf->conn->bits.conn_to_host) { r = Curl_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name); if(r) goto out; } if(cf->conn->bits.conn_to_port) { r = Curl_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port); if(r) goto out; } } if(ssl->version || ssl->version_max) { r = Curl_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version, (ssl->version_max >> 16)); if(r) goto out; } if(ssl->ssl_options) { r = Curl_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options); if(r) goto out; } if(ssl->cipher_list) { r = Curl_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list); if(r) goto out; } if(ssl->cipher_list13) { r = Curl_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13); if(r) goto out; } if(ssl->curves) { r = Curl_dyn_addf(&buf, ":CURVES-%s", ssl->curves); if(r) goto out; } if(ssl->verifypeer) { r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile); if(r) goto out; r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath); if(r) goto out; r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile); if(r) goto out; r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert); if(r) goto out; if(ssl->cert_blob) { r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob); if(r) goto out; } if(ssl->ca_info_blob) { r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob); if(r) goto out; } if(ssl->issuercert_blob) { r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob); if(r) goto out; } } if(ssl->pinned_key && ssl->pinned_key[0]) { r = Curl_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key); if(r) goto out; } if(ssl->clientcert && ssl->clientcert[0]) { r = Curl_dyn_add(&buf, ":CCERT"); if(r) goto out; } #ifdef USE_TLS_SRP if(ssl->username || ssl->password) { r = Curl_dyn_add(&buf, ":SRP-AUTH"); if(r) goto out; } #endif if(!tls_id || !tls_id[0]) { r = CURLE_FAILED_INIT; goto out; } r = Curl_dyn_addf(&buf, ":IMPL-%s", tls_id); if(r) goto out; *ppeer_key = Curl_dyn_take(&buf, &key_len); /* we just added printable char, and dynbuf always 0 terminates, * no need to track length */ out: Curl_dyn_free(&buf); return r; } static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, struct ssl_primary_config *conn_config) { if(!Curl_safecmp(peer->clientcert, conn_config->clientcert)) return FALSE; #ifdef USE_TLS_SRP if(Curl_timestrcmp(peer->srp_username, conn_config->username) || Curl_timestrcmp(peer->srp_password, conn_config->password)) return FALSE; #endif return TRUE; } static CURLcode cf_ssl_find_peer(struct Curl_cfilter *cf, struct Curl_easy *data, struct Curl_ssl_scache *scache, const char *ssl_peer_key, struct Curl_ssl_scache_peer **ppeer) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); size_t i, peer_key_len = 0; CURLcode result = CURLE_OK; *ppeer = NULL; if(!ssl_config || !ssl_config->primary.cache_session) goto out; /* check for entries with known peer_key */ for(i = 0; scache && i < scache->peer_count; i++) { if(scache->peers[i].ssl_peer_key && strcasecompare(ssl_peer_key, scache->peers[i].ssl_peer_key) && cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) { /* yes, we have a cached session for this! */ *ppeer = &scache->peers[i]; goto out; } } /* check for entries with HMAC set but no known peer_key */ for(i = 0; scache && i < scache->peer_count; i++) { if(!scache->peers[i].ssl_peer_key && scache->peers[i].hmac_set && cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) { /* possible entry with unknown peer_key, check hmac */ unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH]; if(!peer_key_len) /* we are lazy */ peer_key_len = strlen(ssl_peer_key); result = Curl_hmacit(&Curl_HMAC_SHA256, scache->peers[i].key_salt, sizeof(scache->peers[i].key_salt), (const unsigned char *)ssl_peer_key, peer_key_len, my_hmac); if(result) goto out; if(!memcmp(scache->peers[i].key_hmac, my_hmac, sizeof(my_hmac))) { /* remember peer_key for future lookups */ scache->peers[i].ssl_peer_key = strdup(ssl_peer_key); if(!scache->peers[i].ssl_peer_key) { result = CURLE_OUT_OF_MEMORY; goto out; } *ppeer = &scache->peers[i]; goto out; } } } out: if(result) CURL_TRC_CF(data, cf, "[SACHE] failure finding scache peer: %d", result); return result; } static CURLcode cf_ssl_add_peer(struct Curl_cfilter *cf, struct Curl_easy *data, struct Curl_ssl_scache *scache, const char *ssl_peer_key, struct Curl_ssl_scache_peer **ppeer) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct Curl_ssl_scache_peer *peer = NULL; size_t i; CURLcode result; *ppeer = NULL; result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer); if(result || !scache->peer_count) return result; if(peer) { *ppeer = peer; return CURLE_OK; } /* not there, find empty or oldest peer */ for(i = 0; i < scache->peer_count; ++i) { /* free peer entry? */ if(!scache->peers[i].ssl_peer_key && !scache->peers[i].hmac_set) { peer = &scache->peers[i]; break; } /* peer without sessions and obj */ if(!scache->peers[i].sobj && !Curl_llist_count(&scache->peers[i].sessions)) { peer = &scache->peers[i]; break; } /* remember "oldest" peer */ if(!peer || (scache->peers[i].age < peer->age)) { peer = &scache->peers[i]; } } DEBUGASSERT(peer); if(!peer) return CURLE_OK; /* clear previous peer and reinit */ cf_ssl_scache_clear_peer(peer); result = cf_ssl_scache_peer_init(peer, ssl_peer_key, conn_config->clientcert, #ifdef USE_TLS_SRP conn_config->username, conn_config->password); #else NULL, NULL); #endif if(result) goto out; /* all ready */ *ppeer = peer; result = CURLE_OK; out: if(result) { cf_ssl_scache_clear_peer(peer); CURL_TRC_CF(data, cf, "[SACHE] failure adding peer: %d", result); } return result; } static CURLcode cf_scache_peer_add_session(struct Curl_cfilter *cf, struct Curl_easy *data, struct Curl_ssl_scache *scache, const char *ssl_peer_key, struct Curl_ssl_session *s) { struct Curl_ssl_scache_peer *peer = NULL; CURLcode result = CURLE_OUT_OF_MEMORY; curl_off_t now = (curl_off_t)time(NULL); if(!scache || !scache->peer_count) { Curl_ssl_session_destroy(s); return CURLE_OK; } if(!s->time_received) s->time_received = now; if(s->lifetime_secs < 0) s->lifetime_secs = scache->default_lifetime_secs; if(cf_scache_session_expired(s, now)) { CURL_TRC_CF(data, cf, "[SCACHE] add, session already expired"); Curl_ssl_session_destroy(s); return CURLE_OK; } result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer); if(result || !peer) { CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result); Curl_ssl_session_destroy(s); goto out; } /* A session not from TLSv1.3 replaces all other. */ if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) { Curl_llist_destroy(&peer->sessions, NULL); Curl_llist_append(&peer->sessions, s, &s->list); } else { /* Expire existing, append, trim from head to obey max_sessions */ cf_scache_peer_remove_expired(peer, now); cf_scache_peer_remove_non13(peer); Curl_llist_append(&peer->sessions, s, &s->list); while(Curl_llist_count(&peer->sessions) > peer->max_sessions) { Curl_node_remove(Curl_llist_head(&peer->sessions)); } } out: if(result) { failf(data, "[SCACHE] failed to add session for %s, error=%d", ssl_peer_key, result); } else CURL_TRC_CF(data, cf, "[SCACHE] added session for %s [proto=0x%x, " "lifetime=%d, alpn=%s], peer has %zu sessions now", ssl_peer_key, s->ietf_tls_id, s->lifetime_secs, s->alpn, Curl_llist_count(&peer->sessions)); return result; } CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, struct Curl_ssl_session *s) { struct Curl_ssl_scache *scache = data->state.ssl_scache; CURLcode result; Curl_ssl_scache_lock(data); result = cf_scache_peer_add_session(cf, data, scache, ssl_peer_key, s); Curl_ssl_scache_unlock(data); return result; } void Curl_ssl_scache_return(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, struct Curl_ssl_session *s) { /* See RFC 8446 C.4: * "Clients SHOULD NOT reuse a ticket for multiple connections." */ if(s && s->ietf_tls_id < 0x304) (void)Curl_ssl_scache_put(cf, data, ssl_peer_key, s); else Curl_ssl_session_destroy(s); } CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, struct Curl_ssl_session **ps) { struct Curl_ssl_scache *scache = data->state.ssl_scache; struct Curl_ssl_scache_peer *peer = NULL; struct Curl_llist_node *n; CURLcode result; *ps = NULL; if(!scache) return CURLE_OK; Curl_ssl_scache_lock(data); result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer); if(!result && peer) { cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL)); n = Curl_llist_head(&peer->sessions); if(n) { *ps = Curl_node_take_elem(n); (scache->age)++; /* increase general age */ peer->age = scache->age; /* set this as used in this age */ } } Curl_ssl_scache_unlock(data); CURL_TRC_CF(data, cf, "[SCACHE] %s cached session for '%s'", *ps ? "Found" : "No", ssl_peer_key); return result; } CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, void *sobj, Curl_ssl_scache_obj_dtor *sobj_free) { struct Curl_ssl_scache *scache = data->state.ssl_scache; struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; DEBUGASSERT(sobj); DEBUGASSERT(sobj_free); result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer); if(result || !peer) { CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result); goto out; } cf_ssl_scache_peer_set_obj(peer, sobj, sobj_free); sobj = NULL; /* peer took ownership */ out: if(sobj && sobj_free) sobj_free(sobj); return result; } bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, void **sobj) { struct Curl_ssl_scache *scache = data->state.ssl_scache; struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; *sobj = NULL; if(!scache) return FALSE; result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer); if(result) return FALSE; if(peer) *sobj = peer->sobj; CURL_TRC_CF(data, cf, "[SACHE] %s cached session for '%s'", *sobj ? "Found" : "No", ssl_peer_key); return !!*sobj; } void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key) { struct Curl_ssl_scache *scache = data->state.ssl_scache; struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; (void)cf; if(!scache) return; Curl_ssl_scache_lock(data); result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer); if(!result && peer) cf_ssl_scache_clear_peer(peer); Curl_ssl_scache_unlock(data); } #endif /* USE_SSL */