changeset 688:f31b19fe7f48 NGINX_1_3_7

nginx 1.3.7 *) Feature: OCSP stapling support. Thanks to Comodo, DigiCert and GlobalSign for sponsoring this work. *) Feature: the "ssl_trusted_certificate" directive. *) Feature: resolver now randomly rotates addresses returned from cache. Thanks to Anton Jouline. *) Bugfix: OpenSSL 0.9.7 compatibility.
author Igor Sysoev <http://sysoev.ru>
date Tue, 02 Oct 2012 00:00:00 +0400
parents a7305f494f1c
children 4d942f0d1703
files CHANGES CHANGES.ru auto/lib/perl/conf auto/make auto/options auto/sources src/core/nginx.h src/core/ngx_core.h src/core/ngx_cycle.c src/core/ngx_cycle.h src/core/ngx_file.c src/core/ngx_file.h src/core/ngx_resolver.c src/event/ngx_event_openssl.c src/event/ngx_event_openssl.h src/event/ngx_event_openssl_stapling.c src/http/modules/ngx_http_ssl_module.c src/http/modules/ngx_http_ssl_module.h src/http/modules/perl/nginx.pm src/os/unix/ngx_process_cycle.c
diffstat 20 files changed, 2110 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,17 @@
 
+Changes with nginx 1.3.7                                         02 Oct 2012
+
+    *) Feature: OCSP stapling support.
+       Thanks to Comodo, DigiCert and GlobalSign for sponsoring this work.
+
+    *) Feature: the "ssl_trusted_certificate" directive.
+
+    *) Feature: resolver now randomly rotates addresses returned from cache.
+       Thanks to Anton Jouline.
+
+    *) Bugfix: OpenSSL 0.9.7 compatibility.
+
+
 Changes with nginx 1.3.6                                         12 Sep 2012
 
     *) Feature: the ngx_http_gunzip_filter_module.
--- a/CHANGES.ru
+++ b/CHANGES.ru
@@ -1,4 +1,18 @@
 
+Изменения в nginx 1.3.7                                           02.10.2012
+
+    *) Добавление: поддержка OCSP stapling.
+       Спасибо Comodo, DigiCert и GlobalSign за спонсирование разработки.
+
+    *) Добавление: директива ssl_trusted_certificate.
+
+    *) Добавление: теперь resolver случайным образом меняет порядок
+       возвращаемых закэшированных адресов.
+       Спасибо Антону Жулину.
+
+    *) Исправление: совместимость с OpenSSL 0.9.7.
+
+
 Изменения в nginx 1.3.6                                           12.09.2012
 
     *) Добавление: модуль ngx_http_gunzip_filter_module.
--- a/auto/lib/perl/conf
+++ b/auto/lib/perl/conf
@@ -12,7 +12,7 @@ NGX_PERL_VER=`$NGX_PERL -v 2>&1 | grep '
 if test -n "$NGX_PERL_VER"; then
     echo " + perl version: $NGX_PERL_VER"
 
-    if [ "`echo 'use 5.006001; print "OK"' | $NGX_PERL 2>&1`" != OK ]; then
+    if [ "`$NGX_PERL -e 'use 5.006001; print "OK"'`" != "OK" ]; then
         echo
         echo "$0: error: perl 5.6.1 or higher is required"
         echo
@@ -20,6 +20,14 @@ if test -n "$NGX_PERL_VER"; then
         exit 1;
     fi
 
+    if [ "`$NGX_PERL -MExtUtils::Embed -e 'print "OK"'`" != "OK" ]; then
+        echo
+        echo "$0: error: perl module ExtUtils::Embed is required"
+        echo
+
+        exit 1;
+    fi
+
     NGX_PERL_CFLAGS="$CFLAGS `$NGX_PERL -MExtUtils::Embed -e ccopts`"
     NGX_PM_CFLAGS=`$NGX_PERL -MExtUtils::Embed -e ccopts`
 
--- a/auto/make
+++ b/auto/make
@@ -49,7 +49,7 @@ END
 ngx_all_srcs="$CORE_SRCS"
 
 
-# the core dependences and include pathes
+# the core dependences and include paths
 
 ngx_deps=`echo $CORE_DEPS $NGX_AUTO_CONFIG_H $NGX_PCH \
     | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
@@ -69,7 +69,7 @@ CORE_INCS = $ngx_include_opt$ngx_incs
 END
 
 
-# the http dependences and include pathes
+# the http dependences and include paths
 
 if [ $HTTP = YES ]; then
 
@@ -95,7 +95,7 @@ END
 fi
 
 
-# the mail dependences and include pathes
+# the mail dependences and include paths
 
 if [ $MAIL = YES ]; then
 
--- a/auto/options
+++ b/auto/options
@@ -388,6 +388,10 @@ cat << END
   --without-http_browser_module      disable ngx_http_browser_module
   --without-http_upstream_ip_hash_module
                                      disable ngx_http_upstream_ip_hash_module
+  --without-http_upstream_least_conn_module
+                                     disable ngx_http_upstream_least_conn_module
+  --without-http_upstream_keepalive_module
+                                     disable ngx_http_upstream_keepalive_module
 
   --with-http_perl_module            enable ngx_http_perl_module
   --with-perl_modules_path=PATH      set Perl modules path
--- a/auto/sources
+++ b/auto/sources
@@ -77,7 +77,8 @@ REGEX_SRCS=src/core/ngx_regex.c
 
 OPENSSL_MODULE=ngx_openssl_module
 OPENSSL_DEPS=src/event/ngx_event_openssl.h
-OPENSSL_SRCS=src/event/ngx_event_openssl.c
+OPENSSL_SRCS="src/event/ngx_event_openssl.c \
+              src/event/ngx_event_openssl_stapling.c"
 
 
 EVENT_MODULES="ngx_events_module ngx_event_core_module"
--- a/src/core/nginx.h
+++ b/src/core/nginx.h
@@ -9,8 +9,8 @@
 #define _NGINX_H_INCLUDED_
 
 
-#define nginx_version      1003006
-#define NGINX_VERSION      "1.3.6"
+#define nginx_version      1003007
+#define NGINX_VERSION      "1.3.7"
 #define NGINX_VER          "nginx/" NGINX_VERSION
 
 #define NGINX_VAR          "NGINX"
--- a/src/core/ngx_core.h
+++ b/src/core/ngx_core.h
@@ -69,12 +69,12 @@ typedef void (*ngx_connection_handler_pt
 #include <ngx_slab.h>
 #include <ngx_inet.h>
 #include <ngx_cycle.h>
+#include <ngx_resolver.h>
 #if (NGX_OPENSSL)
 #include <ngx_event_openssl.h>
 #endif
 #include <ngx_process_cycle.h>
 #include <ngx_conf_file.h>
-#include <ngx_resolver.h>
 #include <ngx_open_file_cache.h>
 #include <ngx_os.h>
 #include <ngx_connection.h>
--- a/src/core/ngx_cycle.c
+++ b/src/core/ngx_cycle.c
@@ -118,18 +118,18 @@ ngx_init_cycle(ngx_cycle_t *old_cycle)
     }
 
 
-    n = old_cycle->pathes.nelts ? old_cycle->pathes.nelts : 10;
+    n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10;
 
-    cycle->pathes.elts = ngx_pcalloc(pool, n * sizeof(ngx_path_t *));
-    if (cycle->pathes.elts == NULL) {
+    cycle->paths.elts = ngx_pcalloc(pool, n * sizeof(ngx_path_t *));
+    if (cycle->paths.elts == NULL) {
         ngx_destroy_pool(pool);
         return NULL;
     }
 
-    cycle->pathes.nelts = 0;
-    cycle->pathes.size = sizeof(ngx_path_t *);
-    cycle->pathes.nalloc = n;
-    cycle->pathes.pool = pool;
+    cycle->paths.nelts = 0;
+    cycle->paths.size = sizeof(ngx_path_t *);
+    cycle->paths.nalloc = n;
+    cycle->paths.pool = pool;
 
 
     if (old_cycle->open_files.part.nelts) {
@@ -334,7 +334,7 @@ ngx_init_cycle(ngx_cycle_t *old_cycle)
     }
 
 
-    if (ngx_create_pathes(cycle, ccf->user) != NGX_OK) {
+    if (ngx_create_paths(cycle, ccf->user) != NGX_OK) {
         goto failed;
     }
 
@@ -1038,6 +1038,8 @@ ngx_signal_process(ngx_cycle_t *cycle, c
 
     ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
 
+    ngx_memzero(&file, sizeof(ngx_file_t));
+
     file.name = ccf->pid;
     file.log = cycle->log;
 
--- a/src/core/ngx_cycle.h
+++ b/src/core/ngx_cycle.h
@@ -48,7 +48,7 @@ struct ngx_cycle_s {
     ngx_queue_t               reusable_connections_queue;
 
     ngx_array_t               listening;
-    ngx_array_t               pathes;
+    ngx_array_t               paths;
     ngx_list_t                open_files;
     ngx_list_t                shared_memory;
 
--- a/src/core/ngx_file.c
+++ b/src/core/ngx_file.c
@@ -412,8 +412,8 @@ ngx_add_path(ngx_conf_t *cf, ngx_path_t 
 
     path = *slot;
 
-    p = cf->cycle->pathes.elts;
-    for (i = 0; i < cf->cycle->pathes.nelts; i++) {
+    p = cf->cycle->paths.elts;
+    for (i = 0; i < cf->cycle->paths.nelts; i++) {
         if (p[i]->name.len == path->name.len
             && ngx_strcmp(p[i]->name.data, path->name.data) == 0)
         {
@@ -457,7 +457,7 @@ ngx_add_path(ngx_conf_t *cf, ngx_path_t 
         }
     }
 
-    p = ngx_array_push(&cf->cycle->pathes);
+    p = ngx_array_push(&cf->cycle->paths);
     if (p == NULL) {
         return NGX_ERROR;
     }
@@ -469,14 +469,14 @@ ngx_add_path(ngx_conf_t *cf, ngx_path_t 
 
 
 ngx_int_t
-ngx_create_pathes(ngx_cycle_t *cycle, ngx_uid_t user)
+ngx_create_paths(ngx_cycle_t *cycle, ngx_uid_t user)
 {
     ngx_err_t         err;
     ngx_uint_t        i;
     ngx_path_t      **path;
 
-    path = cycle->pathes.elts;
-    for (i = 0; i < cycle->pathes.nelts; i++) {
+    path = cycle->paths.elts;
+    for (i = 0; i < cycle->paths.nelts; i++) {
 
         if (ngx_create_dir(path[i]->name.data, 0700) == NGX_FILE_ERROR) {
             err = ngx_errno;
--- a/src/core/ngx_file.h
+++ b/src/core/ngx_file.h
@@ -130,7 +130,7 @@ void ngx_create_hashed_filename(ngx_path
 ngx_int_t ngx_create_path(ngx_file_t *file, ngx_path_t *path);
 ngx_err_t ngx_create_full_path(u_char *dir, ngx_uint_t access);
 ngx_int_t ngx_add_path(ngx_conf_t *cf, ngx_path_t **slot);
-ngx_int_t ngx_create_pathes(ngx_cycle_t *cycle, ngx_uid_t user);
+ngx_int_t ngx_create_paths(ngx_cycle_t *cycle, ngx_uid_t user);
 ngx_int_t ngx_ext_rename_file(ngx_str_t *src, ngx_str_t *to,
     ngx_ext_rename_file_t *ext);
 ngx_int_t ngx_copy_file(u_char *from, u_char *to, ngx_copy_file_t *cf);
--- a/src/core/ngx_resolver.c
+++ b/src/core/ngx_resolver.c
@@ -88,6 +88,8 @@ static void *ngx_resolver_calloc(ngx_res
 static void ngx_resolver_free(ngx_resolver_t *r, void *p);
 static void ngx_resolver_free_locked(ngx_resolver_t *r, void *p);
 static void *ngx_resolver_dup(ngx_resolver_t *r, void *src, size_t size);
+static in_addr_t *ngx_resolver_rotate(ngx_resolver_t *r, in_addr_t *src,
+    ngx_uint_t n);
 static u_char *ngx_resolver_log_error(ngx_log_t *log, u_char *buf, size_t len);
 
 
@@ -445,8 +447,7 @@ ngx_resolve_name_locked(ngx_resolver_t *
 
                 if (naddrs != 1) {
                     addr = 0;
-                    addrs = ngx_resolver_dup(r, rn->u.addrs,
-                                             naddrs * sizeof(in_addr_t));
+                    addrs = ngx_resolver_rotate(r, rn->u.addrs, naddrs);
                     if (addrs == NULL) {
                         return NGX_ERROR;
                     }
@@ -2135,6 +2136,28 @@ ngx_resolver_dup(ngx_resolver_t *r, void
 }
 
 
+static in_addr_t *
+ngx_resolver_rotate(ngx_resolver_t *r, in_addr_t *src, ngx_uint_t n)
+{
+    void        *dst, *p;
+    ngx_uint_t   j;
+
+    dst = ngx_resolver_alloc(r, n * sizeof(in_addr_t));
+
+    j = ngx_random() % n;
+
+    if (j == 0) {
+        ngx_memcpy(dst, src, n * sizeof(in_addr_t));
+        return dst;
+    }
+
+    p = ngx_cpymem(dst, &src[j], (n - j) * sizeof(in_addr_t));
+    ngx_memcpy(p, src, j * sizeof(in_addr_t));
+
+    return dst;
+}
+
+
 char *
 ngx_resolver_strerror(ngx_int_t err)
 {
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -82,6 +82,8 @@ ngx_module_t  ngx_openssl_module = {
 int  ngx_ssl_connection_index;
 int  ngx_ssl_server_conf_index;
 int  ngx_ssl_session_cache_index;
+int  ngx_ssl_certificate_index;
+int  ngx_ssl_stapling_index;
 
 
 ngx_int_t
@@ -94,23 +96,25 @@ ngx_ssl_init(ngx_log_t *log)
 
     OpenSSL_add_all_algorithms();
 
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
 #ifndef SSL_OP_NO_COMPRESSION
     {
     /*
      * Disable gzip compression in OpenSSL prior to 1.0.0 version,
      * this saves about 522K per connection.
      */
-    int                 i, n;
+    int                  n;
     STACK_OF(SSL_COMP)  *ssl_comp_methods;
 
     ssl_comp_methods = SSL_COMP_get_compression_methods();
     n = sk_SSL_COMP_num(ssl_comp_methods);
 
-    for (i = 0; i < n; i++) {
-        (void) sk_SSL_COMP_delete(ssl_comp_methods, i);
+    while (n--) {
+        (void) sk_SSL_COMP_pop(ssl_comp_methods);
     }
     }
 #endif
+#endif
 
     ngx_ssl_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
 
@@ -135,6 +139,22 @@ ngx_ssl_init(ngx_log_t *log)
         return NGX_ERROR;
     }
 
+    ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                         NULL);
+    if (ngx_ssl_certificate_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_stapling_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                      NULL);
+    if (ngx_ssl_stapling_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
     return NGX_OK;
 }
 
@@ -216,19 +236,89 @@ ngx_int_t
 ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
     ngx_str_t *key)
 {
+    BIO     *bio;
+    X509    *x509;
+    u_long   n;
+
     if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
         return NGX_ERROR;
     }
 
-    if (SSL_CTX_use_certificate_chain_file(ssl->ctx, (char *) cert->data)
+    /*
+     * we can't use SSL_CTX_use_certificate_chain_file() as it doesn't
+     * allow to access certificate later from SSL_CTX, so we reimplement
+     * it here
+     */
+
+    bio = BIO_new_file((char *) cert->data, "r");
+    if (bio == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "BIO_new_file(\"%s\") failed", cert->data);
+        return NGX_ERROR;
+    }
+
+    x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
+    if (x509 == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PEM_read_bio_X509_AUX(\"%s\") failed", cert->data);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_use_certificate(\"%s\") failed", cert->data);
+        X509_free(x509);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509)
         == 0)
     {
         ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
-                      "SSL_CTX_use_certificate_chain_file(\"%s\") failed",
-                      cert->data);
+                      "SSL_CTX_set_ex_data() failed");
         return NGX_ERROR;
     }
 
+    X509_free(x509);
+
+    /* read rest of the chain */
+
+    for ( ;; ) {
+
+        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
+        if (x509 == NULL) {
+            n = ERR_peek_last_error();
+
+            if (ERR_GET_LIB(n) == ERR_LIB_PEM
+                && ERR_GET_REASON(n) == PEM_R_NO_START_LINE)
+            {
+                /* end of file */
+                ERR_clear_error();
+                break;
+            }
+
+            /* some real error */
+
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "PEM_read_bio_X509(\"%s\") failed", cert->data);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+
+        if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "SSL_CTX_add_extra_chain_cert(\"%s\") failed",
+                          cert->data);
+            X509_free(x509);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+    }
+
+    BIO_free(bio);
+
     if (ngx_conf_full_name(cf->cycle, key, 1) != NGX_OK) {
         return NGX_ERROR;
     }
@@ -295,6 +385,33 @@ ngx_ssl_client_certificate(ngx_conf_t *c
 
 
 ngx_int_t
+ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
+    ngx_int_t depth)
+{
+    SSL_CTX_set_verify_depth(ssl->ctx, depth);
+
+    if (cert->len == 0) {
+        return NGX_OK;
+    }
+
+    if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_load_verify_locations(\"%s\") failed",
+                      cert->data);
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
 ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl)
 {
     X509_STORE   *store;
@@ -1473,10 +1590,12 @@ ngx_ssl_clear_error(ngx_log_t *log)
 void ngx_cdecl
 ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...)
 {
-    u_long    n;
-    va_list   args;
-    u_char   *p, *last;
-    u_char    errstr[NGX_MAX_CONF_ERRSTR];
+    int          flags;
+    u_long       n;
+    va_list      args;
+    u_char      *p, *last;
+    u_char       errstr[NGX_MAX_CONF_ERRSTR];
+    const char  *data;
 
     last = errstr + NGX_MAX_CONF_ERRSTR;
 
@@ -1488,14 +1607,14 @@ ngx_ssl_error(ngx_uint_t level, ngx_log_
 
     for ( ;; ) {
 
-        n = ERR_get_error();
+        n = ERR_peek_error_line_data(NULL, NULL, &data, &flags);
 
         if (n == 0) {
             break;
         }
 
         if (p >= last) {
-            continue;
+            goto next;
         }
 
         *p++ = ' ';
@@ -1505,6 +1624,15 @@ ngx_ssl_error(ngx_uint_t level, ngx_log_
         while (p < last && *p) {
             p++;
         }
+
+        if (p < last && *data && (flags & ERR_TXT_STRING)) {
+            *p++ = ':';
+            p = ngx_cpystrn(p, (u_char *) data, last - p);
+        }
+
+    next:
+
+        (void) ERR_get_error();
     }
 
     ngx_log_error(level, log, err, "%s)", errstr);
--- a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -17,6 +17,7 @@
 #include <openssl/conf.h>
 #include <openssl/engine.h>
 #include <openssl/evp.h>
+#include <openssl/ocsp.h>
 
 #define NGX_SSL_NAME     "OpenSSL"
 
@@ -101,7 +102,13 @@ ngx_int_t ngx_ssl_certificate(ngx_conf_t
     ngx_str_t *cert, ngx_str_t *key);
 ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
     ngx_str_t *cert, ngx_int_t depth);
+ngx_int_t ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *cert, ngx_int_t depth);
 ngx_int_t ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl);
+ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify);
+ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_resolver_t *resolver, ngx_msec_t resolver_timeout);
 RSA *ngx_ssl_rsa512_key_callback(SSL *ssl, int is_export, int key_length);
 ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file);
 ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name);
@@ -157,6 +164,8 @@ void ngx_ssl_cleanup_ctx(void *data);
 extern int  ngx_ssl_connection_index;
 extern int  ngx_ssl_server_conf_index;
 extern int  ngx_ssl_session_cache_index;
+extern int  ngx_ssl_certificate_index;
+extern int  ngx_ssl_stapling_index;
 
 
 #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_openssl_stapling.c
@@ -0,0 +1,1749 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+
+
+#ifdef SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB
+
+
+typedef struct {
+    ngx_str_t                    staple;
+    ngx_msec_t                   timeout;
+
+    ngx_resolver_t              *resolver;
+    ngx_msec_t                   resolver_timeout;
+
+    ngx_addr_t                  *addrs;
+    ngx_str_t                    host;
+    ngx_str_t                    uri;
+    in_port_t                    port;
+
+    SSL_CTX                     *ssl_ctx;
+
+    X509                        *cert;
+    X509                        *issuer;
+
+    time_t                       valid;
+
+    unsigned                     verify:1;
+    unsigned                     loading:1;
+} ngx_ssl_stapling_t;
+
+
+typedef struct ngx_ssl_ocsp_ctx_s  ngx_ssl_ocsp_ctx_t;
+
+struct ngx_ssl_ocsp_ctx_s {
+    X509                        *cert;
+    X509                        *issuer;
+
+    ngx_uint_t                   naddrs;
+
+    ngx_addr_t                  *addrs;
+    ngx_str_t                    host;
+    ngx_str_t                    uri;
+    in_port_t                    port;
+
+    ngx_resolver_t              *resolver;
+    ngx_msec_t                   resolver_timeout;
+
+    ngx_msec_t                   timeout;
+
+    void                       (*handler)(ngx_ssl_ocsp_ctx_t *r);
+    void                        *data;
+
+    ngx_buf_t                   *request;
+    ngx_buf_t                   *response;
+    ngx_peer_connection_t        peer;
+
+    ngx_int_t                  (*process)(ngx_ssl_ocsp_ctx_t *r);
+
+    ngx_uint_t                   state;
+
+    ngx_uint_t                   code;
+    ngx_uint_t                   count;
+
+    ngx_uint_t                   done;
+
+    u_char                      *header_name_start;
+    u_char                      *header_name_end;
+    u_char                      *header_start;
+    u_char                      *header_end;
+
+    ngx_pool_t                  *pool;
+    ngx_log_t                   *log;
+};
+
+
+static ngx_int_t ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *file);
+static ngx_int_t ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl);
+static ngx_int_t ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *responder);
+
+static int ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn,
+    void *data);
+static void ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple);
+static void ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx);
+
+static void ngx_ssl_stapling_cleanup(void *data);
+
+static ngx_ssl_ocsp_ctx_t *ngx_ssl_ocsp_start(void);
+static void ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx);
+static void ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx);
+static void ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve);
+static void ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx);
+static void ngx_ssl_ocsp_write_handler(ngx_event_t *wev);
+static void ngx_ssl_ocsp_read_handler(ngx_event_t *rev);
+static void ngx_ssl_ocsp_dummy_handler(ngx_event_t *ev);
+
+static ngx_int_t ngx_ssl_ocsp_create_request(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_process_status_line(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_parse_header_line(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx);
+
+static u_char *ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len);
+
+
+ngx_int_t
+ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file,
+    ngx_str_t *responder, ngx_uint_t verify)
+{
+    ngx_int_t                  rc;
+    ngx_pool_cleanup_t        *cln;
+    ngx_ssl_stapling_t        *staple;
+
+    staple = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_stapling_t));
+    if (staple == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_ssl_stapling_cleanup;
+    cln->data = staple;
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_stapling_index, staple)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    staple->ssl_ctx = ssl->ctx;
+    staple->timeout = 60000;
+    staple->verify = verify;
+
+    if (file->len) {
+        /* use OCSP response from the file */
+
+        if (ngx_ssl_stapling_file(cf, ssl, file) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        goto done;
+    }
+
+    rc = ngx_ssl_stapling_issuer(cf, ssl);
+
+    if (rc == NGX_DECLINED) {
+        return NGX_OK;
+    }
+
+    if (rc != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_ssl_stapling_responder(cf, ssl, responder);
+
+    if (rc == NGX_DECLINED) {
+        return NGX_OK;
+    }
+
+    if (rc != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+done:
+
+    SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback);
+    SSL_CTX_set_tlsext_status_arg(ssl->ctx, staple);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file)
+{
+    BIO                 *bio;
+    int                  len;
+    u_char              *p, *buf;
+    OCSP_RESPONSE       *response;
+    ngx_ssl_stapling_t  *staple;
+
+    staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index);
+
+    if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    bio = BIO_new_file((char *) file->data, "r");
+    if (bio == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "BIO_new_file(\"%s\") failed", file->data);
+        return NGX_ERROR;
+    }
+
+    response = d2i_OCSP_RESPONSE_bio(bio, NULL);
+    if (response == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "d2i_OCSP_RESPONSE_bio(\"%s\") failed", file->data);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    len = i2d_OCSP_RESPONSE(response, NULL);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "i2d_OCSP_RESPONSE(\"%s\") failed", file->data);
+        goto failed;
+    }
+
+    buf = ngx_alloc(len, ssl->log);
+    if (buf == NULL) {
+        goto failed;
+    }
+
+    p = buf;
+    len = i2d_OCSP_RESPONSE(response, &p);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "i2d_OCSP_RESPONSE(\"%s\") failed", file->data);
+        ngx_free(buf);
+        goto failed;
+    }
+
+    OCSP_RESPONSE_free(response);
+    BIO_free(bio);
+
+    staple->staple.data = buf;
+    staple->staple.len = len;
+
+    return NGX_OK;
+
+failed:
+
+    OCSP_RESPONSE_free(response);
+    BIO_free(bio);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl)
+{
+    int                  i, n, rc;
+    X509                *cert, *issuer;
+    X509_STORE          *store;
+    X509_STORE_CTX      *store_ctx;
+    STACK_OF(X509)      *chain;
+    ngx_ssl_stapling_t  *staple;
+
+    staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index);
+    cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+    SSL_CTX_get_extra_chain_certs(ssl->ctx, &chain);
+#else
+    chain = ssl->ctx->extra_certs;
+#endif
+
+    n = sk_X509_num(chain);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
+                   "SSL get issuer: %d extra certs", n);
+
+    for (i = 0; i < n; i++) {
+        issuer = sk_X509_value(chain, i);
+        if (X509_check_issued(issuer, cert) == X509_V_OK) {
+            CRYPTO_add(&issuer->references, 1, CRYPTO_LOCK_X509);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
+                           "SSL get issuer: found %p in extra certs", issuer);
+
+            staple->cert = cert;
+            staple->issuer = issuer;
+
+            return NGX_OK;
+        }
+    }
+
+    store = SSL_CTX_get_cert_store(ssl->ctx);
+    if (store == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_get_cert_store() failed");
+        return NGX_ERROR;
+    }
+
+    store_ctx = X509_STORE_CTX_new();
+    if (store_ctx == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "X509_STORE_CTX_new() failed");
+        return NGX_ERROR;
+    }
+
+    if (X509_STORE_CTX_init(store_ctx, store, NULL, NULL) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "X509_STORE_CTX_init() failed");
+        return NGX_ERROR;
+    }
+
+    rc = X509_STORE_CTX_get1_issuer(&issuer, store_ctx, cert);
+
+    if (rc == -1) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "X509_STORE_CTX_get1_issuer() failed");
+        X509_STORE_CTX_free(store_ctx);
+        return NGX_ERROR;
+    }
+
+    if (rc == 0) {
+        ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                      "\"ssl_stapling\" ignored, issuer certificate not found");
+        X509_STORE_CTX_free(store_ctx);
+        return NGX_DECLINED;
+    }
+
+    X509_STORE_CTX_free(store_ctx);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
+                   "SSL get issuer: found %p in cert store", issuer);
+
+    staple->cert = cert;
+    staple->issuer = issuer;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder)
+{
+    ngx_url_t                  u;
+    char                      *s;
+    ngx_ssl_stapling_t        *staple;
+    STACK_OF(OPENSSL_STRING)  *aia;
+
+    staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index);
+
+    if (responder->len == 0) {
+
+        /* extract OCSP responder URL from certificate */
+
+        aia = X509_get1_ocsp(staple->cert);
+        if (aia == NULL) {
+            ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                          "\"ssl_stapling\" ignored, "
+                          "no OCSP responder URL in the certificate");
+            return NGX_DECLINED;
+        }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+        s = sk_OPENSSL_STRING_value(aia, 0);
+#else
+        s = sk_value(aia, 0);
+#endif
+        if (s == NULL) {
+            ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                          "\"ssl_stapling\" ignored, "
+                          "no OCSP responder URL in the certificate");
+            X509_email_free(aia);
+            return NGX_DECLINED;
+        }
+
+        responder->len = ngx_strlen(s);
+        responder->data = ngx_palloc(cf->pool, responder->len);
+        if (responder->data == NULL) {
+            X509_email_free(aia);
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(responder->data, s, responder->len);
+        X509_email_free(aia);
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = *responder;
+    u.default_port = 80;
+    u.uri_part = 1;
+
+    if (u.url.len > 7
+        && ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0)
+    {
+        u.url.len -= 7;
+        u.url.data += 7;
+
+    } else {
+        ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                      "\"ssl_stapling\" ignored, "
+                      "invalid URL prefix in OCSP responder \"%V\"", &u.url);
+        return NGX_DECLINED;
+    }
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                          "\"ssl_stapling\" ignored, "
+                          "%s in OCSP responder \"%V\"", u.err, &u.url);
+            return NGX_DECLINED;
+        }
+
+        return NGX_ERROR;
+    }
+
+    staple->addrs = u.addrs;
+    staple->host = u.host;
+    staple->uri = u.uri;
+    staple->port = u.port;
+
+    if (staple->uri.len == 0) {
+        ngx_str_set(&staple->uri, "/");
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
+{
+    ngx_ssl_stapling_t  *staple;
+
+    staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index);
+
+    staple->resolver = resolver;
+    staple->resolver_timeout = resolver_timeout;
+
+    return NGX_OK;
+}
+
+
+static int
+ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn, void *data)
+{
+    int                  rc;
+    u_char              *p;
+    ngx_connection_t    *c;
+    ngx_ssl_stapling_t  *staple;
+
+    c = ngx_ssl_get_connection(ssl_conn);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL certificate status callback");
+
+    staple = data;
+    rc = SSL_TLSEXT_ERR_NOACK;
+
+    if (staple->staple.len) {
+        /* we have to copy ocsp response as OpenSSL will free it by itself */
+
+        p = OPENSSL_malloc(staple->staple.len);
+        if (p == NULL) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "OPENSSL_malloc() failed");
+            return SSL_TLSEXT_ERR_NOACK;
+        }
+
+        ngx_memcpy(p, staple->staple.data, staple->staple.len);
+
+        SSL_set_tlsext_status_ocsp_resp(ssl_conn, p, staple->staple.len);
+
+        rc = SSL_TLSEXT_ERR_OK;
+    }
+
+    ngx_ssl_stapling_update(staple);
+
+    return rc;
+}
+
+
+static void
+ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple)
+{
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    if (staple->host.len == 0
+        || staple->loading || staple->valid >= ngx_time())
+    {
+        return;
+    }
+
+    staple->loading = 1;
+
+    ctx = ngx_ssl_ocsp_start();
+    if (ctx == NULL) {
+        return;
+    }
+
+    ctx->cert = staple->cert;
+    ctx->issuer = staple->issuer;
+
+    ctx->addrs = staple->addrs;
+    ctx->host = staple->host;
+    ctx->uri = staple->uri;
+    ctx->port = staple->port;
+    ctx->timeout = staple->timeout;
+
+    ctx->resolver = staple->resolver;
+    ctx->resolver_timeout = staple->resolver_timeout;
+
+    ctx->handler = ngx_ssl_stapling_ocsp_handler;
+    ctx->data = staple;
+
+    ngx_ssl_ocsp_request(ctx);
+
+    return;
+}
+
+
+static void
+ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+    const
+#endif
+    u_char                *p;
+    int                    n;
+    size_t                 len;
+    ngx_str_t              response;
+    X509_STORE            *store;
+    STACK_OF(X509)        *chain;
+    OCSP_CERTID           *id;
+    OCSP_RESPONSE         *ocsp;
+    OCSP_BASICRESP        *basic;
+    ngx_ssl_stapling_t    *staple;
+    ASN1_GENERALIZEDTIME  *thisupdate, *nextupdate;
+
+    staple = ctx->data;
+    ocsp = NULL;
+    basic = NULL;
+    id = NULL;
+
+    if (ctx->code != 200) {
+        goto error;
+    }
+
+    /* check the response */
+
+    len = ctx->response->last - ctx->response->pos;
+    p = ctx->response->pos;
+
+    ocsp = d2i_OCSP_RESPONSE(NULL, &p, len);
+    if (ocsp == NULL) {
+        ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
+                      "d2i_OCSP_RESPONSE() failed");
+        goto error;
+    }
+
+    n = OCSP_response_status(ocsp);
+
+    if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP response not successful (%d: %s)",
+                      n, OCSP_response_status_str(n));
+        goto error;
+    }
+
+    basic = OCSP_response_get1_basic(ocsp);
+    if (basic == NULL) {
+        ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP_response_get1_basic() failed");
+        goto error;
+    }
+
+    store = SSL_CTX_get_cert_store(staple->ssl_ctx);
+    if (store == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "SSL_CTX_get_cert_store() failed");
+        goto error;
+    }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+    SSL_CTX_get_extra_chain_certs(staple->ssl_ctx, &chain);
+#else
+    chain = staple->ssl_ctx->extra_certs;
+#endif
+
+    if (OCSP_basic_verify(basic, chain, store,
+                          staple->verify ? OCSP_TRUSTOTHER : OCSP_NOVERIFY)
+        != 1)
+    {
+        ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP_basic_verify() failed");
+        goto error;
+    }
+
+    id = OCSP_cert_to_id(NULL, ctx->cert, ctx->issuer);
+    if (id == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_cert_to_id() failed");
+        goto error;
+    }
+
+    if (OCSP_resp_find_status(basic, id, &n, NULL, NULL,
+                              &thisupdate, &nextupdate)
+        != 1)
+    {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "certificate status not found in the OCSP response",
+                      n, OCSP_response_status_str(n));
+        goto error;
+    }
+
+    if (n != V_OCSP_CERTSTATUS_GOOD) {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "certificate status \"%s\" in the OCSP response",
+                      n, OCSP_cert_status_str(n));
+        goto error;
+    }
+
+    if (OCSP_check_validity(thisupdate, nextupdate, 300, -1) != 1) {
+        ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP_check_validity() failed");
+        goto error;
+    }
+
+    OCSP_CERTID_free(id);
+    OCSP_BASICRESP_free(basic);
+    OCSP_RESPONSE_free(ocsp);
+
+    /* copy the response to memory not in ctx->pool */
+
+    response.len = len;
+    response.data = ngx_alloc(response.len, ctx->log);
+
+    if (response.data == NULL) {
+        goto done;
+    }
+
+    ngx_memcpy(response.data, ctx->response->pos, response.len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp response, %s, %uz",
+                   OCSP_cert_status_str(n), response.len);
+
+    if (staple->staple.data) {
+        ngx_free(staple->staple.data);
+    }
+
+    staple->staple = response;
+
+done:
+
+    staple->loading = 0;
+    staple->valid = ngx_time() + 3600; /* ssl_stapling_valid */
+
+    ngx_ssl_ocsp_done(ctx);
+    return;
+
+error:
+
+    staple->loading = 0;
+    staple->valid = ngx_time() + 300; /* ssl_stapling_err_valid */
+
+    if (id) {
+        OCSP_CERTID_free(id);
+    }
+
+    if (basic) {
+        OCSP_BASICRESP_free(basic);
+    }
+
+    if (ocsp) {
+        OCSP_RESPONSE_free(ocsp);
+    }
+
+    ngx_ssl_ocsp_done(ctx);
+}
+
+
+static void
+ngx_ssl_stapling_cleanup(void *data)
+{
+    ngx_ssl_stapling_t  *staple = data;
+
+    if (staple->issuer) {
+        X509_free(staple->issuer);
+    }
+
+    if (staple->staple.data) {
+        ngx_free(staple->staple.data);
+    }
+}
+
+
+static ngx_ssl_ocsp_ctx_t *
+ngx_ssl_ocsp_start(void)
+{
+    ngx_log_t           *log;
+    ngx_pool_t          *pool;
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    pool = ngx_create_pool(2048, ngx_cycle->log);
+    if (pool == NULL) {
+        return NULL;
+    }
+
+    ctx = ngx_pcalloc(pool, sizeof(ngx_ssl_ocsp_ctx_t));
+    if (ctx == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    log = ngx_palloc(pool, sizeof(ngx_log_t));
+    if (log == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    ctx->pool = pool;
+
+    *log = *ctx->pool->log;
+
+    ctx->pool->log = log;
+    ctx->log = log;
+
+    log->handler = ngx_ssl_ocsp_log_error;
+    log->data = ctx;
+    log->action = "requesting certificate status";
+
+    return ctx;
+}
+
+
+static void
+ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp done");
+
+    if (ctx->peer.connection) {
+        ngx_close_connection(ctx->peer.connection);
+    }
+
+    ngx_destroy_pool(ctx->pool);
+}
+
+
+static void
+ngx_ssl_ocsp_error(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp error");
+
+    ctx->code = 0;
+    ctx->handler(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_resolver_ctx_t  *resolve, temp;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp request");
+
+    if (ngx_ssl_ocsp_create_request(ctx) != NGX_OK) {
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    if (ctx->resolver) {
+        /* resolve OCSP responder hostname */
+
+        temp.name = ctx->host;
+
+        resolve = ngx_resolve_start(ctx->resolver, &temp);
+        if (resolve == NULL) {
+            ngx_ssl_ocsp_error(ctx);
+            return;
+        }
+
+        if (resolve == NGX_NO_RESOLVER) {
+            ngx_log_error(NGX_LOG_WARN, ctx->log, 0,
+                          "no resolver defined to resolve %V", &ctx->host);
+            goto connect;
+        }
+
+        resolve->name = ctx->host;
+        resolve->type = NGX_RESOLVE_A;
+        resolve->handler = ngx_ssl_ocsp_resolve_handler;
+        resolve->data = ctx;
+        resolve->timeout = ctx->resolver_timeout;
+
+        if (ngx_resolve_name(resolve) != NGX_OK) {
+            ngx_ssl_ocsp_error(ctx);
+            return;
+        }
+
+        return;
+    }
+
+connect:
+
+    ngx_ssl_ocsp_connect(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve)
+{
+    ngx_ssl_ocsp_ctx_t *ctx = resolve->data;
+
+    u_char              *p;
+    size_t               len;
+    in_port_t            port;
+    ngx_uint_t           i;
+    struct sockaddr_in  *sin;
+
+    ngx_log_debug0(NGX_LOG_ALERT, ctx->log, 0,
+                   "ssl ocsp resolve handler");
+
+    if (resolve->state) {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "%V could not be resolved (%i: %s)",
+                      &resolve->name, resolve->state,
+                      ngx_resolver_strerror(resolve->state));
+        goto failed;
+    }
+
+#if (NGX_DEBUG)
+    {
+    in_addr_t   addr;
+
+    for (i = 0; i < resolve->naddrs; i++) {
+        addr = ntohl(resolve->addrs[i]);
+
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                       "name was resolved to %ud.%ud.%ud.%ud",
+                       (addr >> 24) & 0xff, (addr >> 16) & 0xff,
+                       (addr >> 8) & 0xff, addr & 0xff);
+    }
+    }
+#endif
+
+    ctx->naddrs = resolve->naddrs;
+    ctx->addrs = ngx_pcalloc(ctx->pool, ctx->naddrs * sizeof(ngx_addr_t));
+
+    if (ctx->addrs == NULL) {
+        goto failed;
+    }
+
+    port = htons(ctx->port);
+
+    for (i = 0; i < resolve->naddrs; i++) {
+
+        sin = ngx_pcalloc(ctx->pool, sizeof(struct sockaddr_in));
+        if (sin == NULL) {
+            goto failed;
+        }
+
+        sin->sin_family = AF_INET;
+        sin->sin_port = port;
+        sin->sin_addr.s_addr = resolve->addrs[i];
+
+        ctx->addrs[i].sockaddr = (struct sockaddr *) sin;
+        ctx->addrs[i].socklen = sizeof(struct sockaddr_in);
+
+        len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
+
+        p = ngx_pnalloc(ctx->pool, len);
+        if (p == NULL) {
+            goto failed;
+        }
+
+        len = ngx_sock_ntop((struct sockaddr *) sin, p, len, 1);
+
+        ctx->addrs[i].name.len = len;
+        ctx->addrs[i].name.data = p;
+    }
+
+    ngx_resolve_name_done(resolve);
+
+    ngx_ssl_ocsp_connect(ctx);
+    return;
+
+failed:
+
+    ngx_resolve_name_done(resolve);
+    ngx_ssl_ocsp_error(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_int_t    rc;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp connect");
+
+    /* TODO: use all ip addresses */
+
+    ctx->peer.sockaddr = ctx->addrs[0].sockaddr;
+    ctx->peer.socklen = ctx->addrs[0].socklen;
+    ctx->peer.name = &ctx->addrs[0].name;
+    ctx->peer.get = ngx_event_get_peer;
+    ctx->peer.log = ctx->log;
+    ctx->peer.log_error = NGX_ERROR_ERR;
+
+    rc = ngx_event_connect_peer(&ctx->peer);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp connect peer done");
+
+    if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    ctx->peer.connection->data = ctx;
+    ctx->peer.connection->pool = ctx->pool;
+
+    ctx->peer.connection->read->handler = ngx_ssl_ocsp_read_handler;
+    ctx->peer.connection->write->handler = ngx_ssl_ocsp_write_handler;
+
+    ctx->process = ngx_ssl_ocsp_process_status_line;
+
+    ngx_add_timer(ctx->peer.connection->read, ctx->timeout);
+    ngx_add_timer(ctx->peer.connection->write, ctx->timeout);
+
+    if (rc == NGX_OK) {
+        ngx_ssl_ocsp_write_handler(ctx->peer.connection->write);
+        return;
+    }
+}
+
+
+static void
+ngx_ssl_ocsp_write_handler(ngx_event_t *wev)
+{
+    ssize_t              n, size;
+    ngx_connection_t    *c;
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    c = wev->data;
+    ctx = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0,
+                   "ssl ocsp write handler");
+
+    if (wev->timedout) {
+        ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT,
+                      "OCSP responder timed out");
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    size = ctx->request->last - ctx->request->pos;
+
+    n = ngx_send(c, ctx->request->pos, size);
+
+    if (n == NGX_ERROR) {
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    if (n > 0) {
+        ctx->request->pos += n;
+
+        if (n == size) {
+            wev->handler = ngx_ssl_ocsp_dummy_handler;
+
+            if (wev->timer_set) {
+                ngx_del_timer(wev);
+            }
+
+            if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+                ngx_ssl_ocsp_error(ctx);
+            }
+
+            return;
+        }
+    }
+
+    if (!wev->timer_set) {
+        ngx_add_timer(wev, ctx->timeout);
+    }
+}
+
+
+static void
+ngx_ssl_ocsp_read_handler(ngx_event_t *rev)
+{
+    ssize_t            n, size;
+    ngx_int_t          rc;
+    ngx_ssl_ocsp_ctx_t    *ctx;
+    ngx_connection_t  *c;
+
+    c = rev->data;
+    ctx = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0,
+                   "ssl ocsp read handler");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
+                      "OCSP responder timed out");
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    if (ctx->response == NULL) {
+        ctx->response = ngx_create_temp_buf(ctx->pool, 16384);
+        if (ctx->response == NULL) {
+            ngx_ssl_ocsp_error(ctx);
+            return;
+        }
+    }
+
+    for ( ;; ) {
+
+        size = ctx->response->end - ctx->response->last;
+
+        n = ngx_recv(c, ctx->response->last, size);
+
+        if (n > 0) {
+            ctx->response->last += n;
+
+            rc = ctx->process(ctx);
+
+            if (rc == NGX_ERROR) {
+                ngx_ssl_ocsp_error(ctx);
+                return;
+            }
+
+            continue;
+        }
+
+        if (n == NGX_AGAIN) {
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_ssl_ocsp_error(ctx);
+            }
+
+            return;
+        }
+
+        break;
+    }
+
+    ctx->done = 1;
+
+    rc = ctx->process(ctx);
+
+    if (rc == NGX_DONE) {
+        /* ctx->handler() was called */
+        return;
+    }
+
+    ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                  "OCSP responder prematurely closed connection");
+
+    ngx_ssl_ocsp_error(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_dummy_handler(ngx_event_t *ev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "ssl ocsp dummy handler");
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_create_request(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    int            len;
+    u_char        *p;
+    uintptr_t      escape;
+    ngx_str_t      binary, base64;
+    ngx_buf_t     *b;
+    OCSP_CERTID   *id;
+    OCSP_REQUEST  *ocsp;
+
+    ocsp = OCSP_REQUEST_new();
+    if (ocsp == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_REQUEST_new() failed");
+        return NGX_ERROR;
+    }
+
+    id = OCSP_cert_to_id(NULL, ctx->cert, ctx->issuer);
+    if (id == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_cert_to_id() failed");
+        goto failed;
+    }
+
+    if (OCSP_request_add0_id(ocsp, id) == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_request_add0_id() failed");
+        goto failed;
+    }
+
+    len = i2d_OCSP_REQUEST(ocsp, NULL);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "i2d_OCSP_REQUEST() failed");
+        goto failed;
+    }
+
+    binary.len = len;
+    binary.data = ngx_palloc(ctx->pool, len);
+    if (binary.data == NULL) {
+        goto failed;
+    }
+
+    p = binary.data;
+    len = i2d_OCSP_REQUEST(ocsp, &p);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ctx->log, 0,
+                      "i2d_OCSP_REQUEST() failed");
+        goto failed;
+    }
+
+    base64.len = ngx_base64_encoded_length(binary.len);
+    base64.data = ngx_palloc(ctx->pool, base64.len);
+    if (base64.data == NULL) {
+        goto failed;
+    }
+
+    ngx_encode_base64(&base64, &binary);
+
+    escape = ngx_escape_uri(NULL, base64.data, base64.len,
+                            NGX_ESCAPE_URI_COMPONENT);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp request length %z, escape %d",
+                   base64.len, escape);
+
+    len = sizeof("GET ") - 1 + ctx->uri.len + sizeof("/") - 1
+          + base64.len + 2 * escape + sizeof(" HTTP/1.0" CRLF) - 1
+          + sizeof("Host: ") - 1 + ctx->host.len + sizeof(CRLF) - 1
+          + sizeof(CRLF) - 1;
+
+    b = ngx_create_temp_buf(ctx->pool, len);
+    if (b == NULL) {
+        goto failed;
+    }
+
+    p = b->last;
+
+    p = ngx_cpymem(p, "GET ", sizeof("GET ") - 1);
+    p = ngx_cpymem(p, ctx->uri.data, ctx->uri.len);
+
+    if (ctx->uri.data[ctx->uri.len - 1] != '/') {
+        *p++ = '/';
+    }
+
+    if (escape == 0) {
+        p = ngx_cpymem(p, base64.data, base64.len);
+
+    } else {
+        p = (u_char *) ngx_escape_uri(p, base64.data, base64.len,
+                                      NGX_ESCAPE_URI_COMPONENT);
+    }
+
+    p = ngx_cpymem(p, " HTTP/1.0" CRLF, sizeof(" HTTP/1.0" CRLF) - 1);
+    p = ngx_cpymem(p, "Host: ", sizeof("Host: ") - 1);
+    p = ngx_cpymem(p, ctx->host.data, ctx->host.len);
+    *p++ = CR; *p++ = LF;
+
+    /* add "\r\n" at the header end */
+    *p++ = CR; *p++ = LF;
+
+    b->last = p;
+    ctx->request = b;
+
+    return NGX_OK;
+
+failed:
+
+    OCSP_REQUEST_free(ocsp);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_process_status_line(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_int_t  rc;
+
+    rc = ngx_ssl_ocsp_parse_status_line(ctx);
+
+    if (rc == NGX_OK) {
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                       "ssl ocsp status line \"%*s\"",
+                       ctx->response->pos - ctx->response->start,
+                       ctx->response->start);
+#endif
+
+        ctx->process = ngx_ssl_ocsp_process_headers;
+        return ctx->process(ctx);
+    }
+
+    if (rc == NGX_AGAIN) {
+        return NGX_AGAIN;
+    }
+
+    /* rc == NGX_ERROR */
+
+    ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                  "OCSP responder sent invalid response");
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    u_char      ch;
+    u_char     *p;
+    ngx_buf_t  *b;
+    enum {
+        sw_start = 0,
+        sw_H,
+        sw_HT,
+        sw_HTT,
+        sw_HTTP,
+        sw_first_major_digit,
+        sw_major_digit,
+        sw_first_minor_digit,
+        sw_minor_digit,
+        sw_status,
+        sw_space_after_status,
+        sw_status_text,
+        sw_almost_done
+    } state;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp process status line");
+
+    state = ctx->state;
+    b = ctx->response;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* "HTTP/" */
+        case sw_start:
+            switch (ch) {
+            case 'H':
+                state = sw_H;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_H:
+            switch (ch) {
+            case 'T':
+                state = sw_HT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HT:
+            switch (ch) {
+            case 'T':
+                state = sw_HTT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTT:
+            switch (ch) {
+            case 'P':
+                state = sw_HTTP;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTTP:
+            switch (ch) {
+            case '/':
+                state = sw_first_major_digit;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        /* the first digit of major HTTP version */
+        case sw_first_major_digit:
+            if (ch < '1' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            state = sw_major_digit;
+            break;
+
+        /* the major HTTP version or dot */
+        case sw_major_digit:
+            if (ch == '.') {
+                state = sw_first_minor_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            break;
+
+        /* the first digit of minor HTTP version */
+        case sw_first_minor_digit:
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            state = sw_minor_digit;
+            break;
+
+        /* the minor HTTP version or the end of the request line */
+        case sw_minor_digit:
+            if (ch == ' ') {
+                state = sw_status;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            break;
+
+        /* HTTP status code */
+        case sw_status:
+            if (ch == ' ') {
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            ctx->code = ctx->code * 10 + ch - '0';
+
+            if (++ctx->count == 3) {
+                state = sw_space_after_status;
+            }
+
+            break;
+
+        /* space or end of line */
+        case sw_space_after_status:
+            switch (ch) {
+            case ' ':
+                state = sw_status_text;
+                break;
+            case '.':                    /* IIS may send 403.1, 403.2, etc */
+                state = sw_status_text;
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        /* any text until end of line */
+        case sw_status_text:
+            switch (ch) {
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            }
+            break;
+
+        /* end of status line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    b->pos = p;
+    ctx->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    b->pos = p + 1;
+    ctx->state = sw_start;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    size_t     len;
+    ngx_int_t  rc;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp process headers");
+
+    for ( ;; ) {
+        rc = ngx_ssl_ocsp_parse_header_line(ctx);
+
+        if (rc == NGX_OK) {
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                           "ssl ocsp header \"%*s: %*s\"",
+                           ctx->header_name_end - ctx->header_name_start,
+                           ctx->header_name_start,
+                           ctx->header_end - ctx->header_start,
+                           ctx->header_start);
+
+            len = ctx->header_name_end - ctx->header_name_start;
+
+            if (len == sizeof("Content-Type") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Content-Type",
+                                   sizeof("Content-Type") - 1)
+                   == 0)
+            {
+                len = ctx->header_end - ctx->header_start;
+
+                if (len != sizeof("application/ocsp-response") - 1
+                    || ngx_strncasecmp(ctx->header_start,
+                                       (u_char *) "application/ocsp-response",
+                                       sizeof("application/ocsp-response") - 1)
+                       != 0)
+                {
+                    ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                                  "OCSP responder sent invalid "
+                                  "\"Content-Type\" header: \"%*s\"",
+                                  ctx->header_end - ctx->header_start,
+                                  ctx->header_start);
+                    return NGX_ERROR;
+                }
+
+                continue;
+            }
+
+            /* TODO: honor Content-Length */
+
+            continue;
+        }
+
+        if (rc == NGX_DONE) {
+            break;
+        }
+
+        if (rc == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        /* rc == NGX_ERROR */
+
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP responder sent invalid response");
+
+        return NGX_ERROR;
+    }
+
+    ctx->process = ngx_ssl_ocsp_process_body;
+    return ctx->process(ctx);
+}
+
+static ngx_int_t
+ngx_ssl_ocsp_parse_header_line(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    u_char      c, ch, *p;
+    enum {
+        sw_start = 0,
+        sw_name,
+        sw_space_before_value,
+        sw_value,
+        sw_space_after_value,
+        sw_almost_done,
+        sw_header_almost_done
+    } state;
+
+    state = ctx->state;
+
+    for (p = ctx->response->pos; p < ctx->response->last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                       "s:%d in:'%02Xd:%c'", state, ch, ch);
+#endif
+
+        switch (state) {
+
+        /* first char */
+        case sw_start:
+
+            switch (ch) {
+            case CR:
+                ctx->header_end = p;
+                state = sw_header_almost_done;
+                break;
+            case LF:
+                ctx->header_end = p;
+                goto header_done;
+            default:
+                state = sw_name;
+                ctx->header_name_start = p;
+
+                c = (u_char) (ch | 0x20);
+                if (c >= 'a' && c <= 'z') {
+                    break;
+                }
+
+                if (ch >= '0' && ch <= '9') {
+                    break;
+                }
+
+                return NGX_ERROR;
+            }
+            break;
+
+        /* header name */
+        case sw_name:
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                break;
+            }
+
+            if (ch == ':') {
+                ctx->header_name_end = p;
+                state = sw_space_before_value;
+                break;
+            }
+
+            if (ch == '-') {
+                break;
+            }
+
+            if (ch >= '0' && ch <= '9') {
+                break;
+            }
+
+            if (ch == CR) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                goto done;
+            }
+
+            return NGX_ERROR;
+
+        /* space* before header value */
+        case sw_space_before_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                ctx->header_start = p;
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                ctx->header_start = p;
+                ctx->header_end = p;
+                goto done;
+            default:
+                ctx->header_start = p;
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* header value */
+        case sw_value:
+            switch (ch) {
+            case ' ':
+                ctx->header_end = p;
+                state = sw_space_after_value;
+                break;
+            case CR:
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                ctx->header_end = p;
+                goto done;
+            }
+            break;
+
+        /* space* before end of header line */
+        case sw_space_after_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* end of header line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+
+        /* end of header */
+        case sw_header_almost_done:
+            switch (ch) {
+            case LF:
+                goto header_done;
+            default:
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    ctx->response->pos = p;
+    ctx->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    ctx->response->pos = p + 1;
+    ctx->state = sw_start;
+
+    return NGX_OK;
+
+header_done:
+
+    ctx->response->pos = p + 1;
+    ctx->state = sw_start;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp process body");
+
+    if (ctx->done) {
+        ctx->handler(ctx);
+        return NGX_DONE;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+static u_char *
+ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    u_char              *p;
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    p = buf;
+
+    if (log->action) {
+        p = ngx_snprintf(buf, len, " while %s", log->action);
+        len -= p - buf;
+    }
+
+    ctx = log->data;
+
+    if (ctx) {
+        p = ngx_snprintf(p, len, ", responder: %V", &ctx->host);
+    }
+
+    return p;
+}
+
+
+#else
+
+
+ngx_int_t
+ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file,
+    ngx_str_t *responder, ngx_uint_t verify)
+{
+    ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                  "\"ssl_stapling\" ignored, not supported");
+
+    return NGX_OK;
+}
+
+ngx_int_t
+ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
+{
+    return NGX_OK;
+}
+
+
+#endif
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -33,6 +33,8 @@ static char *ngx_http_ssl_enable(ngx_con
 static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 
+static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
+
 
 static ngx_conf_bitmask_t  ngx_http_ssl_protocols[] = {
     { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
@@ -124,6 +126,13 @@ static ngx_command_t  ngx_http_ssl_comma
       offsetof(ngx_http_ssl_srv_conf_t, client_certificate),
       NULL },
 
+    { ngx_string("ssl_trusted_certificate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, trusted_certificate),
+      NULL },
+
     { ngx_string("ssl_prefer_server_ciphers"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
       ngx_conf_set_flag_slot,
@@ -152,13 +161,41 @@ static ngx_command_t  ngx_http_ssl_comma
       offsetof(ngx_http_ssl_srv_conf_t, crl),
       NULL },
 
+    { ngx_string("ssl_stapling"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, stapling),
+      NULL },
+
+    { ngx_string("ssl_stapling_file"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, stapling_file),
+      NULL },
+
+    { ngx_string("ssl_stapling_responder"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, stapling_responder),
+      NULL },
+
+    { ngx_string("ssl_stapling_verify"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, stapling_verify),
+      NULL },
+
       ngx_null_command
 };
 
 
 static ngx_http_module_t  ngx_http_ssl_module_ctx = {
     ngx_http_ssl_add_variables,            /* preconfiguration */
-    NULL,                                  /* postconfiguration */
+    ngx_http_ssl_init,                     /* postconfiguration */
 
     NULL,                                  /* create main configuration */
     NULL,                                  /* init main configuration */
@@ -325,9 +362,12 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t 
      *     sscf->dhparam = { 0, NULL };
      *     sscf->ecdh_curve = { 0, NULL };
      *     sscf->client_certificate = { 0, NULL };
+     *     sscf->trusted_certificate = { 0, NULL };
      *     sscf->crl = { 0, NULL };
      *     sscf->ciphers = { 0, NULL };
      *     sscf->shm_zone = NULL;
+     *     sscf->stapling_file = { 0, NULL };
+     *     sscf->stapling_responder = { 0, NULL };
      */
 
     sscf->enable = NGX_CONF_UNSET;
@@ -336,6 +376,8 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t 
     sscf->verify_depth = NGX_CONF_UNSET_UINT;
     sscf->builtin_session_cache = NGX_CONF_UNSET;
     sscf->session_timeout = NGX_CONF_UNSET;
+    sscf->stapling = NGX_CONF_UNSET;
+    sscf->stapling_verify = NGX_CONF_UNSET;
 
     return sscf;
 }
@@ -380,6 +422,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
 
     ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate,
                          "");
+    ngx_conf_merge_str_value(conf->trusted_certificate,
+                         prev->trusted_certificate, "");
     ngx_conf_merge_str_value(conf->crl, prev->crl, "");
 
     ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve,
@@ -387,6 +431,11 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
 
     ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
 
+    ngx_conf_merge_value(conf->stapling, prev->stapling, 0);
+    ngx_conf_merge_value(conf->stapling_verify, prev->stapling_verify, 0);
+    ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, "");
+    ngx_conf_merge_str_value(conf->stapling_responder,
+                         prev->stapling_responder, "");
 
     conf->ssl.log = cf->log;
 
@@ -479,10 +528,18 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
         {
             return NGX_CONF_ERROR;
         }
+    }
 
-        if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) {
-            return NGX_CONF_ERROR;
-        }
+    if (ngx_ssl_trusted_certificate(cf, &conf->ssl,
+                                    &conf->trusted_certificate,
+                                    conf->verify_depth)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
+    if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) {
+        return NGX_CONF_ERROR;
     }
 
     if (conf->prefer_server_ciphers) {
@@ -515,6 +572,17 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
         return NGX_CONF_ERROR;
     }
 
+    if (conf->stapling) {
+
+        if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file,
+                             &conf->stapling_responder, conf->stapling_verify)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
+    }
+
     return NGX_CONF_OK;
 }
 
@@ -650,3 +718,37 @@ invalid:
 
     return NGX_CONF_ERROR;
 }
+
+
+static ngx_int_t
+ngx_http_ssl_init(ngx_conf_t *cf)
+{
+    ngx_uint_t                   s;
+    ngx_http_ssl_srv_conf_t     *sscf;
+    ngx_http_core_loc_conf_t    *clcf;
+    ngx_http_core_srv_conf_t   **cscfp;
+    ngx_http_core_main_conf_t   *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+    cscfp = cmcf->servers.elts;
+
+    for (s = 0; s < cmcf->servers.nelts; s++) {
+
+        sscf = cscfp[s]->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+
+        if (!sscf->stapling) {
+            continue;
+        }
+
+        clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+        if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, clcf->resolver,
+                                      clcf->resolver_timeout)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
--- a/src/http/modules/ngx_http_ssl_module.h
+++ b/src/http/modules/ngx_http_ssl_module.h
@@ -35,12 +35,18 @@ typedef struct {
     ngx_str_t                       dhparam;
     ngx_str_t                       ecdh_curve;
     ngx_str_t                       client_certificate;
+    ngx_str_t                       trusted_certificate;
     ngx_str_t                       crl;
 
     ngx_str_t                       ciphers;
 
     ngx_shm_zone_t                 *shm_zone;
 
+    ngx_flag_t                      stapling;
+    ngx_flag_t                      stapling_verify;
+    ngx_str_t                       stapling_file;
+    ngx_str_t                       stapling_responder;
+
     u_char                         *file;
     ngx_uint_t                      line;
 } ngx_http_ssl_srv_conf_t;
--- a/src/http/modules/perl/nginx.pm
+++ b/src/http/modules/perl/nginx.pm
@@ -50,7 +50,7 @@ our @EXPORT = qw(
     HTTP_INSUFFICIENT_STORAGE
 );
 
-our $VERSION = '1.3.6';
+our $VERSION = '1.3.7';
 
 require XSLoader;
 XSLoader::load('nginx', $VERSION);
--- a/src/os/unix/ngx_process_cycle.c
+++ b/src/os/unix/ngx_process_cycle.c
@@ -371,6 +371,8 @@ ngx_start_worker_processes(ngx_cycle_t *
 
         ngx_pass_open_channel(cycle, &ch);
     }
+
+    cpu_affinity = 0;
 }
 
 
@@ -384,8 +386,8 @@ ngx_start_cache_manager_processes(ngx_cy
     manager = 0;
     loader = 0;
 
-    path = ngx_cycle->pathes.elts;
-    for (i = 0; i < ngx_cycle->pathes.nelts; i++) {
+    path = ngx_cycle->paths.elts;
+    for (i = 0; i < ngx_cycle->paths.nelts; i++) {
 
         if (path[i]->manager) {
             manager = 1;
@@ -1339,8 +1341,8 @@ ngx_cache_manager_process_handler(ngx_ev
 
     next = 60 * 60;
 
-    path = ngx_cycle->pathes.elts;
-    for (i = 0; i < ngx_cycle->pathes.nelts; i++) {
+    path = ngx_cycle->paths.elts;
+    for (i = 0; i < ngx_cycle->paths.nelts; i++) {
 
         if (path[i]->manager) {
             n = path[i]->manager(path[i]->data);
@@ -1368,8 +1370,8 @@ ngx_cache_loader_process_handler(ngx_eve
 
     cycle = (ngx_cycle_t *) ngx_cycle;
 
-    path = cycle->pathes.elts;
-    for (i = 0; i < cycle->pathes.nelts; i++) {
+    path = cycle->paths.elts;
+    for (i = 0; i < cycle->paths.nelts; i++) {
 
         if (ngx_terminate || ngx_quit) {
             break;