]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
dcesrv_core: add dcesrv_call_state->subreq in order to allow tevent_req_cancel()...
authorStefan Metzmacher <metze@samba.org>
Fri, 24 Nov 2023 13:02:02 +0000 (14:02 +0100)
committerStefan Metzmacher <metze@samba.org>
Tue, 9 Jan 2024 10:21:34 +0000 (10:21 +0000)
Requests might be cancelled if the connection got disconnected,
we got an ORPHANED or CO_CANCEL pdu.

But this is all opt-in for the backends to choose.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Günther Deschner <gd@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
librpc/rpc/dcesrv_core.c
librpc/rpc/dcesrv_core.h

index 24f619d7337ba501f97bfa58344b4f7ae4686a1c..c0a4150e3b3c02b13e9af4928149e9399c9d4788 100644 (file)
@@ -165,6 +165,24 @@ static struct dcesrv_call_state *dcesrv_find_fragmented_call(struct dcesrv_conne
        return NULL;
 }
 
+/*
+  find a pending request
+*/
+static struct dcesrv_call_state *dcesrv_find_pending_call(
+                                       struct dcesrv_connection *dce_conn,
+                                       uint32_t call_id)
+{
+       struct dcesrv_call_state *c = NULL;
+
+       for (c = dce_conn->pending_call_list; c != NULL; c = c->next) {
+               if (c->pkt.call_id == call_id) {
+                       return c;
+               }
+       }
+
+       return NULL;
+}
+
 /*
  * register a principal for an auth_type
  *
@@ -2508,11 +2526,68 @@ static NTSTATUS dcesrv_process_ncacn_packet(struct dcesrv_connection *dce_conn,
                status = dcesrv_request(call);
                break;
        case DCERPC_PKT_CO_CANCEL:
+               existing = dcesrv_find_fragmented_call(dce_conn,
+                                                      call->pkt.call_id);
+               if (existing != NULL) {
+                       /*
+                        * If the call is still waiting for
+                        * more fragments, it's not pending yet,
+                        * for now we just remember we got CO_CANCEL,
+                        * but ignore it otherwise.
+                        *
+                        * This matches what windows is doing...
+                        */
+                       existing->got_co_cancel = true;
+                       SMB_ASSERT(existing->subreq == NULL);
+                       existing = NULL;
+               }
+               existing = dcesrv_find_pending_call(dce_conn,
+                                                   call->pkt.call_id);
+               if (existing != NULL) {
+                       /*
+                        * Give the backend a chance to react
+                        * on CO_CANCEL, but note it's ignored
+                        * by default.
+                        */
+                       existing->got_co_cancel = true;
+                       if (existing->subreq != NULL) {
+                               tevent_req_cancel(existing->subreq);
+                       }
+                       existing = NULL;
+               }
+               status = NT_STATUS_OK;
+               TALLOC_FREE(call);
+               break;
        case DCERPC_PKT_ORPHANED:
-               /*
-                * Window just ignores CO_CANCEL and ORPHANED,
-                * so we do...
-                */
+               existing = dcesrv_find_fragmented_call(dce_conn,
+                                                      call->pkt.call_id);
+               if (existing != NULL) {
+                       /*
+                        * If the call is still waiting for
+                        * more fragments, it's not pending yet,
+                        * for now we just remember we got ORPHANED,
+                        * but ignore it otherwise.
+                        *
+                        * This matches what windows is doing...
+                        */
+                       existing->got_orphaned = true;
+                       SMB_ASSERT(existing->subreq == NULL);
+                       existing = NULL;
+               }
+               existing = dcesrv_find_pending_call(dce_conn,
+                                                   call->pkt.call_id);
+               if (existing != NULL) {
+                       /*
+                        * Give the backend a chance to react
+                        * on ORPHANED, but note it's ignored
+                        * by default.
+                        */
+                       existing->got_orphaned = true;
+                       if (existing->subreq != NULL) {
+                               tevent_req_cancel(existing->subreq);
+                       }
+                       existing = NULL;
+               }
                status = NT_STATUS_OK;
                TALLOC_FREE(call);
                break;
@@ -2796,6 +2871,7 @@ const struct dcesrv_critical_sizes *dcerpc_module_version(void)
 _PUBLIC_ void dcesrv_terminate_connection(struct dcesrv_connection *dce_conn, const char *reason)
 {
        struct dcesrv_context *dce_ctx = dce_conn->dce_ctx;
+       struct dcesrv_call_state *c = NULL, *n = NULL;
        struct dcesrv_auth *a = NULL;
 
        dce_conn->wait_send = NULL;
@@ -2811,6 +2887,7 @@ _PUBLIC_ void dcesrv_terminate_connection(struct dcesrv_connection *dce_conn, co
                a->auth_invalid = true;
        }
 
+no_pending:
        if (dce_conn->pending_call_list == NULL) {
                char *full_reason = talloc_asprintf(dce_conn, "dcesrv: %s", reason);
 
@@ -2831,6 +2908,23 @@ _PUBLIC_ void dcesrv_terminate_connection(struct dcesrv_connection *dce_conn, co
                dce_conn->terminate = "dcesrv: deferred terminating connection - no memory";
        }
        DLIST_ADD_END(dce_ctx->broken_connections, dce_conn);
+
+       for (c = dce_conn->pending_call_list; c != NULL; c = n) {
+               n = c->next;
+
+               c->got_disconnect = true;
+               if (c->subreq != NULL) {
+                       tevent_req_cancel(c->subreq);
+               }
+       }
+
+       if (dce_conn->pending_call_list == NULL) {
+               /*
+                * tevent_req_cancel() was able to made progress
+                * and we don't have pending calls anymore.
+                */
+               goto no_pending;
+       }
 }
 
 _PUBLIC_ void dcesrv_cleanup_broken_connections(struct dcesrv_context *dce_ctx)
index e45c85a7f7f7cdc30fa664e3667293cf506d1e48..3758c8d7de2efb9d14d0a8b78e3dd40998a99224 100644 (file)
@@ -158,6 +158,18 @@ struct dcesrv_call_state {
        struct dcerpc_auth in_auth_info;
        struct dcerpc_auth _out_auth_info;
        struct dcerpc_auth *out_auth_info;
+
+       /*
+        * Optional subreq for pending calls,
+        * will be used to call tevent_req_cancel()
+        * if the connection terminates,
+        * we got an ORPHANED PDU
+        * or got a CO_CANCEL PDU
+        */
+       bool got_disconnect;
+       bool got_orphaned;
+       bool got_co_cancel;
+       struct tevent_req *subreq;
 };
 
 /*