+
+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;
+}