return SMB_VFS_FILE_ID_CREATE(conn, sbuf);
}
+/*
+ * Design of the smb_vfs_ev_glue infrastructure:
+ *
+ * smb_vfs_ev_glue makes it possible to pass
+ * down an tevent_context and pthreadpool_tevent
+ * used for impersonation through the SMB_VFS stack.
+ *
+ * tevent_req based function take an tevent_context as
+ * there 2nd argument, e.g.:
+ *
+ * struct tevent_req *something_send(TALLOC_CTX *mem_ctx,
+ * struct tevent_context *ev,
+ * ...);
+ *
+ * For the SMB_VFS stack we'll use the following:
+ *
+ * struct tevent_req *SMB_VFS_SOMETHING_SEND(TALLOC_CTX *mem_ctx,
+ * const struct smb_vfs_ev_glue *evg,
+ * ...);
+ *
+ * Typically the 'evg' is just passed through the stack down
+ * to vfs_default.c. In order to do real work an
+ * tevent_context and pthreadpool_tevent are required
+ * to do call a 'somthing()' syscall in an async fashion.
+ * Therefore it will the following to get the pointer
+ * back out of evg:
+ *
+ * ev = smb_vfs_ev_glue_ev_ctx(evg);
+ * tp = smb_vfs_ev_glue_tp_chdir_safe(evg);
+ *
+ * If some function in the stack is sure it needs to run as root
+ * to get some information (after careful checks!), it used
+ * to frame that work into become_root()/unbecome_root().
+ * This can't work when using async functions!
+ * Now it's possible to use something like this (simplified!):
+ *
+ * ev = smb_vfs_ev_glue_ev_ctx(evg);
+ * root_evg = smb_vfs_ev_glue_get_root_glue(evg);
+ * subreq = SMB_VFS_SOMETHING_NEXT_SEND(state, root_evg, ...);
+ * if (tevent_req_nomem(subreq, req)) {
+ * return tevent_req_post(req, ev);
+ * }
+ * tevent_req_set_callback(subreq, module_something_done, req);
+ *
+ * return req;
+ *
+ * static void module_something_done(struct tevent_req *subreq)
+ * {
+ * ...
+ *
+ * status = SMB_VFS_SOMETHING_NEXT_RECV(subreq, &state->aio_state);
+ * TALLOC_FREE(subreq);
+ *
+ * tevent_req_done(req);
+ * }
+ *
+ * In the code above the something_send_fn() function of the next
+ * module in the stack will be called as root.
+ * The smb_vfs_call_something_*() glue code, which is the magic
+ * behind the SMB_VFS_SOMETHING[_NEXT]_{SEND,RECV}() macros,
+ * will look like this:
+ *
+ * struct smb_vfs_call_something_state {
+ * ssize_t (*recv_fn)(struct tevent_req *req,
+ * struct vfs_aio_state *aio_state,
+ * ...);
+ * ssize_t retval;
+ * struct vfs_aio_state vfs_aio_state;
+ * ...
+ * };
+ *
+ * static void smb_vfs_call_something_done(struct tevent_req *subreq);
+ *
+ * struct tevent_req *smb_vfs_call_something_send(
+ * TALLOC_CTX *mem_ctx,
+ * const struct smb_vfs_ev_glue *evg,
+ * struct vfs_handle_struct *handle,
+ * ...)
+ * {
+ * struct tevent_req *req = NULL;
+ * struct smb_vfs_call_something_state *state = NULL;
+ * struct tevent_req *subreq = NULL;
+ * bool ok;
+ *
+ * req = tevent_req_create(mem_ctx, &state,
+ * struct smb_vfs_call_something_state);
+ * if (req == NULL) {
+ * return NULL;
+ * }
+ *
+ * VFS_FIND(something_send);
+ * state->recv_fn = handle->fns->something_recv_fn;
+ *
+ * ok = smb_vfs_ev_glue_push_use(evg, req);
+ * if (!ok) {
+ * tevent_req_error(req, EIO);
+ * return tevent_req_post(req, evg->return_ev);
+ * }
+ *
+ * subreq = handle->fns->something_send_fn(mem_ctx,
+ * evg->next_glue,
+ * handle,
+ * ...);
+ * smb_vfs_ev_glue_pop_use(evg);
+ *
+ * if (tevent_req_nomem(subreq, req)) {
+ * return tevent_req_post(req, evg->return_ev);
+ * }
+ * tevent_req_set_callback(subreq, smb_vfs_call_something_done, req);
+ *
+ * return req;
+ * }
+ *
+ * static void smb_vfs_call_something_done(struct tevent_req *subreq)
+ * {
+ * struct tevent_req *req =
+ * tevent_req_callback_data(subreq,
+ * struct tevent_req);
+ * struct smb_vfs_call_something_state *state =
+ * tevent_req_data(req,
+ * struct smb_vfs_call_something_state);
+ *
+ * state->retval = state->recv_fn(subreq,
+ * &state->vfs_aio_state,
+ * ....);
+ * TALLOC_FREE(subreq);
+ *
+ * if (state->retval == -1) {
+ * tevent_req_error(req, state->vfs_aio_state.error);
+ * return;
+ * }
+ * tevent_req_done(req);
+ * }
+ *
+ * ssize_t smb_vfs_call_something_recv(struct tevent_req *req,
+ * struct vfs_aio_state *aio_state,
+ * ....)
+ * {
+ * struct smb_vfs_call_something_state *state =
+ * tevent_req_data(req,
+ * struct smb_vfs_call_something_state);
+ * ssize_t retval = state->retval;
+ *
+ * if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ * tevent_req_received(req);
+ * return -1;
+ * }
+ *
+ * *aio_state = state->vfs_aio_state;
+ * ...
+ *
+ * tevent_req_received(req);
+ * return retval;
+ * }
+ *
+ * The most important details are these:
+ *
+ * 1. smb_vfs_ev_glue_push_use(evg, req):
+ * - is a no-op if evg->run_ev and evg->return_ev are the same,
+ * it means that we're already at the correct impersonation
+ * and don't need any additional work to be done.
+ * - Otherwise it will call tevent_req_defer_callback(req, evg->return_ev)
+ * This means that tevent_req_error() and tevent_req_done()
+ * will just trigger an immediate event on evg->return_ev.
+ * Therefore the callers callback function will be called
+ * in the impersonation of evg->return_ev! This is important
+ * in order to get the impersonation correct on the way back
+ * through the stack.
+ * - It will call tevent_context_push_use(evg->run_ev),
+ * which will start the impersonation to run_ev.
+ * So the following code run in the correct context.
+ * 2. handle->fns->something_send_fn(..., evg->next_glue, ...):
+ * - We're passing evg->next_glue to the next module.
+ * - Typically evg->next_glue points to evg again.
+ * - In case evg->run_ev and evg->return_ev are not the same,
+ * next_glue will have run_ev and return_ev pointing to evg->run_ev.
+ * So that the switch from evg->run_ev to evg->return_ev
+ * happens on the correct boundary.
+ * 3. smb_vfs_ev_glue_pop_use(evg):
+ * - is a no-op if evg->run_ev and evg->return_ev are the same,
+ * it means that we're already at the correct impersonation
+ * and don't need any additional work to be done.
+ * - It will call tevent_context_pop_use(evg->run_ev),
+ * which will revert the impersonation done in
+ * smb_vfs_ev_glue_push_use().
+ * 4. smb_vfs_call_something_send():
+ * - The is called in the environment of evg->return_ev.
+ * - So it needs to use tevent_req_post(req, evg->return_ev)
+ * 5. smb_vfs_call_something_done():
+ * - The is called in the environment of evg->run_ev
+ * 6. smb_vfs_call_something_recv():
+ * - The is called in the environment of evg->return_ev again.
+ *
+ *
+ * Here are some more complex examples:
+ *
+ * Example 1: only user_evg without switch to root
+ *
+ * SMBD: already impersonated user_evg
+ * evg'1 = smb2_req->user_evg
+ * r'1 = SMB_VFS_*_SEND(evg'1); # smb_vfs_call_*_send()
+ * |
+ * | smb_vfs_ev_glue_push_use(evg'1, r'1);
+ * | |
+ * | | # no-op run_ev == return_ev
+ * | |
+ * | evg'2 = evg'1->next_glue;
+ * | r'2 = module1_*_send(evg'2);
+ * | |
+ * | | evg'3 = evg'2
+ * | | r'3 = SMB_VFS_*_NEXT_SEND(evg'3); # smb_vfs_call_*_send()
+ * | | |
+ * | | | smb_vfs_ev_glue_push_use(evg'3, r'3);
+ * | | | |
+ * | | | | # no-op run_ev == return_ev
+ * | | | |
+ * | | | evg'4 = evg'3->next_glue;
+ * | | | r'4 = module2_*_send(evg'4);
+ * | | | |
+ * | | | | evg'5 = evg'4
+ * | | | | r'5 = SMB_VFS_*_NEXT_SEND(evg'5); # smb_vfs_call_*_send()
+ * | | | | |
+ * | | | | | smb_vfs_ev_glue_push_use(evg'5, r'5);
+ * | | | | | |
+ * | | | | | | # no-op run_ev == return_ev
+ * | | | | | |
+ * | | | | | evg'6 = evg'5->next_glue;
+ * | | | | | r'6 = default_*_send(evg'6);
+ * | | | | | |
+ * | | | | | | ev'6 = smb_vfs_ev_glue_ev_ctx(evg'6)
+ * | | | | | | tp'6 = smb_vfs_ev_glue_tp_chdir_safe(evg'6)
+ * | | | | | | r'7 = pthreadpool_tevent_send(ev'6, tp'6);
+ * | | | | | | |
+ * | | | | | | | pthread_create...
+ * | | | | | | |
+ * | | | | | | tevent_req_set_callback(r'7, default_*_done, r'6);
+ * | | | | | |
+ * | | | | | smb_vfs_ev_glue_pop_use(evg'5);
+ * | | | | | |
+ * | | | | | | # no-op run_ev == return_ev
+ * | | | | | |
+ * | | | | | tevent_req_set_callback(r'6, smb_vfs_call_*_done, r'5);
+ * | | | | |
+ * | | | | tevent_req_set_callback(r'5, module2_*_done, r'4);
+ * | | | |
+ * | | | smb_vfs_ev_glue_pop_use(evg'3);
+ * | | | |
+ * | | | | # no-op run_ev == return_ev
+ * | | | |
+ * | | | tevent_req_set_callback(r'4, smb_vfs_call_*_done, r'3);
+ * | | |
+ * | | tevent_req_set_callback(r'3, module1_*_done, r'2);
+ * | |
+ * | smb_vfs_ev_glue_pop_use(evg'1);
+ * | |
+ * | | # no-op run_ev == return_ev
+ * | |
+ * | tevent_req_set_callback(r'2, smb_vfs_call_*_done, r'1);
+ * |
+ * tevent_req_set_callback(r'1, smbd_*_done, smb2_req);
+ *
+ * Worker thread finished, just one event handler processes
+ * everything as there's no impersonation change.
+ *
+ * tevent_common_invoke_immediate_handler:
+ * |
+ * | before_immediate_handler(ev'6);
+ * | |
+ * | | change_to_user()
+ * | |
+ * | pthreadpool_tevent_job_done(r'7);
+ * | |
+ * | | default_*_done(r'7);
+ * | | |
+ * | | | pthreadpool_tevent_recv(r'7);
+ * | | | TALLOC_FREE(r'7);
+ * | | | tevent_req_done('r6);
+ * | | | |
+ * | | | | smb_vfs_call_*_done(r'6);
+ * | | | | |
+ * | | | | | default_*_recv(r'6);
+ * | | | | | TALLOC_FREE(r'6)
+ * | | | | | tevent_req_done(r'5);
+ * | | | | | |
+ * | | | | | | module2_*_done(r'5):
+ * | | | | | | |
+ * | | | | | | | SMB_VFS_*_recv(r'5); # smb_vfs_call_*_recv()
+ * | | | | | | | TALLOC_FREE(r'5)
+ * | | | | | | | tevent_req_done(r'4);
+ * | | | | | | | |
+ * | | | | | | | | smb_vfs_call_*_done(r'4);
+ * | | | | | | | | |
+ * | | | | | | | | | module2_*_recv(r'4);
+ * | | | | | | | | | TALLOC_FREE(r'4)
+ * | | | | | | | | | tevent_req_done(r'3);
+ * | | | | | | | | | |
+ * | | | | | | | | | | module1_*_done(r'3):
+ * | | | | | | | | | | |
+ * | | | | | | | | | | | SMB_VFS_*_recv(r'3); # smb_vfs_call_*_recv()
+ * | | | | | | | | | | | TALLOC_FREE(r'3)
+ * | | | | | | | | | | | tevent_req_done(r'2);
+ * | | | | | | | | | | | |
+ * | | | | | | | | | | | | smb_vfs_*_done(r'2);
+ * | | | | | | | | | | | | |
+ * | | | | | | | | | | | | | module1_*_recv(r'2);
+ * | | | | | | | | | | | | | TALLOC_FREE(r'2)
+ * | | | | | | | | | | | | | tevent_req_done(r'1);
+ * | | | | | | | | | | | | | |
+ * | | | | | | | | | | | | | | smbd_*_done(r'1);
+ * | | | | | | | | | | | | | | |
+ * | | | | | | | | | | | | | | | SMB_VFS_*_recv(r'1); # smb_vfs_call_*_recv()
+ * | | | | | | | | | | | | | | | TALLOC_FREE(r'1)
+ * | | | | | | | | | | | | | | | smbd_response_to_client()
+ * | | | | | | | | | | | | | | | return
+ * | | | | | | | | | | | | | | |
+ * | | | | | | | | | | | | | | return
+ * | | | | | | | | | | | | | |
+ * | | | | | | | | | | | | | return
+ * | | | | | | | | | | | | |
+ * | | | | | | | | | | | | return
+ * | | | | | | | | | | | |
+ * | | | | | | | | | | | return
+ * | | | | | | | | | | |
+ * | | | | | | | | | | return
+ * | | | | | | | | | |
+ * | | | | | | | | | return
+ * | | | | | | | | |
+ * | | | | | | | | return
+ * | | | | | | | |
+ * | | | | | | | return
+ * | | | | | | |
+ * | | | | | | return
+ * | | | | | |
+ * | | | | | return
+ * | | | | |
+ * | | | | return
+ * | | | |
+ * | | | return
+ * | | |
+ * | | return
+ * | |
+ * | after_immediate_handler(ev'6);
+ * | |
+ * | | # lazy no change_to_user()
+ * | |
+ * | return
+ *
+ *
+ * Example 2: start with user_evg and let module1 switch to root
+ *
+ * SMBD: already impersonated user_evg
+ * evg'1 = smb2_req->user_evg
+ * r'1 = SMB_VFS_*_SEND(evg'1); # smb_vfs_call_*_send()
+ * |
+ * | smb_vfs_ev_glue_push_use(evg'1, r'1);
+ * | |
+ * | | # no-op run_ev == return_ev
+ * | |
+ * | evg'2 = evg'1->next_glue;
+ * | r'2 = module1_*_send(evg'2);
+ * | |
+ * | | evg'3 = smb_vfs_ev_glue_get_root_glue(evg'2)
+ * | | r'3 = SMB_VFS_*_NEXT_SEND(evg'3); # smb_vfs_call_*_send()
+ * | | |
+ * | | | smb_vfs_ev_glue_push_use(evg'3, r'3);
+ * | | | |
+ * | | | | tevent_req_defer_callback(r'3, evg'3->return_ev);
+ * | | | | tevent_context_push_use(evg'3->run_ev)
+ * | | | | |
+ * | | | | | become_root()
+ * | | | | |
+ * | | | |
+ * | | | evg'4 = evg'3->next_glue;
+ * | | | r'4 = module2_*_send(evg'4);
+ * | | | |
+ * | | | | evg'5 = smb_vfs_ev_glue_get_root_glue(evg'4)
+ * | | | | r'5 = SMB_VFS_*_NEXT_SEND(evg'5); # smb_vfs_call_*_send()
+ * | | | | |
+ * | | | | | smb_vfs_ev_glue_push_use(evg'5, r'5);
+ * | | | | | |
+ * | | | | | | # no-op run_ev == return_ev, already root
+ * | | | | | |
+ * | | | | | evg'6 = evg'5->next_glue;
+ * | | | | | r'6 = default_*_send(evg'6);
+ * | | | | | |
+ * | | | | | | ev'6 = smb_vfs_ev_glue_ev_ctx(evg'6)
+ * | | | | | | tp'6 = smb_vfs_ev_glue_tp_chdir_safe(evg'6)
+ * | | | | | | r'7 = pthreadpool_tevent_send(ev'6, tp'6);
+ * | | | | | | |
+ * | | | | | | | pthread_create...
+ * | | | | | | |
+ * | | | | | | tevent_req_set_callback(r'7, default_*_done, r'6);
+ * | | | | | |
+ * | | | | | smb_vfs_ev_glue_pop_use(evg'5);
+ * | | | | | |
+ * | | | | | | # no-op run_ev == return_ev, still stay as root
+ * | | | | | |
+ * | | | | | tevent_req_set_callback(r'6, smb_vfs_*_done, r'5);
+ * | | | | |
+ * | | | | tevent_req_set_callback(r'5, module2_*_done, r'4);
+ * | | | |
+ * | | | smb_vfs_ev_glue_pop_use(evg'3);
+ * | | | |
+ * | | | | tevent_context_pop_use(evg'3->run_ev)
+ * | | | | |
+ * | | | | | unbecome_root()
+ * | | | |
+ * | | | tevent_req_set_callback(r'4, smb_vfs_*_done, r'3);
+ * | | |
+ * | | tevent_req_set_callback(r'3, module1_*_done, r'2);
+ * | |
+ * | smb_vfs_ev_glue_pop_use(evg'1);
+ * | |
+ * | | # no-op run_ev == return_ev
+ * | |
+ * | tevent_req_set_callback(r'2, smb_vfs_*_done, r'1);
+ * |
+ * tevent_req_set_callback(r'1, smbd_*_done, smb2_req);
+ *
+ * Worker thread finished, just one event handler processes
+ * everything as there's no impersonation change.
+ *
+ * tevent_common_invoke_immediate_handler:
+ * |
+ * | before_immediate_handler(ev'6);
+ * | |
+ * | | become_root()
+ * | |
+ * | pthreadpool_tevent_job_done(r'7);
+ * | |
+ * | | default_*_done(r'7);
+ * | | |
+ * | | | pthreadpool_tevent_recv(r'7);
+ * | | | TALLOC_FREE(r'7);
+ * | | | tevent_req_done('r6);
+ * | | | |
+ * | | | | smb_vfs_*_done(r'6);
+ * | | | | |
+ * | | | | | default_*_recv(r'6);
+ * | | | | | TALLOC_FREE(r'6)
+ * | | | | | tevent_req_done(r'5);
+ * | | | | | |
+ * | | | | | | module2_*_done(r'5):
+ * | | | | | | |
+ * | | | | | | | SMB_VFS_*_recv(r'5);
+ * | | | | | | | TALLOC_FREE(r'5)
+ * | | | | | | | tevent_req_done(r'4);
+ * | | | | | | | |
+ * | | | | | | | | smb_vfs_*_done(r'4);
+ * | | | | | | | | |
+ * | | | | | | | | | module2_*_recv(r'4);
+ * | | | | | | | | | TALLOC_FREE(r'4)
+ * | | | | | | | | | tevent_req_done(r'3);
+ * | | | | | | | | | | return
+ * | | | | | | | | | |
+ * | | | | | | | | | return
+ * | | | | | | | | |
+ * | | | | | | | | return
+ * | | | | | | | |
+ * | | | | | | | return
+ * | | | | | | |
+ * | | | | | | return
+ * | | | | | |
+ * | | | | | return
+ * | | | | |
+ * | | | | return
+ * | | | |
+ * | | | return
+ * | | |
+ * | | return
+ * | |
+ * | |
+ * | after_immediate_handler(ev'6);
+ * | |
+ * | | unbecome_root()
+ * | |
+ * | return
+ * |
+ * tevent_common_invoke_immediate_handler:
+ * |
+ * | before_immediate_handler(ev'6);
+ * | |
+ * | | change_to_user()
+ * | |
+ * | tevent_req_trigger();
+ * | ...
+ * | _tevent_req_notify_callback(r'3)
+ * | |
+ * | | module1_*_done(r'3):
+ * | | |
+ * | | | SMB_VFS_*_recv(r'3);
+ * | | | TALLOC_FREE(r'3)
+ * | | | tevent_req_done(r'2);
+ * | | | |
+ * | | | | smb_vfs_*_done(r'2);
+ * | | | | |
+ * | | | | | module1_*_recv(r'2);
+ * | | | | | TALLOC_FREE(r'2)
+ * | | | | | tevent_req_done(r'1);
+ * | | | | | |
+ * | | | | | | smbd_*_done(r'1);
+ * | | | | | | |
+ * | | | | | | | SMB_VFS_*_recv(r'1);
+ * | | | | | | | TALLOC_FREE(r'1)
+ * | | | | | | | smbd_response_to_client()
+ * | | | | | | | return
+ * | | | | | | |
+ * | | | | | | return
+ * | | | | | |
+ * | | | | | return
+ * | | | | |
+ * | | | | return
+ * | | | |
+ * | | | return
+ * | | |
+ * | | return
+ * | |
+ * | after_immediate_handler(ev'6);
+ * | |
+ * | | # lazy no change_to_user()
+ * | |
+ * | return
+ *
+ */
+struct smb_vfs_ev_glue {
+ /*
+ * The event context that should be used
+ * to report the result back.
+ *
+ * The is basically the callers context.
+ */
+ struct tevent_context *return_ev;
+
+ /*
+ * The event context and threadpool wrappers
+ * the current context should use.
+ *
+ * tp_fd_safe only allows fd based functions
+ * which don't require impersonation, this
+ * is basically the raw threadpool.
+ *
+ * tp_path_safe allows path based functions
+ * to be called under the correct impersonation.
+ * But chdir/fchdir is not allowed!
+ * Typically calls like openat() or other *at()
+ * syscalls.
+ *
+ * tp_chdir_safe is like path_safe, but also
+ * allows chdir/fchdir to be called, the job
+ * can safely return with a changed directory,
+ * the threadpool wrapper takes care of
+ * a cleanup if required.
+ * This is needed if *at() syscalls need
+ * to be simulated by fchdir();$syscall(),
+ * e.g. getxattr().
+ *
+ * The distinction between these threadpool
+ * is required because of OS limitations
+ * (as of 2018):
+ * - only Linux supports per thread
+ * credentials (seteuid....)
+ * - only Linux supports a per thread
+ * current working directory,
+ * using unshare(CLONE_FS). But
+ * in some constrained container
+ * environments even that is not available
+ * on Linux.
+ *
+ * tp_fd_safe is typically the raw threadpool
+ * without a wrapper.
+ *
+ * On Linux tp_path_safe and tp_chdir_safe
+ * are typically the same (if unshare(CLONE_FS) is available)
+ * they're implemented as wrappers of the raw threadpool.
+ *
+ * On other OSes tp_path_safe is a wrapper
+ * arround a sync threadpool (without real threads, just blocking
+ * the main thread), but hidden behind the pthreadpool_tevent
+ * api in order to make the restriction transparent.
+ *
+ * On other OSes tp_chdir_safe is a wrapper
+ * arround a sync threadpool (without real threads, just blocking
+ * the main thread), but hidden behind the pthreadpool_tevent
+ * api in order to make the restriction transparent.
+ * It just remembers/restores the current working directory,
+ * typically using open(".", O_RDONLY | O_DIRECTORY) and fchdir().
+ */
+ struct tevent_context *run_ev;
+ struct pthreadpool_tevent *run_tp_fd_safe;
+ struct pthreadpool_tevent *run_tp_path_safe;
+ struct pthreadpool_tevent *run_tp_chdir_safe;
+
+ /*
+ * The glue that should be passed down
+ * to sub request in the stack.
+ *
+ * Typically this points to itself.
+ *
+ * But smb_vfs_ev_glue_create_switch() allows
+ * to create context that can switch
+ * between two user glues.
+ */
+ const struct smb_vfs_ev_glue *next_glue;
+
+ /*
+ * If some code path wants to run
+ * some constraint code as root,
+ * basically an async version of become_root()
+ * and unbecome_root().
+ *
+ * The caller can call smb_vfs_ev_glue_get_root_glue()
+ * to get a root glue that can be passed
+ * to the SMB_VFS_*_SEND() function that
+ * should run as root.
+ *
+ * Note that the callback (registered with
+ * tevent_req_set_callback()) won't run as
+ * root anymore!
+ */
+ const struct smb_vfs_ev_glue *root_glue;
+};
+
+static struct smb_vfs_ev_glue *smb_vfs_ev_glue_create_internal(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *return_ev,
+ struct tevent_context *run_ev,
+ struct pthreadpool_tevent *run_tp_fd_safe,
+ struct pthreadpool_tevent *run_tp_path_safe,
+ struct pthreadpool_tevent *run_tp_chdir_safe)
+{
+ struct smb_vfs_ev_glue *evg = NULL;
+
+ evg = talloc_zero(mem_ctx, struct smb_vfs_ev_glue);
+ if (evg == NULL) {
+ return NULL;
+ }
+ *evg = (struct smb_vfs_ev_glue) {
+ .return_ev = return_ev,
+ .run_ev = run_ev,
+ .run_tp_fd_safe = run_tp_fd_safe,
+ .run_tp_path_safe = run_tp_path_safe,
+ .run_tp_chdir_safe = run_tp_chdir_safe,
+ .next_glue = evg,
+ };
+
+ return evg;
+}
+
+struct tevent_context *smb_vfs_ev_glue_ev_ctx(const struct smb_vfs_ev_glue *evg)
+{
+ return evg->run_ev;
+}
+
+struct pthreadpool_tevent *smb_vfs_ev_glue_tp_fd_safe(const struct smb_vfs_ev_glue *evg)
+{
+ return evg->run_tp_fd_safe;
+}
+
+struct pthreadpool_tevent *smb_vfs_ev_glue_tp_path_safe(const struct smb_vfs_ev_glue *evg)
+{
+ return evg->run_tp_path_safe;
+}
+
+struct pthreadpool_tevent *smb_vfs_ev_glue_tp_chdir_safe(const struct smb_vfs_ev_glue *evg)
+{
+ return evg->run_tp_chdir_safe;
+}
+
+const struct smb_vfs_ev_glue *smb_vfs_ev_glue_get_root_glue(const struct smb_vfs_ev_glue *evg)
+{
+ return evg->root_glue;
+}
+
+struct smb_vfs_ev_glue *smb_vfs_ev_glue_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *user_ev,
+ struct pthreadpool_tevent *user_tp_fd_safe,
+ struct pthreadpool_tevent *user_tp_path_safe,
+ struct pthreadpool_tevent *user_tp_chdir_safe,
+ struct tevent_context *root_ev,
+ struct pthreadpool_tevent *root_tp_fd_safe,
+ struct pthreadpool_tevent *root_tp_path_safe,
+ struct pthreadpool_tevent *root_tp_chdir_safe)
+{
+ struct smb_vfs_ev_glue *evg_uu = NULL;
+ struct smb_vfs_ev_glue *evg_ru = NULL;
+ struct smb_vfs_ev_glue *evg_rr = NULL;
+
+ /*
+ * The top level glue (directly returned from this function).
+ *
+ * It uses user_ev and user_tp_* only.
+ */
+ evg_uu = smb_vfs_ev_glue_create_internal(mem_ctx,
+ user_ev, /* return_ev */
+ user_ev, /* run_ev */
+ user_tp_fd_safe,
+ user_tp_path_safe,
+ user_tp_chdir_safe);
+ if (evg_uu == NULL) {
+ return NULL;
+ }
+
+ /*
+ * The first root glue (returned by smb_vfs_ev_glue_get_root_glue()).
+ *
+ * It uses root_ev and root_tp, but user_ev as return ev,
+ * which means that the caller's callback (registered with
+ * tevent_req_set_callback()) will run as user_ev.
+ */
+ evg_ru = smb_vfs_ev_glue_create_internal(evg_uu,
+ user_ev, /* return_ev */
+ root_ev, /* run_ev */
+ root_tp_fd_safe,
+ root_tp_path_safe,
+ root_tp_chdir_safe);
+ if (evg_ru == NULL) {
+ TALLOC_FREE(evg_uu);
+ return NULL;
+ }
+
+ /*
+ * The second root glue (returned by smb_vfs_ev_glue_get_root_glue() on
+ * root glue itself. This means code can always call
+ * smb_vfs_ev_glue_get_root_glue() and don't have to care if the
+ * passed glue is already a root glue.
+ *
+ * This will then recursively point to its own root_glue pointer.
+ *
+ * It only uses root_ev and root_tp.
+ */
+ evg_rr = smb_vfs_ev_glue_create_internal(evg_ru,
+ root_ev, /* return_ev */
+ root_ev, /* run_ev */
+ root_tp_fd_safe,
+ root_tp_path_safe,
+ root_tp_chdir_safe);
+ if (evg_rr == NULL) {
+ TALLOC_FREE(evg_uu);
+ return NULL;
+ }
+
+ /*
+ * We now setup the glue hierachie.
+ *
+ * Search for "Design of the smb_vfs_ev_glue infrastructure" above
+ * for a detailed description how the chain works.
+ *
+ * "Example 2: start with user_evg and let module1 switch to root"
+ * explains it for the root_glue chaining.
+ */
+ evg_rr->root_glue = evg_rr;
+ evg_ru->root_glue = evg_rr;
+ evg_uu->root_glue = evg_ru;
+
+ /*
+ * As evg_ru is a boundary with
+ * run_ev != return_ev, we need to
+ * alter its next_glue.
+ */
+ evg_ru->next_glue = evg_rr;
+
+ return evg_uu;
+}
+
+/*
+ * This can be used to create a temporary glue
+ * if you need to switch between two user contexts
+ *
+ * It's the caller's duty to make sure both
+ * glues stay alive for the lifetime of the
+ * created switch.
+ */
+struct smb_vfs_ev_glue *smb_vfs_ev_glue_create_switch(
+ TALLOC_CTX *mem_ctx,
+ const struct smb_vfs_ev_glue *return_evg,
+ const struct smb_vfs_ev_glue *run_evg)
+{
+ const struct smb_vfs_ev_glue *run_root = run_evg->root_glue;
+ struct smb_vfs_ev_glue *evg_u = NULL;
+ struct smb_vfs_ev_glue *evg_r = NULL;
+
+ /*
+ * Here we basically need to dup run_evg (and run_evg->root_glue)
+ * and replace their return_ev with return_evg->return_ev.
+ *
+ * We need to put the new evgs in front of the chain...
+ */
+ evg_u = smb_vfs_ev_glue_create_internal(mem_ctx,
+ return_evg->return_ev,
+ run_evg->run_ev,
+ run_evg->run_tp_fd_safe,
+ run_evg->run_tp_path_safe,
+ run_evg->run_tp_chdir_safe);
+ if (evg_u == NULL) {
+ return NULL;
+ }
+
+ evg_r = smb_vfs_ev_glue_create_internal(evg_u,
+ return_evg->return_ev,
+ run_root->run_ev,
+ run_root->run_tp_fd_safe,
+ run_root->run_tp_path_safe,
+ run_root->run_tp_chdir_safe);
+ if (evg_r == NULL) {
+ return NULL;
+ }
+
+ /*
+ * evg_r is a boundary with run_ev != return_ev.
+ * As run_root is also a boundary, we need to
+ * use run_root->next_glue in order to get
+ * a glue that stays as root.
+ *
+ * The same applies to the chaining of root
+ * glues.
+ */
+ evg_r->next_glue = run_root->next_glue;
+ evg_r->root_glue = run_root->root_glue;
+
+ /*
+ * evg_r is a boundary with run_ev != return_ev.
+ * But run_evg is typically not a boundary,
+ * we use it directly as next_glue.
+ *
+ * And the root_glue is the one we constructed above.
+ */
+ evg_u->next_glue = run_evg;
+ evg_u->root_glue = evg_r;
+
+ return evg_u;
+}
+
+_UNUSED_
+static bool smb_vfs_ev_glue_push_use(const struct smb_vfs_ev_glue *evg,
+ struct tevent_req *req)
+{
+ if (evg->run_ev == evg->return_ev) {
+ /*
+ * We're already in the correct
+ * impersonation environment.
+ */
+ return true;
+ }
+
+ /*
+ * Make sure that our callers callback function
+ * will be called in the return_ev environment.
+ */
+ tevent_req_defer_callback(req, evg->return_ev);
+
+ /*
+ * let the event context wrapper do
+ * the required impersonation.
+ */
+ return tevent_context_push_use(evg->run_ev);
+}
+
+_UNUSED_
+static void smb_vfs_ev_glue_pop_use(const struct smb_vfs_ev_glue *evg)
+{
+ if (evg->run_ev == evg->return_ev) {
+ /*
+ * smb_vfs_ev_glue_push_use() didn't
+ * change the impersonation environment.
+ */
+ return;
+ }
+
+ /*
+ * undo the impersonation
+ */
+ tevent_context_pop_use(evg->run_ev);
+}
+
int smb_vfs_call_connect(struct vfs_handle_struct *handle,
const char *service, const char *user)
{