From eb72f887b0bf91c050fd5d911f58a1b3ff9b8bcc Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Mon, 10 Aug 2020 16:24:04 +0200 Subject: [PATCH] ldap_server: Terminate LDAP connections on krb ticket expiry See RFC4511 section 4.4.1 and https://lists.samba.org/archive/cifs-protocol/2020-August/003515.html for details: Windows terminates LDAP connections when the krb5 ticket expires, Samba should do the same. This patch slightly deviates from Windows behaviour by sending a LDAP exop response with msgid 0 that is ASN1-encoded conforming to RFC4511. Bug: https://bugzilla.samba.org/show_bug.cgi?id=14465 Signed-off-by: Volker Lendecke Reviewed-by: Stefan Metzmacher --- selftest/knownfail.d/ldap | 1 - source4/ldap_server/ldap_backend.c | 37 ++++++++++++++++++ source4/ldap_server/ldap_server.c | 62 ++++++++++++++++++++++++++++++ source4/ldap_server/ldap_server.h | 1 + 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/selftest/knownfail.d/ldap b/selftest/knownfail.d/ldap index 21c1aca7e6e..0331d3687d4 100644 --- a/selftest/knownfail.d/ldap +++ b/selftest/knownfail.d/ldap @@ -1,4 +1,3 @@ # the attributes too long test returns the wrong error ^samba4.ldap.python.+test_attribute_ranges_too_long samba4.ldap.python\(ad_dc_default\).*__main__.BasicTests.test_ldapSearchNoAttributes -^samba4.ldap.session-expiry.session-expiry\(ad_dc_default\) diff --git a/source4/ldap_server/ldap_backend.c b/source4/ldap_server/ldap_backend.c index bf724335a25..2839082daef 100644 --- a/source4/ldap_server/ldap_backend.c +++ b/source4/ldap_server/ldap_backend.c @@ -1384,11 +1384,48 @@ static NTSTATUS ldapsrv_AbandonRequest(struct ldapsrv_call *call) return NT_STATUS_OK; } +static NTSTATUS ldapsrv_expired(struct ldapsrv_call *call) +{ + struct ldapsrv_reply *reply = NULL; + struct ldap_ExtendedResponse *r = NULL; + + DBG_DEBUG("Sending connection expired message\n"); + + reply = ldapsrv_init_reply(call, LDAP_TAG_ExtendedResponse); + if (reply == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * According to RFC4511 section 4.4.1 this has a msgid of 0 + */ + reply->msg->messageid = 0; + + r = &reply->msg->r.ExtendedResponse; + r->response.resultcode = LDB_ERR_UNAVAILABLE; + r->response.errormessage = "The server has timed out this connection"; + r->oid = "1.3.6.1.4.1.1466.20036"; /* see rfc4511 section 4.4.1 */ + + ldapsrv_queue_reply(call, reply); + return NT_STATUS_OK; +} + NTSTATUS ldapsrv_do_call(struct ldapsrv_call *call) { unsigned int i; struct ldap_message *msg = call->request; + struct ldapsrv_connection *conn = call->conn; NTSTATUS status; + bool expired; + + expired = timeval_expired(&conn->limits.expire_time); + if (expired) { + status = ldapsrv_expired(call); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return NT_STATUS_NETWORK_SESSION_EXPIRED; + } /* Check for undecoded critical extensions */ for (i=0; msg->controls && msg->controls[i]; i++) { diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c index 036dfaab5ae..5cd72d3e258 100644 --- a/source4/ldap_server/ldap_server.c +++ b/source4/ldap_server/ldap_server.c @@ -69,6 +69,7 @@ static void ldapsrv_terminate_connection(struct ldapsrv_connection *conn, tevent_queue_stop(conn->sockets.send_queue); TALLOC_FREE(conn->sockets.read_req); + TALLOC_FREE(conn->deferred_expire_disconnect); if (conn->active_call) { tevent_req_cancel(conn->active_call); conn->active_call = NULL; @@ -1014,16 +1015,62 @@ static struct tevent_req *ldapsrv_process_call_send(TALLOC_CTX *mem_ctx, return req; } +static void ldapsrv_disconnect_ticket_expired(struct tevent_req *subreq); + static void ldapsrv_process_call_trigger(struct tevent_req *req, void *private_data) { struct ldapsrv_process_call_state *state = tevent_req_data(req, struct ldapsrv_process_call_state); + struct ldapsrv_connection *conn = state->call->conn; NTSTATUS status; + if (conn->deferred_expire_disconnect != NULL) { + /* + * Just drop this on the floor + */ + tevent_req_done(req); + return; + } + /* make the call */ status = ldapsrv_do_call(state->call); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) { + /* + * For testing purposes, defer the TCP disconnect + * after having sent the msgid 0 + * 1.3.6.1.4.1.1466.20036 exop response. LDAP clients + * should not wait for the TCP connection to close but + * handle this packet equivalent to a TCP + * disconnect. This delay enables testing both cases + * in LDAP client libraries. + */ + + int defer_msec = lpcfg_parm_int( + conn->lp_ctx, + NULL, + "ldap_server", + "delay_expire_disconnect", + 0); + + conn->deferred_expire_disconnect = tevent_wakeup_send( + conn, + conn->connection->event.ctx, + timeval_current_ofs_msec(defer_msec)); + if (tevent_req_nomem(conn->deferred_expire_disconnect, req)) { + return; + } + tevent_req_set_callback( + conn->deferred_expire_disconnect, + ldapsrv_disconnect_ticket_expired, + conn); + + tevent_req_done(req); + return; + } + if (!NT_STATUS_IS_OK(status)) { tevent_req_nterror(req, status); return; @@ -1032,6 +1079,21 @@ static void ldapsrv_process_call_trigger(struct tevent_req *req, tevent_req_done(req); } +static void ldapsrv_disconnect_ticket_expired(struct tevent_req *subreq) +{ + struct ldapsrv_connection *conn = tevent_req_callback_data( + subreq, struct ldapsrv_connection); + bool ok; + + ok = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (!ok) { + DBG_WARNING("tevent_wakeup_recv failed\n"); + } + conn->deferred_expire_disconnect = NULL; + ldapsrv_terminate_connection(conn, "network session expired"); +} + static NTSTATUS ldapsrv_process_call_recv(struct tevent_req *req) { NTSTATUS status; diff --git a/source4/ldap_server/ldap_server.h b/source4/ldap_server/ldap_server.h index 74c19fd2fbc..c94bd914b9b 100644 --- a/source4/ldap_server/ldap_server.h +++ b/source4/ldap_server/ldap_server.h @@ -66,6 +66,7 @@ struct ldapsrv_connection { } limits; struct tevent_req *active_call; + struct tevent_req *deferred_expire_disconnect; struct ldapsrv_call *pending_calls; };