]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
winbind:varlink: Implement user record enumeration
authorSamuel Cabrero <scabrero@samba.org>
Mon, 6 Feb 2023 17:17:52 +0000 (18:17 +0100)
committerAndreas Schneider <asn@cryptomilk.org>
Thu, 20 Feb 2025 08:07:32 +0000 (08:07 +0000)
$> userdbctl -s org.samba.winbind
Enabled services: org.samba.winbind
   NAME                           DISPOSITION        UID   GID REALNAME                     HOME                        SHELL
   ...
   AFOREST+administrator          regular          20500 20513 -                            /home/AFOREST/administrator /bin/bash
   AFOREST+guest                  regular          20501 20513 -                            /home/AFOREST/guest         /bin/bash
   AFOREST+krbtgt                 regular          20502 20513 -                            /home/AFOREST/krbtgt        /bin/bash
   AFOREST+user1                  regular          21105 20513 -                            /home/AFOREST/user1         /bin/bash
   ...

$> SYSTEMD_LOG_LEVEL=7 getent -spasswd:systemd passwd
varlink: Setting state idle-client
/run/systemd/userdb/org.samba.winbind: Sending message: {"method":"io.systemd.UserDatabase.GetUserRecord","parameters":{"service":"org.samba.winbind"},"more":true}
/run/systemd/userdb/org.samba.winbind: Changing state idle-client → awaiting-reply-more
/run/systemd/userdb/org.samba.winbind: New incoming message: {"continues":true,"parameters":{"incomplete":false,"record":{"gid":20513,"homeDirectory":"/home/AFOREST/administrator","service":"org.samba.winbind","shell":"/bin/bash","uid":20500,"userName":"AFOREST+administrator"}}}
/run/systemd/userdb/org.samba.winbind: Changing state awaiting-reply-more → processing-reply
/run/systemd/userdb/org.samba.winbind: Changing state processing-reply → awaiting-reply-more
AFOREST+administrator:x:20500:20513:AFOREST+administrator:/home/AFOREST/administrator:/bin/bash
/run/systemd/userdb/org.samba.winbind: New incoming message: {"continues":true,"parameters":{"incomplete":false,"record":{"gid":20513,"homeDirectory":"/home/AFOREST/guest","service":"org.samba.winbind","shell":"/bin/bash","uid":20501,"userName":"AFOREST+guest"}}}
/run/systemd/userdb/org.samba.winbind: Changing state awaiting-reply-more → processing-reply
/run/systemd/userdb/org.samba.winbind: Changing state processing-reply → awaiting-reply-more
AFOREST+guest:x:20501:20513:AFOREST+guest:/home/AFOREST/guest:/bin/bash
/run/systemd/userdb/org.samba.winbind: New incoming message: {"continues":true,"parameters":{"incomplete":false,"record":{"gid":20513,"homeDirectory":"/home/AFOREST/krbtgt","service":"org.samba.winbind","shell":"/bin/bash","uid":20502,"userName":"AFOREST+krbtgt"}}}
/run/systemd/userdb/org.samba.winbind: Changing state awaiting-reply-more → processing-reply
/run/systemd/userdb/org.samba.winbind: Changing state processing-reply → awaiting-reply-more
AFOREST+krbtgt:x:20502:20513:AFOREST+krbtgt:/home/AFOREST/krbtgt:/bin/bash
/run/systemd/userdb/org.samba.winbind: New incoming message: {"parameters":{"incomplete":false,"record":{"gid":20513,"homeDirectory":"/home/AFOREST/user1","service":"org.samba.winbind","shell":"/bin/bash","uid":21105,"userName":"AFOREST+user1"}}}
/run/systemd/userdb/org.samba.winbind: Changing state awaiting-reply-more → processing-reply
/run/systemd/userdb/org.samba.winbind: Changing state processing-reply → idle-client
AFOREST+user1:x:21105:20513:AFOREST+user1:/home/AFOREST/user1:/bin/bash

$> ./bin/varlink-tool call unix:/run/systemd/userdb/org.samba.winbind/io.systemd.UserDatabase.GetUserRecord "{\"service\":\"org.samba.winbind\"}" -m
...

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_getuserrecord.c [new file with mode: 0644]
source3/winbindd/wscript_build

index 6e5091a8c680fcddde923612b5b57e65ff1e31f0..02cffb84211940733cdc209215dcb88c165e63e8 100644 (file)
@@ -116,9 +116,82 @@ static long io_systemd_getuserrecord(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_name = NULL;
+       const char *parm_service = NULL;
+       const char *service_name = NULL;
+       int64_t parm_uid = -1;
+       NTSTATUS status = NT_STATUS_NOT_IMPLEMENTED;
+       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_name);
+       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_int(parameters, "uid", &parm_uid);
+       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("GetUserRecord call parameters: service='%s', "
+                 "userName='%s', uid='%" PRId64 "'\n",
+                 parm_service,
+                 parm_name,
+                 parm_uid);
+
+       /*
+        * The wb_vl_user_* functions will reply theirselves when return
+        * NT_STATUS_OK
+        */
+       if (parm_name == NULL && parm_uid == -1) {
+               /* Enumeration */
+               status = wb_vl_user_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 long io_systemd_getgrouprecord(VarlinkService *service,
index 1f0290723ab76fcae3a33847ddac4b66d6faf7b4..e26d14f737cb3a6d5d49cabedc0851cfdf8be150 100644 (file)
@@ -41,6 +41,13 @@ NTSTATUS wb_vl_fake_cli_state(VarlinkCall *call,
                              const char *service,
                              struct winbindd_cli_state *cli);
 
+/* GetUserRecord */
+NTSTATUS wb_vl_user_enumerate(TALLOC_CTX *state,
+                             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_getuserrecord.c b/source3/winbindd/winbindd_varlink_getuserrecord.c
new file mode 100644 (file)
index 0000000..85ccf3f
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+   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
+user_record_reply(VarlinkCall *call, struct winbindd_pw *pw, bool continues)
+{
+       VarlinkObject *record = NULL;
+       VarlinkObject *out = NULL;
+       const char *service_name = NULL;
+
+       service_name = lp_parm_const_string(-1,
+                                           "winbind varlink",
+                                           "service name",
+                                           WB_VL_SERVICE_NAME);
+
+       varlink_object_new(&record);
+       varlink_object_set_string(record, "service", service_name);
+       varlink_object_set_string(record, "userName", pw->pw_name);
+       varlink_object_set_int(record, "uid", pw->pw_uid);
+       varlink_object_set_int(record, "gid", pw->pw_gid);
+       if (strlen(pw->pw_dir) > 0) {
+               varlink_object_set_string(record, "homeDirectory", pw->pw_dir);
+       }
+       if (strlen(pw->pw_shell) > 0) {
+               varlink_object_set_string(record, "shell", pw->pw_shell);
+       }
+       if (strlen(pw->pw_gecos) > 0) {
+               varlink_object_set_string(record, "realName", pw->pw_gecos);
+       }
+
+       varlink_object_new(&out);
+       varlink_object_set_object(out, "record", record);
+       varlink_object_set_bool(out, "incomplete", false);
+
+       varlink_call_reply(call, out, continues ? VARLINK_REPLY_CONTINUES : 0);
+       varlink_object_unref(out);
+}
+
+/******************************************************************************
+ * User enumeration
+ *****************************************************************************/
+
+struct user_enum_state {
+       struct tevent_context *ev_ctx;
+       struct winbindd_request *fake_req;
+       struct winbindd_cli_state *fake_cli;
+       VarlinkCall *call;
+
+       struct winbindd_pw *last_pw;
+};
+
+static int user_enum_state_destructor(struct user_enum_state *s)
+{
+       if (s->call != NULL) {
+               s->call = varlink_call_unref(s->call);
+       }
+
+       return 0;
+}
+
+static void user_enum_endpwent_done(struct tevent_req *req)
+{
+       struct user_enum_state *s =
+               tevent_req_callback_data(req, struct user_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_endpwent_recv(req, response);
+       TALLOC_FREE(req);
+
+       if (NT_STATUS_IS_ERR(status)) {
+               DBG_ERR("winbindd_endpwent 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_pw == NULL) {
+               varlink_call_reply_error(s->call,
+                                        WB_VL_REPLY_ERROR_NO_RECORD_FOUND,
+                                        NULL);
+               goto out;
+       }
+
+       user_record_reply(s->call, s->last_pw, false);
+
+out:
+       TALLOC_FREE(s);
+}
+
+static void user_enum_getpwent_done(struct tevent_req *req)
+{
+       struct user_enum_state *s =
+               tevent_req_callback_data(req, struct user_enum_state);
+       struct winbindd_response *response = NULL;
+       struct winbindd_pw *pws = 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_getpwent_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_ENDPWENT;
+               req = winbindd_endpwent_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, user_enum_endpwent_done, s);
+               return;
+       } else if (NT_STATUS_IS_ERR(status)) {
+               DBG_ERR("winbindd_getpwent 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;
+       }
+
+       /*
+        * We got a new chunk, send the last entry from previous chunk with
+        * continue flag set
+        */
+       if (s->last_pw != NULL) {
+               user_record_reply(s->call, s->last_pw, true);
+       }
+
+       /*
+        * Send returned records except last one because we don't know if
+        * will be more coming and the continue flag must be set
+        *
+        * The returned winbindd_pw structs start at the beginning of the
+        * extra data.
+        */
+       pws = (struct winbindd_pw *)response->extra_data.data;
+
+       for (i = 0; i < response->data.num_entries - 1; i++) {
+               struct winbindd_pw *pw = &pws[i];
+               user_record_reply(s->call, pw, true);
+       }
+
+       s->last_pw = talloc_zero(s, struct winbindd_pw);
+       if (s->last_pw == NULL) {
+               DBG_ERR("No memory\n");
+               varlink_call_reply_error(
+                       s->call,
+                       WB_VL_REPLY_ERROR_SERVICE_NOT_AVAILABLE,
+                       NULL);
+               goto out;
+       }
+
+       /* Save last one */
+       *s->last_pw = pws[i];
+
+       /* Get next chunk */
+       TALLOC_FREE(response);
+       ZERO_STRUCTP(s->fake_req);
+       s->fake_req->cmd = WINBINDD_GETPWENT;
+       s->fake_req->data.num_entries = 500;
+       req = winbindd_getpwent_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, user_enum_getpwent_done, s);
+       return;
+out:
+       TALLOC_FREE(s);
+}
+
+static void user_enum_setpwent_done(struct tevent_req *req)
+{
+       struct user_enum_state *s =
+               tevent_req_callback_data(req, struct user_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_setpwent_recv(req, response);
+       TALLOC_FREE(req);
+       TALLOC_FREE(response);
+
+       if (NT_STATUS_IS_ERR(status)) {
+               DBG_ERR("winbindd_setpwent 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_GETPWENT;
+       s->fake_req->data.num_entries = 500;
+
+       req = winbindd_getpwent_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, user_enum_getpwent_done, s);
+       return;
+out:
+       TALLOC_FREE(s);
+}
+
+NTSTATUS wb_vl_user_enumerate(TALLOC_CTX *mem_ctx,
+                             struct tevent_context *ev_ctx,
+                             VarlinkCall *call,
+                             uint64_t flags,
+                             const char *service)
+{
+       struct user_enum_state *s = NULL;
+       struct tevent_req *req = NULL;
+       NTSTATUS status;
+
+       /* Check if enumeration enabled */
+       if (!lp_winbind_enum_users()) {
+               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 user_enum_state);
+       if (s == NULL) {
+               DBG_ERR("No memory\n");
+               return NT_STATUS_NO_MEMORY;
+       }
+       talloc_set_destructor(s, user_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_SETPWENT;
+       req = winbindd_setpwent_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, user_enum_setpwent_done, s);
+
+       return NT_STATUS_OK;
+fail:
+       TALLOC_FREE(s);
+       return status;
+}
index ee331c75976b2e6253ee33b010b5ce546983d155..d2f2b808497268ff8afaa4777ff071b16965ba78 100644 (file)
@@ -174,6 +174,7 @@ bld.SAMBA3_MODULE('idmap_script',
 bld.SAMBA3_SUBSYSTEM('VARLINK',
                      source='''
                             winbindd_varlink.c
+                            winbindd_varlink_getuserrecord.c
                             ''',
                      deps='talloc tevent varlink',
                      enabled=bld.env.with_systemd_userdb)