quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

vtls_scache.c (35984B)


      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  * SPDX-License-Identifier: curl
     22  *
     23  ***************************************************************************/
     24 
     25 #include "../curl_setup.h"
     26 
     27 #ifdef USE_SSL
     28 
     29 #ifdef HAVE_SYS_TYPES_H
     30 #include <sys/types.h>
     31 #endif
     32 #ifdef HAVE_FCNTL_H
     33 #include <fcntl.h>
     34 #endif
     35 
     36 #include "../urldata.h"
     37 #include "../cfilters.h"
     38 
     39 #include "vtls.h" /* generic SSL protos etc */
     40 #include "vtls_int.h"
     41 #include "vtls_scache.h"
     42 #include "vtls_spack.h"
     43 
     44 #include "../strcase.h"
     45 #include "../url.h"
     46 #include "../llist.h"
     47 #include "../share.h"
     48 #include "../curl_trc.h"
     49 #include "../curl_sha256.h"
     50 #include "../rand.h"
     51 #include "../curlx/warnless.h"
     52 #include "../curl_printf.h"
     53 #include "../strdup.h"
     54 
     55 /* The last #include files should be: */
     56 #include "../curl_memory.h"
     57 #include "../memdebug.h"
     58 
     59 
     60 static bool cf_ssl_peer_key_is_global(const char *peer_key);
     61 
     62 /* a peer+tls-config we cache sessions for */
     63 struct Curl_ssl_scache_peer {
     64   char *ssl_peer_key;      /* id for peer + relevant TLS configuration */
     65   char *clientcert;
     66   char *srp_username;
     67   char *srp_password;
     68   struct Curl_llist sessions;
     69   void *sobj;              /* object instance or NULL */
     70   Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */
     71   unsigned char key_salt[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
     72   unsigned char key_hmac[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
     73   size_t max_sessions;
     74   long age;                /* just a number, the higher the more recent */
     75   BIT(hmac_set);           /* if key_salt and key_hmac are present */
     76   BIT(exportable);         /* sessions for this peer can be exported */
     77 };
     78 
     79 #define CURL_SCACHE_MAGIC 0x000e1551
     80 
     81 #define GOOD_SCACHE(x) ((x) && (x)->magic == CURL_SCACHE_MAGIC)
     82 
     83 struct Curl_ssl_scache {
     84   unsigned int magic;
     85   struct Curl_ssl_scache_peer *peers;
     86   size_t peer_count;
     87   int default_lifetime_secs;
     88   long age;
     89 };
     90 
     91 static struct Curl_ssl_scache *cf_ssl_scache_get(struct Curl_easy *data)
     92 {
     93   struct Curl_ssl_scache *scache = NULL;
     94   /* If a share is present, its ssl_scache has preference over the multi */
     95   if(data->share && data->share->ssl_scache)
     96     scache = data->share->ssl_scache;
     97   else if(data->multi && data->multi->ssl_scache)
     98     scache = data->multi->ssl_scache;
     99   if(scache && !GOOD_SCACHE(scache)) {
    100     failf(data, "transfer would use an invalid scache at %p, denied",
    101           (void *)scache);
    102     DEBUGASSERT(0);
    103     return NULL;
    104   }
    105   return scache;
    106 }
    107 
    108 static void cf_ssl_scache_session_ldestroy(void *udata, void *obj)
    109 {
    110   struct Curl_ssl_session *s = obj;
    111   (void)udata;
    112   free(CURL_UNCONST(s->sdata));
    113   free(CURL_UNCONST(s->quic_tp));
    114   free((void *)s->alpn);
    115   free(s);
    116 }
    117 
    118 CURLcode
    119 Curl_ssl_session_create(void *sdata, size_t sdata_len,
    120                         int ietf_tls_id, const char *alpn,
    121                         curl_off_t valid_until, size_t earlydata_max,
    122                         struct Curl_ssl_session **psession)
    123 {
    124   return Curl_ssl_session_create2(sdata, sdata_len, ietf_tls_id, alpn,
    125                                   valid_until, earlydata_max,
    126                                   NULL, 0, psession);
    127 }
    128 
    129 CURLcode
    130 Curl_ssl_session_create2(void *sdata, size_t sdata_len,
    131                          int ietf_tls_id, const char *alpn,
    132                          curl_off_t valid_until, size_t earlydata_max,
    133                          unsigned char *quic_tp, size_t quic_tp_len,
    134                          struct Curl_ssl_session **psession)
    135 {
    136   struct Curl_ssl_session *s;
    137 
    138   if(!sdata || !sdata_len) {
    139     free(sdata);
    140     return CURLE_BAD_FUNCTION_ARGUMENT;
    141   }
    142 
    143   *psession = NULL;
    144   s = calloc(1, sizeof(*s));
    145   if(!s) {
    146     free(sdata);
    147     free(quic_tp);
    148     return CURLE_OUT_OF_MEMORY;
    149   }
    150 
    151   s->ietf_tls_id = ietf_tls_id;
    152   s->valid_until = valid_until;
    153   s->earlydata_max = earlydata_max;
    154   s->sdata = sdata;
    155   s->sdata_len = sdata_len;
    156   s->quic_tp = quic_tp;
    157   s->quic_tp_len = quic_tp_len;
    158   if(alpn) {
    159     s->alpn = strdup(alpn);
    160     if(!s->alpn) {
    161       cf_ssl_scache_session_ldestroy(NULL, s);
    162       return CURLE_OUT_OF_MEMORY;
    163     }
    164   }
    165   *psession = s;
    166   return CURLE_OK;
    167 }
    168 
    169 void Curl_ssl_session_destroy(struct Curl_ssl_session *s)
    170 {
    171   if(s) {
    172     /* if in the list, the list destructor takes care of it */
    173     if(Curl_node_llist(&s->list))
    174       Curl_node_remove(&s->list);
    175     else {
    176       cf_ssl_scache_session_ldestroy(NULL, s);
    177     }
    178   }
    179 }
    180 
    181 static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer)
    182 {
    183   Curl_llist_destroy(&peer->sessions, NULL);
    184   if(peer->sobj) {
    185     DEBUGASSERT(peer->sobj_free);
    186     if(peer->sobj_free)
    187       peer->sobj_free(peer->sobj);
    188     peer->sobj = NULL;
    189   }
    190   peer->sobj_free = NULL;
    191   Curl_safefree(peer->clientcert);
    192 #ifdef USE_TLS_SRP
    193   Curl_safefree(peer->srp_username);
    194   Curl_safefree(peer->srp_password);
    195 #endif
    196   Curl_safefree(peer->ssl_peer_key);
    197   peer->age = 0;
    198   peer->hmac_set = FALSE;
    199 }
    200 
    201 static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer,
    202                                        void *sobj,
    203                                        Curl_ssl_scache_obj_dtor *sobj_free)
    204 {
    205   DEBUGASSERT(peer);
    206   if(peer->sobj_free) {
    207     peer->sobj_free(peer->sobj);
    208   }
    209   peer->sobj = sobj;
    210   peer->sobj_free = sobj_free;
    211 }
    212 
    213 static void cf_ssl_cache_peer_update(struct Curl_ssl_scache_peer *peer)
    214 {
    215   /* The sessions of this peer are exportable if
    216    * - it has no confidential information
    217    * - its peer key is not yet known, because sessions were
    218    *   imported using only the salt+hmac
    219    * - the peer key is global, e.g. carrying no relative paths */
    220   peer->exportable = (!peer->clientcert && !peer->srp_username &&
    221                       !peer->srp_password &&
    222                       (!peer->ssl_peer_key ||
    223                        cf_ssl_peer_key_is_global(peer->ssl_peer_key)));
    224 }
    225 
    226 static CURLcode
    227 cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer,
    228                         const char *ssl_peer_key,
    229                         const char *clientcert,
    230                         const char *srp_username,
    231                         const char *srp_password,
    232                         const unsigned char *salt,
    233                         const unsigned char *hmac)
    234 {
    235   CURLcode result = CURLE_OUT_OF_MEMORY;
    236 
    237   DEBUGASSERT(!peer->ssl_peer_key);
    238   if(ssl_peer_key) {
    239     peer->ssl_peer_key = strdup(ssl_peer_key);
    240     if(!peer->ssl_peer_key)
    241       goto out;
    242     peer->hmac_set = FALSE;
    243   }
    244   else if(salt && hmac) {
    245     memcpy(peer->key_salt, salt, sizeof(peer->key_salt));
    246     memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac));
    247     peer->hmac_set = TRUE;
    248   }
    249   else {
    250     result = CURLE_BAD_FUNCTION_ARGUMENT;
    251     goto out;
    252   }
    253   if(clientcert) {
    254     peer->clientcert = strdup(clientcert);
    255     if(!peer->clientcert)
    256       goto out;
    257   }
    258   if(srp_username) {
    259     peer->srp_username = strdup(srp_username);
    260     if(!peer->srp_username)
    261       goto out;
    262   }
    263   if(srp_password) {
    264     peer->srp_password = strdup(srp_password);
    265     if(!peer->srp_password)
    266       goto out;
    267   }
    268 
    269   cf_ssl_cache_peer_update(peer);
    270   result = CURLE_OK;
    271 out:
    272   if(result)
    273     cf_ssl_scache_clear_peer(peer);
    274   return result;
    275 }
    276 
    277 static void cf_scache_session_remove(struct Curl_ssl_scache_peer *peer,
    278                                      struct Curl_ssl_session *s)
    279 {
    280   (void)peer;
    281   DEBUGASSERT(Curl_node_llist(&s->list) == &peer->sessions);
    282   Curl_ssl_session_destroy(s);
    283 }
    284 
    285 static bool cf_scache_session_expired(struct Curl_ssl_session *s,
    286                                       curl_off_t now)
    287 {
    288   return (s->valid_until > 0) && (s->valid_until < now);
    289 }
    290 
    291 static void cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer *peer,
    292                                           curl_off_t now)
    293 {
    294   struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
    295   while(n) {
    296     struct Curl_ssl_session *s = Curl_node_elem(n);
    297     n = Curl_node_next(n);
    298     if(cf_scache_session_expired(s, now))
    299       cf_scache_session_remove(peer, s);
    300   }
    301 }
    302 
    303 static void cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer *peer)
    304 {
    305   struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
    306   while(n) {
    307     struct Curl_ssl_session *s = Curl_node_elem(n);
    308     n = Curl_node_next(n);
    309     if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3)
    310       cf_scache_session_remove(peer, s);
    311   }
    312 }
    313 
    314 CURLcode Curl_ssl_scache_create(size_t max_peers,
    315                                 size_t max_sessions_per_peer,
    316                                 struct Curl_ssl_scache **pscache)
    317 {
    318   struct Curl_ssl_scache *scache;
    319   struct Curl_ssl_scache_peer *peers;
    320   size_t i;
    321 
    322   *pscache = NULL;
    323   peers = calloc(max_peers, sizeof(*peers));
    324   if(!peers)
    325     return CURLE_OUT_OF_MEMORY;
    326 
    327   scache = calloc(1, sizeof(*scache));
    328   if(!scache) {
    329     free(peers);
    330     return CURLE_OUT_OF_MEMORY;
    331   }
    332 
    333   scache->magic = CURL_SCACHE_MAGIC;
    334   scache->default_lifetime_secs = (24*60*60); /* 1 day */
    335   scache->peer_count = max_peers;
    336   scache->peers = peers;
    337   scache->age = 1;
    338   for(i = 0; i < scache->peer_count; ++i) {
    339     scache->peers[i].max_sessions = max_sessions_per_peer;
    340     Curl_llist_init(&scache->peers[i].sessions,
    341                     cf_ssl_scache_session_ldestroy);
    342   }
    343 
    344   *pscache = scache;
    345   return CURLE_OK;
    346 }
    347 
    348 void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache)
    349 {
    350   if(scache && GOOD_SCACHE(scache)) {
    351     size_t i;
    352     scache->magic = 0;
    353     for(i = 0; i < scache->peer_count; ++i) {
    354       cf_ssl_scache_clear_peer(&scache->peers[i]);
    355     }
    356     free(scache->peers);
    357     free(scache);
    358   }
    359 }
    360 
    361 /* Lock shared SSL session data */
    362 void Curl_ssl_scache_lock(struct Curl_easy *data)
    363 {
    364   if(CURL_SHARE_ssl_scache(data))
    365     Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
    366 }
    367 
    368 /* Unlock shared SSL session data */
    369 void Curl_ssl_scache_unlock(struct Curl_easy *data)
    370 {
    371   if(CURL_SHARE_ssl_scache(data))
    372     Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
    373 }
    374 
    375 static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf,
    376                                          const char *name,
    377                                          char *path,
    378                                          bool *is_local)
    379 {
    380   if(path && path[0]) {
    381     /* We try to add absolute paths, so that the session key can stay
    382      * valid when used in another process with different CWD. However,
    383      * when a path does not exist, this does not work. Then, we add
    384      * the path as is. */
    385 #ifdef UNDER_CE
    386     (void)is_local;
    387     return curlx_dyn_addf(buf, ":%s-%s", name, path);
    388 #elif defined(_WIN32)
    389     char abspath[_MAX_PATH];
    390     if(_fullpath(abspath, path, _MAX_PATH))
    391       return curlx_dyn_addf(buf, ":%s-%s", name, abspath);
    392     *is_local = TRUE;
    393 #elif defined(HAVE_REALPATH)
    394     if(path[0] != '/') {
    395       char *abspath = realpath(path, NULL);
    396       if(abspath) {
    397         CURLcode r = curlx_dyn_addf(buf, ":%s-%s", name, abspath);
    398         (free)(abspath); /* allocated by libc, free without memdebug */
    399         return r;
    400       }
    401       *is_local = TRUE;
    402     }
    403 #endif
    404     return curlx_dyn_addf(buf, ":%s-%s", name, path);
    405   }
    406   return CURLE_OK;
    407 }
    408 
    409 static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf,
    410                                          const char *name,
    411                                          struct curl_blob *blob)
    412 {
    413   CURLcode r = CURLE_OK;
    414   if(blob && blob->len) {
    415     unsigned char hash[CURL_SHA256_DIGEST_LENGTH];
    416     size_t i;
    417 
    418     r = curlx_dyn_addf(buf, ":%s-", name);
    419     if(r)
    420       goto out;
    421     r = Curl_sha256it(hash, blob->data, blob->len);
    422     if(r)
    423       goto out;
    424     for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) {
    425       r = curlx_dyn_addf(buf, "%02x", hash[i]);
    426       if(r)
    427         goto out;
    428     }
    429   }
    430 out:
    431   return r;
    432 }
    433 
    434 #define CURL_SSLS_LOCAL_SUFFIX     ":L"
    435 #define CURL_SSLS_GLOBAL_SUFFIX    ":G"
    436 
    437 static bool cf_ssl_peer_key_is_global(const char *peer_key)
    438 {
    439   size_t len = peer_key ? strlen(peer_key) : 0;
    440   return (len > 2) &&
    441          (peer_key[len - 1] == 'G') &&
    442          (peer_key[len - 2] == ':');
    443 }
    444 
    445 CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
    446                                 const struct ssl_peer *peer,
    447                                 const char *tls_id,
    448                                 char **ppeer_key)
    449 {
    450   struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf);
    451   struct dynbuf buf;
    452   size_t key_len;
    453   bool is_local = FALSE;
    454   CURLcode r;
    455 
    456   *ppeer_key = NULL;
    457   curlx_dyn_init(&buf, 10 * 1024);
    458 
    459   r = curlx_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port);
    460   if(r)
    461     goto out;
    462 
    463   switch(peer->transport) {
    464   case TRNSPRT_TCP:
    465     break;
    466   case TRNSPRT_UDP:
    467     r = curlx_dyn_add(&buf, ":UDP");
    468     break;
    469   case TRNSPRT_QUIC:
    470     r = curlx_dyn_add(&buf, ":QUIC");
    471     break;
    472   case TRNSPRT_UNIX:
    473     r = curlx_dyn_add(&buf, ":UNIX");
    474     break;
    475   default:
    476     r = curlx_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport);
    477     break;
    478   }
    479   if(r)
    480     goto out;
    481 
    482   if(!ssl->verifypeer) {
    483     r = curlx_dyn_add(&buf, ":NO-VRFY-PEER");
    484     if(r)
    485       goto out;
    486   }
    487   if(!ssl->verifyhost) {
    488     r = curlx_dyn_add(&buf, ":NO-VRFY-HOST");
    489     if(r)
    490       goto out;
    491   }
    492   if(ssl->verifystatus) {
    493     r = curlx_dyn_add(&buf, ":VRFY-STATUS");
    494     if(r)
    495       goto out;
    496   }
    497   if(!ssl->verifypeer || !ssl->verifyhost) {
    498     if(cf->conn->bits.conn_to_host) {
    499       r = curlx_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name);
    500       if(r)
    501         goto out;
    502     }
    503     if(cf->conn->bits.conn_to_port) {
    504       r = curlx_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port);
    505       if(r)
    506         goto out;
    507     }
    508   }
    509 
    510   if(ssl->version || ssl->version_max) {
    511     r = curlx_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version,
    512                       (ssl->version_max >> 16));
    513     if(r)
    514       goto out;
    515   }
    516   if(ssl->ssl_options) {
    517     r = curlx_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options);
    518     if(r)
    519       goto out;
    520   }
    521   if(ssl->cipher_list) {
    522     r = curlx_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list);
    523     if(r)
    524       goto out;
    525   }
    526   if(ssl->cipher_list13) {
    527     r = curlx_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13);
    528     if(r)
    529       goto out;
    530   }
    531   if(ssl->curves) {
    532     r = curlx_dyn_addf(&buf, ":CURVES-%s", ssl->curves);
    533     if(r)
    534       goto out;
    535   }
    536   if(ssl->verifypeer) {
    537     r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile, &is_local);
    538     if(r)
    539       goto out;
    540     r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath, &is_local);
    541     if(r)
    542       goto out;
    543     r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile, &is_local);
    544     if(r)
    545       goto out;
    546     r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, &is_local);
    547     if(r)
    548       goto out;
    549     if(ssl->cert_blob) {
    550       r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob);
    551       if(r)
    552         goto out;
    553     }
    554     if(ssl->ca_info_blob) {
    555       r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob);
    556       if(r)
    557         goto out;
    558     }
    559     if(ssl->issuercert_blob) {
    560       r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob);
    561       if(r)
    562         goto out;
    563     }
    564   }
    565   if(ssl->pinned_key && ssl->pinned_key[0]) {
    566     r = curlx_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key);
    567     if(r)
    568       goto out;
    569   }
    570 
    571   if(ssl->clientcert && ssl->clientcert[0]) {
    572     r = curlx_dyn_add(&buf, ":CCERT");
    573     if(r)
    574       goto out;
    575   }
    576 #ifdef USE_TLS_SRP
    577   if(ssl->username || ssl->password) {
    578     r = curlx_dyn_add(&buf, ":SRP-AUTH");
    579     if(r)
    580       goto out;
    581   }
    582 #endif
    583 
    584   if(!tls_id || !tls_id[0]) {
    585     r = CURLE_FAILED_INIT;
    586     goto out;
    587   }
    588   r = curlx_dyn_addf(&buf, ":IMPL-%s", tls_id);
    589   if(r)
    590     goto out;
    591 
    592   r = curlx_dyn_addf(&buf, is_local ?
    593                      CURL_SSLS_LOCAL_SUFFIX : CURL_SSLS_GLOBAL_SUFFIX);
    594   if(r)
    595     goto out;
    596 
    597   *ppeer_key = curlx_dyn_take(&buf, &key_len);
    598   /* we just added printable char, and dynbuf always null-terminates, no need
    599    * to track length */
    600 
    601 out:
    602   curlx_dyn_free(&buf);
    603   return r;
    604 }
    605 
    606 static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
    607                                      struct ssl_primary_config *conn_config)
    608 {
    609   if(!conn_config) {
    610     if(peer->clientcert)
    611       return FALSE;
    612 #ifdef USE_TLS_SRP
    613     if(peer->srp_username || peer->srp_password)
    614       return FALSE;
    615 #endif
    616     return TRUE;
    617   }
    618   else if(!Curl_safecmp(peer->clientcert, conn_config->clientcert))
    619     return FALSE;
    620 #ifdef USE_TLS_SRP
    621    if(Curl_timestrcmp(peer->srp_username, conn_config->username) ||
    622       Curl_timestrcmp(peer->srp_password, conn_config->password))
    623      return FALSE;
    624 #endif
    625   return TRUE;
    626 }
    627 
    628 static CURLcode
    629 cf_ssl_find_peer_by_key(struct Curl_easy *data,
    630                         struct Curl_ssl_scache *scache,
    631                         const char *ssl_peer_key,
    632                         struct ssl_primary_config *conn_config,
    633                         struct Curl_ssl_scache_peer **ppeer)
    634 {
    635   size_t i, peer_key_len = 0;
    636   CURLcode result = CURLE_OK;
    637 
    638   *ppeer = NULL;
    639   if(!GOOD_SCACHE(scache)) {
    640     return CURLE_BAD_FUNCTION_ARGUMENT;
    641   }
    642 
    643   CURL_TRC_SSLS(data, "find peer slot for %s among %zu slots",
    644                 ssl_peer_key, scache->peer_count);
    645 
    646   /* check for entries with known peer_key */
    647   for(i = 0; scache && i < scache->peer_count; i++) {
    648     if(scache->peers[i].ssl_peer_key &&
    649        curl_strequal(ssl_peer_key, scache->peers[i].ssl_peer_key) &&
    650        cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
    651       /* yes, we have a cached session for this! */
    652       *ppeer = &scache->peers[i];
    653       goto out;
    654     }
    655   }
    656   /* check for entries with HMAC set but no known peer_key */
    657   for(i = 0; scache && i < scache->peer_count; i++) {
    658     if(!scache->peers[i].ssl_peer_key &&
    659        scache->peers[i].hmac_set &&
    660        cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
    661       /* possible entry with unknown peer_key, check hmac */
    662       unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
    663       if(!peer_key_len) /* we are lazy */
    664         peer_key_len = strlen(ssl_peer_key);
    665       result = Curl_hmacit(&Curl_HMAC_SHA256,
    666                            scache->peers[i].key_salt,
    667                            sizeof(scache->peers[i].key_salt),
    668                            (const unsigned char *)ssl_peer_key,
    669                            peer_key_len,
    670                            my_hmac);
    671       if(result)
    672         goto out;
    673       if(!memcmp(scache->peers[i].key_hmac, my_hmac, sizeof(my_hmac))) {
    674         /* remember peer_key for future lookups */
    675         CURL_TRC_SSLS(data, "peer entry %zu key recovered: %s",
    676                       i, ssl_peer_key);
    677         scache->peers[i].ssl_peer_key = strdup(ssl_peer_key);
    678         if(!scache->peers[i].ssl_peer_key) {
    679           result = CURLE_OUT_OF_MEMORY;
    680           goto out;
    681         }
    682         cf_ssl_cache_peer_update(&scache->peers[i]);
    683         *ppeer = &scache->peers[i];
    684         goto out;
    685       }
    686     }
    687   }
    688   CURL_TRC_SSLS(data, "peer not found for %s", ssl_peer_key);
    689 out:
    690   return result;
    691 }
    692 
    693 static struct Curl_ssl_scache_peer *
    694 cf_ssl_get_free_peer(struct Curl_ssl_scache *scache)
    695 {
    696   struct Curl_ssl_scache_peer *peer = NULL;
    697   size_t i;
    698 
    699   /* find empty or oldest peer */
    700   for(i = 0; i < scache->peer_count; ++i) {
    701     /* free peer entry? */
    702     if(!scache->peers[i].ssl_peer_key && !scache->peers[i].hmac_set) {
    703       peer = &scache->peers[i];
    704       break;
    705     }
    706     /* peer without sessions and obj */
    707     if(!scache->peers[i].sobj &&
    708        !Curl_llist_count(&scache->peers[i].sessions)) {
    709       peer = &scache->peers[i];
    710       break;
    711     }
    712     /* remember "oldest" peer */
    713     if(!peer || (scache->peers[i].age < peer->age)) {
    714       peer = &scache->peers[i];
    715     }
    716   }
    717   DEBUGASSERT(peer);
    718   if(peer)
    719     cf_ssl_scache_clear_peer(peer);
    720   return peer;
    721 }
    722 
    723 static CURLcode
    724 cf_ssl_add_peer(struct Curl_easy *data,
    725                 struct Curl_ssl_scache *scache,
    726                 const char *ssl_peer_key,
    727                 struct ssl_primary_config *conn_config,
    728                 struct Curl_ssl_scache_peer **ppeer)
    729 {
    730   struct Curl_ssl_scache_peer *peer = NULL;
    731   CURLcode result = CURLE_OK;
    732 
    733   *ppeer = NULL;
    734   if(ssl_peer_key) {
    735     result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
    736                                      &peer);
    737     if(result || !scache->peer_count)
    738       return result;
    739   }
    740 
    741   if(peer) {
    742     *ppeer = peer;
    743     return CURLE_OK;
    744   }
    745 
    746   peer = cf_ssl_get_free_peer(scache);
    747   if(peer) {
    748     const char *ccert = conn_config ? conn_config->clientcert : NULL;
    749     const char *username = NULL, *password = NULL;
    750 #ifdef USE_TLS_SRP
    751     username = conn_config ? conn_config->username : NULL;
    752     password = conn_config ? conn_config->password : NULL;
    753 #endif
    754     result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert,
    755                                      username, password, NULL, NULL);
    756     if(result)
    757       goto out;
    758     /* all ready */
    759     *ppeer = peer;
    760     result = CURLE_OK;
    761   }
    762 
    763 out:
    764   if(result) {
    765     cf_ssl_scache_clear_peer(peer);
    766   }
    767   return result;
    768 }
    769 
    770 static void cf_scache_peer_add_session(struct Curl_ssl_scache_peer *peer,
    771                                        struct Curl_ssl_session *s,
    772                                        curl_off_t now)
    773 {
    774   /* A session not from TLSv1.3 replaces all other. */
    775   if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) {
    776     Curl_llist_destroy(&peer->sessions, NULL);
    777     Curl_llist_append(&peer->sessions, s, &s->list);
    778   }
    779   else {
    780     /* Expire existing, append, trim from head to obey max_sessions */
    781     cf_scache_peer_remove_expired(peer, now);
    782     cf_scache_peer_remove_non13(peer);
    783     Curl_llist_append(&peer->sessions, s, &s->list);
    784     while(Curl_llist_count(&peer->sessions) > peer->max_sessions) {
    785       Curl_node_remove(Curl_llist_head(&peer->sessions));
    786     }
    787   }
    788 }
    789 
    790 static CURLcode cf_scache_add_session(struct Curl_cfilter *cf,
    791                                       struct Curl_easy *data,
    792                                       struct Curl_ssl_scache *scache,
    793                                       const char *ssl_peer_key,
    794                                       struct Curl_ssl_session *s)
    795 {
    796   struct Curl_ssl_scache_peer *peer = NULL;
    797   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
    798   CURLcode result = CURLE_OUT_OF_MEMORY;
    799   curl_off_t now = (curl_off_t)time(NULL);
    800   curl_off_t max_lifetime;
    801 
    802   if(!scache || !scache->peer_count) {
    803     Curl_ssl_session_destroy(s);
    804     return CURLE_OK;
    805   }
    806 
    807   if(s->valid_until <= 0)
    808     s->valid_until = now + scache->default_lifetime_secs;
    809 
    810   max_lifetime = (s->ietf_tls_id == CURL_IETF_PROTO_TLS1_3) ?
    811                  CURL_SCACHE_MAX_13_LIFETIME_SEC :
    812                  CURL_SCACHE_MAX_12_LIFETIME_SEC;
    813   if(s->valid_until > (now + max_lifetime))
    814     s->valid_until = now + max_lifetime;
    815 
    816   if(cf_scache_session_expired(s, now)) {
    817     CURL_TRC_SSLS(data, "add, session already expired");
    818     Curl_ssl_session_destroy(s);
    819     return CURLE_OK;
    820   }
    821 
    822   result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer);
    823   if(result || !peer) {
    824     CURL_TRC_SSLS(data, "unable to add scache peer: %d", result);
    825     Curl_ssl_session_destroy(s);
    826     goto out;
    827   }
    828 
    829   cf_scache_peer_add_session(peer, s, now);
    830 
    831 out:
    832   if(result) {
    833     failf(data, "[SCACHE] failed to add session for %s, error=%d",
    834           ssl_peer_key, result);
    835   }
    836   else
    837     CURL_TRC_SSLS(data, "added session for %s [proto=0x%x, "
    838                   "valid_secs=%" FMT_OFF_T ", alpn=%s, earlydata=%zu, "
    839                   "quic_tp=%s], peer has %zu sessions now",
    840                   ssl_peer_key, s->ietf_tls_id, s->valid_until - now,
    841                   s->alpn, s->earlydata_max, s->quic_tp ? "yes" : "no",
    842                   peer ? Curl_llist_count(&peer->sessions) : 0);
    843   return result;
    844 }
    845 
    846 CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf,
    847                              struct Curl_easy *data,
    848                              const char *ssl_peer_key,
    849                              struct Curl_ssl_session *s)
    850 {
    851   struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
    852   struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
    853   CURLcode result;
    854   DEBUGASSERT(ssl_config);
    855 
    856   if(!scache || !ssl_config->primary.cache_session) {
    857     Curl_ssl_session_destroy(s);
    858     return CURLE_OK;
    859   }
    860 
    861   Curl_ssl_scache_lock(data);
    862   result = cf_scache_add_session(cf, data, scache, ssl_peer_key, s);
    863   Curl_ssl_scache_unlock(data);
    864   return result;
    865 }
    866 
    867 void Curl_ssl_scache_return(struct Curl_cfilter *cf,
    868                             struct Curl_easy *data,
    869                             const char *ssl_peer_key,
    870                             struct Curl_ssl_session *s)
    871 {
    872   /* See RFC 8446 C.4:
    873    * "Clients SHOULD NOT reuse a ticket for multiple connections." */
    874   if(s && s->ietf_tls_id < 0x304)
    875     (void)Curl_ssl_scache_put(cf, data, ssl_peer_key, s);
    876   else
    877     Curl_ssl_session_destroy(s);
    878 }
    879 
    880 CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
    881                               struct Curl_easy *data,
    882                               const char *ssl_peer_key,
    883                               struct Curl_ssl_session **ps)
    884 {
    885   struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
    886   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
    887   struct Curl_ssl_scache_peer *peer = NULL;
    888   struct Curl_llist_node *n;
    889   struct Curl_ssl_session *s = NULL;
    890   CURLcode result;
    891 
    892   *ps = NULL;
    893   if(!scache)
    894     return CURLE_OK;
    895 
    896   Curl_ssl_scache_lock(data);
    897   result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
    898                                    &peer);
    899   if(!result && peer) {
    900     cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL));
    901     n = Curl_llist_head(&peer->sessions);
    902     if(n) {
    903       s = Curl_node_take_elem(n);
    904       (scache->age)++;            /* increase general age */
    905       peer->age = scache->age; /* set this as used in this age */
    906     }
    907   }
    908   Curl_ssl_scache_unlock(data);
    909   if(s) {
    910     *ps = s;
    911     CURL_TRC_SSLS(data, "took session for %s [proto=0x%x, "
    912                   "alpn=%s, earlydata=%zu, quic_tp=%s], %zu sessions remain",
    913                   ssl_peer_key, s->ietf_tls_id, s->alpn,
    914                   s->earlydata_max, s->quic_tp ? "yes" : "no",
    915                   Curl_llist_count(&peer->sessions));
    916   }
    917   else {
    918     CURL_TRC_SSLS(data, "no cached session for %s", ssl_peer_key);
    919   }
    920   return result;
    921 }
    922 
    923 CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf,
    924                                  struct Curl_easy *data,
    925                                  const char *ssl_peer_key,
    926                                  void *sobj,
    927                                  Curl_ssl_scache_obj_dtor *sobj_free)
    928 {
    929   struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
    930   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
    931   struct Curl_ssl_scache_peer *peer = NULL;
    932   CURLcode result;
    933 
    934   DEBUGASSERT(sobj);
    935   DEBUGASSERT(sobj_free);
    936 
    937   if(!scache) {
    938     result = CURLE_BAD_FUNCTION_ARGUMENT;
    939     goto out;
    940   }
    941 
    942   result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer);
    943   if(result || !peer) {
    944     CURL_TRC_SSLS(data, "unable to add scache peer: %d", result);
    945     goto out;
    946   }
    947 
    948   cf_ssl_scache_peer_set_obj(peer, sobj, sobj_free);
    949   sobj = NULL;  /* peer took ownership */
    950 
    951 out:
    952   if(sobj && sobj_free)
    953     sobj_free(sobj);
    954   return result;
    955 }
    956 
    957 void *Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
    958                               struct Curl_easy *data,
    959                               const char *ssl_peer_key)
    960 {
    961   struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
    962   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
    963   struct Curl_ssl_scache_peer *peer = NULL;
    964   CURLcode result;
    965   void *sobj;
    966 
    967   if(!scache)
    968     return NULL;
    969 
    970   result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
    971                                    &peer);
    972   if(result)
    973     return NULL;
    974 
    975   sobj = peer ? peer->sobj : NULL;
    976 
    977   CURL_TRC_SSLS(data, "%s cached session for '%s'",
    978                 sobj ? "Found" : "No", ssl_peer_key);
    979   return sobj;
    980 }
    981 
    982 void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
    983                                 struct Curl_easy *data,
    984                                 const char *ssl_peer_key)
    985 {
    986   struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
    987   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
    988   struct Curl_ssl_scache_peer *peer = NULL;
    989   CURLcode result;
    990 
    991   (void)cf;
    992   if(!scache)
    993     return;
    994 
    995   Curl_ssl_scache_lock(data);
    996   result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
    997                                    &peer);
    998   if(!result && peer)
    999     cf_ssl_scache_clear_peer(peer);
   1000   Curl_ssl_scache_unlock(data);
   1001 }
   1002 
   1003 #ifdef USE_SSLS_EXPORT
   1004 
   1005 #define CURL_SSL_TICKET_MAX   (16*1024)
   1006 
   1007 static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer)
   1008 {
   1009   CURLcode result;
   1010 
   1011   DEBUGASSERT(peer);
   1012   if(!peer->ssl_peer_key)
   1013     return CURLE_BAD_FUNCTION_ARGUMENT;
   1014 
   1015   result = Curl_rand(NULL, peer->key_salt, sizeof(peer->key_salt));
   1016   if(result)
   1017     return result;
   1018 
   1019   result = Curl_hmacit(&Curl_HMAC_SHA256,
   1020                        peer->key_salt, sizeof(peer->key_salt),
   1021                        (const unsigned char *)peer->ssl_peer_key,
   1022                        strlen(peer->ssl_peer_key),
   1023                        peer->key_hmac);
   1024   if(!result)
   1025     peer->hmac_set = TRUE;
   1026   return result;
   1027 }
   1028 
   1029 static CURLcode
   1030 cf_ssl_find_peer_by_hmac(struct Curl_ssl_scache *scache,
   1031                          const unsigned char *salt,
   1032                          const unsigned char *hmac,
   1033                          struct Curl_ssl_scache_peer **ppeer)
   1034 {
   1035   size_t i;
   1036   CURLcode result = CURLE_OK;
   1037 
   1038   *ppeer = NULL;
   1039   if(!GOOD_SCACHE(scache))
   1040     return CURLE_BAD_FUNCTION_ARGUMENT;
   1041 
   1042   /* look for an entry that matches salt+hmac exactly or has a known
   1043    * ssl_peer_key which salt+hmac's to the same. */
   1044   for(i = 0; scache && i < scache->peer_count; i++) {
   1045     struct Curl_ssl_scache_peer *peer = &scache->peers[i];
   1046     if(!cf_ssl_scache_match_auth(peer, NULL))
   1047       continue;
   1048     if(scache->peers[i].hmac_set &&
   1049        !memcmp(peer->key_salt, salt, sizeof(peer->key_salt)) &&
   1050        !memcmp(peer->key_hmac, hmac, sizeof(peer->key_hmac))) {
   1051       /* found exact match, return */
   1052       *ppeer = peer;
   1053       goto out;
   1054     }
   1055     else if(peer->ssl_peer_key) {
   1056       unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
   1057       /* compute hmac for the passed salt */
   1058       result = Curl_hmacit(&Curl_HMAC_SHA256,
   1059                            salt, sizeof(peer->key_salt),
   1060                            (const unsigned char *)peer->ssl_peer_key,
   1061                            strlen(peer->ssl_peer_key),
   1062                            my_hmac);
   1063       if(result)
   1064         goto out;
   1065       if(!memcmp(my_hmac, hmac, sizeof(my_hmac))) {
   1066         /* cryptohash match, take over salt+hmac if no set and return */
   1067         if(!peer->hmac_set) {
   1068           memcpy(peer->key_salt, salt, sizeof(peer->key_salt));
   1069           memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac));
   1070           peer->hmac_set = TRUE;
   1071         }
   1072         *ppeer = peer;
   1073         goto out;
   1074       }
   1075     }
   1076   }
   1077 out:
   1078   return result;
   1079 }
   1080 
   1081 CURLcode Curl_ssl_session_import(struct Curl_easy *data,
   1082                                  const char *ssl_peer_key,
   1083                                  const unsigned char *shmac, size_t shmac_len,
   1084                                  const void *sdata, size_t sdata_len)
   1085 {
   1086   struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
   1087   struct Curl_ssl_scache_peer *peer = NULL;
   1088   struct Curl_ssl_session *s = NULL;
   1089   bool locked = FALSE;
   1090   CURLcode r;
   1091 
   1092   if(!scache) {
   1093     r = CURLE_BAD_FUNCTION_ARGUMENT;
   1094     goto out;
   1095   }
   1096   if(!ssl_peer_key && (!shmac || !shmac_len)) {
   1097     r = CURLE_BAD_FUNCTION_ARGUMENT;
   1098     goto out;
   1099   }
   1100 
   1101   r = Curl_ssl_session_unpack(data, sdata, sdata_len, &s);
   1102   if(r)
   1103     goto out;
   1104 
   1105   Curl_ssl_scache_lock(data);
   1106   locked = TRUE;
   1107 
   1108   if(ssl_peer_key) {
   1109     r = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer);
   1110     if(r)
   1111       goto out;
   1112   }
   1113   else if(shmac_len != (sizeof(peer->key_salt) + sizeof(peer->key_hmac))) {
   1114     /* Either salt+hmac was garbled by caller or is from a curl version
   1115      * that does things differently */
   1116     r = CURLE_BAD_FUNCTION_ARGUMENT;
   1117     goto out;
   1118   }
   1119   else {
   1120     const unsigned char *salt = shmac;
   1121     const unsigned char *hmac = shmac + sizeof(peer->key_salt);
   1122 
   1123     r = cf_ssl_find_peer_by_hmac(scache, salt, hmac, &peer);
   1124     if(r)
   1125       goto out;
   1126     if(!peer) {
   1127       peer = cf_ssl_get_free_peer(scache);
   1128       if(peer) {
   1129         r = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL,
   1130                                     NULL, NULL, salt, hmac);
   1131         if(r)
   1132           goto out;
   1133       }
   1134     }
   1135   }
   1136 
   1137   if(peer) {
   1138     cf_scache_peer_add_session(peer, s, time(NULL));
   1139     s = NULL; /* peer is now owner */
   1140     CURL_TRC_SSLS(data, "successfully imported ticket for peer %s, now "
   1141                   "with %zu tickets",
   1142                   peer->ssl_peer_key ? peer->ssl_peer_key : "without key",
   1143                   Curl_llist_count(&peer->sessions));
   1144   }
   1145 
   1146 out:
   1147   if(locked)
   1148     Curl_ssl_scache_unlock(data);
   1149   Curl_ssl_session_destroy(s);
   1150   return r;
   1151 }
   1152 
   1153 CURLcode Curl_ssl_session_export(struct Curl_easy *data,
   1154                                  curl_ssls_export_cb *export_fn,
   1155                                  void *userptr)
   1156 {
   1157   struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
   1158   struct Curl_ssl_scache_peer *peer;
   1159   struct dynbuf sbuf, hbuf;
   1160   struct Curl_llist_node *n;
   1161   size_t i, npeers = 0, ntickets = 0;
   1162   curl_off_t now = time(NULL);
   1163   CURLcode r = CURLE_OK;
   1164 
   1165   if(!export_fn)
   1166     return CURLE_BAD_FUNCTION_ARGUMENT;
   1167   if(!scache)
   1168     return CURLE_OK;
   1169 
   1170   Curl_ssl_scache_lock(data);
   1171 
   1172   curlx_dyn_init(&hbuf, (CURL_SHA256_DIGEST_LENGTH * 2) + 1);
   1173   curlx_dyn_init(&sbuf, CURL_SSL_TICKET_MAX);
   1174 
   1175   for(i = 0; scache && i < scache->peer_count; i++) {
   1176     peer = &scache->peers[i];
   1177     if(!peer->ssl_peer_key && !peer->hmac_set)
   1178       continue;  /* skip free entry */
   1179     if(!peer->exportable)
   1180       continue;
   1181 
   1182     curlx_dyn_reset(&hbuf);
   1183     cf_scache_peer_remove_expired(peer, now);
   1184     n = Curl_llist_head(&peer->sessions);
   1185     if(n)
   1186       ++npeers;
   1187     while(n) {
   1188       struct Curl_ssl_session *s = Curl_node_elem(n);
   1189       if(!peer->hmac_set) {
   1190         r = cf_ssl_scache_peer_set_hmac(peer);
   1191         if(r)
   1192           goto out;
   1193       }
   1194       if(!curlx_dyn_len(&hbuf)) {
   1195         r = curlx_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt));
   1196         if(r)
   1197           goto out;
   1198         r = curlx_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac));
   1199         if(r)
   1200           goto out;
   1201       }
   1202       curlx_dyn_reset(&sbuf);
   1203       r = Curl_ssl_session_pack(data, s, &sbuf);
   1204       if(r)
   1205         goto out;
   1206 
   1207       r = export_fn(data, userptr, peer->ssl_peer_key,
   1208                     curlx_dyn_uptr(&hbuf), curlx_dyn_len(&hbuf),
   1209                     curlx_dyn_uptr(&sbuf), curlx_dyn_len(&sbuf),
   1210                     s->valid_until, s->ietf_tls_id,
   1211                     s->alpn, s->earlydata_max);
   1212       if(r)
   1213         goto out;
   1214       ++ntickets;
   1215       n = Curl_node_next(n);
   1216     }
   1217 
   1218   }
   1219   r = CURLE_OK;
   1220   CURL_TRC_SSLS(data, "exported %zu session tickets for %zu peers",
   1221                 ntickets, npeers);
   1222 
   1223 out:
   1224   Curl_ssl_scache_unlock(data);
   1225   curlx_dyn_free(&hbuf);
   1226   curlx_dyn_free(&sbuf);
   1227   return r;
   1228 }
   1229 
   1230 #endif /* USE_SSLS_EXPORT */
   1231 
   1232 #endif /* USE_SSL */