]> git.sur5r.net Git - openldap/blobdiff - libraries/libldap/tls_m.c
Merge branch 'mdb.master' of ssh://git-master.openldap.org/~git/git/openldap
[openldap] / libraries / libldap / tls_m.c
index f7840b26902d7b50df08e49afc1351afdafa8c8a..c85d322014fa838341f3fefdea9a5f693fadc079 100644 (file)
 #include <nss/secmod.h>
 #include <nss/cert.h>
 
+#undef NSS_VERSION_INT
+#define        NSS_VERSION_INT ((NSS_VMAJOR << 24) | (NSS_VMINOR << 16) | \
+       (NSS_VPATCH << 8) | NSS_VBUILD)
+
 /* NSS 3.12.5 and later have NSS_InitContext */
-#if NSS_VMAJOR <= 3 && NSS_VMINOR <= 12 && NSS_VPATCH < 5
-/* do nothing */
-#else
+#if NSS_VERSION_INT >= 0x030c0500
 #define HAVE_NSS_INITCONTEXT 1
 #endif
 
+/* NSS 3.12.9 and later have SECMOD_RestartModules */
+#if NSS_VERSION_INT >= 0x030c0900
+#define HAVE_SECMOD_RESTARTMODULES 1
+#endif
+
 /* InitContext does not currently work in server mode */
 /* #define INITCONTEXT_HACK 1 */
 
@@ -89,6 +96,7 @@ typedef struct tlsm_ctx {
 #endif
        PK11GenericObject **tc_pem_objs; /* array of objects to free */
        int tc_n_pem_objs; /* number of objects */
+       PRBool tc_warn_only; /* only warn of errors in validation */
 #ifdef LDAP_R_COMPILE
        ldap_pvt_thread_mutex_t tc_refmutex;
 #endif
@@ -122,9 +130,29 @@ static int tlsm_init( void );
 
 #ifdef LDAP_R_COMPILE
 
+/* it doesn't seem guaranteed that a client will call
+   tlsm_thr_init in a non-threaded context - so we have
+   to wrap the mutex creation in a prcallonce
+*/
+static ldap_pvt_thread_mutex_t tlsm_init_mutex;
+static PRCallOnceType tlsm_init_mutex_callonce = {0,0};
+
+static PRStatus PR_CALLBACK
+tlsm_thr_init_callonce( void )
+{
+       if ( ldap_pvt_thread_mutex_init( &tlsm_init_mutex ) ) {
+               Debug( LDAP_DEBUG_ANY,
+                          "TLS: could not create mutex for moznss initialization: %d\n", errno, 0, 0 );
+               return PR_FAILURE;
+       }
+
+       return PR_SUCCESS;
+}
+
 static void
 tlsm_thr_init( void )
 {
+    ( void )PR_CallOnce( &tlsm_init_mutex_callonce, tlsm_thr_init_callonce );
 }
 
 #endif /* LDAP_R_COMPILE */
@@ -664,6 +692,7 @@ tlsm_bad_cert_handler(void *arg, PRFileDesc *ssl)
        case SEC_ERROR_UNTRUSTED_ISSUER:
        case SEC_ERROR_UNKNOWN_ISSUER:
        case SEC_ERROR_EXPIRED_CERTIFICATE:
+       case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
                if (ctx->tc_verify_cert) {
                        success = SECFailure;
                }
@@ -932,11 +961,16 @@ tlsm_cert_is_self_issued( CERTCertificate *cert )
 
 static SECStatus
 tlsm_verify_cert(CERTCertDBHandle *handle, CERTCertificate *cert, void *pinarg,
-                                PRBool checksig, SECCertUsage certUsage, int errorToIgnore )
+                                PRBool checksig, SECCertificateUsage certUsage, int errorToIgnore )
 {
        CERTVerifyLog verifylog;
        SECStatus ret = SECSuccess;
        const char *name;
+       int debug_level = LDAP_DEBUG_ANY;
+
+       if ( errorToIgnore == -1 ) {
+               debug_level = LDAP_DEBUG_TRACE;
+       }
 
        /* the log captures information about every cert in the chain, so we can tell
           which cert caused the problem and what the problem was */
@@ -957,7 +991,7 @@ tlsm_verify_cert(CERTCertDBHandle *handle, CERTCertificate *cert, void *pinarg,
                /* it is possible for CERT_VerifyCertificate return with an error with no logging */
                if ( ret != SECSuccess ) {
                        PRErrorCode errcode = PR_GetError();
-                       Debug( LDAP_DEBUG_ANY,
+                       Debug( debug_level,
                                   "TLS: certificate [%s] is not valid - error %d:%s.\n",
                                   name ? name : "(unknown)",
                                   errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
@@ -987,17 +1021,17 @@ tlsm_verify_cert(CERTCertDBHandle *handle, CERTCertificate *cert, void *pinarg,
                                                           "please fix your certs if possible\n", name, 0, 0 );
                                        } else { /* does not have basicconstraint, or some other error */
                                                ret = SECFailure;
-                                               Debug( LDAP_DEBUG_ANY,
+                                               Debug( debug_level,
                                                           "TLS: certificate [%s] is not valid - CA cert is not valid\n",
                                                           name, 0, 0 );
                                        }
                                } else if ( errorToIgnore && ( node->error == errorToIgnore ) ) {
-                                       Debug( LDAP_DEBUG_ANY,
+                                       Debug( debug_level,
                                                   "TLS: Warning: ignoring error for certificate [%s] - error %ld:%s.\n",
                                                   name, node->error, PR_ErrorToString( node->error, PR_LANGUAGE_I_DEFAULT ) );
                                } else {
                                        ret = SECFailure;
-                                       Debug( LDAP_DEBUG_ANY,
+                                       Debug( debug_level,
                                                   "TLS: certificate [%s] is not valid - error %ld:%s.\n",
                                                   name, node->error, PR_ErrorToString( node->error, PR_LANGUAGE_I_DEFAULT ) );
                                }
@@ -1012,7 +1046,9 @@ tlsm_verify_cert(CERTCertDBHandle *handle, CERTCertificate *cert, void *pinarg,
        if ( ret == SECSuccess ) {
                Debug( LDAP_DEBUG_TRACE,
                           "TLS: certificate [%s] is valid\n", name, 0, 0 );
-       }               
+       } else if ( errorToIgnore == -1 ) {
+               ret = SECSuccess;
+       }
 
        return ret;
 }
@@ -1021,12 +1057,19 @@ static SECStatus
 tlsm_auth_cert_handler(void *arg, PRFileDesc *fd,
                        PRBool checksig, PRBool isServer)
 {
-       SECCertUsage certUsage = isServer ? certUsageSSLClient : certUsageSSLServer;
+       SECCertificateUsage certUsage = isServer ? certificateUsageSSLClient : certificateUsageSSLServer;
        SECStatus ret = SECSuccess;
+       CERTCertificate *peercert = SSL_PeerCertificate( fd );
+       int errorToIgnore = 0;
+       tlsm_ctx *ctx = (tlsm_ctx *)arg;
+
+       if (ctx && ctx->tc_warn_only )
+               errorToIgnore = -1;
 
-       ret = tlsm_verify_cert( (CERTCertDBHandle *)arg, SSL_PeerCertificate( fd ),
+       ret = tlsm_verify_cert( ctx->tc_certdb, peercert,
                                                        SSL_RevealPinArg( fd ),
-                                                       checksig, certUsage, 0 );
+                                                       checksig, certUsage, errorToIgnore );
+       CERT_DestroyCertificate( peercert );
 
        return ret;
 }
@@ -1056,7 +1099,6 @@ tlsm_nss_shutdown_cb( void *appData, void *nssData )
        SECStatus rc = SECSuccess;
 
        SSL_ShutdownServerSessionIDCache();
-       SSL_ClearSessionCache();
 
        if ( pem_module ) {
                SECMOD_UnloadUserModule( pem_module );
@@ -1066,6 +1108,24 @@ tlsm_nss_shutdown_cb( void *appData, void *nssData )
        return rc;
 }
 
+static PRCallOnceType tlsm_register_shutdown_callonce = {0,0};
+static PRStatus PR_CALLBACK
+tlsm_register_nss_shutdown_cb( void )
+{
+       if ( SECSuccess == NSS_RegisterShutdown( tlsm_nss_shutdown_cb,
+                                                                                        NULL ) ) {
+               return PR_SUCCESS;
+       }
+       return PR_FAILURE;
+}
+
+static PRStatus
+tlsm_register_nss_shutdown( void )
+{
+       return PR_CallOnce( &tlsm_register_shutdown_callonce,
+                                               tlsm_register_nss_shutdown_cb );
+}
+
 static int
 tlsm_init_pem_module( void )
 {
@@ -1313,7 +1373,7 @@ static int
 tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir )
 {
        PRBool isca = PR_TRUE;
-       PRStatus status = PR_FAILURE;
+       PRStatus status = PR_SUCCESS;
        PRErrorCode errcode = PR_SUCCESS;
 
        if ( !cacertfile && !cacertdir ) {
@@ -1329,14 +1389,24 @@ tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir
                                   "TLS: %s is not a valid CA certificate file - error %d:%s.\n",
                                   cacertfile, errcode,
                                   PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ) );
+                       /* failure with cacertfile is a hard failure even if cacertdir is
+                          also specified and contains valid CA cert files */
+                       status = PR_FAILURE;
                } else {
                        Debug( LDAP_DEBUG_TRACE,
                                   "TLS: loaded CA certificate file %s.\n",
                                   cacertfile, 0, 0 );
-                       status = PR_SUCCESS; /* have at least one good CA - we can proceed */
                }
        }
 
+       /* if cacertfile above failed, we will return failure, even
+          if there is a valid CA cert in cacertdir - but we still
+          process cacertdir in case the user has enabled trace level
+          debugging so they can see the processing for cacertdir too */
+       /* any cacertdir failures are "soft" failures - if the user specifies
+          no cert checking, then we allow the tls/ssl to continue, no matter
+          what was specified for cacertdir, or the contents of the directory
+          - this is different behavior than that of cacertfile */
        if ( cacertdir ) {
                PRFileInfo fi;
                PRDir *dir;
@@ -1390,7 +1460,6 @@ tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir
                                        Debug( LDAP_DEBUG_TRACE,
                                                   "TLS: loaded CA certificate file %s from CA certificate directory %s.\n",
                                                   fullpath, cacertdir, 0 );
-                                       status = PR_SUCCESS; /* found at least 1 valid CA file in the dir */
                                } else {
                                        errcode = PR_GetError();
                                        Debug( LDAP_DEBUG_TRACE,
@@ -1405,14 +1474,6 @@ tlsm_init_ca_certs( tlsm_ctx *ctx, const char *cacertfile, const char *cacertdir
        }
 done:
        if ( status != PR_SUCCESS ) {
-               const char *fmtstr = NULL;
-               if ( cacertfile && cacertdir ) {
-                       fmtstr = "TLS: did not find any valid CA certificates in %s or %s\n";
-               } else {
-                       fmtstr = "TLS: did not find any valid CA certificates in %s%s\n";
-               }
-               Debug( LDAP_DEBUG_ANY, fmtstr, cacertdir ? cacertdir : "",
-                          cacertfile ? cacertfile : "", 0 );
                return -1;
        }
 
@@ -1482,11 +1543,35 @@ tlsm_deferred_init( void *arg )
        SECStatus rc;
        int done = 0;
 
+#ifdef HAVE_SECMOD_RESTARTMODULES
+       /* NSS enforces the pkcs11 requirement that modules should be unloaded after
+          a fork() - since there is no portable way to determine if NSS has been
+          already initialized in a parent process, we just call SECMOD_RestartModules
+          with force == FALSE - if the module has been unloaded due to a fork, it will
+          be reloaded, otherwise, it is a no-op */
+       if ( SECFailure == ( rc = SECMOD_RestartModules(PR_FALSE /* do not force */) ) ) {
+               errcode = PORT_GetError();
+               if ( errcode != SEC_ERROR_NOT_INITIALIZED ) {
+                       Debug( LDAP_DEBUG_TRACE,
+                                  "TLS: could not restart the security modules: %d:%s\n",
+                                  errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 );
+               } else {
+                       errcode = 1;
+               }
+       }
+#endif
+
 #ifdef HAVE_NSS_INITCONTEXT
        memset( &initParams, 0, sizeof( initParams ) );
        initParams.length = sizeof( initParams );
 #endif /* HAVE_NSS_INITCONTEXT */
 
+#ifdef LDAP_R_COMPILE
+       if ( PR_CallOnce( &tlsm_init_mutex_callonce, tlsm_thr_init_callonce ) ) {
+               return -1;
+       }
+#endif /* LDAP_R_COMPILE */
+
 #ifndef HAVE_NSS_INITCONTEXT
        if ( !NSS_IsInitialized() ) {
 #endif /* HAVE_NSS_INITCONTEXT */
@@ -1514,6 +1599,8 @@ tlsm_deferred_init( void *arg )
                        }
 
                        tlsm_get_certdb_prefix( securitydir, &realcertdir, &prefix );
+                       LDAP_MUTEX_LOCK( &tlsm_init_mutex );
+
 #ifdef HAVE_NSS_INITCONTEXT
 #ifdef INITCONTEXT_HACK
                        if ( !NSS_IsInitialized() && ctx->tc_is_server ) {
@@ -1532,6 +1619,8 @@ tlsm_deferred_init( void *arg )
                        rc = NSS_Initialize( realcertdir, prefix, prefix, SECMOD_DB, NSS_INIT_READONLY );
 #endif
 
+                       LDAP_MUTEX_UNLOCK( &tlsm_init_mutex );
+
                        if ( rc != SECSuccess ) {
                                errcode = PORT_GetError();
                                if ( securitydirs[ii] != lt->lt_cacertdir) {
@@ -1555,6 +1644,7 @@ tlsm_deferred_init( void *arg )
                }
 
                if ( errcode ) { /* no moznss db found, or not using moznss db */
+                       LDAP_MUTEX_LOCK( &tlsm_init_mutex );
 #ifdef HAVE_NSS_INITCONTEXT
                        int flags = NSS_INIT_READONLY|NSS_INIT_NOCERTDB|NSS_INIT_NOMODDB;
 #ifdef INITCONTEXT_HACK
@@ -1573,6 +1663,7 @@ tlsm_deferred_init( void *arg )
 #else
                        rc = NSS_NoDB_Init( NULL );
 #endif
+                       LDAP_MUTEX_UNLOCK( &tlsm_init_mutex );
                        if ( rc != SECSuccess ) {
                                errcode = PORT_GetError();
                                Debug( LDAP_DEBUG_ANY,
@@ -1586,13 +1677,16 @@ tlsm_deferred_init( void *arg )
 #endif
 
                        /* initialize the PEM module */
+                       LDAP_MUTEX_LOCK( &tlsm_init_mutex );
                        if ( tlsm_init_pem_module() ) {
+                               LDAP_MUTEX_UNLOCK( &tlsm_init_mutex );
                                errcode = PORT_GetError();
                                Debug( LDAP_DEBUG_ANY,
                                           "TLS: could not initialize moznss PEM module - error %d:%s.\n",
                                           errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 );
                                return -1;
                        }
+                       LDAP_MUTEX_UNLOCK( &tlsm_init_mutex );
 
                        if ( tlsm_init_ca_certs( ctx, lt->lt_cacertfile, lt->lt_cacertdir ) ) {
                                /* if we tried to use lt->lt_cacertdir as an NSS key/cert db, errcode 
@@ -1627,10 +1721,13 @@ tlsm_deferred_init( void *arg )
                PK11_SetPasswordFunc( tlsm_pin_prompt );
 
                /* register cleanup function */
-               /* delete the old one, if any */
-               NSS_UnregisterShutdown( tlsm_nss_shutdown_cb, NULL );
-               NSS_RegisterShutdown( tlsm_nss_shutdown_cb, NULL );
-
+               if ( tlsm_register_nss_shutdown() ) {
+                       errcode = PORT_GetError();
+                       Debug( LDAP_DEBUG_ANY,
+                                  "TLS: could not register NSS shutdown function: %d:%s\n",
+                                  errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 );
+                       return -1;
+               }
 #ifndef HAVE_NSS_INITCONTEXT
        }
 #endif /* HAVE_NSS_INITCONTEXT */
@@ -1729,6 +1826,8 @@ tlsm_find_and_verify_cert_key(tlsm_ctx *ctx, PRFileDesc *fd, const char *certnam
                SECCertificateUsage certUsage;
                PRBool checkSig = PR_TRUE;
                SECStatus status;
+               /* may not have a CA cert - ok - ignore SEC_ERROR_UNKNOWN_ISSUER */
+               int errorToIgnore = SEC_ERROR_UNKNOWN_ISSUER;
 
                if ( pRetKey ) {
                        *pRetKey = key; /* caller will deal with this */
@@ -1745,9 +1844,11 @@ tlsm_find_and_verify_cert_key(tlsm_ctx *ctx, PRFileDesc *fd, const char *certnam
                } else {
                        checkSig = PR_FALSE;
                }
-               /* may not have a CA cert - ok - ignore SEC_ERROR_UNKNOWN_ISSUER */
+               if ( ctx->tc_warn_only ) {
+                       errorToIgnore = -1;
+               }
                status = tlsm_verify_cert( ctx->tc_certdb, cert, pin_arg,
-                                                                  checkSig, certUsage, SEC_ERROR_UNKNOWN_ISSUER );
+                                                                  checkSig, certUsage, errorToIgnore );
                if ( status == SECSuccess ) {
                        rc = 0;
                }
@@ -1774,14 +1875,26 @@ tlsm_get_client_auth_data( void *arg, PRFileDesc *fd,
 {
        tlsm_ctx *ctx = (tlsm_ctx *)arg;
        int rc;
+       PRBool saveval;
 
        /* don't need caNames - this function will call CERT_VerifyCertificateNow
           which will verify the cert against the known CAs */
+       saveval = ctx->tc_warn_only;
+       ctx->tc_warn_only = PR_TRUE;
        rc = tlsm_find_and_verify_cert_key( ctx, fd, ctx->tc_certname, 0, pRetCert, pRetKey );
+       ctx->tc_warn_only = saveval;
        if ( rc ) {
                Debug( LDAP_DEBUG_ANY,
                           "TLS: error: unable to perform client certificate authentication for "
                           "certificate named %s\n", ctx->tc_certname, 0, 0 );
+               if ( pRetKey && *pRetKey ) {
+                       SECKEY_DestroyPrivateKey( *pRetKey );
+                       *pRetKey = NULL;
+               }
+               if ( pRetCert && *pRetCert ) {
+                       CERT_DestroyCertificate( *pRetCert );
+                       *pRetCert = NULL;
+               }
                return SECFailure;
        }
 
@@ -1800,8 +1913,12 @@ tlsm_clientauth_init( tlsm_ctx *ctx )
 {
        SECStatus status = SECFailure;
        int rc;
+       PRBool saveval;
 
+       saveval = ctx->tc_warn_only;
+       ctx->tc_warn_only = PR_TRUE;
        rc = tlsm_find_and_verify_cert_key( ctx, ctx->tc_model, ctx->tc_certname, 0, NULL, NULL );
+       ctx->tc_warn_only = saveval;
        if ( rc ) {
                Debug( LDAP_DEBUG_ANY,
                           "TLS: error: unable to set up client certificate authentication for "
@@ -1822,6 +1939,9 @@ tlsm_clientauth_init( tlsm_ctx *ctx )
 static void
 tlsm_destroy( void )
 {
+#ifdef LDAP_R_COMPILE
+       ldap_pvt_thread_mutex_destroy( &tlsm_init_mutex );
+#endif
 }
 
 static tls_ctx *
@@ -1850,6 +1970,7 @@ tlsm_ctx_new ( struct ldapoptions *lo )
 #endif /* HAVE_NSS_INITCONTEXT */
                ctx->tc_pem_objs = NULL;
                ctx->tc_n_pem_objs = 0;
+               ctx->tc_warn_only = PR_FALSE;
        }
        return (tls_ctx *)ctx;
 }
@@ -1886,8 +2007,16 @@ tlsm_ctx_free ( tls_ctx *ctx )
        PL_strfree( c->tc_slotname );           
        tlsm_free_pem_objs( c );
 #ifdef HAVE_NSS_INITCONTEXT
-       if (c->tc_initctx)
-               NSS_ShutdownContext( c->tc_initctx );
+       if ( c->tc_initctx ) {
+               LDAP_MUTEX_LOCK( &tlsm_init_mutex );
+               if ( NSS_ShutdownContext( c->tc_initctx ) ) {
+                       PRErrorCode errcode = PR_GetError();
+                       Debug( LDAP_DEBUG_ANY,
+                                  "TLS: could not shutdown NSS - error %d:%s.\n",
+                                  errcode, PR_ErrorToString( errcode, PR_LANGUAGE_I_DEFAULT ), 0 );
+               }
+               LDAP_MUTEX_UNLOCK( &tlsm_init_mutex );
+       }
        c->tc_initctx = NULL;
 #endif /* HAVE_NSS_INITCONTEXT */
 #ifdef LDAP_R_COMPILE
@@ -2011,7 +2140,9 @@ tlsm_deferred_ctx_init( void *arg )
                return -1;
        }
 
-       if ( ctx->tc_require_cert ) {
+       if ( !ctx->tc_require_cert ) {
+               ctx->tc_verify_cert = PR_FALSE;
+       } else if ( !ctx->tc_is_server ) {
                request_cert = PR_TRUE;
                require_cert = SSL_REQUIRE_NO_ERROR;
                if ( ctx->tc_require_cert == LDAP_OPT_X_TLS_DEMAND ||
@@ -2020,8 +2151,22 @@ tlsm_deferred_ctx_init( void *arg )
                }
                if ( ctx->tc_require_cert != LDAP_OPT_X_TLS_ALLOW )
                        ctx->tc_verify_cert = PR_TRUE;
-       } else {
-               ctx->tc_verify_cert = PR_FALSE;
+       } else { /* server */
+               /* server does not request certs by default */
+               /* if allow - client may send cert, server will ignore if errors */
+               /* if try - client may send cert, server will error if bad cert */
+               /* if hard or demand - client must send cert, server will error if bad cert */
+               request_cert = PR_TRUE;
+               require_cert = SSL_REQUIRE_NO_ERROR;
+               if ( ctx->tc_require_cert == LDAP_OPT_X_TLS_DEMAND ||
+                    ctx->tc_require_cert == LDAP_OPT_X_TLS_HARD ) {
+                       require_cert = SSL_REQUIRE_ALWAYS;
+               }
+               if ( ctx->tc_require_cert != LDAP_OPT_X_TLS_ALLOW ) {
+                       ctx->tc_verify_cert = PR_TRUE;
+               } else {
+                       ctx->tc_warn_only = PR_TRUE;
+               }
        }
 
        if ( SECSuccess != SSL_OptionSet( ctx->tc_model, SSL_REQUEST_CERTIFICATE, request_cert ) ) {
@@ -2133,6 +2278,8 @@ tlsm_deferred_ctx_init( void *arg )
                        Debug( LDAP_DEBUG_ANY, 
                               "TLS: error: unable to find and verify server's cert and key for certificate %s\n",
                               ctx->tc_certname, 0, 0 );
+                       CERT_DestroyCertificate( serverCert );
+                       SECKEY_DestroyPrivateKey( serverKey );
                        return -1;
                }
 
@@ -2154,7 +2301,7 @@ tlsm_deferred_ctx_init( void *arg )
 
        /* Callback for authenticating certificate */
        if ( SSL_AuthCertificateHook( ctx->tc_model, tlsm_auth_cert_handler,
-                                  ctx->tc_certdb ) != SECSuccess ) {
+                                  ctx ) != SECSuccess ) {
                PRErrorCode err = PR_GetError();
                Debug( LDAP_DEBUG_ANY, 
                       "TLS: error: could not set auth cert handler for moznss - error %d:%s\n",
@@ -2247,7 +2394,7 @@ tlsm_is_non_ssl_message( PRFileDesc *fd, ber_tag_t *thebyte )
        }
 
        if ( p->firsttag == LBER_SEQUENCE ) {
-               if ( *thebyte ) {
+               if ( thebyte ) {
                        *thebyte = p->firsttag;
                }
                return 1;
@@ -2744,7 +2891,7 @@ tlsm_PR_GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data)
        struct tls_data         *p;
        p = tlsm_get_pvt_tls_data( fd );
 
-       if ( !data ) {
+       if ( p == NULL || data == NULL ) {
                return PR_FAILURE;
        }
 
@@ -2847,10 +2994,29 @@ static const PRIOMethods tlsm_PR_methods = {
 static int
 tlsm_init( void )
 {
+       char *nofork = PR_GetEnv( "NSS_STRICT_NOFORK" );
+
        PR_Init(0, 0, 0);
 
        tlsm_layer_id = PR_GetUniqueIdentity( "OpenLDAP" );
 
+       /*
+        * There are some applications that acquire a crypto context in the parent process
+        * and expect that crypto context to work after a fork().  This does not work
+        * with NSS using strict PKCS11 compliance mode.  We set this environment
+        * variable here to tell the software encryption module/token to allow crypto
+        * contexts to persist across a fork().  However, if you are using some other
+        * module or encryption device that supports and expects full PKCS11 semantics,
+        * the only recourse is to rewrite the application with atfork() handlers to save
+        * the crypto context in the parent and restore (and SECMOD_RestartModules) the
+        * context in the child.
+        */
+       if ( !nofork ) {
+               /* will leak one time */
+               char *noforkenvvar = PL_strdup( "NSS_STRICT_NOFORK=DISABLED" );
+               PR_SetEnv( noforkenvvar );
+       }
+
        return 0;
 }