From 1b46f866277a8706fb04fdb4945038fb62a95ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 25 Sep 2017 11:17:04 +0100 Subject: [PATCH] Client TLS support --- servers/lloadd/client.c | 112 ++++++++++++++++++++++++++++++++++++ servers/lloadd/extended.c | 80 +++++++++++++++++++++++++- servers/lloadd/proto-slap.h | 1 + 3 files changed, 192 insertions(+), 1 deletion(-) diff --git a/servers/lloadd/client.c b/servers/lloadd/client.c index fe22f5c606..5c08041b8e 100644 --- a/servers/lloadd/client.c +++ b/servers/lloadd/client.c @@ -240,6 +240,99 @@ handle_one_request( Connection *c ) return handler( c, op ); } +/* + * The connection has a token assigned to it when the callback is set up. + */ +void +client_tls_handshake_cb( evutil_socket_t s, short what, void *arg ) +{ + Connection *c = arg; + int rc = 0; + + CONNECTION_LOCK_DECREF(c); + if ( what & EV_TIMEOUT ) { + Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: " + "connid=%lu, timeout reached, destroying\n", + c->c_connid ); + goto fail; + } + + /* + * In case of StartTLS, make sure we flush the response first. + * Also before we try to read anything from the connection, it isn't + * permitted to Abandon a StartTLS exop per RFC4511 anyway. + */ + ldap_pvt_thread_mutex_lock( &c->c_io_mutex ); + if ( c->c_pendingber ) { + ldap_pvt_thread_mutex_unlock( &c->c_io_mutex ); + CONNECTION_UNLOCK_INCREF(c); + connection_write_cb( s, what, arg ); + ldap_pvt_thread_mutex_lock( &c->c_io_mutex ); + CONNECTION_LOCK_DECREF(c); + + if ( !c->c_live ) { + ldap_pvt_thread_mutex_unlock( &c->c_io_mutex ); + goto fail; + } + + /* Do we still have data pending? If so, connection_write_cb would + * already have arranged the write callback to trigger again */ + if ( c->c_pendingber ) { + ldap_pvt_thread_mutex_unlock( &c->c_io_mutex ); + CONNECTION_UNLOCK_INCREF(c); + return; + } + } + ldap_pvt_thread_mutex_unlock( &c->c_io_mutex ); + + rc = ldap_pvt_tls_accept( c->c_sb, slap_tls_ctx ); + 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, "client_tls_handshake_cb: " + "connid=%lu finished\n", + c->c_connid ); + + c->c_is_tls = LLOAD_TLS_ESTABLISHED; + + /* The temporary reference established for us is no longer needed */ + CONNECTION_UNLOCK_OR_DESTROY(c); + return; + } 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, "client_tls_handshake_cb: " + "connid=%lu need write rc=%d\n", + c->c_connid, rc ); + } + CONNECTION_UNLOCK_INCREF(c); + return; + +fail: + Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: " + "connid=%lu failed rc=%d\n", + c->c_connid, rc ); + CONNECTION_DESTROY(c); +} + Connection * client_init( ber_socket_t s, @@ -266,6 +359,25 @@ client_init( c->c_state = LLOAD_C_READY; + if ( flags & CONN_IS_TLS ) { + int rc; + + c->c_is_tls = LLOAD_LDAPS; + + rc = ldap_pvt_tls_accept( c->c_sb, slap_tls_ctx ); + if ( rc < 0 ) { + Debug( LDAP_DEBUG_CONNS, "client_init: " + "connid=%lu failed initial TLS accept rc=%d\n", + c->c_connid, rc ); + goto fail; + } + + if ( rc ) { + c->c_refcnt++; + read_cb = write_cb = client_tls_handshake_cb; + } + } + event = event_new( base, s, EV_READ|EV_PERSIST, read_cb, c ); if ( !event ) { Debug( LDAP_DEBUG_ANY, "client_init: " diff --git a/servers/lloadd/extended.c b/servers/lloadd/extended.c index c5ed71a765..1348dd4aca 100644 --- a/servers/lloadd/extended.c +++ b/servers/lloadd/extended.c @@ -22,6 +22,82 @@ Avlnode *lload_exop_handlers = NULL; +int +handle_starttls( Connection *c, Operation *op ) +{ + struct event_base *base = event_get_base( c->c_read_event ); + BerElement *output; + char *msg = NULL; + int rc = LDAP_SUCCESS; + + tavl_delete( &c->c_ops, op, operation_client_cmp ); + + if ( c->c_is_tls == LLOAD_TLS_ESTABLISHED ) { + rc = LDAP_OPERATIONS_ERROR; + msg = "TLS layer already in effect"; + } else if ( c->c_state == LLOAD_C_BINDING ) { + rc = LDAP_OPERATIONS_ERROR; + msg = "bind in progress"; + } else if ( c->c_ops ) { + rc = LDAP_OPERATIONS_ERROR; + msg = "cannot start TLS when operations are outstanding"; + } else if ( !slap_tls_ctx ) { + rc = LDAP_UNAVAILABLE; + msg = "Could not initialize TLS"; + } + + Debug( LDAP_DEBUG_STATS, "handle_starttls: " + "handling StartTLS exop connid=%lu rc=%d msg=%s\n", + c->c_connid, rc, msg ); + + if ( rc ) { + /* We've already removed the operation from the queue */ + return operation_send_reject_locked( op, rc, msg, 1 ); + } + + CONNECTION_UNLOCK_INCREF(c); + + event_del( c->c_read_event ); + event_del( c->c_write_event ); + /* + * At this point, we are the only thread handling the connection: + * - there are no upstream operations + * - the I/O callbacks have been successfully removed + * + * This means we can safely reconfigure both I/O events now. + */ + + ldap_pvt_thread_mutex_lock( &c->c_io_mutex ); + output = c->c_pendingber; + if ( output == NULL && (output = ber_alloc()) == NULL ) { + ldap_pvt_thread_mutex_unlock( &c->c_io_mutex ); + CONNECTION_LOCK_DECREF(c); + operation_destroy_from_client( op ); + CONNECTION_DESTROY(c); + return -1; + } + c->c_pendingber = output; + ber_printf( output, "t{tit{ess}}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, op->o_client_msgid, + LDAP_RES_EXTENDED, LDAP_SUCCESS, "", "" ); + ldap_pvt_thread_mutex_unlock( &c->c_io_mutex ); + + CONNECTION_LOCK_DECREF(c); + event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST, + client_tls_handshake_cb, c ); + event_add( c->c_read_event, NULL ); + + event_assign( c->c_write_event, base, c->c_fd, EV_WRITE, + client_tls_handshake_cb, c ); + /* We already have something to write */ + event_add( c->c_write_event, lload_write_timeout ); + + operation_destroy_from_client( op ); + CONNECTION_UNLOCK_INCREF(c); + + return -1; +} + int request_extended( Connection *c, Operation *op ) { @@ -67,7 +143,9 @@ request_extended( Connection *c, Operation *op ) return request_process( c, op ); } -ExopHandler lload_exops[] = { { BER_BVNULL } +ExopHandler lload_exops[] = { + { BER_BVC(LDAP_EXOP_START_TLS), handle_starttls }, + { BER_BVNULL } }; int diff --git a/servers/lloadd/proto-slap.h b/servers/lloadd/proto-slap.h index 45eb2c1da2..22d9eeca63 100644 --- a/servers/lloadd/proto-slap.h +++ b/servers/lloadd/proto-slap.h @@ -74,6 +74,7 @@ LDAP_SLAPD_F (int) handle_vc_bind_response( Operation *op, BerElement *ber ); LDAP_SLAPD_F (int) request_abandon( Connection *c, Operation *op ); LDAP_SLAPD_F (int) request_process( Connection *c, Operation *op ); LDAP_SLAPD_F (int) handle_one_request( Connection *c ); +LDAP_SLAPD_F (void) client_tls_handshake_cb( evutil_socket_t s, short what, void *arg ); LDAP_SLAPD_F (Connection *) client_init( ber_socket_t s, Listener *url, const char *peername, struct event_base *base, int use_tls ); LDAP_SLAPD_F (void) client_reset( Connection *c ); LDAP_SLAPD_F (void) client_destroy( Connection *c );