362 lines
10 KiB
Diff
362 lines
10 KiB
Diff
|
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;
|
||
|
}
|
||
|
|