]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
vtls: TLS session storage overhaul
authorStefan Eissing <stefan@eissing.org>
Fri, 26 Apr 2024 08:11:51 +0000 (10:11 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 26 Apr 2024 11:58:36 +0000 (13:58 +0200)
- add session with destructor callback
- remove vtls `session_free` method
- let `Curl_ssl_addsessionid()` take ownership
  of session object, freeing it also on failures
- change tls backend use
- test_17, add tests for SSL session resumption

Closes #13386

17 files changed:
lib/urldata.h
lib/vquic/curl_ngtcp2.c
lib/vtls/bearssl.c
lib/vtls/gtls.c
lib/vtls/mbedtls.c
lib/vtls/openssl.c
lib/vtls/openssl.h
lib/vtls/rustls.c
lib/vtls/schannel.c
lib/vtls/sectransp.c
lib/vtls/vtls.c
lib/vtls/vtls_int.h
lib/vtls/wolfssl.c
tests/http/test_17_ssl_use.py [new file with mode: 0644]
tests/http/testenv/curl.py
tests/http/testenv/httpd.py
tests/http/testenv/mod_curltest/mod_curltest.c

index 8bccffb0f59b2077056d60bcb6380786a1b65786..aa3cc44474ad5ec4316e58e92cc31924ed1183e7 100644 (file)
@@ -345,6 +345,8 @@ struct ssl_general_config {
   int ca_cache_timeout;  /* Certificate store cache timeout (seconds) */
 };
 
+typedef void Curl_ssl_sessionid_dtor(void *sessionid, size_t idsize);
+
 /* information stored about one single SSL session */
 struct Curl_ssl_session {
   char *name;       /* host name for which this ID was used */
@@ -352,6 +354,7 @@ struct Curl_ssl_session {
   const char *scheme; /* protocol scheme used */
   void *sessionid;  /* as returned from the SSL layer */
   size_t idsize;    /* if known, otherwise 0 */
+  Curl_ssl_sessionid_dtor *sessionid_free; /* free `sessionid` callback */
   long age;         /* just a number, the higher the more recent */
   int remote_port;  /* remote port */
   int conn_to_port; /* remote port for the connection (may be -1) */
index 5171125f2fc4f3f7167a8b8c8a1ddaa120181fa7..5d9fab9190dc549efe570a013f4f257d683e4324 100644 (file)
@@ -1992,9 +1992,8 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
   ctx = cf? cf->ctx : NULL;
   data = cf? CF_DATA_CURRENT(cf) : NULL;
   if(cf && data && ctx) {
-    CURLcode result = Curl_ossl_add_session(cf, data, &ctx->peer,
-                                            ssl_sessionid);
-    return result? 0 : 1;
+    Curl_ossl_add_session(cf, data, &ctx->peer, ssl_sessionid);
+    return 1;
   }
   return 0;
 }
index 0d28d26f14c6ed64a08508a1511243e061dfdbf6..b3918b73fbed0ae12c553baf4613acca5c19bf2f 100644 (file)
@@ -830,7 +830,7 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf,
       CURL_TRC_CF(data, cf, "ssl_recv(len=%zu) -> %zd, %d", len, ret, result);
       if(ret == 0) {
         failf(data, "SSL: EOF without close notify");
-        return CURLE_READ_ERROR;
+        return CURLE_RECV_ERROR;
       }
       if(ret <= 0) {
         return result;
@@ -873,6 +873,12 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf,
   return ret;
 }
 
+static void bearssl_session_free(void *sessionid, size_t idsize)
+{
+  (void)idsize;
+  free(sessionid);
+}
+
 static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
                                       struct Curl_easy *data)
 {
@@ -896,7 +902,6 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
 
   if(ssl_config->primary.sessionid) {
     bool incache;
-    bool added = FALSE;
     void *oldsession;
     br_ssl_session_parameters *session;
 
@@ -909,13 +914,12 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
                                       &oldsession, NULL));
     if(incache)
       Curl_ssl_delsessionid(data, oldsession);
-    ret = Curl_ssl_addsessionid(cf, data, &connssl->peer, session, 0, &added);
+
+    ret = Curl_ssl_addsessionid(cf, data, &connssl->peer, session, 0,
+                                bearssl_session_free);
     Curl_ssl_sessionid_unlock(data);
-    if(!added)
-      free(session);
-    if(ret) {
-      return CURLE_OUT_OF_MEMORY;
-    }
+    if(ret)
+      return ret;
   }
 
   connssl->connecting_state = ssl_connect_done;
@@ -1174,11 +1178,6 @@ static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
   }
 }
 
-static void bearssl_session_free(void *ptr)
-{
-  free(ptr);
-}
-
 static CURLcode bearssl_sha256sum(const unsigned char *input,
                                   size_t inputlen,
                                   unsigned char *sha256sum,
@@ -1211,7 +1210,6 @@ const struct Curl_ssl Curl_ssl_bearssl = {
   bearssl_get_internals,           /* get_internals */
   bearssl_close,                   /* close_one */
   Curl_none_close_all,             /* close_all */
-  bearssl_session_free,            /* session_free */
   Curl_none_set_engine,            /* set_engine */
   Curl_none_set_engine_default,    /* set_engine_default */
   Curl_none_engines_list,          /* engines_list */
index a0d1fccfb891b06fe2f6c7dcea42c9f44c528897..5cf3bf9527cc73707a3044c71842516a83aff96d 100644 (file)
@@ -532,6 +532,88 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf,
   return CURLE_OK;
 }
 
+static void gtls_sessionid_free(void *sessionid, size_t idsize)
+{
+  (void)idsize;
+  free(sessionid);
+}
+
+static CURLcode gtls_update_session_id(struct Curl_cfilter *cf,
+                                       struct Curl_easy *data,
+                                       gnutls_session_t session)
+{
+  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+  struct ssl_connect_data *connssl = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  if(ssl_config->primary.sessionid) {
+    /* we always unconditionally get the session id here, as even if we
+       already got it from the cache and asked to use it in the connection, it
+       might've been rejected and then a new one is in use now and we need to
+       detect that. */
+    void *connect_sessionid;
+    size_t connect_idsize = 0;
+
+    /* get the session ID data size */
+    gnutls_session_get_data(session, NULL, &connect_idsize);
+    connect_sessionid = malloc(connect_idsize); /* get a buffer for it */
+    if(!connect_sessionid) {
+      return CURLE_OUT_OF_MEMORY;
+    }
+    else {
+      bool incache;
+      void *ssl_sessionid;
+
+      /* extract session ID to the allocated buffer */
+      gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
+
+      DEBUGF(infof(data, "get session id (len=%zu) and store in cache",
+                   connect_idsize));
+      Curl_ssl_sessionid_lock(data);
+      incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer,
+                                        &ssl_sessionid, NULL));
+      if(incache) {
+        /* there was one before in the cache, so instead of risking that the
+           previous one was rejected, we just kill that and store the new */
+        Curl_ssl_delsessionid(data, ssl_sessionid);
+      }
+
+      /* store this session id, takes ownership */
+      result = Curl_ssl_addsessionid(cf, data, &connssl->peer,
+                                     connect_sessionid, connect_idsize,
+                                     gtls_sessionid_free);
+      Curl_ssl_sessionid_unlock(data);
+    }
+  }
+  return result;
+}
+
+static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
+                             unsigned when, unsigned int incoming,
+                             const gnutls_datum_t *msg)
+{
+  struct Curl_cfilter *cf = gnutls_session_get_ptr(session);
+
+  (void)msg;
+  (void)incoming;
+  if(when) { /* after message has been processed */
+    struct Curl_easy *data = CF_DATA_CURRENT(cf);
+    if(data) {
+      DEBUGF(infof(data, "handshake: %s message type %d",
+             incoming? "incoming" : "outgoing", htype));
+      switch(htype) {
+      case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
+        gtls_update_session_id(cf, data, session);
+        break;
+      }
+      default:
+        break;
+      }
+    }
+  }
+  return 0;
+}
+
 static CURLcode gtls_client_init(struct Curl_cfilter *cf,
                                  struct Curl_easy *data,
                                  struct ssl_peer *peer,
@@ -828,10 +910,13 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
     Curl_ssl_sessionid_lock(data);
     if(!Curl_ssl_getsessionid(cf, data, peer, &ssl_sessionid, &ssl_idsize)) {
       /* we got a session id, use it! */
-      gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize);
+      int rc;
 
-      /* Informational message */
-      infof(data, "SSL reusing session ID");
+      rc = gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize);
+      if(rc < 0)
+        infof(data, "SSL failed to set session ID");
+      else
+        infof(data, "SSL reusing session ID (size=%zu)", ssl_idsize);
     }
     Curl_ssl_sessionid_unlock(data);
   }
@@ -869,6 +954,9 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
   if(result)
     return result;
 
+  gnutls_handshake_set_hook_function(backend->gtls.session,
+                                     GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST,
+                                     gtls_handshake_cb);
 
   /* register callback functions and handle to send and receive data. */
   gnutls_transport_set_ptr(backend->gtls.session, cf);
@@ -1403,49 +1491,10 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf,
       Curl_alpn_set_negotiated(cf, data, NULL, 0);
   }
 
-  if(ssl_config->primary.sessionid) {
-    /* we always unconditionally get the session id here, as even if we
-       already got it from the cache and asked to use it in the connection, it
-       might've been rejected and then a new one is in use now and we need to
-       detect that. */
-    void *connect_sessionid;
-    size_t connect_idsize = 0;
-
-    /* get the session ID data size */
-    gnutls_session_get_data(session, NULL, &connect_idsize);
-    connect_sessionid = malloc(connect_idsize); /* get a buffer for it */
-
-    if(connect_sessionid) {
-      bool incache;
-      bool added = FALSE;
-      void *ssl_sessionid;
-
-      /* extract session ID to the allocated buffer */
-      gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
-
-      Curl_ssl_sessionid_lock(data);
-      incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer,
-                                        &ssl_sessionid, NULL));
-      if(incache) {
-        /* there was one before in the cache, so instead of risking that the
-           previous one was rejected, we just kill that and store the new */
-        Curl_ssl_delsessionid(data, ssl_sessionid);
-      }
-
-      /* store this session id */
-      result = Curl_ssl_addsessionid(cf, data, &connssl->peer,
-                                     connect_sessionid, connect_idsize,
-                                     &added);
-      Curl_ssl_sessionid_unlock(data);
-      if(!added)
-        free(connect_sessionid);
-      if(result) {
-        result = CURLE_OUT_OF_MEMORY;
-      }
-    }
-    else
-      result = CURLE_OUT_OF_MEMORY;
-  }
+  /* Only on TLSv1.2 or lower do we have the session id now. For
+   * TLSv1.3 we get it via a SESSION_TICKET message that arrives later. */
+  if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3)
+    result = gtls_update_session_id(cf, data, session);
 
 out:
   return result;
@@ -1734,11 +1783,6 @@ out:
   return ret;
 }
 
-static void gtls_session_free(void *ptr)
-{
-  free(ptr);
-}
-
 static size_t gtls_version(char *buffer, size_t size)
 {
   return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL));
@@ -1805,7 +1849,6 @@ const struct Curl_ssl Curl_ssl_gnutls = {
   gtls_get_internals,            /* get_internals */
   gtls_close,                    /* close_one */
   Curl_none_close_all,           /* close_all */
-  gtls_session_free,             /* session_free */
   Curl_none_set_engine,          /* set_engine */
   Curl_none_set_engine_default,  /* set_engine_default */
   Curl_none_engines_list,        /* engines_list */
index 02c929944931b708d93c30caa4e57f9821363b3f..a90db9e9773c9c19c97963d52109d66aed1449d2 100644 (file)
@@ -1033,6 +1033,13 @@ pinnedpubkey_error:
   return CURLE_OK;
 }
 
+static void mbedtls_session_free(void *sessionid, size_t idsize)
+{
+  (void)idsize;
+  mbedtls_ssl_session_free(sessionid);
+  free(sessionid);
+}
+
 static CURLcode
 mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
@@ -1049,7 +1056,6 @@ mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
     int ret;
     mbedtls_ssl_session *our_ssl_sessionid;
     void *old_ssl_sessionid = NULL;
-    bool added = FALSE;
 
     our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session));
     if(!our_ssl_sessionid)
@@ -1073,16 +1079,11 @@ mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
       Curl_ssl_delsessionid(data, old_ssl_sessionid);
 
     retcode = Curl_ssl_addsessionid(cf, data, &connssl->peer,
-                                    our_ssl_sessionid, 0, &added);
+                                    our_ssl_sessionid, 0,
+                                    mbedtls_session_free);
     Curl_ssl_sessionid_unlock(data);
-    if(!added) {
-      mbedtls_ssl_session_free(our_ssl_sessionid);
-      free(our_ssl_sessionid);
-    }
-    if(retcode) {
-      failf(data, "failed to store ssl session");
+    if(retcode)
       return retcode;
-    }
   }
 
   connssl->connecting_state = ssl_connect_done;
@@ -1176,12 +1177,6 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
   return len;
 }
 
-static void mbedtls_session_free(void *ptr)
-{
-  mbedtls_ssl_session_free(ptr);
-  free(ptr);
-}
-
 static size_t mbedtls_version(char *buffer, size_t size)
 {
 #ifdef MBEDTLS_VERSION_C
@@ -1460,7 +1455,6 @@ const struct Curl_ssl Curl_ssl_mbedtls = {
   mbedtls_get_internals,            /* get_internals */
   mbedtls_close,                    /* close_one */
   mbedtls_close_all,                /* close_all */
-  mbedtls_session_free,             /* session_free */
   Curl_none_set_engine,             /* set_engine */
   Curl_none_set_engine_default,     /* set_engine_default */
   Curl_none_engines_list,           /* engines_list */
index 2fa12c3a985291807c673333f93020d4497638bd..1c66491a325d5af7b2672b26fb2dd81faa4e2b22 100644 (file)
@@ -2074,10 +2074,11 @@ static int ossl_shutdown(struct Curl_cfilter *cf,
   return retval;
 }
 
-static void ossl_session_free(void *ptr)
+static void ossl_session_free(void *sessionid, size_t idsize)
 {
   /* free the ID */
-  SSL_SESSION_free(ptr);
+  (void)idsize;
+  SSL_SESSION_free(sessionid);
 }
 
 /*
@@ -2935,52 +2936,45 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options,
 CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
                                struct Curl_easy *data,
                                const struct ssl_peer *peer,
-                               SSL_SESSION *ssl_sessionid)
+                               SSL_SESSION *session)
 {
   const struct ssl_config_data *config;
   bool isproxy;
-  CURLcode result = CURLE_WRITE_ERROR;
+  bool added = FALSE;
 
   if(!cf || !data)
-    return result;
+    goto out;
 
   isproxy = Curl_ssl_cf_is_proxy(cf);
 
   config = Curl_ssl_cf_get_config(cf, data);
   if(config->primary.sessionid) {
     bool incache;
-    bool added = FALSE;
-    void *old_ssl_sessionid = NULL;
+    void *old_session = NULL;
 
     Curl_ssl_sessionid_lock(data);
     if(isproxy)
       incache = FALSE;
     else
       incache = !(Curl_ssl_getsessionid(cf, data, peer,
-                                        &old_ssl_sessionid, NULL));
-    if(incache) {
-      if(old_ssl_sessionid != ssl_sessionid) {
-        infof(data, "old SSL session ID is stale, removing");
-        Curl_ssl_delsessionid(data, old_ssl_sessionid);
-        incache = FALSE;
-      }
+                                        &old_session, NULL));
+    if(incache && (old_session != session)) {
+      infof(data, "old SSL session ID is stale, removing");
+      Curl_ssl_delsessionid(data, old_session);
+      incache = FALSE;
     }
 
     if(!incache) {
-      if(!Curl_ssl_addsessionid(cf, data, peer, ssl_sessionid,
-                                0 /* unknown size */, &added)) {
-        if(added) {
-          /* the session has been put into the session cache */
-          result = CURLE_OK;
-        }
-      }
-      else
-        failf(data, "failed to store ssl session");
+      added = TRUE;
+      Curl_ssl_addsessionid(cf, data, peer, session, 0, ossl_session_free);
     }
     Curl_ssl_sessionid_unlock(data);
   }
 
-  return result;
+out:
+  if(!added)
+    ossl_session_free(session, 0);
+  return CURLE_OK;
 }
 
 /* The "new session" callback must return zero if the session can be removed
@@ -2991,13 +2985,12 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
   struct Curl_cfilter *cf;
   struct Curl_easy *data;
   struct ssl_connect_data *connssl;
-  CURLcode result;
 
   cf = (struct Curl_cfilter*) SSL_get_app_data(ssl);
   connssl = cf? cf->ctx : NULL;
   data = connssl? CF_DATA_CURRENT(cf) : NULL;
-  result = Curl_ossl_add_session(cf, data, &connssl->peer, ssl_sessionid);
-  return result? 0 : 1;
+  Curl_ossl_add_session(cf, data, &connssl->peer, ssl_sessionid);
+  return 1;
 }
 
 static CURLcode load_cacert_from_memory(X509_STORE *store,
@@ -5291,7 +5284,6 @@ const struct Curl_ssl Curl_ssl_openssl = {
   ossl_get_internals,       /* get_internals */
   ossl_close,               /* close_one */
   ossl_close_all,           /* close_all */
-  ossl_session_free,        /* session_free */
   ossl_set_engine,          /* set_engine */
   ossl_set_engine_default,  /* set_engine_default */
   ossl_engines_list,        /* engines_list */
index 47bb282a9b4d5a670c5e86fa8b5520e2789e0e07..55e06bda4406992e9bd2a4ef267c5a0a247cd472 100644 (file)
@@ -100,7 +100,7 @@ CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf,
                                  SSL_CTX *ssl_ctx);
 
 /*
- * Add a new session to the cache.
+ * Add a new session to the cache. Takes ownership of the session.
  */
 CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
                                struct Curl_easy *data,
index fbb6dec0271fd9729cd09545c11295ac47301d5e..7a989c95e19da2415097774ce3b9f295bd802aed 100644 (file)
@@ -742,7 +742,6 @@ const struct Curl_ssl Curl_ssl_rustls = {
   cr_get_internals,                /* get_internals */
   cr_close,                        /* close_one */
   Curl_none_close_all,             /* close_all */
-  Curl_none_session_free,          /* session_free */
   Curl_none_set_engine,            /* set_engine */
   Curl_none_set_engine_default,    /* set_engine_default */
   Curl_none_engines_list,          /* engines_list */
index cec7fa9710278cfd80bb633617292d63ac93e22d..19cdc4b20b8a4cb7e741e8c53a50f42877d2ab34 100644 (file)
@@ -1675,6 +1675,28 @@ add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, bool reverse_order,
   return args->result == CURLE_OK;
 }
 
+static void schannel_session_free(void *sessionid, size_t idsize)
+{
+  /* this is expected to be called under sessionid lock */
+  struct Curl_schannel_cred *cred = sessionid;
+
+  (void)idsize;
+  if(cred) {
+    cred->refcount--;
+    if(cred->refcount == 0) {
+      s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
+      curlx_unicodefree(cred->sni_hostname);
+#ifdef HAS_CLIENT_CERT_PATH
+      if(cred->client_cert_store) {
+        CertCloseStore(cred->client_cert_store, 0);
+        cred->client_cert_store = NULL;
+      }
+#endif
+      Curl_safefree(cred);
+    }
+  }
+}
+
 static CURLcode
 schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
@@ -1752,7 +1774,6 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
   /* save the current session data for possible reuse */
   if(ssl_config->primary.sessionid) {
     bool incache;
-    bool added = FALSE;
     struct Curl_schannel_cred *old_cred = NULL;
 
     Curl_ssl_sessionid_lock(data);
@@ -1768,20 +1789,15 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
       }
     }
     if(!incache) {
+      /* Up ref count since call takes ownership */
+      backend->cred->refcount++;
       result = Curl_ssl_addsessionid(cf, data, &connssl->peer, backend->cred,
                                      sizeof(struct Curl_schannel_cred),
-                                     &added);
+                                     schannel_session_free);
       if(result) {
         Curl_ssl_sessionid_unlock(data);
-        failf(data, "schannel: failed to store credential handle");
         return result;
       }
-      else if(added) {
-        /* this cred session is now also referenced by sessionid cache */
-        backend->cred->refcount++;
-        DEBUGF(infof(data,
-                     "schannel: stored credential handle in session cache"));
-      }
     }
     Curl_ssl_sessionid_unlock(data);
   }
@@ -2456,27 +2472,6 @@ static bool schannel_data_pending(struct Curl_cfilter *cf,
     return FALSE;
 }
 
-static void schannel_session_free(void *ptr)
-{
-  /* this is expected to be called under sessionid lock */
-  struct Curl_schannel_cred *cred = ptr;
-
-  if(cred) {
-    cred->refcount--;
-    if(cred->refcount == 0) {
-      s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
-      curlx_unicodefree(cred->sni_hostname);
-#ifdef HAS_CLIENT_CERT_PATH
-      if(cred->client_cert_store) {
-        CertCloseStore(cred->client_cert_store, 0);
-        cred->client_cert_store = NULL;
-      }
-#endif
-      Curl_safefree(cred);
-    }
-  }
-}
-
 /* shut down the SSL connection and clean up related memory.
    this function can be called multiple times on the same connection including
    if the SSL connection failed (eg connection made but failed handshake). */
@@ -2560,7 +2555,7 @@ static int schannel_shutdown(struct Curl_cfilter *cf,
   /* free SSPI Schannel API credential handle */
   if(backend->cred) {
     Curl_ssl_sessionid_lock(data);
-    schannel_session_free(backend->cred);
+    schannel_session_free(backend->cred, 0);
     Curl_ssl_sessionid_unlock(data);
     backend->cred = NULL;
   }
@@ -2923,7 +2918,6 @@ const struct Curl_ssl Curl_ssl_schannel = {
   schannel_get_internals,            /* get_internals */
   schannel_close,                    /* close_one */
   Curl_none_close_all,               /* close_all */
-  schannel_session_free,             /* session_free */
   Curl_none_set_engine,              /* set_engine */
   Curl_none_set_engine_default,      /* set_engine_default */
   Curl_none_engines_list,            /* engines_list */
index 26162ff25cb234d72f921745eade7e72f8655ef5..f49db6481cf085f2dfbf0d3145b189c4a8ef8ee9 100644 (file)
@@ -1636,6 +1636,18 @@ static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data,
   return CURLE_OK;
 }
 
+static void sectransp_session_free(void *sessionid, size_t idsize)
+{
+  /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a
+     cached session ID inside the Security framework. There is a private
+     function that does this, but I don't want to have to explain to you why I
+     got your application rejected from the App Store due to the use of a
+     private API, so the best we can do is free up our own char array that we
+     created way back in sectransp_connect_step1... */
+  (void)idsize;
+  Curl_safefree(sessionid);
+}
+
 static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
                                         struct Curl_easy *data)
 {
@@ -2078,12 +2090,11 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
       }
 
       result = Curl_ssl_addsessionid(cf, data, &connssl->peer, ssl_sessionid,
-                                     ssl_sessionid_len, NULL);
+                                     ssl_sessionid_len,
+                                     sectransp_session_free);
       Curl_ssl_sessionid_unlock(data);
-      if(result) {
-        failf(data, "failed to store ssl session");
+      if(result)
         return result;
-      }
     }
   }
 
@@ -3225,17 +3236,6 @@ static int sectransp_shutdown(struct Curl_cfilter *cf,
   return rc;
 }
 
-static void sectransp_session_free(void *ptr)
-{
-  /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a
-     cached session ID inside the Security framework. There is a private
-     function that does this, but I don't want to have to explain to you why I
-     got your application rejected from the App Store due to the use of a
-     private API, so the best we can do is free up our own char array that we
-     created way back in sectransp_connect_step1... */
-  Curl_safefree(ptr);
-}
-
 static size_t sectransp_version(char *buffer, size_t size)
 {
   return msnprintf(buffer, size, "SecureTransport");
@@ -3469,7 +3469,6 @@ const struct Curl_ssl Curl_ssl_sectransp = {
   sectransp_get_internals,            /* get_internals */
   sectransp_close,                    /* close_one */
   Curl_none_close_all,                /* close_all */
-  sectransp_session_free,             /* session_free */
   Curl_none_set_engine,               /* set_engine */
   Curl_none_set_engine_default,       /* set_engine_default */
   Curl_none_engines_list,             /* engines_list */
index c14b398274416ac147630b1a59695f0319c52cb6..af7ebe65c5b990d35770a1b14587eeb8f37e417b 100644 (file)
@@ -605,9 +605,10 @@ void Curl_ssl_kill_session(struct Curl_ssl_session *session)
     /* defensive check */
 
     /* free the ID the SSL-layer specific way */
-    Curl_ssl->session_free(session->sessionid);
+    session->sessionid_free(session->sessionid, session->idsize);
 
     session->sessionid = NULL;
+    session->sessionid_free = NULL;
     session->age = 0; /* fresh */
 
     Curl_free_primary_ssl_config(&session->ssl_config);
@@ -645,42 +646,41 @@ CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf,
                                const struct ssl_peer *peer,
                                void *ssl_sessionid,
                                size_t idsize,
-                               bool *added)
+                               Curl_ssl_sessionid_dtor *sessionid_free_cb)
 {
   struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   size_t i;
   struct Curl_ssl_session *store;
   long oldest_age;
-  char *clone_host;
-  char *clone_conn_to_host;
+  char *clone_host = NULL;
+  char *clone_conn_to_host = NULL;
   int conn_to_port;
   long *general_age;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
 
-  if(added)
-    *added = FALSE;
+  DEBUGASSERT(ssl_sessionid);
+  DEBUGASSERT(sessionid_free_cb);
 
-  if(!data->state.session)
+  if(!data->state.session) {
+    sessionid_free_cb(ssl_sessionid, idsize);
     return CURLE_OK;
+  }
 
   store = &data->state.session[0];
   oldest_age = data->state.session[0].age; /* zero if unused */
-  (void)ssl_config;
   DEBUGASSERT(ssl_config->primary.sessionid);
+  (void)ssl_config;
 
   clone_host = strdup(peer->hostname);
   if(!clone_host)
-    return CURLE_OUT_OF_MEMORY; /* bail out */
+    goto out;
 
   if(cf->conn->bits.conn_to_host) {
     clone_conn_to_host = strdup(cf->conn->conn_to_host.name);
-    if(!clone_conn_to_host) {
-      free(clone_host);
-      return CURLE_OUT_OF_MEMORY; /* bail out */
-    }
+    if(!clone_conn_to_host)
+      goto out;
   }
-  else
-    clone_conn_to_host = NULL;
 
   if(cf->conn->bits.conn_to_port)
     conn_to_port = cf->conn->conn_to_port;
@@ -713,34 +713,43 @@ CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf,
     store = &data->state.session[i]; /* use this slot */
 
   /* now init the session struct wisely */
+  if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) {
+    Curl_free_primary_ssl_config(&store->ssl_config);
+    store->sessionid = NULL; /* let caller free sessionid */
+    goto out;
+  }
   store->sessionid = ssl_sessionid;
   store->idsize = idsize;
+  store->sessionid_free = sessionid_free_cb;
   store->age = *general_age;    /* set current age */
   /* free it if there's one already present */
   free(store->name);
   free(store->conn_to_host);
   store->name = clone_host;               /* clone host name */
+  clone_host = NULL;
   store->conn_to_host = clone_conn_to_host; /* clone connect to host name */
+  clone_conn_to_host = NULL;
   store->conn_to_port = conn_to_port; /* connect to port number */
   /* port number */
   store->remote_port = peer->port;
   store->scheme = cf->conn->handler->scheme;
   store->transport = peer->transport;
 
-  if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) {
-    Curl_free_primary_ssl_config(&store->ssl_config);
-    store->sessionid = NULL; /* let caller free sessionid */
-    free(clone_host);
-    free(clone_conn_to_host);
-    return CURLE_OUT_OF_MEMORY;
-  }
-
-  if(added)
-    *added = TRUE;
+  result = CURLE_OK;
 
-  DEBUGF(infof(data, "Added Session ID to cache for %s://%s:%d [%s]",
-               store->scheme, store->name, store->remote_port,
-               Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"));
+out:
+  free(clone_host);
+  free(clone_conn_to_host);
+  if(result) {
+    failf(data, "Failed to add Session ID to cache for %s://%s:%d [%s]",
+          store->scheme, store->name, store->remote_port,
+          Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server");
+    sessionid_free_cb(ssl_sessionid, idsize);
+    return result;
+  }
+  CURL_TRC_CF(data, cf, "Added Session ID to cache for %s://%s:%d [%s]",
+              store->scheme, store->name, store->remote_port,
+              Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server");
   return CURLE_OK;
 }
 
@@ -1323,7 +1332,6 @@ static const struct Curl_ssl Curl_ssl_multi = {
   multissl_get_internals,            /* get_internals */
   multissl_close,                    /* close_one */
   Curl_none_close_all,               /* close_all */
-  Curl_none_session_free,            /* session_free */
   Curl_none_set_engine,              /* set_engine */
   Curl_none_set_engine_default,      /* set_engine_default */
   Curl_none_engines_list,            /* engines_list */
index 8aca1d33311648e0bf6e871f78a74c67767354b2..5259babb251a5b944839516ded037c1179d4ed53 100644 (file)
@@ -123,7 +123,6 @@ struct Curl_ssl {
   void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info);
   void (*close)(struct Curl_cfilter *cf, struct Curl_easy *data);
   void (*close_all)(struct Curl_easy *data);
-  void (*session_free)(void *ptr);
 
   CURLcode (*set_engine)(struct Curl_easy *data, const char *engine);
   CURLcode (*set_engine_default)(struct Curl_easy *data);
@@ -186,13 +185,15 @@ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf,
  * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
  * Caller must ensure that it has properly shared ownership of this sessionid
  * object with cache (e.g. incrementing refcount on success)
+ * Call takes ownership of `ssl_sessionid`, using `sessionid_free_cb`
+ * to destroy it in case of failure or later removal.
  */
 CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf,
                                struct Curl_easy *data,
                                const struct ssl_peer *peer,
                                void *ssl_sessionid,
                                size_t idsize,
-                               bool *added);
+                               Curl_ssl_sessionid_dtor *sessionid_free_cb);
 
 #include "openssl.h"        /* OpenSSL versions */
 #include "gtls.h"           /* GnuTLS versions */
index c747990ee987f75c8e1494f773ceb4453da290be..2c92f56ea417227bb9dc7501e9ceed6a963ca758 100644 (file)
@@ -1058,6 +1058,13 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
 }
 
 
+static void wolfssl_session_free(void *sessionid, size_t idsize)
+{
+  (void)idsize;
+  wolfSSL_SESSION_free(sessionid);
+}
+
+
 static CURLcode
 wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
@@ -1071,42 +1078,27 @@ wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
   DEBUGASSERT(backend);
 
   if(ssl_config->primary.sessionid) {
-    bool incache;
-    bool added = FALSE;
-    void *old_ssl_sessionid = NULL;
     /* wolfSSL_get1_session allocates memory that has to be freed. */
     WOLFSSL_SESSION *our_ssl_sessionid = wolfSSL_get1_session(backend->handle);
 
     if(our_ssl_sessionid) {
+      void *old_ssl_sessionid = NULL;
+      bool incache;
       Curl_ssl_sessionid_lock(data);
       incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer,
                                         &old_ssl_sessionid, NULL));
       if(incache) {
-        if(old_ssl_sessionid != our_ssl_sessionid) {
-          infof(data, "old SSL session ID is stale, removing");
-          Curl_ssl_delsessionid(data, old_ssl_sessionid);
-          incache = FALSE;
-        }
+        Curl_ssl_delsessionid(data, old_ssl_sessionid);
       }
 
-      if(!incache) {
-        result = Curl_ssl_addsessionid(cf, data, &connssl->peer,
-                                       our_ssl_sessionid, 0, NULL);
-        if(result) {
-          Curl_ssl_sessionid_unlock(data);
-          wolfSSL_SESSION_free(our_ssl_sessionid);
-          failf(data, "failed to store ssl session");
-          return result;
-        }
-        else {
-          added = TRUE;
-        }
-      }
+      /* call takes ownership of `our_ssl_sessionid` */
+      result = Curl_ssl_addsessionid(cf, data, &connssl->peer,
+                                     our_ssl_sessionid, 0,
+                                     wolfssl_session_free);
       Curl_ssl_sessionid_unlock(data);
-
-      if(!added) {
-        /* If the session info wasn't added to the cache, free our copy. */
-        wolfSSL_SESSION_free(our_ssl_sessionid);
+      if(result) {
+        failf(data, "failed to store ssl session");
+        return result;
       }
     }
   }
@@ -1240,12 +1232,6 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf,
 }
 
 
-static void wolfssl_session_free(void *ptr)
-{
-  wolfSSL_SESSION_free(ptr);
-}
-
-
 static size_t wolfssl_version(char *buffer, size_t size)
 {
 #if LIBWOLFSSL_VERSION_HEX >= 0x03006000
@@ -1525,7 +1511,6 @@ const struct Curl_ssl Curl_ssl_wolfssl = {
   wolfssl_get_internals,           /* get_internals */
   wolfssl_close,                   /* close_one */
   Curl_none_close_all,             /* close_all */
-  wolfssl_session_free,            /* session_free */
   Curl_none_set_engine,            /* set_engine */
   Curl_none_set_engine_default,    /* set_engine_default */
   Curl_none_engines_list,          /* engines_list */
diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py
new file mode 100644 (file)
index 0000000..f9d2465
--- /dev/null
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+#                                  _   _ ____  _
+#  Project                     ___| | | |  _ \| |
+#                             / __| | | | |_) | |
+#                            | (__| |_| |  _ <| |___
+#                             \___|\___/|_| \_\_____|
+#
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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
+#
+###########################################################################
+#
+import difflib
+import filecmp
+import json
+import logging
+import os
+from datetime import timedelta
+import pytest
+
+from testenv import Env, CurlClient, LocalClient, ExecResult
+
+
+log = logging.getLogger(__name__)
+
+
+class TestSSLUse:
+
+    @pytest.fixture(autouse=True, scope='class')
+    def _class_scope(self, env, httpd, nghttpx):
+        if env.have_h3():
+            nghttpx.start_if_needed()
+        httpd.clear_extra_configs()
+        httpd.reload()
+
+    def test_17_01_sslinfo_plain(self, env: Env, httpd, nghttpx, repeat):
+        proto = 'http/1.1'
+        curl = CurlClient(env=env)
+        url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
+        r = curl.http_get(url=url, alpn_proto=proto)
+        assert r.json['HTTPS'] == 'on', f'{r.json}'
+        assert 'SSL_SESSION_ID' in r.json, f'{r.json}'
+        assert 'SSL_SESSION_RESUMED' in r.json, f'{r.json}'
+        assert r.json['SSL_SESSION_RESUMED'] == 'Initial', f'{r.json}'
+
+    @pytest.mark.parametrize("tls_max", ['1.2', '1.3'])
+    def test_17_02_sslinfo_reconnect(self, env: Env, httpd, nghttpx, tls_max, repeat):
+        proto = 'http/1.1'
+        count = 3
+        exp_resumed = 'Resumed'
+        xargs = ['--sessionid', '--tls-max', tls_max, f'--tlsv{tls_max}']
+        if env.curl_uses_lib('gnutls'):
+            if tls_max == '1.3':
+                exp_resumed = 'Initial'  # 1.2 works in gnutls, but 1.3 does not, TODO
+        if env.curl_uses_lib('libressl'):
+            if tls_max == '1.3':
+                exp_resumed = 'Initial'  # 1.2 works in libressl, but 1.3 does not, TODO
+        if env.curl_uses_lib('wolfssl'):
+            xargs = ['--sessionid', f'--tlsv{tls_max}']
+            if tls_max == '1.3':
+                exp_resumed = 'Initial'  # 1.2 works in wolfssl, but 1.3 does not, TODO
+        if env.curl_uses_lib('rustls-ffi'):
+            exp_resumed = 'Initial'  # rustls does not support sessions, TODO
+        if env.curl_uses_lib('bearssl') and tls_max == '1.3':
+            pytest.skip('BearSSL does not support TLSv1.3')
+        if env.curl_uses_lib('mbedtls') and tls_max == '1.3':
+            pytest.skip('mbedtls does not support TLSv1.3')
+
+        curl = CurlClient(env=env)
+        # tell the server to close the connection after each request
+        urln = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo?'\
+               f'id=[0-{count-1}]&close'
+        r = curl.http_download(urls=[urln], alpn_proto=proto, with_stats=True,
+                               extra_args=xargs)
+        r.check_response(count=count, http_status=200)
+        # should have used one connection for each request, sessions after
+        # first should have been resumed
+        assert r.total_connects == count, r.dump_logs()
+        for i in range(count):
+            dfile = curl.download_file(i)
+            assert os.path.exists(dfile)
+            with open(dfile) as f:
+                djson = json.load(f)
+            assert djson['HTTPS'] == 'on', f'{i}: {djson}'
+            if i == 0:
+                assert djson['SSL_SESSION_RESUMED'] == 'Initial', f'{i}: {djson}'
+            else:
+                assert djson['SSL_SESSION_RESUMED'] == exp_resumed, f'{i}: {djson}'
+
+
index bfd6fdefc70b699c4627ee267bd5b4a8dedfa289..ad529934ae74c465b1361bbc33c95f519a4b023b 100644 (file)
@@ -415,9 +415,15 @@ class CurlClient:
         return xargs
 
     def http_get(self, url: str, extra_args: Optional[List[str]] = None,
-                 def_tracing: bool = True, with_profile: bool = False):
-        return self._raw(url, options=extra_args, with_stats=False,
-                         def_tracing=def_tracing, with_profile=with_profile)
+                 alpn_proto: Optional[str] = None,
+                 def_tracing: bool = True,
+                 with_stats: bool = False,
+                 with_profile: bool = False):
+        return self._raw(url, options=extra_args,
+                         with_stats=with_stats,
+                         alpn_proto=alpn_proto,
+                         def_tracing=def_tracing,
+                         with_profile=with_profile)
 
     def http_download(self, urls: List[str],
                       alpn_proto: Optional[str] = None,
index 188160b58c0ac4b068fcaddf0928284243d45fc6..5580c8427c341cd003f7a6196dcc5abd2a9edaa7 100644 (file)
@@ -397,6 +397,10 @@ class Httpd:
                 f'    Redirect 302 /curltest/echo302 /curltest/echo',
                 f'    Redirect 303 /curltest/echo303 /curltest/echo',
                 f'    Redirect 307 /curltest/echo307 /curltest/echo',
+                f'    <Location /curltest/sslinfo>',
+                f'      SSLOptions StdEnvVars',
+                f'      SetHandler curltest-sslinfo',
+                f'    </Location>',
                 f'    <Location /curltest/echo>',
                 f'      SetHandler curltest-echo',
                 f'    </Location>',
index b98a58932cf223a0e9e8abdb01ade21fa227bac7..911103bcf485d88cffde740ba874edd9c9a3a82e 100644 (file)
@@ -40,6 +40,7 @@ static int curltest_echo_handler(request_rec *r);
 static int curltest_put_handler(request_rec *r);
 static int curltest_tweak_handler(request_rec *r);
 static int curltest_1_1_required(request_rec *r);
+static int curltest_sslinfo_handler(request_rec *r);
 
 AP_DECLARE_MODULE(curltest) = {
   STANDARD20_MODULE_STUFF,
@@ -88,6 +89,7 @@ static void curltest_hooks(apr_pool_t *pool)
   ap_hook_handler(curltest_put_handler, NULL, NULL, APR_HOOK_MIDDLE);
   ap_hook_handler(curltest_tweak_handler, NULL, NULL, APR_HOOK_MIDDLE);
   ap_hook_handler(curltest_1_1_required, NULL, NULL, APR_HOOK_MIDDLE);
+  ap_hook_handler(curltest_sslinfo_handler, NULL, NULL, APR_HOOK_MIDDLE);
 }
 
 #define SECS_PER_HOUR      (60*60)
@@ -628,3 +630,113 @@ cleanup:
   }
   return DECLINED;
 }
+
+static int brigade_env_var(request_rec *r, apr_bucket_brigade *bb,
+                           const char *name)
+{
+  const char *s;
+  s = apr_table_get(r->subprocess_env, name);
+  if(s)
+    return apr_brigade_printf(bb, NULL, NULL, ",\n  \"%s\": \"%s\"", name, s);
+  return 0;
+}
+
+static int curltest_sslinfo_handler(request_rec *r)
+{
+  conn_rec *c = r->connection;
+  apr_bucket_brigade *bb;
+  apr_bucket *b;
+  apr_status_t rv;
+  apr_array_header_t *args = NULL;
+  const char *request_id = NULL;
+  int close_conn = 0;
+  long l;
+  int i;
+
+  if(strcmp(r->handler, "curltest-sslinfo")) {
+    return DECLINED;
+  }
+  if(r->method_number != M_GET) {
+    return DECLINED;
+  }
+
+  if(r->args) {
+    apr_array_header_t *args = apr_cstr_split(r->args, "&", 1, r->pool);
+    for(i = 0; i < args->nelts; ++i) {
+      char *s, *val, *arg = APR_ARRAY_IDX(args, i, char*);
+      s = strchr(arg, '=');
+      if(s) {
+        *s = '\0';
+        val = s + 1;
+        if(!strcmp("id", arg)) {
+          /* just an id for repeated requests with curl's url globbing */
+          request_id = val;
+          continue;
+        }
+      }
+      else if(!strcmp("close", arg)) {
+        /* we are asked to close the connection */
+        close_conn = 1;
+        continue;
+      }
+      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
+                    "understood: '%s' in %s",
+                    arg, r->args);
+      ap_die(HTTP_BAD_REQUEST, r);
+      return OK;
+    }
+  }
+
+  ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "sslinfo: processing");
+  r->status = 200;
+  r->clength = -1;
+  r->chunked = 1;
+  apr_table_unset(r->headers_out, "Content-Length");
+  /* Discourage content-encodings */
+  apr_table_unset(r->headers_out, "Content-Encoding");
+  apr_table_setn(r->subprocess_env, "no-brotli", "1");
+  apr_table_setn(r->subprocess_env, "no-gzip", "1");
+
+  ap_set_content_type(r, "application/json");
+
+  bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+  apr_brigade_puts(bb, NULL, NULL, "{\n  \"Name\": \"SSL-Information\"");
+  brigade_env_var(r, bb, "HTTPS");
+  brigade_env_var(r, bb, "SSL_PROTOCOL");
+  brigade_env_var(r, bb, "SSL_CIPHER");
+  brigade_env_var(r, bb, "SSL_SESSION_ID");
+  brigade_env_var(r, bb, "SSL_SESSION_RESUMED");
+  brigade_env_var(r, bb, "SSL_SRP_USER");
+  brigade_env_var(r, bb, "SSL_SRP_USERINFO");
+  apr_brigade_puts(bb, NULL, NULL, "}\n");
+
+  /* flush response */
+  b = apr_bucket_flush_create(c->bucket_alloc);
+  APR_BRIGADE_INSERT_TAIL(bb, b);
+  rv = ap_pass_brigade(r->output_filters, bb);
+  if (APR_SUCCESS != rv) goto cleanup;
+
+  /* we are done */
+  b = apr_bucket_eos_create(c->bucket_alloc);
+  APR_BRIGADE_INSERT_TAIL(bb, b);
+  ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: request read");
+
+  rv = ap_pass_brigade(r->output_filters, bb);
+
+cleanup:
+  if(close_conn)
+    r->connection->keepalive = AP_CONN_CLOSE;
+  if(rv == APR_SUCCESS
+     || r->status != HTTP_OK
+     || c->aborted) {
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler: done");
+    return OK;
+  }
+  else {
+    /* no way to know what type of error occurred */
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler failed");
+    return AP_FILTER_ERROR;
+  }
+  return DECLINED;
+}