From 1251a536df4b1df58d9ddacab03d3ebe6f4e5b60 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Sun, 8 Jul 2018 16:28:02 +0200 Subject: [PATCH] s3: vfs: add smb_vfs_ev_glue This adds VFS helper functions and that work on a struct smb_vfs_ev_glue object which bundles two event contexts and a few threadpools. This will be used to streamline the use of impersonating wrappers in the SMB_VFS. Notice the verbose comments in source3/smbd/vfs.c. This will allow us to introduce path based async operations to the SMB_VFS layer. Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Stefan Metzmacher Signed-off-by: Ralph Boehme --- source3/include/vfs.h | 29 ++ source3/smbd/vfs.c | 874 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 903 insertions(+) diff --git a/source3/include/vfs.h b/source3/include/vfs.h index 90d39acd3fc..a8bb9c121ac 100644 --- a/source3/include/vfs.h +++ b/source3/include/vfs.h @@ -257,6 +257,7 @@ /* Version 39 - Remove struct dfree_cached_info pointer from connection struct */ /* Bump to version 40, Samba 4.10 will ship with that */ +/* Version 40 - Introduce smb_vfs_ev_glue infrastructure. */ #define SMB_VFS_INTERFACE_VERSION 40 @@ -279,6 +280,9 @@ struct smb_file_time; struct blocking_lock_record; struct smb_filename; struct dfs_GetDFSReferral; +struct tevent_context; +struct pthreadpool_tevent; +struct smb_vfs_ev_glue; typedef union unid_t { uid_t uid; @@ -1500,4 +1504,29 @@ void *vfs_fetch_fsp_extension(vfs_handle_struct *handle, files_struct *fsp); void smb_vfs_assert_all_fns(const struct vfs_fn_pointers* fns, const char *module); +/* + * Notice the "Design of the smb_vfs_ev_glue infrastructure" + * comment in source3/smbd/vfs.c! + * + * This explains smb_vfs_ev_glue infrastructure in detail. + */ +struct tevent_context *smb_vfs_ev_glue_ev_ctx(const struct smb_vfs_ev_glue *evg); +struct pthreadpool_tevent *smb_vfs_ev_glue_tp_fd_safe(const struct smb_vfs_ev_glue *evg); +struct pthreadpool_tevent *smb_vfs_ev_glue_tp_path_safe(const struct smb_vfs_ev_glue *evg); +struct pthreadpool_tevent *smb_vfs_ev_glue_tp_chdir_safe(const struct smb_vfs_ev_glue *evg); +const struct smb_vfs_ev_glue *smb_vfs_ev_glue_get_root_glue(const struct smb_vfs_ev_glue *evg); +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 *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); + #endif /* _VFS_H */ diff --git a/source3/smbd/vfs.c b/source3/smbd/vfs.c index a6f42802065..a0fc005dad3 100644 --- a/source3/smbd/vfs.c +++ b/source3/smbd/vfs.c @@ -1455,6 +1455,880 @@ struct file_id vfs_file_id_from_sbuf(connection_struct *conn, const SMB_STRUCT_S 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) { -- 2.47.3