s3: vfs: add user_vfs_evg to connection_struct
authorRalph Boehme <slow@samba.org>
Thu, 5 Jul 2018 11:09:53 +0000 (13:09 +0200)
committerStefan Metzmacher <metze@samba.org>
Wed, 25 Jul 2018 15:49:06 +0000 (17:49 +0200)
This will be used to in order to pass down the
impersonation magic from the SMB layer through
the SMB_VFS layer.

This includes the following options:

smbd:force sync user path safe threadpool
smbd:force sync user chdir safe threadpool
smbd:force sync root path safe threadpool
smbd:force sync root chdir safe threadpool

They can be used in order to test the non linux code
path on linux, once we get code that makes full use
of the new infrastructure.

Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Ralph Boehme <slow@samba.org>
source3/include/vfs.h
source3/modules/vfs_readonly.c
source3/smbd/conn.c
source3/smbd/msdfs.c
source3/smbd/proto.h
source3/smbd/uid.c

index a8bb9c121ac8da6d86fafc372701adfa18ad6206..377383362548c4fb6888284d9d0bd041be4b1b63 100644 (file)
@@ -404,7 +404,7 @@ typedef struct files_struct {
 
 struct vuid_cache_entry {
        struct auth_session_info *session_info;
-       struct tevent_context *user_ev_ctx;
+       struct smb_vfs_ev_glue *user_vfs_evg;
        uint64_t vuid; /* SMB2 compat */
        bool read_only;
        uint32_t share_access;
@@ -453,6 +453,7 @@ typedef struct connection_struct {
         */
        struct auth_session_info *session_info;
        struct tevent_context *user_ev_ctx;
+       struct smb_vfs_ev_glue *user_vfs_evg;
 
        /*
         * If the "force group" parameter is set, this is the primary gid that
index e7e12747a2225dab74794a67583d6039c4607b9a..37a9e806a1562168b5e3ee0c5651b3b64d7ab04e 100644 (file)
@@ -84,7 +84,7 @@ static int readonly_connect(vfs_handle_struct *handle,
       for (i=0; i< VUID_CACHE_SIZE; i++) {
         struct vuid_cache_entry *ent = &conn->vuid_cache->array[i];
         ent->vuid = UID_FIELD_INVALID;
-        TALLOC_FREE(ent->user_ev_ctx);
+        TALLOC_FREE(ent->user_vfs_evg);
         TALLOC_FREE(ent->session_info);
         ent->read_only = false;
         ent->share_access = 0;
index cfff6404608f395414490e412d15d07139ba5bee..d8dc1c27d427a27fccc1c6c45331cf581cbc2757 100644 (file)
@@ -95,10 +95,12 @@ static void conn_clear_vuid_cache(connection_struct *conn, uint64_t vuid)
                if (ent->vuid == vuid) {
                        ent->vuid = UID_FIELD_INVALID;
 
-                       if (conn->user_ev_ctx == ent->user_ev_ctx) {
-                               conn->user_ev_ctx = NULL;
+                       conn->user_ev_ctx = NULL;
+
+                       if (conn->user_vfs_evg == ent->user_vfs_evg) {
+                               conn->user_vfs_evg = NULL;
                        }
-                       TALLOC_FREE(ent->user_ev_ctx);
+                       TALLOC_FREE(ent->user_vfs_evg);
 
                        /*
                         * We need to keep conn->session_info around
index bf9b3abee4a797a3c35c36b332c633447f2bf62d..ffa7ccb809bf42e92a1d8edddc46e23ddcf03a2e 100644 (file)
@@ -252,6 +252,10 @@ static NTSTATUS create_conn_struct_as_root(TALLOC_CTX *ctx,
        const char *vfs_user;
        struct smbd_server_connection *sconn;
        const char *servicename = lp_const_servicename(snum);
+       const struct security_unix_token *unix_token = NULL;
+       struct tevent_context *user_ev_ctx = NULL;
+       struct pthreadpool_tevent *user_tp_chdir_safe = NULL;
+       struct pthreadpool_tevent *root_tp_chdir_safe = NULL;
        int ret;
 
        sconn = talloc_zero(ctx, struct smbd_server_connection);
@@ -328,6 +332,7 @@ static NTSTATUS create_conn_struct_as_root(TALLOC_CTX *ctx,
                        TALLOC_FREE(conn);
                        return NT_STATUS_NO_MEMORY;
                }
+               unix_token = conn->session_info->unix_token;
                /* unix_info could be NULL in session_info */
                if (conn->session_info->unix_info != NULL) {
                        vfs_user = conn->session_info->unix_info->unix_name;
@@ -339,6 +344,10 @@ static NTSTATUS create_conn_struct_as_root(TALLOC_CTX *ctx,
                vfs_user = get_current_username();
        }
 
+       if (unix_token == NULL) {
+               unix_token = get_current_utok(conn);
+       }
+
        /*
         * The impersonation has to be done by the caller
         * of create_conn_struct_tos[_cwd]().
@@ -352,14 +361,65 @@ static NTSTATUS create_conn_struct_as_root(TALLOC_CTX *ctx,
         * to avoid crashes because TALLOC_FREE(conn->user_ev_ctx)
         * would also remove sconn->raw_ev_ctx.
         */
-       conn->user_ev_ctx = smbd_impersonate_debug_create(sconn->raw_ev_ctx,
-                                                         "FAKE impersonation",
-                                                         DBGLVL_DEBUG);
-       if (conn->user_ev_ctx == NULL) {
+       user_ev_ctx = smbd_impersonate_debug_create(sconn->raw_ev_ctx,
+                                                   "FAKE impersonation",
+                                                   DBGLVL_DEBUG);
+       if (user_ev_ctx == NULL) {
+               TALLOC_FREE(conn);
+               return NT_STATUS_NO_MEMORY;
+       }
+       SMB_ASSERT(talloc_reparent(sconn->raw_ev_ctx, conn, user_ev_ctx));
+
+       user_tp_chdir_safe = smbd_impersonate_tp_current_create(conn,
+                                               sconn->sync_thread_pool,
+                                               conn,
+                                               conn->vuid,
+                                               true, /* chdir_safe */
+                                               unix_token);
+       if (user_tp_chdir_safe == NULL) {
+               TALLOC_FREE(conn);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       root_tp_chdir_safe = smbd_impersonate_tp_become_create(conn,
+                                               sconn->sync_thread_pool,
+                                               true, /* chdir_safe */
+                                               become_root,
+                                               unbecome_root);
+       if (root_tp_chdir_safe == NULL) {
                TALLOC_FREE(conn);
                return NT_STATUS_NO_MEMORY;
        }
 
+       /*
+        * We only use the chdir_safe wrappers
+        * for everything in order to keep
+        * it simple.
+        */
+       conn->user_vfs_evg = smb_vfs_ev_glue_create(conn,
+                                                   user_ev_ctx,
+                                                   user_tp_chdir_safe,
+                                                   user_tp_chdir_safe,
+                                                   user_tp_chdir_safe,
+                                                   sconn->root_ev_ctx,
+                                                   root_tp_chdir_safe,
+                                                   root_tp_chdir_safe,
+                                                   root_tp_chdir_safe);
+       if (conn->user_vfs_evg == NULL) {
+               TALLOC_FREE(conn);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       SMB_ASSERT(talloc_reparent(conn, conn->user_vfs_evg, user_ev_ctx));
+       SMB_ASSERT(talloc_reparent(conn, conn->user_vfs_evg, user_tp_chdir_safe));
+       SMB_ASSERT(talloc_reparent(conn, conn->user_vfs_evg, root_tp_chdir_safe));
+
+       conn->user_ev_ctx = smb_vfs_ev_glue_ev_ctx(conn->user_vfs_evg);
+       if (conn->user_ev_ctx == NULL) {
+               TALLOC_FREE(conn);
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
        set_conn_connectpath(conn, connpath);
 
        /*
index f1a281231e7fd9a05f79b583a6e5ae1ec5faff40..0697c1b9c56d94756ab3e69bfe54f0c2eacb5dc3 100644 (file)
@@ -1229,6 +1229,19 @@ struct tevent_context *smbd_impersonate_conn_sess_create(
 struct tevent_context *smbd_impersonate_root_create(struct tevent_context *main_ev);
 struct tevent_context *smbd_impersonate_guest_create(struct tevent_context *main_ev);
 
+struct pthreadpool_tevent *smbd_impersonate_tp_current_create(
+                               TALLOC_CTX *mem_ctx,
+                               struct pthreadpool_tevent *sync_tp,
+                               struct connection_struct *conn,
+                               uint64_t vuid, bool chdir_safe,
+                               const struct security_unix_token *unix_token);
+struct pthreadpool_tevent *smbd_impersonate_tp_become_create(
+                                       TALLOC_CTX *mem_ctx,
+                                       struct pthreadpool_tevent *sync_tp,
+                                       bool chdir_safe,
+                                       void (*become_fn)(void),
+                                       void (*unbecome_fn)(void));
+
 /* The following definitions come from smbd/utmp.c  */
 
 void sys_utmp_claim(const char *username, const char *hostname,
index fcc4d51a698c544365b5f2b9f01098372ee72345..41bb66e2df1d3e42e350fa2f5559285d485be5dd 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include "includes.h"
+#include "system/filesys.h"
 #include "system/passwd.h"
 #include "smbd/smbd.h"
 #include "smbd/globals.h"
 #include "passdb/lookup_sid.h"
 #include "auth.h"
 #include "lib/util/time_basic.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+
+static struct smb_vfs_ev_glue *smbd_impersonate_user_ev_glue_create(
+                               struct connection_struct *conn,
+                               uint64_t vuid,
+                               struct auth_session_info *session_info);
 
 struct smbd_impersonate_debug_state {
        int dbg_lvl;
@@ -306,7 +313,8 @@ static void free_conn_session_info_if_unused(connection_struct *conn)
                }
        }
        /* Not used, safe to free. */
-       TALLOC_FREE(conn->user_ev_ctx);
+       conn->user_ev_ctx = NULL;
+       TALLOC_FREE(conn->user_vfs_evg);
        TALLOC_FREE(conn->session_info);
 }
 
@@ -432,10 +440,13 @@ static bool check_user_ok(connection_struct *conn,
                        }
                        free_conn_session_info_if_unused(conn);
                        conn->session_info = ent->session_info;
-                       conn->user_ev_ctx = ent->user_ev_ctx;
+                       conn->user_vfs_evg = ent->user_vfs_evg;
                        conn->read_only = ent->read_only;
                        conn->share_access = ent->share_access;
                        conn->vuid = ent->vuid;
+                       conn->user_ev_ctx = smb_vfs_ev_glue_ev_ctx(
+                                               conn->user_vfs_evg);
+                       SMB_ASSERT(conn->user_ev_ctx != NULL);
                        return(True);
                }
        }
@@ -481,22 +492,12 @@ static bool check_user_ok(connection_struct *conn,
                ent->session_info->unix_token->uid = sec_initial_uid();
        }
 
-       if (vuid == UID_FIELD_INVALID) {
-               ent->user_ev_ctx = smbd_impersonate_conn_sess_create(
-                       conn->sconn->raw_ev_ctx, conn, ent->session_info);
-               if (ent->user_ev_ctx == NULL) {
-                       TALLOC_FREE(ent->session_info);
-                       ent->vuid = UID_FIELD_INVALID;
-                       return false;
-               }
-       } else {
-               ent->user_ev_ctx = smbd_impersonate_conn_vuid_create(
-                       conn->sconn->raw_ev_ctx, conn, vuid);
-               if (ent->user_ev_ctx == NULL) {
-                       TALLOC_FREE(ent->session_info);
-                       ent->vuid = UID_FIELD_INVALID;
-                       return false;
-               }
+       ent->user_vfs_evg = smbd_impersonate_user_ev_glue_create(conn,
+                                                       vuid, ent->session_info);
+       if (ent->user_vfs_evg == NULL) {
+               TALLOC_FREE(ent->session_info);
+               ent->vuid = UID_FIELD_INVALID;
+               return false;
        }
 
        /*
@@ -511,7 +512,10 @@ static bool check_user_ok(connection_struct *conn,
        free_conn_session_info_if_unused(conn);
        conn->session_info = ent->session_info;
        conn->vuid = ent->vuid;
-       conn->user_ev_ctx = ent->user_ev_ctx;
+       conn->user_vfs_evg = ent->user_vfs_evg;
+       conn->user_ev_ctx = smb_vfs_ev_glue_ev_ctx(conn->user_vfs_evg);
+       SMB_ASSERT(conn->user_ev_ctx != NULL);
+
        if (vuid == UID_FIELD_INVALID) {
                /*
                 * Not strictly needed, just make it really
@@ -520,7 +524,7 @@ static bool check_user_ok(connection_struct *conn,
                ent->read_only = false;
                ent->share_access = 0;
                ent->session_info = NULL;
-               ent->user_ev_ctx = NULL;
+               ent->user_vfs_evg = NULL;
        }
 
        conn->read_only = readonly_share;
@@ -1932,3 +1936,713 @@ struct tevent_context *smbd_impersonate_guest_create(struct tevent_context *main
 
        return ev;
 }
+
+struct smbd_impersonate_tp_current_state {
+       const void *conn_ptr;
+       uint64_t vuid; /* SMB2 compat */
+       struct security_unix_token partial_ut;
+       bool chdir_safe;
+       int saved_cwd_fd;
+};
+
+static int smbd_impersonate_tp_current_state_destructor(
+               struct smbd_impersonate_tp_current_state *state)
+{
+       if (state->saved_cwd_fd != -1) {
+               smb_panic(__location__);
+       }
+
+       return 0;
+}
+
+static bool smbd_impersonate_tp_current_before_job(struct pthreadpool_tevent *wrap,
+                                                  void *private_data,
+                                                  struct pthreadpool_tevent *main,
+                                                  const char *location)
+{
+       struct smbd_impersonate_tp_current_state *state =
+               talloc_get_type_abort(private_data,
+               struct smbd_impersonate_tp_current_state);
+
+       if (state->conn_ptr != current_user.conn) {
+               smb_panic(__location__);
+       }
+
+       if (state->vuid != current_user.vuid) {
+               smb_panic(__location__);
+       }
+
+       if (state->partial_ut.uid != current_user.ut.uid) {
+               smb_panic(__location__);
+       }
+
+       if (state->partial_ut.gid != current_user.ut.gid) {
+               smb_panic(__location__);
+       }
+
+       if (state->partial_ut.ngroups != current_user.ut.ngroups) {
+               smb_panic(__location__);
+       }
+
+       /*
+        * We don't verify the group list, we should have hit
+        * an assert before. We only want to catch programmer
+        * errors here!
+        *
+        * We just have a sync pool and want to make sure
+        * we're already in the correct state.
+        *
+        * So we don't do any active impersonation.
+        */
+
+       /*
+        * we may need to remember the current working directory
+        * and later restore it in the after_job hook.
+        */
+       if (state->chdir_safe) {
+               int open_flags = O_RDONLY;
+               bool ok;
+
+#ifdef O_DIRECTORY
+               open_flags |= O_DIRECTORY;
+#endif
+#ifdef O_CLOEXEC
+               open_flags |= O_CLOEXEC;
+#endif
+
+               state->saved_cwd_fd = open(".", open_flags);
+               if (state->saved_cwd_fd == -1) {
+                       DBG_ERR("unable to open '.' with open_flags[0x%x] - %s\n",
+                               open_flags, strerror(errno));
+                       smb_panic("smbd_impersonate_tp_current_before_job: "
+                                 "unable to open cwd '.'");
+                       return false;
+               }
+               ok = smb_set_close_on_exec(state->saved_cwd_fd);
+               SMB_ASSERT(ok);
+       }
+
+       return true;
+}
+
+static bool smbd_impersonate_tp_current_after_job(struct pthreadpool_tevent *wrap,
+                                                 void *private_data,
+                                                 struct pthreadpool_tevent *main,
+                                                 const char *location)
+{
+       struct smbd_impersonate_tp_current_state *state =
+               talloc_get_type_abort(private_data,
+               struct smbd_impersonate_tp_current_state);
+       int ret;
+
+       /*
+        * There's no impersonation to revert.
+        *
+        * But we may need to reset the current working directory.
+        */
+       if (state->saved_cwd_fd == -1) {
+               return true;
+       }
+
+       ret = fchdir(state->saved_cwd_fd);
+       if (ret != 0) {
+               DBG_ERR("unable to fchdir to the original directory - %s\n",
+                       strerror(errno));
+               smb_panic("smbd_impersonate_tp_current_after_job: "
+                         "unable restore cwd with fchdir.");
+               return false;
+       }
+
+       close(state->saved_cwd_fd);
+       state->saved_cwd_fd = -1;
+
+       return true;
+}
+
+static const struct pthreadpool_tevent_wrapper_ops smbd_impersonate_tp_current_ops = {
+       .name           = "smbd_impersonate_tp_current",
+       .before_job     = smbd_impersonate_tp_current_before_job,
+       .after_job      = smbd_impersonate_tp_current_after_job,
+};
+
+struct pthreadpool_tevent *smbd_impersonate_tp_current_create(
+                               TALLOC_CTX *mem_ctx,
+                               struct pthreadpool_tevent *sync_tp,
+                               struct connection_struct *conn,
+                               uint64_t vuid, bool chdir_safe,
+                               const struct security_unix_token *unix_token)
+{
+       struct pthreadpool_tevent *wrap_tp = NULL;
+       struct smbd_impersonate_tp_current_state *state = NULL;
+       size_t max_threads;
+
+       max_threads = pthreadpool_tevent_max_threads(sync_tp);
+       SMB_ASSERT(max_threads == 0);
+
+       /*
+        * We have a fake threadpool without real threads.
+        * So we just provide a a wrapper that asserts that
+        * we are already in the required impersonation state.
+        */
+
+       wrap_tp = pthreadpool_tevent_wrapper_create(sync_tp,
+                                       mem_ctx,
+                                       &smbd_impersonate_tp_current_ops,
+                                       &state,
+                                       struct smbd_impersonate_tp_current_state);
+       if (wrap_tp == NULL) {
+               return NULL;
+       }
+
+       state->conn_ptr = conn;
+       state->vuid = vuid;
+       state->partial_ut = *unix_token;
+       state->partial_ut.groups = NULL;
+       state->chdir_safe = chdir_safe;
+       state->saved_cwd_fd = -1;
+
+       if (chdir_safe) {
+               pthreadpool_tevent_force_per_thread_cwd(wrap_tp, state);
+       }
+
+       talloc_set_destructor(state, smbd_impersonate_tp_current_state_destructor);
+
+       return wrap_tp;
+}
+
+struct smbd_impersonate_tp_sess_state {
+       const struct security_unix_token *unix_token;
+};
+
+static bool smbd_impersonate_tp_sess_before_job(struct pthreadpool_tevent *wrap,
+                                               void *private_data,
+                                               struct pthreadpool_tevent *main,
+                                               const char *location)
+{
+       struct smbd_impersonate_tp_sess_state *state =
+               talloc_get_type_abort(private_data,
+               struct smbd_impersonate_tp_sess_state);
+       int ret;
+
+       /* Become the correct credential on this thread. */
+       ret = set_thread_credentials(state->unix_token->uid,
+                                    state->unix_token->gid,
+                                    (size_t)state->unix_token->ngroups,
+                                    state->unix_token->groups);
+       if (ret != 0) {
+               return false;
+       }
+
+       return true;
+}
+
+static bool smbd_impersonate_tp_sess_after_job(struct pthreadpool_tevent *wrap,
+                                              void *private_data,
+                                              struct pthreadpool_tevent *main,
+                                              const char *location)
+{
+       /*
+        * We skip the 'unbecome' here, if the following
+        * job cares, it already called set_thread_credentials() again.
+        *
+        * fd based jobs on the raw pool, don't really care...
+        */
+       return true;
+}
+
+static const struct pthreadpool_tevent_wrapper_ops smbd_impersonate_tp_sess_ops = {
+       .name           = "smbd_impersonate_tp_sess",
+       .before_job     = smbd_impersonate_tp_sess_before_job,
+       .after_job      = smbd_impersonate_tp_sess_after_job,
+};
+
+static struct pthreadpool_tevent *smbd_impersonate_tp_sess_create(
+                               TALLOC_CTX *mem_ctx,
+                               struct pthreadpool_tevent *main_tp,
+                               struct auth_session_info *session_info)
+{
+       struct pthreadpool_tevent *wrap_tp = NULL;
+       struct smbd_impersonate_tp_sess_state *state = NULL;
+       size_t max_threads;
+
+       max_threads = pthreadpool_tevent_max_threads(main_tp);
+       SMB_ASSERT(max_threads > 0);
+
+       wrap_tp = pthreadpool_tevent_wrapper_create(main_tp,
+                                       mem_ctx,
+                                       &smbd_impersonate_tp_sess_ops,
+                                       &state,
+                                       struct smbd_impersonate_tp_sess_state);
+       if (wrap_tp == NULL) {
+               return NULL;
+       }
+
+       state->unix_token = copy_unix_token(state, session_info->unix_token);
+       if (state->unix_token == NULL) {
+               int saved_errno = errno;
+               TALLOC_FREE(wrap_tp);
+               errno = saved_errno;
+               return NULL;
+       }
+
+       return wrap_tp;
+}
+
+struct smbd_impersonate_tp_become_state {
+       void (*become_fn)(void);
+       void (*unbecome_fn)(void);
+       bool chdir_safe;
+       int saved_cwd_fd;
+};
+
+static int smbd_impersonate_tp_become_state_destructor(
+               struct smbd_impersonate_tp_become_state *state)
+{
+       if (state->saved_cwd_fd != -1) {
+               smb_panic(__location__);
+       }
+
+       return 0;
+}
+
+
+static bool smbd_impersonate_tp_become_before_job(struct pthreadpool_tevent *wrap,
+                                                  void *private_data,
+                                                  struct pthreadpool_tevent *main,
+                                                  const char *location)
+{
+       struct smbd_impersonate_tp_become_state *state =
+               talloc_get_type_abort(private_data,
+               struct smbd_impersonate_tp_become_state);
+
+       /*
+        * we may need to remember the current working directory
+        * and later restore it in the after_job hook.
+        */
+       if (state->chdir_safe) {
+               int open_flags = O_RDONLY;
+               bool ok;
+
+#ifdef O_DIRECTORY
+               open_flags |= O_DIRECTORY;
+#endif
+#ifdef O_CLOEXEC
+               open_flags |= O_CLOEXEC;
+#endif
+
+               state->saved_cwd_fd = open(".", open_flags);
+               if (state->saved_cwd_fd == -1) {
+                       DBG_ERR("unable to open '.' with open_flags[0x%x] - %s\n",
+                               open_flags, strerror(errno));
+                       smb_panic("smbd_impersonate_tp_current_before_job: "
+                                 "unable to open cwd '.'");
+                       return false;
+               }
+               ok = smb_set_close_on_exec(state->saved_cwd_fd);
+               SMB_ASSERT(ok);
+       }
+
+       /*
+        * The function should abort on error...
+        */
+       state->become_fn();
+
+       return true;
+}
+
+static bool smbd_impersonate_tp_become_after_job(struct pthreadpool_tevent *wrap,
+                                                 void *private_data,
+                                                 struct pthreadpool_tevent *main,
+                                                 const char *location)
+{
+       struct smbd_impersonate_tp_become_state *state =
+               talloc_get_type_abort(private_data,
+               struct smbd_impersonate_tp_become_state);
+       int ret;
+
+       /*
+        * The function should abort on error...
+        */
+       state->unbecome_fn();
+
+       /*
+        * There's no impersonation to revert.
+        *
+        * But we may need to reset the current working directory.
+        */
+       if (state->saved_cwd_fd == -1) {
+               return true;
+       }
+
+       ret = fchdir(state->saved_cwd_fd);
+       if (ret != 0) {
+               DBG_ERR("unable to fchdir to the original directory - %s\n",
+                       strerror(errno));
+               smb_panic("smbd_impersonate_tp_current_after_job: "
+                         "unable restore cwd with fchdir.");
+               return false;
+       }
+
+       close(state->saved_cwd_fd);
+       state->saved_cwd_fd = -1;
+
+       return true;
+}
+
+static const struct pthreadpool_tevent_wrapper_ops smbd_impersonate_tp_become_ops = {
+       .name           = "smbd_impersonate_tp_become",
+       .before_job     = smbd_impersonate_tp_become_before_job,
+       .after_job      = smbd_impersonate_tp_become_after_job,
+};
+
+struct pthreadpool_tevent *smbd_impersonate_tp_become_create(
+                                       TALLOC_CTX *mem_ctx,
+                                       struct pthreadpool_tevent *sync_tp,
+                                       bool chdir_safe,
+                                       void (*become_fn)(void),
+                                       void (*unbecome_fn)(void))
+{
+       struct pthreadpool_tevent *wrap_tp = NULL;
+       struct smbd_impersonate_tp_become_state *state = NULL;
+       size_t max_threads;
+
+       max_threads = pthreadpool_tevent_max_threads(sync_tp);
+       SMB_ASSERT(max_threads == 0);
+
+       /*
+        * We have a fake threadpool without real threads.
+        * So we just provide a a wrapper that asserts that
+        * we are already in the required impersonation state.
+        */
+
+       wrap_tp = pthreadpool_tevent_wrapper_create(sync_tp,
+                                       mem_ctx,
+                                       &smbd_impersonate_tp_become_ops,
+                                       &state,
+                                       struct smbd_impersonate_tp_become_state);
+       if (wrap_tp == NULL) {
+               return NULL;
+       }
+
+       state->become_fn = become_fn;
+       state->unbecome_fn = unbecome_fn;
+       state->chdir_safe = chdir_safe;
+       state->saved_cwd_fd = -1;
+
+       if (chdir_safe) {
+               pthreadpool_tevent_force_per_thread_cwd(wrap_tp, state);
+       }
+
+       talloc_set_destructor(state, smbd_impersonate_tp_become_state_destructor);
+
+       return wrap_tp;
+}
+
+struct smbd_impersonate_tp_root_state {
+       const struct security_unix_token *fallback_token;
+};
+
+static bool smbd_impersonate_tp_root_before_job(struct pthreadpool_tevent *wrap,
+                                               void *private_data,
+                                               struct pthreadpool_tevent *main,
+                                               const char *location)
+{
+       int ret;
+
+       /*
+        * Become root in this thread.
+        */
+       ret = set_thread_credentials(0, 0, 0, NULL);
+       if (ret != 0) {
+               return false;
+       }
+
+       return true;
+}
+
+static bool smbd_impersonate_tp_root_after_job(struct pthreadpool_tevent *wrap,
+                                              void *private_data,
+                                              struct pthreadpool_tevent *main,
+                                              const char *location)
+{
+       struct smbd_impersonate_tp_root_state *state =
+               talloc_get_type_abort(private_data,
+               struct smbd_impersonate_tp_root_state);
+       int ret;
+
+       /*
+        * Move to a non root token again.
+        * We just use the one of the user_ev_ctx.
+        *
+        * The main goal is that we don't leave
+        * a thread arround with a root token.
+        */
+       ret = set_thread_credentials(state->fallback_token->uid,
+                                    state->fallback_token->gid,
+                                    (size_t)state->fallback_token->ngroups,
+                                    state->fallback_token->groups);
+       if (ret != 0) {
+               return false;
+       }
+
+       return true;
+}
+
+static const struct pthreadpool_tevent_wrapper_ops smbd_impersonate_tp_root_ops = {
+       .name           = "smbd_impersonate_tp_root",
+       .before_job     = smbd_impersonate_tp_root_before_job,
+       .after_job      = smbd_impersonate_tp_root_after_job,
+};
+
+static struct pthreadpool_tevent *smbd_impersonate_tp_root_create(
+                               TALLOC_CTX *mem_ctx,
+                               struct pthreadpool_tevent *main_tp,
+                               int snum,
+                               const struct security_unix_token *fallback_token)
+{
+       struct pthreadpool_tevent *wrap_tp = NULL;
+       struct smbd_impersonate_tp_root_state *state = NULL;
+       size_t max_threads;
+
+       max_threads = pthreadpool_tevent_max_threads(main_tp);
+       SMB_ASSERT(max_threads > 0);
+
+       wrap_tp = pthreadpool_tevent_wrapper_create(main_tp,
+                               mem_ctx,
+                               &smbd_impersonate_tp_root_ops,
+                               &state,
+                               struct smbd_impersonate_tp_root_state);
+       if (wrap_tp == NULL) {
+               return NULL;
+       }
+
+       state->fallback_token = copy_unix_token(state, fallback_token);
+       if (state->fallback_token == NULL) {
+               int saved_errno = errno;
+               TALLOC_FREE(wrap_tp);
+               errno = saved_errno;
+               return NULL;
+       }
+
+       return wrap_tp;
+}
+
+static struct smb_vfs_ev_glue *smbd_impersonate_user_ev_glue_create(
+                               struct connection_struct *conn,
+                               uint64_t vuid,
+                               struct auth_session_info *session_info)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       struct smb_vfs_ev_glue *user_vfs_evg = NULL;
+       struct tevent_context *user_ev_ctx = NULL;
+       struct pthreadpool_tevent *user_tp_fd_safe = NULL;
+       struct pthreadpool_tevent *user_tp_path_safe = NULL;
+       bool user_tp_path_sync = true;
+       struct pthreadpool_tevent *user_tp_chdir_safe = NULL;
+       bool user_tp_chdir_sync = true;
+       struct pthreadpool_tevent *root_tp_fd_safe = NULL;
+       struct pthreadpool_tevent *root_tp_path_safe = NULL;
+       bool root_tp_path_sync = true;
+       struct pthreadpool_tevent *root_tp_chdir_safe = NULL;
+       bool root_tp_chdir_sync = true;
+       size_t max_threads;
+
+       if (vuid == UID_FIELD_INVALID) {
+               user_ev_ctx = smbd_impersonate_conn_sess_create(
+                       conn->sconn->raw_ev_ctx, conn, session_info);
+               if (user_ev_ctx == NULL) {
+                       TALLOC_FREE(frame);
+                       return NULL;
+               }
+       } else {
+               user_ev_ctx = smbd_impersonate_conn_vuid_create(
+                       conn->sconn->raw_ev_ctx, conn, vuid);
+               if (user_ev_ctx == NULL) {
+                       TALLOC_FREE(frame);
+                       return NULL;
+               }
+       }
+       SMB_ASSERT(talloc_reparent(conn, frame, user_ev_ctx));
+
+#ifdef HAVE_LINUX_THREAD_CREDENTIALS
+       user_tp_path_sync = lp_parm_bool(SNUM(conn),
+                                        "smbd",
+                                        "force sync user path safe threadpool",
+                                        false);
+       user_tp_chdir_sync = lp_parm_bool(SNUM(conn),
+                                         "smbd",
+                                         "force sync user chdir safe threadpool",
+                                         false);
+       root_tp_path_sync = lp_parm_bool(SNUM(conn),
+                                        "smbd",
+                                        "force sync root path safe threadpool",
+                                        false);
+       root_tp_chdir_sync = lp_parm_bool(SNUM(conn),
+                                         "smbd",
+                                         "force sync root chdir safe threadpool",
+                                         false);
+#endif
+
+       max_threads = pthreadpool_tevent_max_threads(conn->sconn->raw_thread_pool);
+       if (max_threads == 0) {
+               /*
+                * We don't have real threads, so we need to force
+                * the sync versions...
+                */
+               user_tp_path_sync = true;
+               user_tp_chdir_sync = true;
+               root_tp_path_sync = true;
+               root_tp_chdir_sync = true;
+       }
+
+       /*
+        * fd_safe is easy :-)
+        */
+       user_tp_fd_safe = conn->sconn->raw_thread_pool;
+       root_tp_fd_safe = conn->sconn->raw_thread_pool;
+
+       if (user_tp_path_sync) {
+               /*
+                * We don't have support for per thread credentials,
+                * so we just provide a sync thread pool with a wrapper
+                * that asserts that we are already in the required
+                * impersonation state.
+                */
+               user_tp_path_safe = smbd_impersonate_tp_current_create(conn,
+                                               conn->sconn->sync_thread_pool,
+                                               conn,
+                                               vuid,
+                                               false, /* chdir_safe */
+                                               session_info->unix_token);
+               if (user_tp_path_safe == NULL) {
+                       TALLOC_FREE(frame);
+                       return NULL;
+               }
+       } else {
+               user_tp_path_safe = smbd_impersonate_tp_sess_create(conn,
+                                               conn->sconn->raw_thread_pool,
+                                               session_info);
+               if (user_tp_path_safe == NULL) {
+                       TALLOC_FREE(frame);
+                       return NULL;
+               }
+       }
+       SMB_ASSERT(talloc_reparent(conn, frame, user_tp_path_safe));
+
+       if (pthreadpool_tevent_per_thread_cwd(user_tp_path_safe)) {
+               user_tp_chdir_safe = user_tp_path_safe;
+       } else {
+               user_tp_chdir_sync = true;
+       }
+
+       if (user_tp_chdir_sync) {
+               /*
+                * We don't have support for per thread credentials,
+                * so we just provide a sync thread pool with a wrapper
+                * that asserts that we are already in the required
+                * impersonation state.
+                *
+                * And it needs to cleanup after [f]chdir() within
+                * the job...
+                */
+               user_tp_chdir_safe = smbd_impersonate_tp_current_create(conn,
+                                               conn->sconn->sync_thread_pool,
+                                               conn,
+                                               vuid,
+                                               true, /* chdir_safe */
+                                               session_info->unix_token);
+               if (user_tp_chdir_safe == NULL) {
+                       TALLOC_FREE(frame);
+                       return NULL;
+               }
+               SMB_ASSERT(talloc_reparent(conn, frame, user_tp_chdir_safe));
+       } else {
+               SMB_ASSERT(user_tp_chdir_safe != NULL);
+       }
+
+       if (root_tp_path_sync) {
+               /*
+                * We don't have support for per thread credentials,
+                * so we just provide a sync thread pool with a wrapper
+                * that wrapps the job in become_root()/unbecome_root().
+                */
+               root_tp_path_safe = smbd_impersonate_tp_become_create(conn,
+                                               conn->sconn->sync_thread_pool,
+                                               false, /* chdir_safe */
+                                               become_root,
+                                               unbecome_root);
+               if (root_tp_path_safe == NULL) {
+                       TALLOC_FREE(frame);
+                       return NULL;
+               }
+       } else {
+               root_tp_path_safe = smbd_impersonate_tp_root_create(conn,
+                                               conn->sconn->raw_thread_pool,
+                                               SNUM(conn),
+                                               session_info->unix_token);
+               if (root_tp_path_safe == NULL) {
+                       TALLOC_FREE(frame);
+                       return NULL;
+               }
+       }
+       SMB_ASSERT(talloc_reparent(conn, frame, root_tp_path_safe));
+
+       if (pthreadpool_tevent_per_thread_cwd(root_tp_path_safe)) {
+               root_tp_chdir_safe = root_tp_path_safe;
+       } else {
+               root_tp_chdir_sync = true;
+       }
+
+       if (root_tp_chdir_sync) {
+               /*
+                * We don't have support for per thread credentials,
+                * so we just provide a sync thread pool with a wrapper
+                * that wrapps the job in become_root()/unbecome_root().
+                *
+                * And it needs to cleanup after [f]chdir() within
+                * the job...
+                */
+               root_tp_chdir_safe = smbd_impersonate_tp_become_create(conn,
+                                               conn->sconn->sync_thread_pool,
+                                               true, /* chdir_safe */
+                                               become_root,
+                                               unbecome_root);
+               if (root_tp_chdir_safe == NULL) {
+                       TALLOC_FREE(frame);
+                       return NULL;
+               }
+               SMB_ASSERT(talloc_reparent(conn, frame, root_tp_chdir_safe));
+       } else {
+               SMB_ASSERT(root_tp_chdir_safe != NULL);
+       }
+
+       user_vfs_evg = smb_vfs_ev_glue_create(conn,
+                                             user_ev_ctx,
+                                             user_tp_fd_safe,
+                                             user_tp_path_safe,
+                                             user_tp_chdir_safe,
+                                             conn->sconn->root_ev_ctx,
+                                             root_tp_fd_safe,
+                                             root_tp_path_safe,
+                                             root_tp_chdir_safe);
+       if (user_vfs_evg == NULL) {
+               TALLOC_FREE(frame);
+               return NULL;
+       }
+
+       /*
+        * Make sure everything is a talloc child of user_vfs_evg
+        */
+       SMB_ASSERT(talloc_reparent(frame, user_vfs_evg, user_ev_ctx));
+       SMB_ASSERT(talloc_reparent(frame, user_vfs_evg, user_tp_path_safe));
+       if (user_tp_path_safe != user_tp_chdir_safe) {
+               SMB_ASSERT(talloc_reparent(frame, user_vfs_evg, user_tp_chdir_safe));
+       }
+       SMB_ASSERT(talloc_reparent(frame, user_vfs_evg, root_tp_path_safe));
+       if (root_tp_path_safe != root_tp_chdir_safe) {
+               SMB_ASSERT(talloc_reparent(frame, user_vfs_evg, root_tp_chdir_safe));
+       }
+
+       TALLOC_FREE(frame);
+       return user_vfs_evg;
+}