diff src/event/ngx_event_openssl.c @ 272:29a6403156b0 NGINX_0_5_6

nginx 0.5.6 *) Change: now the ngx_http_index_module ignores all methods except the GET, HEAD, and POST methods. *) Feature: the ngx_http_limit_zone_module. *) Feature: the $binary_remote_addr variable. *) Feature: the "ssl_session_cache" directives of the ngx_http_ssl_module and ngx_imap_ssl_module. *) Feature: the DELETE method supports recursive removal. *) Bugfix: the byte-ranges were transferred incorrectly if the $r->sendfile() was used.
author Igor Sysoev <http://sysoev.ru>
date Tue, 09 Jan 2007 00:00:00 +0300
parents 559bc7ec214e
children 052a7b1d40e5
line wrap: on
line diff
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -22,6 +22,17 @@ static void ngx_ssl_read_handler(ngx_eve
 static void ngx_ssl_shutdown_handler(ngx_event_t *ev);
 static void ngx_ssl_connection_error(ngx_connection_t *c, int sslerr,
     ngx_err_t err, char *text);
+
+static ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone,
+    void *data);
+static int ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn,
+    ngx_ssl_session_t *sess);
+static ngx_ssl_session_t *ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn,
+    u_char *id, int len, int *copy);
+static void ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
+static void ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
+    ngx_slab_pool_t *shpool, ngx_uint_t n);
+
 static void *ngx_openssl_create_conf(ngx_cycle_t *cycle);
 static char *ngx_openssl_init_conf(ngx_cycle_t *cycle, void *conf);
 static void ngx_openssl_exit(ngx_cycle_t *cycle);
@@ -84,12 +95,18 @@ static long  ngx_ssl_protocols[] = {
 };
 
 
-int  ngx_connection_index;
+int  ngx_ssl_connection_index;
+int  ngx_ssl_server_conf_index;
+int  ngx_ssl_session_cache_index;
 
 
 ngx_int_t
 ngx_ssl_init(ngx_log_t *log)
 {
+#if OPENSSL_VERSION_NUMBER >= 0x00907000
+    OPENSSL_config(NULL);
+#endif
+
     SSL_library_init();
     SSL_load_error_strings();
 
@@ -97,10 +114,26 @@ ngx_ssl_init(ngx_log_t *log)
     ENGINE_load_builtin_engines();
 #endif
 
-    ngx_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+    ngx_ssl_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+    if (ngx_ssl_connection_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
 
-    if (ngx_connection_index == -1) {
-        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_get_ex_new_index() failed");
+    ngx_ssl_server_conf_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                         NULL);
+    if (ngx_ssl_server_conf_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_session_cache_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                           NULL);
+    if (ngx_ssl_session_cache_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
         return NGX_ERROR;
     }
 
@@ -109,7 +142,7 @@ ngx_ssl_init(ngx_log_t *log)
 
 
 ngx_int_t
-ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols)
+ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data)
 {
     ssl->ctx = SSL_CTX_new(SSLv23_method());
 
@@ -118,6 +151,12 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_
         return NGX_ERROR;
     }
 
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_server_conf_index, data) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
     /* client side options */
 
     SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_SESS_ID_BUG);
@@ -260,7 +299,7 @@ ngx_http_ssl_verify_callback(int ok, X50
     name = X509_get_issuer_name(cert);
     issuer = name ? X509_NAME_oneline(name, NULL, 0) : "(none)";
 
-    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
+    ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
                    "verify:%d, error:%d, depth:%d, "
                    "subject:\"%s\",issuer: \"%s\"",
                    ok, err, depth, subject, issuer);
@@ -332,7 +371,7 @@ ngx_ssl_create_connection(ngx_ssl_t *ssl
         SSL_set_accept_state(sc->connection);
     }
 
-    if (SSL_set_ex_data(sc->connection, ngx_connection_index, c) == 0) {
+    if (SSL_set_ex_data(sc->connection, ngx_ssl_connection_index, c) == 0) {
         ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed");
         return NGX_ERROR;
     }
@@ -1014,7 +1053,7 @@ ngx_ssl_shutdown_handler(ngx_event_t *ev
         c->timedout = 1;
     }
 
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "SSL shutdown handler");
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "SSL shutdown handler");
 
     if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
         return;
@@ -1100,6 +1139,433 @@ ngx_ssl_error(ngx_uint_t level, ngx_log_
 }
 
 
+ngx_int_t
+ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
+    ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout)
+{
+    long  cache_mode;
+
+    cache_mode = SSL_SESS_CACHE_SERVER;
+
+    if (shm_zone && builtin_session_cache == NGX_SSL_NO_BUILTIN_SCACHE) {
+        cache_mode |= SSL_SESS_CACHE_NO_INTERNAL;
+    }
+
+    SSL_CTX_set_session_cache_mode(ssl->ctx, cache_mode);
+
+    SSL_CTX_set_session_id_context(ssl->ctx, sess_ctx->data, sess_ctx->len);
+
+    if (builtin_session_cache != NGX_SSL_NO_BUILTIN_SCACHE) {
+
+        if (builtin_session_cache != NGX_SSL_DFLT_BUILTIN_SCACHE) {
+            SSL_CTX_sess_set_cache_size(ssl->ctx, builtin_session_cache);
+        }
+
+        SSL_CTX_set_timeout(ssl->ctx, timeout);
+    }
+
+    if (shm_zone) {
+        shm_zone->init = ngx_ssl_session_cache_init;
+
+        SSL_CTX_sess_set_new_cb(ssl->ctx, ngx_ssl_new_session);
+        SSL_CTX_sess_set_get_cb(ssl->ctx, ngx_ssl_get_cached_session);
+        SSL_CTX_sess_set_remove_cb(ssl->ctx, ngx_ssl_remove_session);
+
+        if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_cache_index, shm_zone)
+            == 0)
+        {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "SSL_CTX_set_ex_data() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data)
+{
+    ngx_slab_pool_t          *shpool;
+    ngx_rbtree_node_t        *sentinel;
+    ngx_ssl_session_cache_t  *cache;
+
+    if (data) {
+        shm_zone->data = data;
+        return NGX_OK;
+    }
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    cache = ngx_slab_alloc(shpool, sizeof(ngx_ssl_session_cache_t));
+    if (cache == NULL) {
+        return NGX_ERROR;
+    }
+
+    cache->session_cache_head.prev = NULL;
+    cache->session_cache_head.next = &cache->session_cache_tail;
+
+    cache->session_cache_tail.prev = &cache->session_cache_head;
+    cache->session_cache_tail.next = NULL;
+
+    cache->session_rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
+    if (cache->session_rbtree == NULL) {
+        return NGX_ERROR;
+    }
+
+    sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
+    if (sentinel == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_rbtree_sentinel_init(sentinel);
+
+    cache->session_rbtree->root = sentinel;
+    cache->session_rbtree->sentinel = sentinel;
+    cache->session_rbtree->insert = ngx_rbtree_insert_value;
+
+    shm_zone->data = cache;
+
+    return NGX_OK;
+}
+
+
+/*
+ * OpenSSL's i2d_SSL_SESSION() and d2i_SSL_SESSION are slow,
+ * so they are outside the code locked by shared pool mutex
+ */
+
+static int
+ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
+{
+    int                       len;
+    u_char                   *p, *id;
+    uint32_t                  hash;
+    SSL_CTX                  *ssl_ctx;
+    ngx_time_t               *tp;
+    ngx_shm_zone_t           *shm_zone;
+    ngx_connection_t         *c;
+    ngx_slab_pool_t          *shpool;
+    ngx_ssl_sess_id_t        *sess_id;
+    ngx_ssl_cached_sess_t    *cached_sess;
+    ngx_ssl_session_cache_t  *cache;
+    u_char                    buf[NGX_SSL_MAX_SESSION_SIZE];
+
+    len = i2d_SSL_SESSION(sess, NULL);
+
+    /* do not cache too big session */
+
+    if (len > (int) NGX_SSL_MAX_SESSION_SIZE) {
+        return 0;
+    }
+
+    p = buf;
+    i2d_SSL_SESSION(sess, &p);
+
+    c = ngx_ssl_get_connection(ssl_conn);
+
+    ssl_ctx = SSL_get_SSL_CTX(ssl_conn);
+    shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
+
+    cache = shm_zone->data;
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    /* drop one or two expired sessions */
+    ngx_ssl_expire_sessions(cache, shpool, 1);
+
+    cached_sess = ngx_slab_alloc_locked(shpool,
+                                  offsetof(ngx_ssl_cached_sess_t, asn1) + len);
+
+    if (cached_sess == NULL) {
+
+        /* drop the oldest non-expired session and try once more */
+
+        ngx_ssl_expire_sessions(cache, shpool, 0);
+
+        cached_sess = ngx_slab_alloc_locked(shpool,
+                                  offsetof(ngx_ssl_cached_sess_t, asn1) + len);
+
+        if (cached_sess == NULL) {
+            id = NULL;
+            goto failed;
+        }
+    }
+
+    id = ngx_slab_alloc_locked(shpool, sess->session_id_length);
+    if (id == NULL) {
+        goto failed;
+    }
+
+    sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+    if (sess_id == NULL) {
+        goto failed;
+    }
+
+    ngx_memcpy(&cached_sess->asn1[0], buf, len);
+
+    ngx_memcpy(id, sess->session_id, sess->session_id_length);
+
+    hash = ngx_crc32_short(sess->session_id, sess->session_id_length);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "http ssl new session: %08XD:%d:%d",
+                   hash, sess->session_id_length, len);
+
+    sess_id->node.key = hash;
+    sess_id->node.data = (u_char) sess->session_id_length;
+    sess_id->id = id;
+    sess_id->len = len;
+    sess_id->session = cached_sess;
+
+    tp = ngx_timeofday();
+
+    cached_sess->expire = tp->sec + SSL_CTX_get_timeout(ssl_ctx);
+    cached_sess->sess_id = sess_id;
+
+    cached_sess->next = cache->session_cache_head.next;
+    cached_sess->next->prev = cached_sess;
+    cached_sess->prev = &cache->session_cache_head;
+    cache->session_cache_head.next = cached_sess;
+
+    ngx_rbtree_insert(cache->session_rbtree, &sess_id->node);
+
+    ngx_shmtx_unlock(&shpool->mutex);
+
+    return 0;
+
+failed:
+
+    if (cached_sess) {
+        ngx_slab_free_locked(shpool, cached_sess);
+    }
+
+    if (id) {
+        ngx_slab_free_locked(shpool, id);
+    }
+
+    ngx_shmtx_unlock(&shpool->mutex);
+
+    ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                  "could not add new SSL session to the session cache");
+
+    return 0;
+}
+
+
+static ngx_ssl_session_t *
+ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn, u_char *id, int len,
+    int *copy)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+    const
+#endif
+    u_char                   *p;
+    uint32_t                  hash;
+    ngx_time_t               *tp;
+    ngx_shm_zone_t           *shm_zone;
+    ngx_slab_pool_t          *shpool;
+    ngx_connection_t         *c;
+    ngx_rbtree_node_t        *node, *sentinel;
+    ngx_ssl_session_t        *sess;
+    ngx_ssl_sess_id_t        *sess_id;
+    ngx_ssl_cached_sess_t    *cached_sess;
+    ngx_ssl_session_cache_t  *cache;
+    u_char                    buf[NGX_SSL_MAX_SESSION_SIZE];
+
+    c = ngx_ssl_get_connection(ssl_conn);
+
+    hash = ngx_crc32_short(id, len);
+    *copy = 0;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "http ssl get session: %08XD:%d", hash, len);
+
+    shm_zone = SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl_conn),
+                                   ngx_ssl_session_cache_index);
+
+    cache = shm_zone->data;
+
+    if (cache->session_rbtree == NULL) {
+        return NULL;
+    }
+
+    sess = NULL;
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    node = cache->session_rbtree->root;
+    sentinel = cache->session_rbtree->sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        if (hash == node->key && (u_char) len == node->data) {
+            sess_id = (ngx_ssl_sess_id_t *) node;
+
+            if (ngx_strncmp(id, sess_id->id, len) == 0) {
+
+                cached_sess = sess_id->session;
+
+                tp = ngx_timeofday();
+
+                if (cached_sess->expire > tp->sec) {
+                    ngx_memcpy(buf, &cached_sess->asn1[0], sess_id->len);
+
+                    ngx_shmtx_unlock(&shpool->mutex);
+
+                    p = buf;
+                    sess = d2i_SSL_SESSION(NULL, &p, sess_id->len);
+
+                    return sess;
+                }
+
+                cached_sess->next->prev = cached_sess->prev;
+                cached_sess->prev->next = cached_sess->next;
+
+                ngx_rbtree_delete(cache->session_rbtree, node);
+
+                ngx_slab_free_locked(shpool, cached_sess);
+                ngx_slab_free_locked(shpool, sess_id->id);
+                ngx_slab_free_locked(shpool, sess_id);
+
+                sess = NULL;
+
+                break;
+            }
+        }
+
+        node = node->right;
+    }
+
+    ngx_shmtx_unlock(&shpool->mutex);
+
+    return sess;
+}
+
+
+static void
+ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
+{
+    u_char                   *id, len;
+    uint32_t                  hash;
+    ngx_shm_zone_t           *shm_zone;
+    ngx_slab_pool_t          *shpool;
+    ngx_rbtree_node_t        *node, *sentinel;
+    ngx_ssl_sess_id_t        *sess_id;
+    ngx_ssl_cached_sess_t    *cached_sess;
+    ngx_ssl_session_cache_t  *cache;
+
+    shm_zone = SSL_CTX_get_ex_data(ssl, ngx_ssl_session_cache_index);
+
+    cache = shm_zone->data;
+
+    id = sess->session_id;
+    len = (u_char) sess->session_id_length;
+
+    hash = ngx_crc32_short(id, (size_t) len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
+                   "http ssl remove session: %08XD:%d", hash, len);
+
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    node = cache->session_rbtree->root;
+    sentinel = cache->session_rbtree->sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        if (hash == node->key && len == node->data) {
+            sess_id = (ngx_ssl_sess_id_t *) node;
+
+            if (ngx_strncmp(id, sess_id->id, (size_t) len) == 0) {
+
+                cached_sess = sess_id->session;
+
+                cached_sess->next->prev = cached_sess->prev;
+                cached_sess->prev->next = cached_sess->next;
+
+                ngx_rbtree_delete(cache->session_rbtree, node);
+
+                ngx_slab_free_locked(shpool, cached_sess);
+                ngx_slab_free_locked(shpool, sess_id->id);
+                ngx_slab_free_locked(shpool, sess_id);
+
+                break;
+            }
+        }
+
+        node = node->right;
+    }
+
+    ngx_shmtx_unlock(&shpool->mutex);
+}
+
+
+static void
+ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
+    ngx_slab_pool_t *shpool, ngx_uint_t n)
+{
+    ngx_time_t                  *tp;
+    ngx_ssl_sess_id_t      *sess_id;
+    ngx_ssl_cached_sess_t  *sess;
+
+    tp = ngx_timeofday();
+
+    while (n < 3) {
+
+        sess = cache->session_cache_tail.prev;
+
+        if (sess == &cache->session_cache_head) {
+            return;
+        }
+
+        if (n++ != 0 && sess->expire > tp->sec) {
+            break;
+        }
+
+        sess->next->prev = sess->prev;
+        sess->prev->next = sess->next;
+
+        sess_id = sess->sess_id;
+
+        ngx_rbtree_delete(cache->session_rbtree, &sess_id->node);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
+                       "expire session: %08Xi", sess_id->node.key);
+
+        ngx_slab_free_locked(shpool, sess);
+        ngx_slab_free_locked(shpool, sess_id->id);
+        ngx_slab_free_locked(shpool, sess_id);
+    }
+}
+
+
 void
 ngx_ssl_cleanup_ctx(void *data)
 {