diff --git a/servers/lloadd/config.c b/servers/lloadd/config.c index 1417368c35..a0bdc9853c 100644 --- a/servers/lloadd/config.c +++ b/servers/lloadd/config.c @@ -569,13 +569,14 @@ config_backend( ConfigArgs *c ) } #else /* HAVE_TLS */ + /* Specifying ldaps:// overrides starttls= settings */ tmp = ldap_pvt_url_scheme2tls( lud->lud_scheme ); if ( tmp ) { b->b_tls = LLOAD_LDAPS; } if ( !lud->lud_port ) { - b->b_port = b->b_tls ? LDAPS_PORT : LDAP_PORT; + b->b_port = tmp ? LDAPS_PORT : LDAP_PORT; } else { b->b_port = lud->lud_port; } @@ -1829,9 +1830,9 @@ config_push_cleanup( ConfigArgs *ca, ConfigDriver *cleanup ) } static slap_verbmasks tlskey[] = { - { BER_BVC("no"), SB_TLS_OFF }, - { BER_BVC("yes"), SB_TLS_ON }, - { BER_BVC("critical"), SB_TLS_CRITICAL }, + { BER_BVC("no"), LLOAD_CLEARTEXT }, + { BER_BVC("yes"), LLOAD_STARTTLS_OPTIONAL }, + { BER_BVC("critical"), LLOAD_STARTTLS }, { BER_BVNULL, 0 } }; @@ -1955,6 +1956,7 @@ static slap_cf_aux_table backendkey[] = { { BER_BVC("max-pending-ops="), offsetof(Backend, b_max_pending), 'i', 0, NULL }, { BER_BVC("conn-max-pending="), offsetof(Backend, b_max_conn_pending), 'i', 0, NULL }, + { BER_BVC("starttls="), offsetof(Backend, b_tls), 'i', 0, tlskey }, { BER_BVNULL, 0, 0, 0, NULL } }; @@ -1971,7 +1973,6 @@ static slap_cf_aux_table bindkey[] = { { BER_BVC("authzID="), offsetof(slap_bindconf, sb_authzId), 'b', 1, NULL }, { BER_BVC("keepalive="), offsetof(slap_bindconf, sb_keepalive), 'x', 0, (slap_verbmasks *)slap_keepalive_parse }, #ifdef HAVE_TLS - { BER_BVC("starttls="), offsetof(slap_bindconf, sb_tls), 'i', 0, tlskey }, { BER_BVC("tls_cert="), offsetof(slap_bindconf, sb_tls_cert), 's', 1, NULL }, { BER_BVC("tls_key="), offsetof(slap_bindconf, sb_tls_key), 's', 1, NULL }, { BER_BVC("tls_cacert="), offsetof(slap_bindconf, sb_tls_cacert), 's', 1, NULL }, diff --git a/servers/lloadd/connection.c b/servers/lloadd/connection.c index 2b1a43326e..bacb93f5eb 100644 --- a/servers/lloadd/connection.c +++ b/servers/lloadd/connection.c @@ -335,10 +335,6 @@ connection_init( ber_socket_t s, const char *peername, int flags ) assert( peername != NULL ); -#ifndef HAVE_TLS - assert( !(flags & CONN_IS_TLS) ); -#endif - if ( s == AC_SOCKET_INVALID ) { Debug( LDAP_DEBUG_ANY, "connection_init: " "init of socket fd=%ld invalid\n", @@ -362,10 +358,6 @@ connection_init( ber_socket_t s, const char *peername, int flags ) #endif ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_fd, LBER_SBIOD_LEVEL_PROVIDER, (void *)&s ); -#ifdef LDAP_PF_LOCAL_SENDMSG - if ( !BER_BVISEMPTY( peerbv ) ) - ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_UNGET_BUF, peerbv ); -#endif } else #endif /* LDAP_PF_LOCAL */ { @@ -382,18 +374,6 @@ connection_init( ber_socket_t s, const char *peername, int flags ) c->c_sb, &ber_sockbuf_io_debug, INT_MAX, (void *)"lload_" ); #endif -#ifdef HAVE_TLS - if ( flags & CONN_IS_TLS ) { - /* TODO: will need an asynchronous TLS implementation in libldap */ - assert(0); - c->c_is_tls = 1; - c->c_needs_tls_accept = 1; - } else { - c->c_is_tls = 0; - c->c_needs_tls_accept = 0; - } -#endif - c->c_next_msgid = 1; c->c_refcnt = c->c_live = 1; c->c_destroy = connection_destroy; diff --git a/servers/lloadd/slap.h b/servers/lloadd/slap.h index 396458e2cb..f131687abb 100644 --- a/servers/lloadd/slap.h +++ b/servers/lloadd/slap.h @@ -244,7 +244,9 @@ typedef enum { enum lload_tls_type { LLOAD_CLEARTEXT = 0, LLOAD_LDAPS, + LLOAD_STARTTLS_OPTIONAL, LLOAD_STARTTLS, + LLOAD_TLS_ESTABLISHED, }; struct PendingConnection { @@ -405,7 +407,6 @@ struct Connection { #ifdef HAVE_TLS enum lload_tls_type c_is_tls; /* true if this LDAP over raw TLS */ - char c_needs_tls_accept; /* true if SSL_accept should be called */ #endif long c_n_ops_executing; /* num of ops currently executing */ diff --git a/servers/lloadd/upstream.c b/servers/lloadd/upstream.c index c64f6258d9..49ae368fa5 100644 --- a/servers/lloadd/upstream.c +++ b/servers/lloadd/upstream.c @@ -426,6 +426,180 @@ upstream_finish( Connection *c ) return rc; } +static void +upstream_tls_handshake_cb( evutil_socket_t s, short what, void *arg ) +{ + Connection *c = arg; + Backend *b; + int rc = LDAP_SUCCESS; + + CONNECTION_LOCK(c); + if ( what & EV_TIMEOUT ) { + Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: " + "connid=%lu, timeout reached, destroying\n", + c->c_connid ); + goto fail; + } + b = c->c_private; + + rc = ldap_pvt_tls_connect( slap_tls_ld, c->c_sb, b->b_host ); + if ( rc < 0 ) { + goto fail; + } + + if ( rc == 0 ) { + struct event_base *base = event_get_base( c->c_read_event ); + + /* + * We're finished, replace the callbacks + * + * This is deadlock-safe, since both share the same base - the one + * that's just running us. + */ + event_del( c->c_read_event ); + event_del( c->c_write_event ); + + event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST, + connection_read_cb, c ); + event_add( c->c_read_event, NULL ); + + event_assign( c->c_write_event, base, c->c_fd, EV_WRITE, + connection_write_cb, c ); + Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: " + "connid=%lu finished\n", + c->c_connid ); + c->c_is_tls = LLOAD_TLS_ESTABLISHED; + + CONNECTION_UNLOCK_INCREF(c); + ldap_pvt_thread_mutex_lock( &b->b_mutex ); + CONNECTION_LOCK_DECREF(c); + + rc = upstream_finish( c ); + + ldap_pvt_thread_mutex_unlock( &b->b_mutex ); + + if ( rc == LDAP_SUCCESS ) { + backend_retry( b ); + } + } else if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL ) ) { + event_add( c->c_write_event, lload_write_timeout ); + Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: " + "connid=%lu need write rc=%d\n", + c->c_connid, rc ); + } + CONNECTION_UNLOCK_OR_DESTROY(c); + return; + +fail: + Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: " + "connid=%lu failed rc=%d\n", + c->c_connid, rc ); + CONNECTION_DESTROY(c); +} + +static int +upstream_starttls( Connection *c ) +{ + BerValue matcheddn, message, responseOid, + startTLSOid = BER_BVC(LDAP_EXOP_START_TLS); + BerElement *ber = c->c_currentber; + struct event_base *base; + ber_int_t msgid, result; + ber_tag_t tag; + + c->c_currentber = NULL; + + if ( ber_scanf( ber, "it", &msgid, &tag ) == LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "upstream_starttls: " + "protocol violation from server\n" ); + goto fail; + } + + if ( msgid != ( c->c_next_msgid - 1 ) || tag != LDAP_RES_EXTENDED ) { + Debug( LDAP_DEBUG_ANY, "upstream_starttls: " + "unexpected %s from server, msgid=%d\n", + slap_msgtype2str( tag ), msgid ); + goto fail; + } + + if ( ber_scanf( ber, "{emm}", &result, &matcheddn, &message ) == + LBER_ERROR ) { + Debug( LDAP_DEBUG_ANY, "upstream_starttls: " + "protocol violation on StartTLS response\n" ); + goto fail; + } + + if ( (tag = ber_get_tag( ber )) != LBER_DEFAULT ) { + if ( tag != LDAP_TAG_EXOP_RES_OID || + ber_scanf( ber, "{m}", &responseOid ) == LBER_DEFAULT ) { + Debug( LDAP_DEBUG_ANY, "upstream_starttls: " + "protocol violation on StartTLS response\n" ); + goto fail; + } + + if ( ber_bvcmp( &responseOid, &startTLSOid ) ) { + Debug( LDAP_DEBUG_ANY, "upstream_starttls: " + "oid=%s not a StartTLS response\n", + responseOid.bv_val ); + goto fail; + } + } + + if ( result != LDAP_SUCCESS ) { + Backend *b = c->c_private; + int rc; + + Debug( LDAP_DEBUG_STATS, "upstream_starttls: " + "server doesn't support StartTLS rc=%d message='%s'%s\n", + result, message.bv_val, + (c->c_is_tls == LLOAD_STARTTLS_OPTIONAL) ? ", ignored" : "" ); + if ( c->c_is_tls != LLOAD_STARTTLS_OPTIONAL ) { + goto fail; + } + c->c_is_tls = LLOAD_CLEARTEXT; + + ber_free( ber, 1 ); + + CONNECTION_UNLOCK_INCREF(c); + ldap_pvt_thread_mutex_lock( &b->b_mutex ); + CONNECTION_LOCK_DECREF(c); + + rc = upstream_finish( c ); + + ldap_pvt_thread_mutex_unlock( &b->b_mutex ); + + if ( rc == LDAP_SUCCESS ) { + backend_retry( b ); + } + + CONNECTION_UNLOCK_OR_DESTROY(c); + return rc; + } + + base = event_get_base( c->c_read_event ); + + event_del( c->c_read_event ); + event_del( c->c_write_event ); + + event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST, + upstream_tls_handshake_cb, c ); + event_assign( c->c_write_event, base, c->c_fd, EV_WRITE, + upstream_tls_handshake_cb, c ); + + event_add( c->c_read_event, NULL ); + event_add( c->c_write_event, lload_write_timeout ); + + CONNECTION_UNLOCK(c); + + ber_free( ber, 1 ); + return -1; + +fail: + ber_free( ber, 1 ); + CONNECTION_DESTROY(c); + return -1; +} + /* * We must already hold b->b_mutex when called. */ @@ -439,7 +613,7 @@ upstream_init( ber_socket_t s, Backend *b ) assert( b != NULL ); - flags = (b->b_tls == LLOAD_LDAPS) ? CONN_IS_TLS : 0; + flags = (b->b_proto == LDAP_PROTO_IPC) ? CONN_IS_IPC : 0; if ( (c = connection_init( s, b->b_host, flags )) == NULL ) { return NULL; } @@ -473,11 +647,37 @@ upstream_init( ber_socket_t s, Backend *b ) /* We only add the write event when we have data pending */ c->c_write_event = event; - rc = upstream_finish( c ); - if ( rc < 0 ) { - goto fail; - } + if ( c->c_is_tls == LLOAD_CLEARTEXT ) { + rc = upstream_finish( c ); + if ( rc < 0 ) { + goto fail; + } + } else if ( c->c_is_tls == LLOAD_LDAPS ) { + event_assign( c->c_read_event, base, s, EV_READ|EV_PERSIST, + upstream_tls_handshake_cb, c ); + event_assign( c->c_write_event, base, s, EV_WRITE, + upstream_tls_handshake_cb, c ); + event_add( c->c_write_event, lload_write_timeout ); + } else if ( c->c_is_tls == LLOAD_STARTTLS || + c->c_is_tls == LLOAD_STARTTLS_OPTIONAL ) { + BerElement *output; + ldap_pvt_thread_mutex_lock( &c->c_io_mutex ); + if ( (output = c->c_pendingber = ber_alloc()) == NULL ) { + ldap_pvt_thread_mutex_unlock( &c->c_io_mutex ); + goto fail; + } + ber_printf( output, "t{tit{ts}}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, c->c_next_msgid++, + LDAP_REQ_EXTENDED, + LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_START_TLS ); + ldap_pvt_thread_mutex_unlock( &c->c_io_mutex ); + + c->c_pdu_cb = upstream_starttls; + CONNECTION_UNLOCK_INCREF(c); + connection_write_cb( s, 0, c ); + CONNECTION_LOCK_DECREF(c); + } event_add( c->c_read_event, NULL ); c->c_destroy = upstream_destroy;