rpc_server3: Use fdopen_keepfd()
[samba.git] / source3 / rpc_server / rpc_worker.c
index 9a547fcbdfe8710356d9f4e3a1cff855687d9d3c..81c7a90972180391a53fd6e2b3412e80c79ecfce 100644 (file)
@@ -24,6 +24,7 @@
 #include "source3/librpc/gen_ndr/ndr_rpc_host.h"
 #include "lib/util/debug.h"
 #include "lib/util/fault.h"
+#include "lib/util/util_file.h"
 #include "rpc_server.h"
 #include "rpc_pipes.h"
 #include "source3/smbd/proto.h"
@@ -42,6 +43,8 @@
 #include "nsswitch/winbind_client.h"
 #include "source3/include/messages.h"
 #include "libcli/security/security_token.h"
+#include "libcli/security/dom_sid.h"
+#include "source3/include/proto.h"
 
 /*
  * This is the generic code that becomes the
@@ -93,11 +96,13 @@ static void rpc_worker_print_interface(
 
 static NTSTATUS rpc_worker_report_status(struct rpc_worker *worker)
 {
-       uint8_t buf[6];
+       uint8_t buf[16];
        DATA_BLOB blob = { .data = buf, .length = sizeof(buf), };
        enum ndr_err_code ndr_err;
        NTSTATUS status;
 
+       worker->status.num_association_groups = worker->dce_ctx->assoc_groups_num;
+
        if (DEBUGLEVEL >= 10) {
                NDR_PRINT_DEBUG(rpc_worker_status, &worker->status);
        }
@@ -127,7 +132,19 @@ static void rpc_worker_connection_terminated(
        NTSTATUS status;
        bool found = false;
 
-       SMB_ASSERT(worker->status.num_clients > 0);
+       /*
+        * We need to drop the association group reference
+        * explicitly here in order to avoid the order given
+        * by the destructors. rpc_worker_report_status() below,
+        * expects worker->dce_ctx->assoc_groups_num to be updated
+        * already.
+        */
+       if (conn->assoc_group != NULL) {
+               talloc_unlink(conn, conn->assoc_group);
+               conn->assoc_group = NULL;
+       }
+
+       SMB_ASSERT(worker->status.num_connections > 0);
 
        for (w = worker->conns; w != NULL; w = w->next) {
                if (w == ncacn_conn) {
@@ -139,7 +156,7 @@ static void rpc_worker_connection_terminated(
 
        DLIST_REMOVE(worker->conns, ncacn_conn);
 
-       worker->status.num_clients -= 1;
+       worker->status.num_connections -= 1;
 
        status = rpc_worker_report_status(worker);
        if (!NT_STATUS_IS_OK(status)) {
@@ -170,7 +187,9 @@ static void rpc_worker_new_client(
        int sock)
 {
        struct dcesrv_context *dce_ctx = worker->dce_ctx;
-       struct named_pipe_auth_req_info5 *info5 = client->npa_info5;
+       struct named_pipe_auth_req_info8 *info8 = client->npa_info8;
+       struct tsocket_address *remote_client_addr = NULL;
+       struct tsocket_address *local_server_addr = NULL;
        struct dcerpc_binding *b = NULL;
        enum dcerpc_transport_t transport;
        struct dcesrv_endpoint *ep = NULL;
@@ -179,6 +198,9 @@ static void rpc_worker_new_client(
        struct dcesrv_connection *dcesrv_conn = NULL;
        DATA_BLOB buffer = { .data = NULL };
        struct ncacn_packet *pkt = NULL;
+       struct security_token *token = NULL;
+       uint32_t npa_flags, state_flags;
+       bool found_npa_flags;
        NTSTATUS status;
        int ret;
 
@@ -257,87 +279,85 @@ static void rpc_worker_new_client(
        };
 
        if (transport == NCALRPC) {
-               ret = tsocket_address_unix_from_path(
-                       ncacn_conn,
-                       info5->remote_client_addr,
-                       &ncacn_conn->remote_client_addr);
+               ret = tsocket_address_unix_from_path(ncacn_conn,
+                                                    info8->remote_client_addr,
+                                                    &remote_client_addr);
                if (ret == -1) {
                        DBG_DEBUG("tsocket_address_unix_from_path"
                                  "(%s) failed: %s\n",
-                                 info5->remote_client_addr,
+                                 info8->remote_client_addr,
                                  strerror(errno));
                        goto fail;
                }
 
-               ncacn_conn->remote_client_name = talloc_strdup(
-                       ncacn_conn, info5->remote_client_name);
+               ncacn_conn->remote_client_name =
+                       talloc_strdup(ncacn_conn, info8->remote_client_name);
                if (ncacn_conn->remote_client_name == NULL) {
                        DBG_DEBUG("talloc_strdup(%s) failed\n",
-                                 info5->remote_client_name);
+                                 info8->remote_client_name);
                        goto fail;
                }
 
-               ret = tsocket_address_unix_from_path(
-                       ncacn_conn,
-                       info5->local_server_addr,
-                       &ncacn_conn->local_server_addr);
+               ret = tsocket_address_unix_from_path(ncacn_conn,
+                                                    info8->local_server_addr,
+                                                    &local_server_addr);
                if (ret == -1) {
                        DBG_DEBUG("tsocket_address_unix_from_path"
                                  "(%s) failed: %s\n",
-                                 info5->local_server_addr,
+                                 info8->local_server_addr,
                                  strerror(errno));
                        goto fail;
                }
 
-               ncacn_conn->local_server_name = talloc_strdup(
-                       ncacn_conn, info5->local_server_name);
+               ncacn_conn->local_server_name =
+                       talloc_strdup(ncacn_conn, info8->local_server_name);
                if (ncacn_conn->local_server_name == NULL) {
                        DBG_DEBUG("talloc_strdup(%s) failed\n",
-                                 info5->local_server_name);
+                                 info8->local_server_name);
                        goto fail;
                }
        } else {
                ret = tsocket_address_inet_from_strings(
                        ncacn_conn,
                        "ip",
-                       info5->remote_client_addr,
-                       info5->remote_client_port,
-                       &ncacn_conn->remote_client_addr);
+                       info8->remote_client_addr,
+                       info8->remote_client_port,
+                       &remote_client_addr);
                if (ret == -1) {
                        DBG_DEBUG("tsocket_address_inet_from_strings"
-                                 "(%s, %"PRIu16") failed: %s\n",
-                                 info5->remote_client_addr,
-                                 info5->remote_client_port,
+                                 "(%s, %" PRIu16 ") failed: %s\n",
+                                 info8->remote_client_addr,
+                                 info8->remote_client_port,
                                  strerror(errno));
                        goto fail;
                }
-               ncacn_conn->remote_client_name = talloc_strdup(
-                       ncacn_conn, info5->remote_client_name);
+               ncacn_conn->remote_client_name =
+                       talloc_strdup(ncacn_conn, info8->remote_client_name);
                if (ncacn_conn->remote_client_name == NULL) {
                        DBG_DEBUG("talloc_strdup(%s) failed\n",
-                                 info5->remote_client_name);
+                                 info8->remote_client_name);
                        goto fail;
                }
 
                ret = tsocket_address_inet_from_strings(
                        ncacn_conn,
                        "ip",
-                       info5->local_server_addr,
-                       info5->local_server_port,
-                       &ncacn_conn->local_server_addr);
+                       info8->local_server_addr,
+                       info8->local_server_port,
+                       &local_server_addr);
                if (ret == -1) {
                        DBG_DEBUG("tsocket_address_inet_from_strings"
-                                 "(%s, %"PRIu16") failed: %s\n",
-                                 info5->local_server_addr,
-                                 info5->local_server_port,
+                                 "(%s, %" PRIu16 ") failed: %s\n",
+                                 info8->local_server_addr,
+                                 info8->local_server_port,
                                  strerror(errno));
                        goto fail;
                }
-               ncacn_conn->local_server_name = talloc_strdup(
-                       ncacn_conn, info5->local_server_name);
+               ncacn_conn->local_server_name =
+                       talloc_strdup(ncacn_conn, info8->local_server_name);
                if (ncacn_conn->local_server_name == NULL) {
                        DBG_DEBUG("talloc_strdup(%s) failed\n",
-                                 info5->local_server_name);
+                                 info8->local_server_name);
                        goto fail;
                }
        }
@@ -359,10 +379,10 @@ static void rpc_worker_new_client(
                 * socket that the client connected to, passed in from
                 * samba-dcerpcd via the binding. For NCACN_NP (root
                 * only by unix permissions) we got a
-                * named_pipe_auth_req_info5 where the transport can
+                * named_pipe_auth_req_info8 where the transport can
                 * be overridden.
                 */
-               transport = info5->transport;
+               transport = info8->transport;
        } else {
                ret = tstream_bsd_existing_socket(
                        ncacn_conn, sock, &tstream);
@@ -371,35 +391,46 @@ static void rpc_worker_new_client(
                                  strerror(errno));
                        goto fail;
                }
+               /* as server we want to fail early */
+               tstream_bsd_fail_readv_first_error(tstream, true);
        }
        sock = -1;
 
-       ncacn_conn->session_info = talloc_move(
-               ncacn_conn, &info5->session_info->session_info);
+       token = info8->session_info->session_info->security_token;
 
-       if (security_token_is_system(
-                   ncacn_conn->session_info->security_token) &&
-           (transport != NCALRPC)) {
+       if (security_token_is_system(token) && (transport != NCALRPC)) {
                DBG_DEBUG("System token only allowed on NCALRPC\n");
                goto fail;
        }
 
-       ncacn_conn->p = talloc_zero(ncacn_conn, struct pipes_struct);
-       if (ncacn_conn->p == NULL) {
-               DBG_DEBUG("talloc failed\n");
-               goto fail;
+       state_flags = DCESRV_CALL_STATE_FLAG_MAY_ASYNC;
+
+       found_npa_flags = security_token_find_npa_flags(token, &npa_flags);
+       if (found_npa_flags) {
+               if (npa_flags & SAMBA_NPA_FLAGS_WINBIND_OFF) {
+                       state_flags |=
+                               DCESRV_CALL_STATE_FLAG_WINBIND_OFF;
+               }
+
+               /*
+                * Delete the flags so that we don't bail in
+                * local_np_connect_send() on subsequent
+                * connects. Once we connect to another RPC service, a
+                * new flags sid will be added if required.
+                */
+               security_token_del_npa_flags(token);
        }
-       ncacn_conn->p->msg_ctx = global_messaging_context();
-       ncacn_conn->p->transport = transport;
-
-       status = dcesrv_endpoint_connect(
-               dce_ctx,
-               ncacn_conn,
-               ep,
-               ncacn_conn->session_info,
-               global_event_context(),
-               DCESRV_CALL_STATE_FLAG_MAY_ASYNC,
-               &dcesrv_conn);
+
+       ncacn_conn->p.msg_ctx = global_messaging_context();
+       ncacn_conn->p.transport = transport;
+
+       status = dcesrv_endpoint_connect(dce_ctx,
+                                        ncacn_conn,
+                                        ep,
+                                        info8->session_info->session_info,
+                                        global_event_context(),
+                                        state_flags,
+                                        &dcesrv_conn);
        if (!NT_STATUS_IS_OK(status)) {
                DBG_DEBUG("Failed to connect to endpoint: %s\n",
                          nt_errstr(status));
@@ -422,8 +453,10 @@ static void rpc_worker_new_client(
        }
 
        dcesrv_conn->stream = talloc_move(dcesrv_conn, &tstream);
-       dcesrv_conn->local_address = ncacn_conn->local_server_addr;
-       dcesrv_conn->remote_address = ncacn_conn->remote_client_addr;
+       dcesrv_conn->local_address =
+               talloc_move(dcesrv_conn, &local_server_addr);
+       dcesrv_conn->remote_address =
+               talloc_move(dcesrv_conn, &remote_client_addr);
 
        if (client->bind_packet.length == 0) {
                DBG_DEBUG("Expected bind packet\n");
@@ -451,7 +484,7 @@ static void rpc_worker_new_client(
        TALLOC_FREE(client);
 
        DLIST_ADD(worker->conns, ncacn_conn);
-       worker->status.num_clients += 1;
+       worker->status.num_connections += 1;
 
        dcesrv_loop_next_packet(dcesrv_conn, pkt, buffer);
 
@@ -537,7 +570,6 @@ static bool rpc_worker_status_filter(
                private_data, struct rpc_worker);
        struct dcerpc_ncacn_conn *conn = NULL;
        FILE *f = NULL;
-       int fd;
 
        if (rec->msg_type != MSG_RPC_DUMP_STATUS) {
                return false;
@@ -548,18 +580,9 @@ static bool rpc_worker_status_filter(
                return false;
        }
 
-       fd = dup(rec->fds[0]);
-       if (fd == -1) {
-               DBG_DEBUG("dup(%"PRIi64") failed: %s\n",
-                         rec->fds[0],
-                         strerror(errno));
-               return false;
-       }
-
-       f = fdopen(fd, "w");
+       f = fdopen_keepfd(rec->fds[0], "w");
        if (f == NULL) {
-               DBG_DEBUG("fdopen failed: %s\n", strerror(errno));
-               close(fd);
+               DBG_DEBUG("fdopen_keepfd failed: %s\n", strerror(errno));
                return false;
        }
 
@@ -596,7 +619,7 @@ static struct dcesrv_assoc_group *rpc_worker_assoc_group_reference(
        void *id_ptr = NULL;
 
        /* find an association group given a assoc_group_id */
-       id_ptr = idr_find(conn->dce_ctx->assoc_groups_idr, id & 0xffffff);
+       id_ptr = idr_find(conn->dce_ctx->assoc_groups_idr, id & UINT16_MAX);
        if (id_ptr == NULL) {
                DBG_NOTICE("Failed to find assoc_group 0x%08x\n", id);
                return NULL;
@@ -610,7 +633,7 @@ static struct dcesrv_assoc_group *rpc_worker_assoc_group_reference(
                        transport);
 
                DBG_NOTICE("assoc_group 0x%08x (transport %s) "
-                          "is not available on transport %s",
+                          "is not available on transport %s\n",
                           id, at, ct);
                return NULL;
        }
@@ -631,11 +654,14 @@ static int rpc_worker_assoc_group_destructor(
 
        ret = idr_remove(
                assoc_group->dce_ctx->assoc_groups_idr,
-               assoc_group->id & 0xffffff);
+               assoc_group->id & UINT16_MAX);
        if (ret != 0) {
                DBG_WARNING("Failed to remove assoc_group 0x%08x\n",
                            assoc_group->id);
        }
+
+       SMB_ASSERT(assoc_group->dce_ctx->assoc_groups_num > 0);
+       assoc_group->dce_ctx->assoc_groups_num -= 1;
        return 0;
 }
 
@@ -643,7 +669,7 @@ static int rpc_worker_assoc_group_destructor(
   allocate a new association group
  */
 static struct dcesrv_assoc_group *rpc_worker_assoc_group_new(
-       struct dcesrv_connection *conn, uint8_t worker_index)
+       struct dcesrv_connection *conn, uint16_t worker_index)
 {
        struct dcesrv_context *dce_ctx = conn->dce_ctx;
        const struct dcesrv_endpoint *endpoint = conn->endpoint;
@@ -657,19 +683,27 @@ static struct dcesrv_assoc_group *rpc_worker_assoc_group_new(
                return NULL;
        }
 
+       /*
+        * We use 16-bit to encode the worker index,
+        * have 16-bits left within the worker to form a
+        * 32-bit association group id.
+        */
        id = idr_get_new_random(
-               dce_ctx->assoc_groups_idr, assoc_group, UINT16_MAX);
+               dce_ctx->assoc_groups_idr, assoc_group, 1, UINT16_MAX);
        if (id == -1) {
                talloc_free(assoc_group);
                DBG_WARNING("Out of association groups!\n");
                return NULL;
        }
-       assoc_group->id = (worker_index << 24) + id;
+       assoc_group->id = (((uint32_t)worker_index) << 16) | id;
        assoc_group->transport = transport;
        assoc_group->dce_ctx = dce_ctx;
 
        talloc_set_destructor(assoc_group, rpc_worker_assoc_group_destructor);
 
+       SMB_ASSERT(dce_ctx->assoc_groups_num < UINT16_MAX);
+       dce_ctx->assoc_groups_num += 1;
+
        return assoc_group;
 }
 
@@ -682,10 +716,10 @@ static NTSTATUS rpc_worker_assoc_group_find(
        uint32_t assoc_group_id = call->pkt.u.bind.assoc_group_id;
 
        if (assoc_group_id != 0) {
-               uint8_t worker_index = (assoc_group_id & 0xff000000) >> 24;
+               uint16_t worker_index = (assoc_group_id & 0xffff0000) >> 16;
                if (worker_index != w->status.worker_index) {
-                       DBG_DEBUG("Wrong worker id %"PRIu8", "
-                                 "expected %"PRIu8"\n",
+                       DBG_DEBUG("Wrong worker id %"PRIu16", "
+                                 "expected %"PRIu32"\n",
                                  worker_index,
                                  w->status.worker_index);
                        return NT_STATUS_NOT_FOUND;
@@ -722,6 +756,8 @@ static struct rpc_worker *rpc_worker_new(
        worker->cb = (struct dcesrv_context_callbacks) {
                .log.successful_authz = dcesrv_log_successful_authz,
                .auth.gensec_prepare = dcesrv_auth_gensec_prepare,
+               .auth.become_root = become_root,
+               .auth.unbecome_root = unbecome_root,
                .assoc_group.find = rpc_worker_assoc_group_find,
                .assoc_group.private_data = worker,
        };
@@ -783,7 +819,7 @@ static struct tevent_req *rpc_worker_send(
                tevent_req_error(req, EINVAL);
                return tevent_req_post(req, ev);
        }
-       if ((worker_index < 0) || ((unsigned)worker_index > UINT32_MAX)) {
+       if ((worker_index < 0) || ((unsigned)worker_index > UINT16_MAX)) {
                DBG_ERR("Invalid worker index %d\n", worker_index);
                tevent_req_error(req, EINVAL);
                return tevent_req_post(req, ev);
@@ -937,7 +973,7 @@ static NTSTATUS register_ep_server(
  *
  * get_servers() is called when the process is about to do the real
  * work. So more heavy-weight initialization should happen here. It
- * should return the number of server implementations provided.
+ * should return NT_STATUS_OK and the number of server implementations provided.
  *
  * @param[in] argc argc from main()
  * @param[in] argv argv from main()
@@ -956,9 +992,10 @@ int rpc_worker_main(
        size_t (*get_interfaces)(
                const struct ndr_interface_table ***ifaces,
                void *private_data),
-       size_t (*get_servers)(
+       NTSTATUS (*get_servers)(
                struct dcesrv_context *dce_ctx,
                const struct dcesrv_endpoint_server ***ep_servers,
+               size_t *num_ep_servers,
                void *private_data),
        void *private_data)
 {
@@ -1104,10 +1141,12 @@ int rpc_worker_main(
        /* Ignore children - no zombies. */
        CatchChild();
 
-       DEBUG(0, ("%s version %s started.\n",
-                 progname,
-                 samba_version_string()));
-       DEBUGADD(0,("%s\n", COPYRIGHT_STARTUP_MESSAGE));
+       reopen_logs();
+
+       DBG_STARTUP_NOTICE("%s version %s started.\n%s\n",
+                          progname,
+                          samba_version_string(),
+                          samba_copyright_string());
 
        msg_ctx = global_messaging_context();
        if (msg_ctx == NULL) {
@@ -1167,15 +1206,24 @@ int rpc_worker_main(
 
        DBG_INFO("Initializing DCE/RPC registered endpoint servers\n");
 
-       num_servers = get_servers(dce_ctx, &ep_servers, private_data);
+       status = get_servers(dce_ctx,
+                            &ep_servers,
+                            &num_servers,
+                            private_data);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("get_servers failed: %s\n", nt_errstr(status));
+               global_messaging_context_free();
+               TALLOC_FREE(frame);
+               exit(1);
+       }
 
        DBG_DEBUG("get_servers() returned %zu servers\n", num_servers);
 
        for (i=0; i<num_servers; i++) {
                status = register_ep_server(dce_ctx, ep_servers[i]);
                if (!NT_STATUS_IS_OK(status)) {
-                       DBG_DEBUG("register_ep_server failed: %s\n",
-                                 nt_errstr(status));
+                       DBG_ERR("register_ep_server failed: %s\n",
+                               nt_errstr(status));
                        global_messaging_context_free();
                        TALLOC_FREE(frame);
                        exit(1);