homectl: expose "register" verb to register a user record locally

This commit is contained in:
Lennart Poettering
2025-02-20 10:21:57 +01:00
parent cbf9a1c888
commit e8801cc5b3
3 changed files with 149 additions and 1 deletions

View File

@@ -1197,6 +1197,55 @@
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><command>register</command> <replaceable>FILE</replaceable> [<replaceable>FILE</replaceable>…]</term>
<listitem><para>Registers one or more users, without creating their home directories. Takes one or
more paths to JSON user record files. If the path is specified as <literal>-</literal> reads the
JSON user record from standard input.</para>
<para>Registering a user makes it accessible on the local system without creating a new home
directory. This is particularly useful for making a user accessible on a system it was not originally
created on.</para>
<para>Here's an example how to make a local user account with its home directory accessible on a
remote system, using SMB/CIFS file sharing. With Samba installed in its default configuration invoke
as <literal>root</literal>:</para>
<programlisting># smbpasswd -a lennart</programlisting>
<para>Continue as regular user <literal>lennart</literal>:</para>
<programlisting>$ homectl update lennart --ssh-authorized-keys=… -N --storage=cifs --cifs-service="//$HOSTNAME/lennart"
$ homectl get-signing-key | ssh targetsystem homectl add-signing-key --key-name="$HOSTNAME".public
$ homectl inspect -E lennart | ssh targetsystem homectl register -
$ ssh lennart@targetsystem</programlisting>
<para>This first ensures the user account <literal>lennart</literal> is known to and accessible by
Samba. It then registers a local SSH access that shall be used for accessing this user, and
configures CIFS as default storage for non-local systems on the account. It then adds the local
system's account signing key to the target system. Then it registers the local user account with the
target system. Finally it logs into the account on the target system. The target system will then
connect back via SMB/CIFS to access the home directory.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><command>unregister</command> <replaceable>USER</replaceable></term>
<listitem><para>Unregisters one or more user accounts. This only removes the user record from the
local system, it does not delete the home directory. The home directory can be readded via the
<command>register</command> or <command>adopt</command> command later, on this or another
system. Note that unregistering a user whose home directory is placed in <filename>/home/</filename>
will not make the user disappear from the local user database, as all supported home directories
placed there will show up in the user database. However, the user record will become "unfixated",
i.e. lose its binding to the local system. When logged into it will automatically regain the binding,
and acquire a local UID/GID pair.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><command>remove</command> <replaceable>USER</replaceable></term>

View File

@@ -173,7 +173,7 @@ _homectl() {
fi
local -A VERBS=(
[STANDALONE]='list lock-all adopt'
[STANDALONE]='list lock-all register unregister adopt'
[CREATE]='create'
[NAMES]='activate deactivate inspect authenticate remove lock unlock'
[NAME]='update passwd'

View File

@@ -1596,6 +1596,101 @@ static int verb_adopt_home(int argc, char *argv[], void *userdata) {
return ret;
}
static int register_home_one(sd_bus *bus, FILE *f, const char *path) {
int r;
assert(bus);
assert(path);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
unsigned line = 0, column = 0;
r = sd_json_parse_file(f, path, SD_JSON_PARSE_SENSITIVE, &v, &line, &column);
if (r < 0)
return log_error_errno(r, "[%s:%u:%u] Failed to parse user record: %m", path, line, column);
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = bus_message_new_method_call(bus, &m, bus_mgr, "RegisterHome");
if (r < 0)
return bus_log_create_error(r);
_cleanup_free_ char *formatted = NULL;
r = sd_json_variant_format(v, /* flags= */ 0, &formatted);
if (r < 0)
return log_error_errno(r, "Failed to format JSON record: %m");
r = sd_bus_message_append(m, "s", formatted);
if (r < 0)
return bus_log_create_error(r);
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to register home: %s", bus_error_message(&error, r));
return 0;
}
static int verb_register_home(int argc, char *argv[], void *userdata) {
int r;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
if (arg_identity) {
if (argc > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not accepting an arguments if --identity= is specified, refusing.");
return register_home_one(bus, /* f= */ NULL, arg_identity);
}
if (argc == 1 || (argc == 2 && streq(argv[1], "-")))
return register_home_one(bus, /* f= */ stdin, "<stdio>");
r = 0;
STRV_FOREACH(i, strv_skip(argv, 1)) {
if (streq(*i, "-"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing reading from standard input if multiple user records are specified.");
RET_GATHER(r, register_home_one(bus, /* f= */ NULL, *i));
}
return r;
}
static int verb_unregister_home(int argc, char *argv[], void *userdata) {
int r;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
int ret = 0;
STRV_FOREACH(i, strv_skip(argv, 1)) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = bus_message_new_method_call(bus, &m, bus_mgr, "UnregisterHome");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", *i);
if (r < 0)
return bus_log_create_error(r);
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, /* ret_reply= */ NULL);
if (r < 0)
RET_GATHER(ret, log_error_errno(r, "Failed to unregister home: %s", bus_error_message(&error, r)));
}
return ret;
}
static int remove_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, ret = 0;
@@ -2841,6 +2936,8 @@ static int help(int argc, char *argv[], void *userdata) {
" authenticate USER… Authenticate a home area\n"
" create USER Create a home area\n"
" adopt PATH… Add an existing home area on this system\n"
" register PATH… Register a user record locally\n"
" unregister USER… Unregister a user record locally\n"
" remove USER… Remove a home area\n"
" update USER Update a home area\n"
" passwd USER Change password of a home area\n"
@@ -5276,6 +5373,8 @@ static int run(int argc, char *argv[]) {
{ "authenticate", VERB_ANY, VERB_ANY, 0, authenticate_home },
{ "create", VERB_ANY, 2, 0, create_home },
{ "adopt", VERB_ANY, VERB_ANY, 0, verb_adopt_home },
{ "register", VERB_ANY, VERB_ANY, 0, verb_register_home },
{ "unregister", 2, VERB_ANY, 0, verb_unregister_home },
{ "remove", 2, VERB_ANY, 0, remove_home },
{ "update", VERB_ANY, 2, 0, update_home },
{ "passwd", VERB_ANY, 2, 0, passwd_home },