From: Samuel Cabrero Date: Mon, 6 Feb 2023 17:49:07 +0000 (+0100) Subject: winbind:varlink: Implement memberships enumeration X-Git-Tag: tevent-0.17.0~736 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6f437ae49d5561947a8d63f25ed7faa1e4e51441;p=thirdparty%2Fsamba.git winbind:varlink: Implement memberships enumeration $> varlink call -m unix:/run/systemd/userdb/org.samba.winbind/io.systemd.UserDatabase.GetMemberships "{\"service\":\"org.samba.winbind\"}" { "groupName": "AFOREST+schema admins", "userName": "AFOREST+administrator" } { "groupName": "AFOREST+enterprise admins", "userName": "AFOREST+administrator" } { "groupName": "AFOREST+domain admins", "userName": "AFOREST+administrator" } { "groupName": "AFOREST+domain users", "userName": "AFOREST+administrator" } { "groupName": "AFOREST+domain users", "userName": "AFOREST+user1" } { "groupName": "AFOREST+domain users", "userName": "AFOREST+krbtgt" } { "groupName": "AFOREST+domain guests", "userName": "AFOREST+guest" } { "groupName": "AFOREST+group policy creator owners", "userName": "AFOREST+administrator" } { "groupName": "AFOREST+denied rodc password replication group", "userName": "AFOREST+krbtgt" } Signed-off-by: Samuel Cabrero Reviewed-by: Andreas Schneider --- diff --git a/source3/winbindd/winbindd_varlink.c b/source3/winbindd/winbindd_varlink.c index 1b77bd49ea0..94a47c22aa5 100644 --- a/source3/winbindd/winbindd_varlink.c +++ b/source3/winbindd/winbindd_varlink.c @@ -334,9 +334,84 @@ static long io_systemd_getmemberships(VarlinkService *service, uint64_t flags, void *userdata) { - return varlink_call_reply_error(call, - WB_VL_REPLY_ERROR_NO_RECORD_FOUND, - NULL); + struct wb_vl_state *state = + talloc_get_type_abort(userdata, struct wb_vl_state); + const char *parm_service = NULL; + const char *parm_username = NULL; + const char *parm_groupname = NULL; + const char *service_name = NULL; + NTSTATUS status; + long rc; + + rc = varlink_object_get_string(parameters, "service", &parm_service); + if (rc < 0) { + DBG_ERR("Failed to get service parameter: %s\n", + varlink_error_string(rc)); + varlink_call_reply_error(call, + WB_VL_REPLY_ERROR_BAD_SERVICE, + NULL); + return 0; + } + + service_name = lp_parm_const_string(-1, + "winbind varlink", + "service name", + WB_VL_SERVICE_NAME); + + if (!strequal(parm_service, service_name)) { + varlink_call_reply_error(call, + WB_VL_REPLY_ERROR_BAD_SERVICE, + NULL); + return 0; + } + + rc = varlink_object_get_string(parameters, "userName", &parm_username); + if (rc < 0 && rc != -VARLINK_ERROR_UNKNOWN_FIELD) { + DBG_ERR("Failed to get name parameter: %ld (%s)\n", + rc, + varlink_error_string(rc)); + goto fail; + } + + rc = varlink_object_get_string(parameters, + "groupName", + &parm_groupname); + if (rc < 0 && rc != -VARLINK_ERROR_UNKNOWN_FIELD) { + DBG_ERR("Failed to get uid parameter: %ld (%s)\n", + rc, + varlink_error_string(rc)); + goto fail; + } + + DBG_DEBUG("GetMemberships call parameters: service='%s', " + "userName='%s', groupName='%s'\n", + parm_service, + parm_username, + parm_groupname); + + /* + * The wb_vl_membership_* functions will reply theirselves when return + * NT_STATUS_OK + */ + if (parm_username == NULL && parm_groupname == NULL) { + /* Enumeration */ + status = wb_vl_memberships_enumerate(state, + state->ev_ctx, + call, + flags, + parm_service); + } + + if (NT_STATUS_IS_ERR(status)) { + goto fail; + } + + return 0; +fail: + varlink_call_reply_error(call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + return 0; } static void varlink_listen_fde_handler(struct tevent_context *ev, diff --git a/source3/winbindd/winbindd_varlink.h b/source3/winbindd/winbindd_varlink.h index aa74ebf3a17..b7353a5d2ae 100644 --- a/source3/winbindd/winbindd_varlink.h +++ b/source3/winbindd/winbindd_varlink.h @@ -93,6 +93,13 @@ NTSTATUS wb_vl_group_by_name_and_gid(TALLOC_CTX *mem_ctx, const char *group_name, int64_t gid); +/* GetMemberships */ +NTSTATUS wb_vl_memberships_enumerate(TALLOC_CTX *mem_ctx, + struct tevent_context *ev_ctx, + VarlinkCall *call, + uint64_t flags, + const char *service); + bool winbind_setup_varlink(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx); #endif /* _SOURCE3_WINBIND_VARLINK_H_ */ diff --git a/source3/winbindd/winbindd_varlink_getmemberships.c b/source3/winbindd/winbindd_varlink_getmemberships.c new file mode 100644 index 00000000000..123a95b1555 --- /dev/null +++ b/source3/winbindd/winbindd_varlink_getmemberships.c @@ -0,0 +1,396 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Samuel Cabrero 2023 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include "includes.h" +#include "winbindd.h" +#include "winbindd_varlink.h" + +static void membership_reply(VarlinkCall *call, + const char *username, + const char *groupname, + bool continues) +{ + VarlinkObject *out = NULL; + + varlink_object_new(&out); + varlink_object_set_string(out, "userName", username); + varlink_object_set_string(out, "groupName", groupname); + + varlink_call_reply(call, out, continues ? VARLINK_REPLY_CONTINUES : 0); + varlink_object_unref(out); +} + +static void member_list_reply(VarlinkCall *call, + struct winbindd_gr *gr, + char *gr_members, + bool continues) +{ + char *name = NULL; + char *p = NULL; + int i; + + for ((name = strtok_r(gr_members, ",", &p)), i = 0; name != NULL; + name = strtok_r(NULL, ",", &p), i++) { + if (i == gr->num_gr_mem) { + break; + } + membership_reply(call, + name, + gr->gr_name, + continues || ((i + 1) < gr->num_gr_mem)); + } +} + +/****************************************************************************** + * Membership enumeration + *****************************************************************************/ + +struct membership_enum_state { + struct tevent_context *ev_ctx; + struct winbindd_request *fake_req; + struct winbindd_cli_state *fake_cli; + VarlinkCall *call; + + struct winbindd_gr *last_gr; + char *last_members; +}; + +static int membership_enum_state_destructor(struct membership_enum_state *s) +{ + if (s->call != NULL) { + s->call = varlink_call_unref(s->call); + } + + return 0; +} + +static void membership_enum_endgrent_done(struct tevent_req *req) +{ + struct membership_enum_state *s = + tevent_req_callback_data(req, struct membership_enum_state); + struct winbindd_response *response = NULL; + NTSTATUS status; + + /* winbindd_*_recv functions expect a talloc-allocated response */ + response = talloc_zero(s, struct winbindd_response); + if (response == NULL) { + DBG_ERR("No memory\n"); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + + status = winbindd_endgrent_recv(req, response); + TALLOC_FREE(req); + + if (NT_STATUS_IS_ERR(status)) { + DBG_ERR("winbindd_endgrent failed: %s\n", nt_errstr(status)); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + + if (s->last_gr == NULL || s->last_gr->num_gr_mem == 0) { + varlink_call_reply_error(s->call, + WB_VL_REPLY_ERROR_NO_RECORD_FOUND, + NULL); + goto out; + } + + member_list_reply(s->call, s->last_gr, s->last_members, false); +out: + TALLOC_FREE(s); +} + +static void membership_enum_getgrent_done(struct tevent_req *req) +{ + struct membership_enum_state *s = + tevent_req_callback_data(req, struct membership_enum_state); + struct winbindd_response *response = NULL; + struct winbindd_gr *grs = NULL; + char *member_data = NULL; + NTSTATUS status; + uint32_t i; + + /* winbindd_*_recv functions expect a talloc-allocated response */ + response = talloc_zero(s, struct winbindd_response); + if (response == NULL) { + DBG_ERR("No memory\n"); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + + status = winbindd_getgrent_recv(req, response); + TALLOC_FREE(req); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) { + ZERO_STRUCTP(s->fake_req); + s->fake_req->cmd = WINBINDD_ENDGRENT; + req = winbindd_endgrent_send(s, + s->ev_ctx, + s->fake_cli, + s->fake_req); + if (req == NULL) { + DBG_ERR("No memory\n"); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + tevent_req_set_callback(req, membership_enum_endgrent_done, s); + return; + } else if (NT_STATUS_IS_ERR(status)) { + DBG_ERR("winbindd_getgrent failed: %s\n", nt_errstr(status)); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + + if (response->data.num_entries == 0) { + varlink_call_reply_error(s->call, + WB_VL_REPLY_ERROR_NO_RECORD_FOUND, + NULL); + goto out; + } + + /* + * The returned winbindd_gr structs start at the beginning of the + * extra data. + */ + grs = (struct winbindd_gr *)response->extra_data.data; + + /* The memberships stats after all returned winbindd_gr structs */ + member_data = (char *)response->extra_data.data + + response->data.num_entries * sizeof(struct winbindd_gr); + + for (i = 0; i < response->data.num_entries; i++) { + struct winbindd_gr *gr = &grs[i]; + char *gr_members = &member_data[gr->gr_mem_ofs]; + + /* Skip groups without members */ + if (gr->num_gr_mem == 0) { + continue; + } + + /* + * When the first group with members from the current batch is + * found, send the saved one with continue flag set and save + * the first one from the current batch. + * + * If there are no more groups with members in this batch, + * the saved one will be sent either in the endgrent callback + * or in the next getgrent batch. + */ + if (s->last_gr != NULL) { + member_list_reply(s->call, + s->last_gr, + s->last_members, + true); + TALLOC_FREE(s->last_gr); + TALLOC_FREE(s->last_members); + } + + if (s->last_gr == NULL) { + s->last_gr = talloc_zero(s, struct winbindd_gr); + if (s->last_gr == NULL) { + DBG_ERR("No memory\n"); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + *s->last_gr = grs[i]; + s->last_members = talloc_strdup( + s, + &member_data[s->last_gr->gr_mem_ofs]); + if (s->last_members == NULL) { + DBG_ERR("No memory\n"); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + + continue; + } + + member_list_reply(s->call, gr, gr_members, true); + } + + /* Get next chunk */ + TALLOC_FREE(response); + ZERO_STRUCTP(s->fake_req); + s->fake_req->cmd = WINBINDD_GETGRENT; + s->fake_req->data.num_entries = 500; + req = winbindd_getgrent_send(s, s->ev_ctx, s->fake_cli, s->fake_req); + if (req == NULL) { + DBG_ERR("No memory"); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + tevent_req_set_callback(req, membership_enum_getgrent_done, s); + return; +out: + TALLOC_FREE(s); +} + +static void membership_enum_setgrent_done(struct tevent_req *req) +{ + struct membership_enum_state *s = + tevent_req_callback_data(req, struct membership_enum_state); + struct winbindd_response *response = NULL; + NTSTATUS status; + + /* winbindd_*_recv functions expect a talloc-allocated response */ + response = talloc_zero(s, struct winbindd_response); + if (response == NULL) { + DBG_ERR("No memory\n"); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + + status = winbindd_setgrent_recv(req, response); + TALLOC_FREE(req); + TALLOC_FREE(response); + + if (NT_STATUS_IS_ERR(status)) { + DBG_ERR("winbindd_setgrent failed: %s\n", nt_errstr(status)); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + + ZERO_STRUCTP(s->fake_req); + s->fake_req->cmd = WINBINDD_GETGRENT; + s->fake_req->data.num_entries = 500; + + req = winbindd_getgrent_send(s, s->ev_ctx, s->fake_cli, s->fake_req); + if (req == NULL) { + DBG_ERR("No memory\n"); + varlink_call_reply_error( + s->call, + WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE, + NULL); + goto out; + } + tevent_req_set_callback(req, membership_enum_getgrent_done, s); + return; +out: + TALLOC_FREE(s); +} + +NTSTATUS wb_vl_memberships_enumerate(TALLOC_CTX *mem_ctx, + struct tevent_context *ev_ctx, + VarlinkCall *call, + uint64_t flags, + const char *service) +{ + struct membership_enum_state *s = NULL; + struct tevent_req *req = NULL; + NTSTATUS status; + + /* Check if enumeration enabled */ + if (!lp_winbind_enum_groups()) { + varlink_call_reply_error( + call, + WB_VL_REPLY_ERROR_ENUMERATION_NOT_SUPPORTED, + NULL); + return NT_STATUS_OK; + } + + /* Check if group expansion is enabled */ + if (!lp_winbind_expand_groups()) { + varlink_call_reply_error( + call, + WB_VL_REPLY_ERROR_ENUMERATION_NOT_SUPPORTED, + NULL); + return NT_STATUS_OK; + } + + /* Check more flag is set */ + if (!(flags & VARLINK_CALL_MORE)) { + DBG_WARNING("Enum request without more flag set\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + s = talloc_zero(mem_ctx, struct membership_enum_state); + if (s == NULL) { + DBG_ERR("No memory\n"); + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(s, membership_enum_state_destructor); + + s->fake_cli = talloc_zero(s, struct winbindd_cli_state); + if (s->fake_cli == NULL) { + DBG_ERR("No memory\n"); + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + s->fake_req = talloc_zero(s, struct winbindd_request); + if (s->fake_req == NULL) { + DBG_ERR("No memory\n"); + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + s->ev_ctx = ev_ctx; + s->call = varlink_call_ref(call); + + status = wb_vl_fake_cli_state(call, service, s->fake_cli); + if (NT_STATUS_IS_ERR(status)) { + DBG_ERR("Failed to create fake winbindd_cli_state: %s\n", + nt_errstr(status)); + goto fail; + } + + s->fake_req->cmd = WINBINDD_SETGRENT; + req = winbindd_setgrent_send(s, s->ev_ctx, s->fake_cli, s->fake_req); + if (req == NULL) { + DBG_ERR("No memory\n"); + status = NT_STATUS_NO_MEMORY; + goto fail; + } + tevent_req_set_callback(req, membership_enum_setgrent_done, s); + + return NT_STATUS_OK; +fail: + TALLOC_FREE(s); + return status; +} diff --git a/source3/winbindd/wscript_build b/source3/winbindd/wscript_build index 4282d122736..2d7521c3f87 100644 --- a/source3/winbindd/wscript_build +++ b/source3/winbindd/wscript_build @@ -176,6 +176,7 @@ bld.SAMBA3_SUBSYSTEM('VARLINK', winbindd_varlink.c winbindd_varlink_getuserrecord.c winbindd_varlink_getgrouprecord.c + winbindd_varlink_getmemberships.c ''', deps='talloc tevent varlink', enabled=bld.env.with_systemd_userdb)