tstellar / rpms / openldap

Forked from rpms/openldap 3 years ago
Clone
Blob Blame History Raw
Mozilla NSS - implement full non-blocking semantics

Resolves: #652822
Upstream ITS: #6714
Author: Rich Megginson (rmeggins@redhat.com)

diff -u -uNPrp openldap-2.4.23/libraries/libldap/tls_m.c openldap-2.4.23.new/libraries/libldap/tls_m.c
--- openldap-2.4.23/libraries/libldap/tls_m.c	2010-11-22 15:50:48.752386500 +0100
+++ openldap-2.4.23.new/libraries/libldap/tls_m.c	2010-11-22 15:53:44.936512466 +0100
@@ -2105,49 +2105,74 @@ struct tls_data {
 	   we will just see if the IO op returns EAGAIN or EWOULDBLOCK,
 	   and just set this flag */
 	PRBool              nonblock;
+	/*
+	 * NSS tries hard to be backwards compatible with SSLv2 clients, or
+	 * clients that send an SSLv2 client hello.  This message is not
+	 * tagged in any way, so NSS has no way to know if the incoming
+	 * message is a valid SSLv2 client hello or just some bogus data
+	 * (or cleartext LDAP).  We store the first byte read from the
+	 * client here.  The most common case will be a client sending
+	 * LDAP data instead of SSL encrypted LDAP data.  This can happen,
+	 * for example, if using ldapsearch -Z - if the starttls fails,
+	 * the client will fallback to plain cleartext LDAP.  So if we
+	 * see that the firstbyte is a valid LDAP tag, we can be
+	 * pretty sure this is happening.
+	 */
+	ber_tag_t           firsttag;
+	/*
+	 * NSS doesn't return SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, etc.
+	 * when it is blocked, so we have to set a flag in the wrapped send
+	 * and recv calls that tells us what operation NSS was last blocked
+	 * on
+	 */
+#define TLSM_READ  1
+#define TLSM_WRITE 2
+	int io_flag;
 };
 
-static int
-tlsm_is_io_ready( PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags )
+static struct tls_data *
+tlsm_get_pvt_tls_data( PRFileDesc *fd )
 {
 	struct tls_data		*p;
-	PRFileDesc *pollfd = NULL;
 	PRFileDesc *myfd;
-	PRPollDesc polldesc;
-	int rc;
+
+	if ( !fd ) {
+		return NULL;
+	}
 
 	myfd = PR_GetIdentitiesLayer( fd, tlsm_layer_id );
 
 	if ( !myfd ) {
-		return 0;
+		return NULL;
 	}
 
 	p = (struct tls_data *)myfd->secret;
 
+	return p;
+}
+
+static int
+tlsm_is_non_ssl_message( PRFileDesc *fd, ber_tag_t *thebyte )
+{
+	struct tls_data		*p;
+
+	if ( thebyte ) {
+		*thebyte = LBER_DEFAULT;
+	}
+
+	p = tlsm_get_pvt_tls_data( fd );
 	if ( p == NULL || p->sbiod == NULL ) {
 		return 0;
 	}
 
-	/* wrap the sockbuf fd with a NSPR FD created especially
-	   for use with polling, and only with polling */
-	pollfd = PR_CreateSocketPollFd( p->sbiod->sbiod_sb->sb_fd );
-	polldesc.fd = pollfd;
-	polldesc.in_flags = in_flags;
-	polldesc.out_flags = 0;
-
-	/* do the poll - no waiting, no blocking */
-	rc = PR_Poll( &polldesc, 1, PR_INTERVAL_NO_WAIT );
-
-	/* unwrap the socket */
-	PR_DestroySocketPollFd( pollfd );
-
-	/* rc will be either 1 if IO is ready, 0 if IO is not
-	   ready, or -1 if there was some error (and the caller
-	   should use PR_GetError() to figure out what */
-	if (out_flags) {
-		*out_flags = polldesc.out_flags;
+	if ( p->firsttag == LBER_SEQUENCE ) {
+		if ( *thebyte ) {
+			*thebyte = p->firsttag;
+		}
+		return 1;
 	}
-	return rc;
+
+	return 0;
 }
 
 static tls_session *
@@ -2157,6 +2182,7 @@ tlsm_session_new ( tls_ctx * ctx, int is
 	tlsm_session *session;
 	PRFileDesc *fd;
 	PRStatus status;
+	int rc;
 
 	c->tc_is_server = is_server;
 	status = PR_CallOnceWithArg( &c->tc_callonce, tlsm_deferred_ctx_init, c );
@@ -2184,121 +2210,80 @@ tlsm_session_new ( tls_ctx * ctx, int is
 		SSL_ConfigServerSessionIDCache( 0, 0, 0, NULL );
 	}
 
+	rc = SSL_ResetHandshake( session, is_server );
+	if ( rc ) {
+		PRErrorCode err = PR_GetError();
+		Debug( LDAP_DEBUG_TRACE, 
+			   "TLS: error: new session - reset handshake failure %d - error %d:%s\n",
+			   rc, err,
+			   err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" );
+		PR_DELETE( fd );
+		PR_Close( session );
+		session = NULL;
+	}
+
 	return (tls_session *)session;
 } 
 
 static int
-tlsm_session_accept( tls_session *session )
+tlsm_session_accept_or_connect( tls_session *session, int is_accept )
 {
 	tlsm_session *s = (tlsm_session *)session;
-	int rc;
-	PRErrorCode err;
-	int waitcounter = 0;
-
-	rc = SSL_ResetHandshake( s, PR_TRUE /* server */ );
-	if (rc) {
-		err = PR_GetError();
-		Debug( LDAP_DEBUG_TRACE, 
-			   "TLS: error: accept - reset handshake failure %d - error %d:%s\n",
-			   rc, err,
-			   err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" );
-	}
+	int rc = SSL_ForceHandshake( s );
+	const char *op = is_accept ? "accept" : "connect";
 
-	do {
-		PRInt32 filesReady;
-		PRInt16 in_flags;
-		PRInt16 out_flags;
-
-		errno = 0;
-		rc = SSL_ForceHandshake( s );
-		if (rc == SECSuccess) {
-			rc = 0;
-			break; /* done */
-		}
-		err = PR_GetError();
-		if ( errno == EAGAIN || errno == EWOULDBLOCK ) {
-			waitcounter++;
-			in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
-			out_flags = 0;
-			errno = 0;
-			filesReady = tlsm_is_io_ready( s, in_flags, &out_flags );
-			if ( filesReady < 0 ) {
-				err = PR_GetError();
-				Debug( LDAP_DEBUG_ANY, 
-					   "TLS: error: accept - error waiting for socket to be ready: %d - error %d:%s\n",
-					   errno, err,
-					   err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" );
-				rc = -1;
-				break; /* hard error */
-			} else if ( out_flags & PR_POLL_NVAL ) {
-				PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
-				Debug( LDAP_DEBUG_ANY, 
-					   "TLS: error: accept failure - invalid socket\n",
-					   NULL, NULL, NULL );
-				rc = -1;
-				break;
-			} else if ( out_flags & PR_POLL_EXCEPT ) {
-				err = PR_GetError();
+	if ( rc ) {
+		PRErrorCode err = PR_GetError();
+		rc = -1;
+		if ( err == PR_WOULD_BLOCK_ERROR ) {
+			ber_tag_t thetag = LBER_DEFAULT;
+			/* see if we are blocked because of a bogus packet */
+			if ( tlsm_is_non_ssl_message( s, &thetag ) ) { /* see if we received a non-SSL message */
 				Debug( LDAP_DEBUG_ANY, 
-					   "TLS: error: accept - error waiting for socket to be ready: %d - error %d:%s\n",
-					   errno, err,
-					   err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" );
-				rc = -1;
-				break; /* hard error */
+					   "TLS: error: %s - error - received non-SSL message [0x%x]\n",
+					   op, (unsigned int)thetag, 0 );
+				/* reset error to something more descriptive */
+				PR_SetError( SSL_ERROR_RX_MALFORMED_HELLO_REQUEST, EPROTO );
 			}
-		} else { /* hard error */
-			err = PR_GetError();
+		} else {
 			Debug( LDAP_DEBUG_ANY, 
-				   "TLS: error: accept - force handshake failure: %d - error %d:%s\n",
-				   errno, err,
-				   err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" );
-			rc = -1;
-			break; /* hard error */
+				   "TLS: error: %s - force handshake failure: errno %d - moznss error %d\n",
+				   op, errno, err );
 		}
-	} while (rc == SECFailure);
-
-	Debug( LDAP_DEBUG_TRACE, 
-		   "TLS: accept completed after %d waits\n", waitcounter, NULL, NULL );
+	}
 
 	return rc;
 }
+static int
+tlsm_session_accept( tls_session *session )
+{
+	return tlsm_session_accept_or_connect( session, 1 );
+}
 
 static int
 tlsm_session_connect( LDAP *ld, tls_session *session )
 {
-	tlsm_session *s = (tlsm_session *)session;
-	int rc;
-	PRErrorCode err;
-
-	rc = SSL_ResetHandshake( s, PR_FALSE /* server */ );
-	if (rc) {
-		err = PR_GetError();
-		Debug( LDAP_DEBUG_TRACE, 
-			   "TLS: error: connect - reset handshake failure %d - error %d:%s\n",
-			   rc, err,
-			   err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" );
-	}
-
-	rc = SSL_ForceHandshake( s );
-	if (rc) {
-		err = PR_GetError();
-		Debug( LDAP_DEBUG_TRACE, 
-			   "TLS: error: connect - force handshake failure %d - error %d:%s\n",
-			   rc, err,
-			   err ? PR_ErrorToString( err, PR_LANGUAGE_I_DEFAULT ) : "unknown" );
-	}
-
-	return rc;
+	return tlsm_session_accept_or_connect( session, 0 );
 }
 
 static int
 tlsm_session_upflags( Sockbuf *sb, tls_session *session, int rc )
 {
-	/* Should never happen */
-	rc = PR_GetError();
+	int prerror = PR_GetError();
+
+	if ( ( prerror == PR_PENDING_INTERRUPT_ERROR ) || ( prerror == PR_WOULD_BLOCK_ERROR ) ) {
+		tlsm_session *s = (tlsm_session *)session;
+		struct tls_data *p = tlsm_get_pvt_tls_data( s );
+
+		if ( p && ( p->io_flag == TLSM_READ ) ) {
+			sb->sb_trans_needs_read = 1;
+			return 1;
+		} else if ( p && ( p->io_flag == TLSM_WRITE ) ) {
+			sb->sb_trans_needs_write = 1;
+			return 1;
+		}
+	}
 
-	if ( rc != PR_PENDING_INTERRUPT_ERROR && rc != PR_WOULD_BLOCK_ERROR )
-		return 0;
 	return 0;
 }
 
@@ -2587,7 +2572,7 @@ tlsm_PR_Recv(PRFileDesc *fd, void *buf, 
 
 	if ( buf == NULL || len <= 0 ) return 0;
 
-	p = (struct tls_data *)fd->secret;
+	p = tlsm_get_pvt_tls_data( fd );
 
 	if ( p == NULL || p->sbiod == NULL ) {
 		return 0;
@@ -2603,7 +2588,10 @@ tlsm_PR_Recv(PRFileDesc *fd, void *buf, 
 			       "TLS: error: tlsm_PR_Recv returned %d - error %d:%s\n",
 			       rc, errno, STRERROR(errno) );
 		}
+	} else if ( ( rc > 0 ) && ( len > 0 ) && ( p->firsttag == LBER_DEFAULT ) ) {
+		p->firsttag = (ber_tag_t)*((char *)buf);
 	}
+	p->io_flag = TLSM_READ;
 
 	return rc;
 }
@@ -2617,7 +2605,7 @@ tlsm_PR_Send(PRFileDesc *fd, const void 
 
 	if ( buf == NULL || len <= 0 ) return 0;
 
-	p = (struct tls_data *)fd->secret;
+	p = tlsm_get_pvt_tls_data( fd );
 
 	if ( p == NULL || p->sbiod == NULL ) {
 		return 0;
@@ -2634,6 +2622,7 @@ tlsm_PR_Send(PRFileDesc *fd, const void 
 			       rc, errno, STRERROR(errno) );
 		}
 	}
+	p->io_flag = TLSM_WRITE;
 
 	return rc;
 }
@@ -2656,7 +2645,7 @@ tlsm_PR_GetPeerName(PRFileDesc *fd, PRNe
 	struct tls_data		*p;
 	ber_socklen_t len;
 
-	p = (struct tls_data *)fd->secret;
+ 	p = tlsm_get_pvt_tls_data( fd );
 
 	if ( p == NULL || p->sbiod == NULL ) {
 		return PR_FAILURE;
@@ -2669,7 +2658,7 @@ static PRStatus PR_CALLBACK
 tlsm_PR_GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data)
 {
 	struct tls_data		*p;
-	p = (struct tls_data *)fd->secret;
+ 	p = tlsm_get_pvt_tls_data( fd );
 
 	if ( !data ) {
 		return PR_FAILURE;
@@ -2804,6 +2793,7 @@ tlsm_sb_setup( Sockbuf_IO_Desc *sbiod, v
 	fd->secret = (PRFilePrivate *)p;
 	p->session = session;
 	p->sbiod = sbiod;
+	p->firsttag = LBER_DEFAULT;
 	sbiod->sbiod_pvt = p;
 	return 0;
 }
@@ -2851,7 +2841,7 @@ tlsm_sb_ctrl( Sockbuf_IO_Desc *sbiod, in
 		return 1;
 		
 	} else if ( opt == LBER_SB_OPT_DATA_READY ) {
-		if ( tlsm_is_io_ready( p->session, PR_POLL_READ, NULL ) > 0 ) {
+		if ( p && ( SSL_DataPending( p->session ) > 0 ) ) {
 			return 1;
 		}