]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
winbind:varlink: Implement memberships enumeration
authorSamuel Cabrero <scabrero@samba.org>
Mon, 6 Feb 2023 17:49:07 +0000 (18:49 +0100)
committerAndreas Schneider <asn@cryptomilk.org>
Thu, 20 Feb 2025 08:07:32 +0000 (08:07 +0000)
$> 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 <scabrero@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
source3/winbindd/winbindd_varlink.c
source3/winbindd/winbindd_varlink.h
source3/winbindd/winbindd_varlink_getmemberships.c [new file with mode: 0644]
source3/winbindd/wscript_build

index 1b77bd49ea0f28f0183aaac17bc39e785a0610c9..94a47c22aa58881dfa97d8a1b62a3652af9fb961 100644 (file)
@@ -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,
index aa74ebf3a1777becc5fca19b3a23b52eb8ac5242..b7353a5d2aeee21864ee37b1fd471fea826c1389 100644 (file)
@@ -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 (file)
index 0000000..123a95b
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   Copyright (C) Samuel Cabrero <scabrero@samba.org> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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;
+}
index 4282d122736241f2d592aef61d0a4e062a31e8f3..2d7521c3f87592f3a64da51ef08c70d9db24ec2d 100644 (file)
@@ -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)