Compare commits

..

8 commits

Author SHA1 Message Date
Iker Pedrosa
103ee1cc47 Rebase to version 4.20.0
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
2026-07-29 15:27:11 +02:00
Iker Pedrosa
c8aec6bdba Tests: update dependencies
System test framework requires gcc, python3-devel and libssh-devel
installed before pip tries to compile ansible-pylibssh.

Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
2026-07-23 12:38:33 +02:00
Iker Pedrosa
8cb145bd73 Rebase to v4.20.0-rc3
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
2026-07-23 12:38:31 +02:00
Fedora Release Engineering
8bd71d9f91 Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild 2026-07-17 06:27:57 +00:00
Iker Pedrosa
871df3223d Enable use of PAM for chpasswd and newusers
Resolves: #2461179 and #2283963
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
2026-05-27 11:40:27 +02:00
Iker Pedrosa
4a56d06039 Tests: run system tests
Tests comprehensive functionality from shadow using the upstream system
test suite in testing farm.

- Add plans/system-tests.fmf: run upstream shadow system tests via
  pytest-mh.
- Add tests/mhc-fedora-ci.yaml: topology configuration to connect to the
  test system.
- Add .fmf/version: enable testing farm integration.

Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
2026-04-29 10:39:59 +02:00
Iker Pedrosa
0fba1e26ce btrfs: simplify checks improve useradd behavior for non-btrfs
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
2026-04-23 15:20:03 +02:00
77fbaa4e9a Rebase to version 4.19.3
Resolves: #2426288

https://src.fedoraproject.org/rpms/shadow-utils/pull-request/27
2026-02-13 14:20:25 +01:00
14 changed files with 104 additions and 1556 deletions

1
.fmf/version Normal file
View file

@ -0,0 +1 @@
1

10
.gitignore vendored
View file

@ -44,3 +44,13 @@ shadow-4.1.4.2.tar.bz2
/shadow-4.18.0.tar.xz.asc
/shadow-4.19.0.tar.xz
/shadow-4.19.0.tar.xz.asc
/shadow-4.19.2.tar.xz
/shadow-4.19.2.tar.xz.asc
/shadow-4.19.3.tar.xz
/shadow-4.19.3.tar.xz.asc
/shadow-4.20.0-rc2.tar.xz
/shadow-4.20.0-rc2.tar.xz.asc
/shadow-4.20.0-rc3.tar.xz
/shadow-4.20.0-rc3.tar.xz.asc
/shadow-4.20.0.tar.xz
/shadow-4.20.0.tar.xz.asc

View file

@ -1,40 +0,0 @@
From b721cdd03daa3416127289da02fe781efb2562df Mon Sep 17 00:00:00 2001
From: Adam Williamson <awilliam@redhat.com>
Date: Tue, 27 Jan 2026 15:11:07 -0800
Subject: [PATCH] lib/chkhash.c: fix escaping in SHA-256 / SHA-512 / MD5
regexes
`\\n` inside square brackets doesn't include or exclude the
newline character. It includes or excludes a literal slash and
the literal character 'n'.
Fixes: c44f1e096a19 (2025-07-20; "chpasswd: Check hash before write when using -e")
Closes: <https://github.com/shadow-maint/shadow/issues/1519>
Signed-off-by: Adam Williamson <awilliam@redhat.com>
---
lib/chkhash.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/chkhash.c b/lib/chkhash.c
index 101f2004e8..9123038ade 100644
--- a/lib/chkhash.c
+++ b/lib/chkhash.c
@@ -62,15 +62,15 @@ is_valid_hash(const char *hash)
return true;
// SHA-512: $6$ + salt + $ + 86-char hash
- if (match_regex("^\\$6\\$(rounds=[1-9][0-9]{3,8}\\$)?[^$:\\n]{1,16}\\$[./A-Za-z0-9]{86}$", hash))
+ if (match_regex("^\\$6\\$(rounds=[1-9][0-9]{3,8}\\$)?[^$:\n]{1,16}\\$[./A-Za-z0-9]{86}$", hash))
return true;
// SHA-256: $5$ + salt + $ + 43-char hash
- if (match_regex("^\\$5\\$(rounds=[1-9][0-9]{3,8}\\$)?[^$:\\n]{1,16}\\$[./A-Za-z0-9]{43}$", hash))
+ if (match_regex("^\\$5\\$(rounds=[1-9][0-9]{3,8}\\$)?[^$:\n]{1,16}\\$[./A-Za-z0-9]{43}$", hash))
return true;
// MD5: $1$ + salt + $ + 22-char hash
- if (match_regex("^\\$1\\$[^$:\\n]{1,8}\\$[./A-Za-z0-9]{22}$", hash))
+ if (match_regex("^\\$1\\$[^$:\n]{1,8}\\$[./A-Za-z0-9]{22}$", hash))
return true;
// DES: exactly 13 characters from [A-Za-z0-9./]

View file

@ -0,0 +1,51 @@
summary: Tier 0 functional tests for shadow-utils
description: |
Run comprehensive system tests for shadow-utils. These tests validate user and group
account management functionality by testing actual system operations including user
creation, password management, group operations, and verification of system files
(i.e. /etc/passwd, /etc/shadow).
provision:
how: virtual
image: fedora
prepare:
- name: Install general dependencies
how: install
package:
- expect
- gcc
- git
- libssh-devel
- python3-devel
- python3-pip
- name: Setup SSH keys for localhost testing
how: shell
script:
- ssh-keygen -t rsa -f /root/.ssh/id_rsa -N "" -q
- cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys
- chmod 600 /root/.ssh/authorized_keys
- ssh-keyscan -H localhost >> /root/.ssh/known_hosts
- name: Clone shadow repository
how: shell
script:
- git clone https://github.com/shadow-maint/shadow.git /tmp/shadow-test
- name: Copy test topology for Fedora CI
how: shell
script:
- cp tests/mhc-fedora-ci.yaml /tmp/shadow-test/tests/system
- name: Install test dependencies
how: shell
script:
- pip3 install -r /tmp/shadow-test/tests/system/requirements.txt
execute:
how: tmt
duration: 30m
script: |
cd /tmp/shadow-test/tests/system
pytest --mh-config=mhc-fedora-ci.yaml --mh-lazy-ssh -v

View file

@ -1,149 +0,0 @@
From 3e8c105f0703264e947d8c034b90419794955d49 Mon Sep 17 00:00:00 2001
From: Hadi Chokr <hadichokr@icloud.com>
Date: Fri, 12 Dec 2025 11:18:06 +0100
Subject: [PATCH] src/useradd: Support config for creating home dirs as Btrfs
subvolumes
Closes: #1162
Co-authored-by: Neal Gompa <ngompa@velocitylimitless.com>
Signed-off-by: Hadi Chokr <hadichokr@icloud.com>
Signed-off-by: Neal Gompa <ngompa@velocitylimitless.com>
Reviewed-by: Alejandro Colomar <alx@kernel.org>
---
man/useradd.8.xml | 27 +++++++++++++++++++++++++++
src/useradd.c | 24 ++++++++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/man/useradd.8.xml b/man/useradd.8.xml
index e6db7f6d..c745a620 100644
--- a/man/useradd.8.xml
+++ b/man/useradd.8.xml
@@ -134,6 +134,33 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <option>--btrfs-subvolume-home</option>
+ </term>
+ <listitem>
+ <para>
+ Create the user's home directory as a Btrfs subvolume.
+ </para>
+ <para>
+ If this option is not specified,
+ <command>useradd</command> will follow the default behavior
+ defined by the <option>BTRFS_SUBVOLUME_HOME</option> variable
+ in <filename>/etc/default/useradd</filename>.
+ If this variable is not set, the default value is no.
+ </para>
+ <para>
+ When the <option>--btrfs-subvolume-home</option> command-line option
+ is specified,
+ a Btrfs subvolume is created
+ regardless of any configuration file settings.
+ </para>
+ <para>
+ Note: this feature works only if the underlying filesystem supports
+ Btrfs subvolumes.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term>
<option>-c</option>, <option>--comment</option>&nbsp;<replaceable>COMMENT</replaceable>
diff --git a/src/useradd.c b/src/useradd.c
index 899efe3c..8433ed8a 100644
--- a/src/useradd.c
+++ b/src/useradd.c
@@ -114,6 +114,7 @@ static const char *def_shell = "/bin/bash";
static const char *def_template = SKEL_DIR;
static const char *def_usrtemplate = USRSKELDIR;
static const char *def_create_mail_spool = "yes";
+static const char *def_btrfs_subvolume_home = "no";
static const char *def_log_init = "yes";
static long def_inactive = -1;
@@ -222,6 +223,7 @@ static bool home_added = false;
#define DSKEL "SKEL"
#define DUSRSKEL "USRSKEL"
#define DCREATE_MAIL_SPOOL "CREATE_MAIL_SPOOL"
+#define DBTRFS_SUBVOLUME_HOME "BTRFS_SUBVOLUME_HOME"
#define DLOG_INIT "LOG_INIT"
/* local function prototypes */
@@ -456,6 +458,7 @@ get_defaults(const struct option_flags *flags)
def_usrtemplate = xstrdup(ccp);
}
}
+
/*
* Create by default user mail spool or not ?
*/
@@ -466,6 +469,15 @@ get_defaults(const struct option_flags *flags)
def_create_mail_spool = xstrdup(ccp);
}
+ /*
+ * Create home directories as Btrfs subvolumes by default?
+ */
+ else if (streq(buf, DBTRFS_SUBVOLUME_HOME)) {
+ if (streq(ccp, ""))
+ ccp = "no";
+ def_btrfs_subvolume_home = xstrdup(ccp);
+ }
+
/*
* By default do we add the user to the lastlog and faillog databases ?
*/
@@ -500,6 +512,7 @@ static void show_defaults (void)
printf ("SKEL=%s\n", def_template);
printf ("USRSKEL=%s\n", def_usrtemplate);
printf ("CREATE_MAIL_SPOOL=%s\n", def_create_mail_spool);
+ printf ("BTRFS_SUBVOLUME_HOME=%s\n", def_btrfs_subvolume_home);
printf ("LOG_INIT=%s\n", def_log_init);
}
@@ -523,6 +536,7 @@ set_defaults(void)
bool out_skel = false;
bool out_usrskel = false;
bool out_create_mail_spool = false;
+ bool out_btrfs_subvolume_home = false;
bool out_log_init = false;
char buf[1024];
char *new_file = NULL;
@@ -639,6 +653,11 @@ set_defaults(void)
DCREATE_MAIL_SPOOL "=%s\n",
def_create_mail_spool);
out_create_mail_spool = true;
+ } else if (!out_btrfs_subvolume_home && streq(buf, DBTRFS_SUBVOLUME_HOME)) {
+ fprintf(ofp,
+ DBTRFS_SUBVOLUME_HOME "=%s\n",
+ def_btrfs_subvolume_home);
+ out_btrfs_subvolume_home = true;
} else if (!out_log_init && streq(buf, DLOG_INIT)) {
fprintf(ofp, DLOG_INIT "=%s\n", def_log_init);
out_log_init = true;
@@ -673,6 +692,8 @@ set_defaults(void)
if (!out_create_mail_spool)
fprintf (ofp, DCREATE_MAIL_SPOOL "=%s\n", def_create_mail_spool);
+ if (!out_btrfs_subvolume_home)
+ fprintf (ofp, DBTRFS_SUBVOLUME_HOME "=%s\n", def_btrfs_subvolume_home);
if (!out_log_init)
fprintf (ofp, DLOG_INIT "=%s\n", def_log_init);
/*
@@ -1431,6 +1452,9 @@ static void process_flags (int argc, char **argv, struct option_flags *flags)
}
}
+ if (!subvolflg && strcaseeq(def_btrfs_subvolume_home, "yes"))
+ subvolflg = true;
+
if (!gflg && !Nflg && !Uflg) {
/* Get the settings from login.defs */
Uflg = getdef_bool ("USERGROUPS_ENAB");
--
2.52.0

View file

@ -1,162 +0,0 @@
diff -up shadow-4.15.0/man/groupmems.8.xml.manfix shadow-4.15.0/man/groupmems.8.xml
--- shadow-4.15.0/man/groupmems.8.xml.manfix 2023-05-26 04:56:11.000000000 +0200
+++ shadow-4.15.0/man/groupmems.8.xml 2024-02-09 10:42:20.337036378 +0100
@@ -156,20 +156,10 @@
<refsect1 id='setup'>
<title>SETUP</title>
<para>
- The <command>groupmems</command> executable should be in mode
- <literal>2710</literal> as user <emphasis>root</emphasis> and in group
- <emphasis>groups</emphasis>. The system administrator can add users to
- group <emphasis>groups</emphasis> to allow or disallow them using the
- <command>groupmems</command> utility to manage their own group
- membership list.
+ In this operating system the <command>groupmems</command> executable
+ is not setuid and regular users cannot use it to manipulate
+ the membership of their own group.
</para>
-
- <programlisting>
- $ groupadd -r groups
- $ chmod 2710 groupmems
- $ chown root:groups groupmems
- $ groupmems -g groups -a gk4
- </programlisting>
</refsect1>
<refsect1 id='configuration'>
diff -up shadow-4.15.0/man/ja/man5/login.defs.5.manfix shadow-4.15.0/man/ja/man5/login.defs.5
--- shadow-4.15.0/man/ja/man5/login.defs.5.manfix 2023-03-13 21:58:56.000000000 +0100
+++ shadow-4.15.0/man/ja/man5/login.defs.5 2024-02-09 10:42:20.337036378 +0100
@@ -123,10 +123,6 @@ 以下の参照表は、
shadow パスワード機能のどのプログラムが
どのパラメータを使用するかを示したものである。
.na
-.IP chfn 12
-CHFN_AUTH CHFN_RESTRICT
-.IP chsh 12
-CHFN_AUTH
.IP groupadd 12
GID_MAX GID_MIN
.IP newusers 12
diff -up shadow-4.15.0/man/login.defs.5.xml.manfix shadow-4.15.0/man/login.defs.5.xml
--- shadow-4.15.0/man/login.defs.5.xml.manfix 2024-01-22 22:36:43.000000000 +0100
+++ shadow-4.15.0/man/login.defs.5.xml 2024-02-09 10:45:49.014407259 +0100
@@ -144,6 +144,17 @@
long numeric parameters is machine-dependent.
</para>
+ <para>
+ Please note that the parameters in this configuration file control the
+ behavior of the tools from the shadow-utils component. None of these
+ tools uses the PAM mechanism, and the utilities that use PAM (such as the
+ passwd command) should be configured elsewhere. The only values that
+ affect PAM modules are <emphasis>ENCRYPT_METHOD</emphasis> and <emphasis>SHA_CRYPT_MAX_ROUNDS</emphasis>
+ for pam_unix module, <emphasis>FAIL_DELAY</emphasis> for pam_faildelay module,
+ and <emphasis>UMASK</emphasis> for pam_umask module. Refer to
+ pam(8) for more information.
+ </para>
+
<para>The following configuration items are provided:</para>
<variablelist remap='IP'>
@@ -240,16 +251,6 @@
</listitem>
</varlistentry>
<varlistentry>
- <term>chfn</term>
- <listitem>
- <para>
- <phrase condition="no_pam">CHFN_AUTH</phrase>
- CHFN_RESTRICT
- <phrase condition="no_pam">LOGIN_STRING</phrase>
- </para>
- </listitem>
- </varlistentry>
- <varlistentry>
<term>chgpasswd</term>
<listitem>
<para>
@@ -276,14 +277,6 @@
</para>
</listitem>
</varlistentry>
- <varlistentry condition="no_pam">
- <term>chsh</term>
- <listitem>
- <para>
- CHSH_AUTH LOGIN_STRING
- </para>
- </listitem>
- </varlistentry>
<!-- expiry: no variables (CONSOLE_GROUPS linked, but not used) -->
<!-- faillog: no variables -->
<varlistentry>
@@ -352,34 +345,6 @@
<para>LASTLOG_UID_MAX</para>
</listitem>
</varlistentry>
- <varlistentry>
- <term>login</term>
- <listitem>
- <para>
- <phrase condition="no_pam">CONSOLE</phrase>
- CONSOLE_GROUPS DEFAULT_HOME
- <phrase condition="no_pam">ENV_HZ ENV_PATH ENV_SUPATH
- ENV_TZ ENVIRON_FILE</phrase>
- ERASECHAR FAIL_DELAY
- <phrase condition="no_pam">FAILLOG_ENAB</phrase>
- FAKE_SHELL
- <phrase condition="no_pam">FTMP_FILE</phrase>
- HUSHLOGIN_FILE
- <phrase condition="no_pam">ISSUE_FILE</phrase>
- KILLCHAR
- <phrase condition="no_pam">LASTLOG_ENAB LASTLOG_UID_MAX</phrase>
- LOGIN_RETRIES
- <phrase condition="no_pam">LOGIN_STRING</phrase>
- LOGIN_TIMEOUT LOG_OK_LOGINS LOG_UNKFAIL_ENAB
- <phrase condition="no_pam">MAIL_CHECK_ENAB MAIL_DIR MAIL_FILE
- MOTD_FILE NOLOGINS_FILE PORTTIME_CHECKS_ENAB
- QUOTAS_ENAB</phrase>
- TTYGROUP TTYPERM TTYTYPE_FILE
- <phrase condition="no_pam">ULIMIT UMASK</phrase>
- USERGROUPS_ENAB
- </para>
- </listitem>
- </varlistentry>
<!-- logoutd: no variables -->
<varlistentry>
<term>newgrp / sg</term>
@@ -451,32 +416,6 @@
</para>
</listitem>
</varlistentry>
- <varlistentry>
- <term>su</term>
- <listitem>
- <para>
- <phrase condition="no_pam">CONSOLE</phrase>
- CONSOLE_GROUPS DEFAULT_HOME
- <phrase condition="no_pam">ENV_HZ ENVIRON_FILE</phrase>
- ENV_PATH ENV_SUPATH
- <phrase condition="no_pam">ENV_TZ LOGIN_STRING MAIL_CHECK_ENAB
- MAIL_DIR MAIL_FILE QUOTAS_ENAB</phrase>
- SULOG_FILE SU_NAME
- <phrase condition="no_pam">SU_WHEEL_ONLY</phrase>
- SYSLOG_SU_ENAB
- <phrase condition="no_pam">USERGROUPS_ENAB</phrase>
- </para>
- </listitem>
- </varlistentry>
- <varlistentry condition="no_pam">
- <term>sulogin</term>
- <listitem>
- <para>
- ENV_HZ
- ENV_TZ
- </para>
- </listitem>
- </varlistentry>
<varlistentry>
<term>useradd</term>
<listitem>

View file

@ -1,377 +0,0 @@
diff -up shadow-4.19.0-rc1/src/chpasswd.c.account-tools-setuid shadow-4.19.0-rc1/src/chpasswd.c
--- shadow-4.19.0-rc1/src/chpasswd.c.account-tools-setuid 2025-12-07 16:56:38.563285789 +0100
+++ shadow-4.19.0-rc1/src/chpasswd.c 2025-12-09 14:54:29.232282499 +0100
@@ -466,9 +466,11 @@ int main (int argc, char **argv)
char *newpwd;
const char *salt;
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
bool use_pam = true;
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
bool errors = false;
intmax_t line = 0;
@@ -495,19 +497,23 @@ int main (int argc, char **argv)
process_root_flag ("-R", argc, argv);
prefix = process_prefix_flag ("-P", argc, argv);
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
if (md5flg || eflg || cflg || prefix[0]) {
use_pam = false;
}
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
OPENLOG (Prog);
check_perms ();
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
if (!use_pam)
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
{
is_shadow_pwd = spw_file_present ();
@@ -562,6 +568,7 @@ int main (int argc, char **argv)
}
newpwd = cp;
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
if (use_pam) {
if (do_pam_passwd_non_interactive (Prog, name, newpwd) != 0) {
@@ -572,6 +579,7 @@ int main (int argc, char **argv)
}
} else
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
{
/*
@@ -706,9 +714,11 @@ int main (int argc, char **argv)
* password database.
*/
if (errors) {
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
if (!use_pam)
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
{
fprintf (stderr,
_("%s: error detected, changes ignored\n"),
@@ -717,9 +727,11 @@ int main (int argc, char **argv)
fail_exit (1, process_selinux);
}
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
if (!use_pam)
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
{
/* Save the changes */
close_files (&flags);
diff -up shadow-4.19.0-rc1/src/groupmems.c.account-tools-setuid shadow-4.19.0-rc1/src/groupmems.c
--- shadow-4.19.0-rc1/src/groupmems.c.account-tools-setuid 2025-12-07 16:56:38.564285805 +0100
+++ shadow-4.19.0-rc1/src/groupmems.c 2025-12-09 14:57:23.767054141 +0100
@@ -14,9 +14,11 @@
#include <grp.h>
#include <stdio.h>
#include <sys/types.h>
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
#include "pam_defs.h"
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
#include <pwd.h>
#include "alloc/malloc.h"
@@ -432,6 +434,7 @@ static void
check_perms(MAYBE_UNUSED bool process_selinux)
{
if (!list) {
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
pam_handle_t *pamh = NULL;
int retval;
@@ -465,7 +468,8 @@ check_perms(MAYBE_UNUSED bool process_se
fail_exit (1, process_selinux);
}
(void) pam_end (pamh, retval);
-#endif
+#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
}
}
diff -up shadow-4.19.0-rc1/src/newusers.c.account-tools-setuid shadow-4.19.0-rc1/src/newusers.c
--- shadow-4.19.0-rc1/src/newusers.c.account-tools-setuid 2025-12-07 16:56:38.565285821 +0100
+++ shadow-4.19.0-rc1/src/newusers.c 2025-12-09 15:07:40.115500988 +0100
@@ -74,6 +74,7 @@ struct option_flags {
static const char Prog[] = "newusers";
static bool rflg = false; /* create a system account */
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
static /*@null@*//*@observer@*/char *crypt_method = NULL;
#define cflg (NULL != crypt_method)
@@ -90,6 +91,7 @@ static long bcrypt_rounds = 13;
static long yescrypt_cost = 5;
#endif /* USE_YESCRYPT */
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
static bool is_shadow;
#ifdef SHADOWGRP
@@ -112,9 +114,11 @@ NORETURN static void fail_exit (int, boo
static int add_group (const char *, const char *, gid_t *, gid_t);
static int get_user_id (const char *, uid_t *);
static int add_user (const char *, uid_t, gid_t);
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
static int update_passwd (struct passwd *, const char *);
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
static int add_passwd (struct passwd *, const char *);
static void process_flags (int argc, char **argv, struct option_flags *flags);
static void check_flags (void);
@@ -136,6 +140,7 @@ static void usage (int status)
"Options:\n"),
Prog);
(void) fputs (_(" -b, --badname allow bad names\n"), usageout);
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
(void) fprintf (usageout,
_(" -c, --crypt-method METHOD the crypt method (one of %s)\n"),
@@ -151,9 +156,11 @@ static void usage (int status)
#endif
);
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
(void) fputs (_(" -r, --system create system accounts\n"), usageout);
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
(void) fputs (_(" -s, --sha-rounds number of rounds for the SHA, BCRYPT\n"
@@ -161,6 +168,7 @@ static void usage (int status)
usageout);
#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
(void) fputs ("\n", usageout);
exit (status);
@@ -426,6 +434,7 @@ static int add_user (const char *name, u
return (pw_update (&pwent) == 0) ? -1 : 0;
}
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
/*
* update_passwd - update the password in the passwd entry
@@ -478,6 +487,7 @@ static int update_passwd (struct passwd
return 0;
}
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
/*
* add_passwd - add or update the encrypted password
@@ -487,10 +497,13 @@ add_passwd(struct passwd *pwd, MAYBE_UNU
{
const struct spwd *sp;
struct spwd spent;
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
char *cp;
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
void *crypt_arg = NULL;
if (NULL != crypt_method) {
@@ -528,13 +541,14 @@ add_passwd(struct passwd *pwd, MAYBE_UNU
return update_passwd (pwd, password);
}
#endif /* USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
/*
* Do the first and easiest shadow file case. The user already
* exists in the shadow password file.
*/
sp = spw_locate (pwd->pw_name);
-#ifndef USE_PAM
+#if !defined(ACCT_TOOLS_SETUID) && !defined(USE_PAM)
if (NULL != sp) {
spent = *sp;
if ( (NULL != crypt_method)
@@ -571,7 +585,7 @@ add_passwd(struct passwd *pwd, MAYBE_UNU
if (!streq(pwd->pw_passwd, "x")) {
return update_passwd (pwd, password);
}
-#else /* USE_PAM */
+#else /* !ACCT_TOOLS_SETUID && !USE_PAM */
/*
* If there is already a shadow entry, do not touch it.
* If there is already a passwd entry with a password, do not
@@ -582,14 +596,14 @@ add_passwd(struct passwd *pwd, MAYBE_UNU
|| !streq(pwd->pw_passwd, "x")) {
return 0;
}
-#endif /* USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID && !USE_PAM */
/*
* Now the really hard case - I need to create an entirely new
* shadow password file entry.
*/
spent.sp_namp = pwd->pw_name;
-#ifndef USE_PAM
+#if !defined(ACCT_TOOLS_SETUID) && !defined(USE_PAM)
if ((crypt_method != NULL) && streq(crypt_method, "NONE")) {
spent.sp_pwdp = (char *)password;
} else {
@@ -647,32 +647,38 @@ add_passwd(struct passwd *pwd, MAYBE_UNU
static void process_flags (int argc, char **argv, struct option_flags *flags)
{
int c;
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
int bad_s;
#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
static struct option long_options[] = {
{"badname", no_argument, NULL, 'b'},
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
{"crypt-method", required_argument, NULL, 'c'},
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
{"help", no_argument, NULL, 'h'},
{"system", no_argument, NULL, 'r'},
{"root", required_argument, NULL, 'R'},
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
{"sha-rounds", required_argument, NULL, 's'},
#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
{NULL, 0, NULL, '\0'}
};
while ((c = getopt_long (argc, argv,
-#ifndef USE_PAM
+#if !defined(ACCT_TOOLS_SETUID) && !defined(USE_PAM)
#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
"c:bhrs:",
-#else /* !USE_SHA_CRYPT && !USE_BCRYPT && !USE_YESCRYPT */
+#else /* !ACCT_TOOLS_SETUID && !USE_PAM */
"c:bhr",
#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
#else /* USE_PAM */
@@ -670,11 +690,13 @@ static void process_flags (int argc, cha
case 'b':
allow_bad_names = true;
break;
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
case 'c':
crypt_method = optarg;
break;
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
case 'h':
usage (EXIT_SUCCESS);
break;
@@ -684,6 +706,7 @@ static void process_flags (int argc, cha
case 'R': /* no-op, handled in process_root_flag () */
flags->chroot = true;
break;
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
case 's':
@@ -723,6 +746,7 @@ static void process_flags (int argc, cha
break;
#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
default:
usage (EXIT_FAILURE);
break;
@@ -755,6 +779,7 @@ static void process_flags (int argc, cha
*/
static void check_flags (void)
{
+#ifndef ACCT_TOOLS_SETUID
#ifndef USE_PAM
#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT)
if (sflg && !cflg) {
@@ -787,6 +812,7 @@ static void check_flags (void)
}
}
#endif /* !USE_PAM */
+#endif /* !ACCT_TOOLS_SETUID */
}
/*
@@ -1065,12 +1091,14 @@ int main (int argc, char **argv)
intmax_t line = 0;
uid_t uid;
gid_t gid;
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
intmax_t *lines = NULL;
char **usernames = NULL;
char **passwords = NULL;
size_t nusers = 0;
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
struct option_flags flags = {.chroot = false};
bool process_selinux;
@@ -1193,6 +1221,7 @@ int main (int argc, char **argv)
}
newpw = *pw;
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
/* keep the list of user/password for later update by PAM */
nusers++;
@@ -1207,6 +1236,7 @@ int main (int argc, char **argv)
usernames[nusers-1] = xstrdup(fields[0]);
passwords[nusers-1] = xstrdup(fields[1]);
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
if (!streq(fields[1], "") && add_passwd(&newpw, fields[1]) != 0) {
fprintf (stderr,
_("%s: line %jd: can't update password\n"),
@@ -1321,6 +1351,7 @@ int main (int argc, char **argv)
nscd_flush_cache ("group");
sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP);
+#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
/* Now update the passwords using PAM */
for (size_t i = 0; i < nusers; i++) {
@@ -1334,6 +1365,7 @@ int main (int argc, char **argv)
}
}
#endif /* USE_PAM */
+#endif /* ACCT_TOOLS_SETUID */
exit (EXIT_SUCCESS);
}

View file

@ -1,88 +0,0 @@
From 87ec7a52ab25dd6e91253c274fd651f16844cf2a Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Wed, 7 Jan 2026 23:39:53 +0100
Subject: [PATCH 1/2] lib/chkhash.c: is_valid_hash(): Accept a leading '!'
A leading '!' means that the account is locked.
Fixes: c44f1e096a19 (2025-07-20; "chpasswd: Check hash before write when using -e")
Link: <https://github.com/shadow-maint/shadow/issues/1483>
Link: <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1124835>
Reported-by: Chris Hofstaedtler <zeha@debian.org>
Reviewed-by: Chris Hofstaedtler <zeha@debian.org>
Cc: vinz <mmpx09@protonmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/chkhash.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/chkhash.c b/lib/chkhash.c
index 66870500..cb45f3aa 100644
--- a/lib/chkhash.c
+++ b/lib/chkhash.c
@@ -7,6 +7,8 @@
#include <stddef.h>
#include <string.h>
+#include "string/strcmp/strprefix.h"
+
/*
* match_regex - return true if match, false if not
@@ -37,6 +39,8 @@ match_regex(const char *pattern, const char *string)
bool
is_valid_hash(const char *hash)
{
+ hash = strprefix(hash, "!") ?: hash;
+
// Minimum hash length
if (strlen(hash) < 13)
return false;
--
2.52.0
From ddc2549f87e3001f663d5179e9b6d7fe2e1f3b3f Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Wed, 7 Jan 2026 23:44:26 +0100
Subject: [PATCH 2/2] lib/chkhash.c: is_valid_hash(): Accept '*' as the hash
This is widely accepted as an invalid hash, to remove password access
for an account (that is, no passwords will match the "hash").
Fixes: c44f1e096a19 (2025-07-20; "chpasswd: Check hash before write when using -e")
Closes: <https://github.com/shadow-maint/shadow/issues/1483>
Closes: <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1124835>
Reported-by: Chris Hofstaedtler <zeha@debian.org>
Reviewed-by: Chris Hofstaedtler <zeha@debian.org>
Cc: vinz <mmpx09@protonmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/chkhash.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/chkhash.c b/lib/chkhash.c
index cb45f3aa..71e0fb4e 100644
--- a/lib/chkhash.c
+++ b/lib/chkhash.c
@@ -7,6 +7,7 @@
#include <stddef.h>
#include <string.h>
+#include "string/strcmp/streq.h"
#include "string/strcmp/strprefix.h"
@@ -41,6 +42,9 @@ is_valid_hash(const char *hash)
{
hash = strprefix(hash, "!") ?: hash;
+ if (streq(hash, "*"))
+ return true;
+
// Minimum hash length
if (strlen(hash) < 13)
return false;
--
2.52.0

View file

@ -1,137 +0,0 @@
From 9b67543987e3d140c86f1b8e2b5db5c10d8bc3c5 Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Thu, 15 Jan 2026 01:11:52 +0100
Subject: [PATCH 1/4] lib/chkhash.c: is_valid_hash(): Accept an empty hash
It represents a passwordless account.
That is discouraged, but accepted.
Fixes: c44f1e096a19 (2025-07-20; "chpasswd: Check hash before write when using -e")
Link: <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1124835>
Reported-by: Marc 'Zugschlus' Haber <mh+githubvisible@zugschlus.de>
Reported-by: "Serge E. Hallyn" <serge@hallyn.com>
Reported-by: Adam Williamson <awilliam@redhat.com>
Co-authored-by: "Serge E. Hallyn" <serge@hallyn.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/chkhash.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/chkhash.c b/lib/chkhash.c
index 71e0fb4e..d0a8dc01 100644
--- a/lib/chkhash.c
+++ b/lib/chkhash.c
@@ -42,6 +42,10 @@ is_valid_hash(const char *hash)
{
hash = strprefix(hash, "!") ?: hash;
+ // Passwordless account; discouraged
+ if (streq(hash, ""))
+ return true;
+
if (streq(hash, "*"))
return true;
--
2.52.0
From bce404a7a831de911cf20fc8d4c548957f70bc72 Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Thu, 15 Jan 2026 01:14:46 +0100
Subject: [PATCH 2/4] lib/chkhash.c: is_valid_hash(): Update comment
This checks the entire shadow(5) 2nd field, which is more than just
a hash.
Reported-by: Tobias Stoeckmann <tobias@stoeckmann.org>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/chkhash.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lib/chkhash.c b/lib/chkhash.c
index d0a8dc01..1723f862 100644
--- a/lib/chkhash.c
+++ b/lib/chkhash.c
@@ -31,9 +31,7 @@ match_regex(const char *pattern, const char *string)
/*
- * is_valid_hash - check if the given string is a valid password hash
- *
- * Returns true if the string appears to be a valid hash, false otherwise.
+ * is_valid_hash - check if the string is a valid shadow(5) 2nd field.
*
* regex from: https://man.archlinux.org/man/crypt.5.en
*/
--
2.52.0
From 9a86c515a1e7caeeb9e60fa63871170a2dfd98f0 Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Thu, 15 Jan 2026 01:24:10 +0100
Subject: [PATCH 3/4] lib/chkhash.c: is_valid_hash(): Comment meaning of !hash
and *
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/chkhash.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lib/chkhash.c b/lib/chkhash.c
index 1723f862..101f2004 100644
--- a/lib/chkhash.c
+++ b/lib/chkhash.c
@@ -38,12 +38,14 @@ match_regex(const char *pattern, const char *string)
bool
is_valid_hash(const char *hash)
{
+ // Password temporarily locked
hash = strprefix(hash, "!") ?: hash;
// Passwordless account; discouraged
if (streq(hash, ""))
return true;
+ // Password permanently locked (and forgotten)
if (streq(hash, "*"))
return true;
--
2.52.0
From 6be13b2f84a2c1a0d0f4129b5258b4b443e7f86c Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Fri, 16 Jan 2026 01:52:32 +0100
Subject: [PATCH 4/4] man/shadow.5.xml: Document "*"
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
man/shadow.5.xml | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/man/shadow.5.xml b/man/shadow.5.xml
index dc7fdfde..d3cc0398 100644
--- a/man/shadow.5.xml
+++ b/man/shadow.5.xml
@@ -82,8 +82,12 @@
If the password field begins with an exclamation mark <emphasis>!</emphasis>,
the password is locked.
The remaining characters on the
- line represent the password field before the password was
- locked.
+ line represent the password hash.
+ </para>
+ <para>
+ If the password hash consists of a star <emphasis>*</emphasis>,
+ password access is effectively disallowed;
+ this is because no password can produce a hash like this.
</para>
<para>
Refer to <citerefentry><refentrytitle>crypt</refentrytitle>
--
2.52.0

View file

@ -1,492 +0,0 @@
From 4079070c4b8ced8df9d86366ad113fc3c2e4e49c Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Wed, 22 Apr 2026 11:02:36 +0200
Subject: [PATCH 1/5] lib/btrfs.c: btrfs_is_subvolume(): Simplify error check
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/btrfs.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lib/btrfs.c b/lib/btrfs.c
index 85ec7dbc..fe20cf30 100644
--- a/lib/btrfs.c
+++ b/lib/btrfs.c
@@ -67,8 +67,7 @@ int btrfs_is_subvolume(const char *path)
if (ret <= 0)
return ret;
- ret = stat(path, &st);
- if (ret == -1)
+ if (stat(path, &st) == -1)
return -1;
if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) {
--
2.53.0
From 7a4faaaf46314547cc5e6c8264c3a11753ef9352 Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Wed, 22 Apr 2026 11:14:20 +0200
Subject: [PATCH 2/5] lib/, src/: Move statfs(2) call out of is_btrfs()
This simplifies the return value of is_btrfs() into a boolean.
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/btrfs.c | 25 +++++++++----------------
lib/prototypes.h | 3 ++-
src/useradd.c | 11 +++++++++--
3 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/lib/btrfs.c b/lib/btrfs.c
index fe20cf30..065c7938 100644
--- a/lib/btrfs.c
+++ b/lib/btrfs.c
@@ -60,12 +60,13 @@ int btrfs_remove_subvolume(const char *path)
*/
int btrfs_is_subvolume(const char *path)
{
- struct stat st;
- int ret;
+ struct stat st;
+ struct statfs sfs;
- ret = is_btrfs(path);
- if (ret <= 0)
- return ret;
+ if (statfs(path, &sfs) == -1)
+ return -1;
+ if (!is_btrfs(&sfs))
+ return 0;
if (stat(path, &st) == -1)
return -1;
@@ -78,16 +79,8 @@ int btrfs_is_subvolume(const char *path)
}
-/* Adapted from btrfsprogs */
-int is_btrfs(const char *path)
+bool
+is_btrfs(const struct statfs *sfs)
{
- struct statfs sfs;
- int ret;
-
- ret = statfs(path, &sfs);
- if (ret == -1)
- return -1;
-
- return sfs.f_type == BTRFS_SUPER_MAGIC;
+ return sfs->f_type == BTRFS_SUPER_MAGIC;
}
-
diff --git a/lib/prototypes.h b/lib/prototypes.h
index 9a03e312..42aa2923 100644
--- a/lib/prototypes.h
+++ b/lib/prototypes.h
@@ -23,6 +23,7 @@
#include <sys/socket.h>
#include <sys/stat.h>
+#include <sys/statfs.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
@@ -54,7 +55,7 @@ extern int isexpired (const struct passwd *, /*@null@*/const struct spwd *);
extern int btrfs_create_subvolume(const char *path);
extern int btrfs_remove_subvolume(const char *path);
extern int btrfs_is_subvolume(const char *path);
-extern int is_btrfs(const char *path);
+extern bool is_btrfs(const struct statfs *sfs);
#endif
/* basename() renamed to Basename() to avoid libc name space confusion */
diff --git a/src/useradd.c b/src/useradd.c
index df679d28..5ce6cd6a 100644
--- a/src/useradd.c
+++ b/src/useradd.c
@@ -25,6 +25,7 @@
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/statfs.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
@@ -2265,6 +2266,7 @@ static void create_home(const struct option_flags *flags)
#if WITH_BTRFS
if (subvolflg && (strlen(prefix_user_home) - (int)strlen(path)) <= 1) {
char *btrfs_check = strdup(path);
+ struct statfs sfs;
if (!btrfs_check) {
fprintf(stderr,
@@ -2273,13 +2275,18 @@ static void create_home(const struct option_flags *flags)
fail_exit(E_HOMEDIR, process_selinux);
}
stpcpy(&btrfs_check[strlen(path) - strlen(cp) - 1], "");
- if (is_btrfs(btrfs_check) <= 0) {
+ if (statfs(btrfs_check, &sfs) == -1) {
+ fprintf(stderr, "%s: statfs(\"%s\"): %s\n",
+ Prog, btrfs_check, strerrno());
+ fail_exit(E_HOMEDIR, process_selinux);
+ }
+ free(btrfs_check);
+ if (!is_btrfs(&sfs)) {
fprintf(stderr,
_("%s: home directory \"%s\" must be mounted on BTRFS\n"),
Prog, path);
fail_exit(E_HOMEDIR, process_selinux);
}
- free(btrfs_check);
// make subvolume to mount for user instead of directory
if (btrfs_create_subvolume(path)) {
fprintf(stderr,
--
2.53.0
From 96460c7240264c0022cebc9ba1774993ee2594cf Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Wed, 22 Apr 2026 11:21:14 +0200
Subject: [PATCH 3/5] lib/, src/: Move btrfs.c prototypes to lib/btrfs.h
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/Makefile.am | 2 +-
lib/btrfs.c | 4 ++++
lib/btrfs.h | 23 +++++++++++++++++++++++
lib/prototypes.h | 9 ---------
src/useradd.c | 1 +
src/userdel.c | 1 +
src/usermod.c | 1 +
7 files changed, 31 insertions(+), 10 deletions(-)
create mode 100644 lib/btrfs.h
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 7c488f82..0664e610 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -290,7 +290,7 @@ libshadow_la_SOURCES += tcbfuncs.c tcbfuncs.h
endif
if WITH_BTRFS
-libshadow_la_SOURCES += btrfs.c
+libshadow_la_SOURCES += btrfs.c btrfs.h
endif
if ENABLE_LASTLOG
diff --git a/lib/btrfs.c b/lib/btrfs.c
index 065c7938..15b6b315 100644
--- a/lib/btrfs.c
+++ b/lib/btrfs.c
@@ -1,3 +1,7 @@
+#include "config.h"
+
+#include "btrfs.h"
+
#include <linux/btrfs_tree.h>
#include <linux/magic.h>
#include <sys/statfs.h>
diff --git a/lib/btrfs.h b/lib/btrfs.h
new file mode 100644
index 00000000..50b70488
--- /dev/null
+++ b/lib/btrfs.h
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2026, Alejandro Colomar <alx@kernel.org>
+// SPDX-License-Identifier: BSD-3-Clause
+
+
+#ifndef SHADOW_INCLUDE_LIB_BTRFS_H_
+#define SHADOW_INCLUDE_LIB_BTRFS_H_
+
+
+#include "config.h"
+
+#include <stdbool.h>
+#include <sys/statfs.h>
+
+
+#ifdef WITH_BTRFS
+int btrfs_create_subvolume(const char *path);
+int btrfs_remove_subvolume(const char *path);
+int btrfs_is_subvolume(const char *path);
+bool is_btrfs(const struct statfs *sfs);
+#endif
+
+
+#endif // include guard
diff --git a/lib/prototypes.h b/lib/prototypes.h
index 42aa2923..d22c477a 100644
--- a/lib/prototypes.h
+++ b/lib/prototypes.h
@@ -23,7 +23,6 @@
#include <sys/socket.h>
#include <sys/stat.h>
-#include <sys/statfs.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
@@ -50,14 +49,6 @@ extern int expire (const struct passwd *, /*@null@*/const struct spwd *);
/* isexpired.c */
extern int isexpired (const struct passwd *, /*@null@*/const struct spwd *);
-/* btrfs.c */
-#ifdef WITH_BTRFS
-extern int btrfs_create_subvolume(const char *path);
-extern int btrfs_remove_subvolume(const char *path);
-extern int btrfs_is_subvolume(const char *path);
-extern bool is_btrfs(const struct statfs *sfs);
-#endif
-
/* basename() renamed to Basename() to avoid libc name space confusion */
/* basename.c */
extern /*@observer@*/const char *Basename (const char *str);
diff --git a/src/useradd.c b/src/useradd.c
index 5ce6cd6a..9bd32b11 100644
--- a/src/useradd.c
+++ b/src/useradd.c
@@ -34,6 +34,7 @@
#include "alloc/malloc.h"
#include "atoi/a2i.h"
#include "atoi/getnum.h"
+#include "btrfs.h"
#include "chkname.h"
#include "defines.h"
#include "faillog.h"
diff --git a/src/userdel.c b/src/userdel.c
index 9b7d81be..ebd064c0 100644
--- a/src/userdel.c
+++ b/src/userdel.c
@@ -20,6 +20,7 @@
#include <sys/types.h>
#include <unistd.h>
+#include "btrfs.h"
#ifdef ACCT_TOOLS_SETUID
#ifdef USE_PAM
#include "pam_defs.h"
diff --git a/src/usermod.c b/src/usermod.c
index 7c0321a6..3afe19ea 100644
--- a/src/usermod.c
+++ b/src/usermod.c
@@ -32,6 +32,7 @@
#include "alloc/malloc.h"
#include "atoi/a2i.h"
#include "atoi/getnum.h"
+#include "btrfs.h"
#include "chkname.h"
#include "defines.h"
#include "faillog.h"
--
2.53.0
From 7be9a6540f1cad477fd4f4e83d25904a5095bb86 Mon Sep 17 00:00:00 2001
From: Hadi Chokr <hadichokr@icloud.com>
Date: Tue, 21 Apr 2026 09:18:19 +0200
Subject: [PATCH 4/5] useradd(8): fallback to regular dir for BTRFS home on
non-BTRFS parent
When the --btrfs-subvolume-home option is used but the parent directory
is not on a BTRFS filesystem, useradd previously failed with an error.
This is too strict; instead, fall back to creating a regular directory
and issue a warning. The subvolume creation is attempted only when the
parent is BTRFS. Otherwise, a regular directory is created and a
syslog(3) warning is logged.
Fixes: 3e8c105 (2026-01-02; "src/useradd: Support config for creating home dirs as Btrfs subvolumes")
Co-authored-by: Hadi Chokr <hadichokr@icloud.com>
Co-authored-by: Alejandro Colomar <alx@kernel.org>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
man/useradd.8.xml | 27 ++++++++++++++++++++++--
src/useradd.c | 52 +++++++++++++++++++++++------------------------
2 files changed, 51 insertions(+), 28 deletions(-)
diff --git a/man/useradd.8.xml b/man/useradd.8.xml
index 8a087e13..0bde6683 100644
--- a/man/useradd.8.xml
+++ b/man/useradd.8.xml
@@ -2,6 +2,7 @@
<!--
SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
+ SPDX-FileCopyrightText: 2025 - 2026, Hadi Chokr
SPDX-License-Identifier: BSD-3-Clause
-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.5//EN"
@@ -156,8 +157,30 @@
regardless of any configuration file settings.
</para>
<para>
- Note: this feature works only if the underlying filesystem supports
- Btrfs subvolumes.
+ If the parent directory of the user's home directory is
+ <emphasis>not</emphasis> on a Btrfs filesystem,
+ <command>useradd</command> will <emphasis>not</emphasis> create a
+ subvolume.
+ Instead, it creates a regular directory,
+ prints a warning to standard error,
+ and logs the event via syslog at level
+ <constant>LOG_WARN</constant>.
+ The user account is still created successfully.
+ </para>
+ <para>
+ If the filesystem type cannot be determined (e.g., because of
+ insufficient permissions, an I/O error,
+ or a
+ <citerefentry>
+ <refentrytitle>statfs</refentrytitle>
+ <manvolnum>2</manvolnum>
+ </citerefentry>
+ failure),
+ <command>useradd</command> treats this as a fatal error:
+ the home directory is not created,
+ the command exits with a nonzero status
+ (<literal>E_HOMEDIR</literal>, 12),
+ and an error message is printed.
</para>
</listitem>
</varlistentry>
diff --git a/src/useradd.c b/src/useradd.c
index 9bd32b11..9210379c 100644
--- a/src/useradd.c
+++ b/src/useradd.c
@@ -1,11 +1,11 @@
-/*
- * SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
- * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
- * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
- * SPDX-FileCopyrightText: 2007 - 2012, Nicolas François
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
+// SPDX-FileCopyrightText: 1991-1994, Julianne Frances Haugh
+// SPDX-FileCopyrightText: 1996-2000, Marek Michałkiewicz
+// SPDX-FileCopyrightText: 2000-2006, Tomasz Kłoczko
+// SPDX-FileCopyrightText: 2007-2012, Nicolas François
+// SPDX-FileCopyrightText: 2025-2026, Hadi Chokr
+// SPDX-FileCopyrightText: 2026, Alejandro Colomar <alx@kernel.org>
+// SPDX-License-Identifier: BSD-3-Clause
+
#include "config.h"
@@ -2251,6 +2251,8 @@ static void create_home(const struct option_flags *flags)
owner root:root.
*/
for (cp = strtok(bhome, "/"); cp != NULL; cp = strtok(NULL, "/")) {
+ bool dir_created;
+
/* Avoid turning a relative path into an absolute path. */
if (strprefix(bhome, "/") || !streq(path, ""))
strcat(path, "/");
@@ -2260,10 +2262,7 @@ static void create_home(const struct option_flags *flags)
continue;
}
- /* Check if parent directory is BTRFS, fail if requesting
- subvolume but no BTRFS. The paths could be different by the
- trailing slash
- */
+ dir_created = false;
#if WITH_BTRFS
if (subvolflg && (strlen(prefix_user_home) - (int)strlen(path)) <= 1) {
char *btrfs_check = strdup(path);
@@ -2284,25 +2283,26 @@ static void create_home(const struct option_flags *flags)
free(btrfs_check);
if (!is_btrfs(&sfs)) {
fprintf(stderr,
- _("%s: home directory \"%s\" must be mounted on BTRFS\n"),
- Prog, path);
- fail_exit(E_HOMEDIR, process_selinux);
+ _("%s: warning: \"%s\" is not on BTRFS; creating regular directory instead of subvolume\n"),
+ Prog, prefix_user_home);
+ } else {
+ if (btrfs_create_subvolume(path)) {
+ fprintf(stderr,
+ _("%s: failed to create BTRFS subvolume: %s\n"),
+ Prog, path);
+ fail_exit(E_HOMEDIR, process_selinux);
+ }
+ dir_created = true;
}
- // make subvolume to mount for user instead of directory
- if (btrfs_create_subvolume(path)) {
- fprintf(stderr,
- _("%s: failed to create BTRFS subvolume: %s\n"),
+ }
+#endif
+ if (!dir_created) {
+ if (mkdir(path, 0) != 0) {
+ fprintf(stderr, _("%s: cannot create directory %s\n"),
Prog, path);
fail_exit(E_HOMEDIR, process_selinux);
}
}
- else
-#endif
- if (mkdir(path, 0) != 0) {
- fprintf(stderr, _("%s: cannot create directory %s\n"),
- Prog, path);
- fail_exit(E_HOMEDIR, process_selinux);
- }
if (chown(path, 0, 0) < 0) {
fprintf(stderr,
_("%s: warning: chown on `%s' failed: %m\n"),
--
2.53.0
From 827f69b864461ab6d7549762bef06ab4495d2587 Mon Sep 17 00:00:00 2001
From: Hadi Chokr <hadichokr@icloud.com>
Date: Mon, 20 Apr 2026 12:27:31 +0200
Subject: [PATCH 5/5] man/useradd.8.xml: Remove trailing spaces from
useradd.8.xml
Signed-off-by: Hadi Chokr <hadichokr@icloud.com>
Reviewed-by: Alejandro Colomar <alx@kernel.org>
---
man/useradd.8.xml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/man/useradd.8.xml b/man/useradd.8.xml
index 0bde6683..f85780e2 100644
--- a/man/useradd.8.xml
+++ b/man/useradd.8.xml
@@ -207,7 +207,7 @@
user's login directory. The default is to append the
<replaceable>LOGIN</replaceable> name to
<replaceable>BASE_DIR</replaceable> and use that as the
- login directory name.
+ login directory name.
The directory <replaceable>HOME_DIR</replaceable> is not created by
default. However it will be created for non-system users if either the
<option>-m</option> flag is specifed or
@@ -422,7 +422,7 @@
</term>
<listitem>
<para>
- Create the user's home directory if it does not exist.
+ Create the user's home directory if it does not exist.
The files and directories contained in the skeleton directory
(which can be defined with the <option>-k</option> option)
will be copied to the home directory.
@@ -512,7 +512,7 @@
password himself.
</para>
<para>
- <emphasis role="bold">Note:</emphasis>Avoid this option on the command
+ <emphasis role="bold">Note:</emphasis>Avoid this option on the command
line because the password (or encrypted password) will
be visible by users listing the processes.
</para>
--
2.53.0

View file

@ -1,75 +0,0 @@
From 958b4859991e700b61af2f9e07e3aa87ad1d9218 Mon Sep 17 00:00:00 2001
From: Alejandro Colomar <alx@kernel.org>
Date: Sat, 17 Jan 2026 00:56:21 +0100
Subject: [PATCH] Revert "src/usermod.c: Remove optimizations"
This wasn't only an optimization; it also skipped some checks that were
now spuriously triggering errors. We may be able to get rid of the
optimizations, but that will need more analysis. For now, let's revert
to a known-good state.
Fixes: 6a8a25dc7de6 (2025-10-15; "src/usermod.c: Remove optimizations")
Reverts: 6a8a25dc7de6 (2025-10-15; "src/usermod.c: Remove optimizations")
Closes: <https://github.com/shadow-maint/shadow/issues/1509>
Reported-by: Adam Williamson <awilliam@redhat.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
src/usermod.c | 42 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/src/usermod.c b/src/usermod.c
index 7400bf84..7cd7a859 100644
--- a/src/usermod.c
+++ b/src/usermod.c
@@ -1391,6 +1391,48 @@ process_flags(int argc, char **argv, struct option_flags *flags)
}
#endif /* WITH_SELINUX */
+ if (user_newid == user_id) {
+ uflg = false;
+ oflg = false;
+ }
+ if (user_newgid == user_gid) {
+ gflg = false;
+ }
+ if ( (NULL != user_newshell)
+ && streq(user_newshell, user_shell)) {
+ sflg = false;
+ }
+ if (streq(user_newname, user_name)) {
+ lflg = false;
+ }
+ if (user_newinactive == user_inactive) {
+ fflg = false;
+ }
+ if (user_newexpire == user_expire) {
+ eflg = false;
+ }
+ if ( (NULL != user_newhome)
+ && streq(user_newhome, user_home)) {
+ dflg = false;
+ mflg = false;
+ }
+ if ( (NULL != user_newcomment)
+ && streq(user_newcomment, user_comment)) {
+ cflg = false;
+ }
+
+ if (!(Uflg || uflg || sflg || pflg || mflg || Lflg ||
+ lflg || Gflg || gflg || fflg || eflg || dflg || cflg
+#ifdef ENABLE_SUBIDS
+ || vflg || Vflg || wflg || Wflg
+#endif /* ENABLE_SUBIDS */
+#ifdef WITH_SELINUX
+ || Zflg
+#endif /* WITH_SELINUX */
+ )) {
+ exit (E_SUCCESS);
+ }
+
if (!is_shadow_pwd && (eflg || fflg)) {
fprintf (stderr,
_("%s: shadow passwords required for -e and -f\n"),
--
2.52.0

View file

@ -1,12 +1,12 @@
Summary: Utilities for managing accounts and shadow password files
Name: shadow-utils
Version: 4.19.0
Release: 7%{?dist}
Version: 4.20.0
Release: 1%{?dist}
Epoch: 2
License: BSD-3-Clause AND GPL-2.0-or-later
URL: https://github.com/shadow-maint/shadow
Source0: https://github.com/shadow-maint/shadow/releases/download/4.19.0/shadow-4.19.0.tar.xz
Source1: https://github.com/shadow-maint/shadow/releases/download/4.19.0/shadow-4.19.0.tar.xz.asc
Source0: https://github.com/shadow-maint/shadow/releases/download/4.20.0/shadow-4.20.0.tar.xz
Source1: https://github.com/shadow-maint/shadow/releases/download/4.20.0/shadow-4.20.0.tar.xz.asc
Source2: shadow-utils.useradd
Source3: shadow-utils.login.defs
Source4: shadow-bsd.txt
@ -20,25 +20,6 @@ Source7: passwd.pamd
%global _ld_strict_symbol_defs 1
### Patches ###
# Misc manual page changes - non-upstreamable
Patch0: shadow-4.15.0-manfix.patch
# Probably non-upstreamable
Patch1: shadow-4.19.0-account-tools-setuid.patch
# https://github.com/shadow-maint/shadow/commit/3e8c105f0703264e947d8c034b90419794955d49
Patch2: shadow-4-19-useradd-support-btrfs.patch
# https://github.com/shadow-maint/shadow/commit/3e8c105f0703264e947d8c034b90419794955d49
Patch3: shadow-4.19.0-chkhash1.patch
# https://github.com/shadow-maint/shadow/commit/9b67543987e3d140c86f1b8e2b5db5c10d8bc3c5
Patch4: shadow-4.19.0-chkhash2.patch
# https://github.com/shadow-maint/shadow/commit/958b4859991e700b61af2f9e07e3aa87ad1d9218
Patch5: shadow-4.19.0-usermod-add-optimizations.patch
# https://github.com/shadow-maint/shadow/pull/1520
# Approved by upstream for 4.19.3 this weekend:
# https://github.com/shadow-maint/shadow/issues/1521
# Fixes the hash check to accept hashes with \ or n in the salt
Patch6: 1520.patch
# https://github.com/shadow-maint/shadow/commit/827f69b864461ab6d7549762bef06ab4495d2587
Patch7: shadow-4.19.0-useradd-fix-btrfs.patch
### Dependencies ###
Requires: audit-libs >= 1.6.5
@ -117,7 +98,7 @@ Requires: shadow-utils-subid = %{epoch}:%{version}-%{release}
Development files for shadow-utils-subid.
%prep
%autosetup -p 1 -S git -n shadow-4.19.0
%autosetup -p 1 -S git -n shadow-4.20.0
iconv -f ISO88591 -t utf-8 doc/HOWTO > doc/HOWTO.utf8
cp -f doc/HOWTO.utf8 doc/HOWTO
@ -155,6 +136,8 @@ install -d -m 755 $RPM_BUILD_ROOT%{_sysconfdir}/default
install -p -c -m 0644 %{SOURCE3} $RPM_BUILD_ROOT%{_sysconfdir}/login.defs
install -p -c -m 0600 %{SOURCE2} $RPM_BUILD_ROOT%{_sysconfdir}/default/useradd
install -d -m 755 $RPM_BUILD_ROOT%{_pam_confdir}
install -m 644 %{SOURCE7} $RPM_BUILD_ROOT%{_pam_confdir}/chpasswd
install -m 644 %{SOURCE7} $RPM_BUILD_ROOT%{_pam_confdir}/newusers
install -m 644 %{SOURCE7} $RPM_BUILD_ROOT%{_pam_confdir}/passwd
@ -172,26 +155,20 @@ mv -v $RPM_BUILD_ROOT/usr/sbin/* $RPM_BUILD_ROOT%{_bindir}/
# Remove binaries we don't use.
rm $RPM_BUILD_ROOT%{_bindir}/chfn
rm $RPM_BUILD_ROOT%{_bindir}/chsh
rm $RPM_BUILD_ROOT%{_bindir}/expiry
rm $RPM_BUILD_ROOT%{_bindir}/login
rm $RPM_BUILD_ROOT%{_bindir}/su
rm $RPM_BUILD_ROOT%{_bindir}/faillog
rm $RPM_BUILD_ROOT%{_sbindir}/logoutd
rm $RPM_BUILD_ROOT%{_sbindir}/nologin
rm $RPM_BUILD_ROOT%{_mandir}/man1/chfn.*
rm $RPM_BUILD_ROOT%{_mandir}/*/man1/chfn.*
rm $RPM_BUILD_ROOT%{_mandir}/man1/chsh.*
rm $RPM_BUILD_ROOT%{_mandir}/*/man1/chsh.*
rm $RPM_BUILD_ROOT%{_mandir}/man1/expiry.*
rm $RPM_BUILD_ROOT%{_mandir}/*/man1/expiry.*
rm $RPM_BUILD_ROOT%{_mandir}/man1/login.*
rm $RPM_BUILD_ROOT%{_mandir}/*/man1/login.*
rm $RPM_BUILD_ROOT%{_mandir}/man1/su.*
rm $RPM_BUILD_ROOT%{_mandir}/*/man1/su.*
rm $RPM_BUILD_ROOT%{_mandir}/man5/passwd.*
rm $RPM_BUILD_ROOT%{_mandir}/*/man5/passwd.*
rm $RPM_BUILD_ROOT%{_mandir}/man8/logoutd.*
rm $RPM_BUILD_ROOT%{_mandir}/*/man8/logoutd.*
rm $RPM_BUILD_ROOT%{_mandir}/man8/nologin.*
rm $RPM_BUILD_ROOT%{_mandir}/*/man8/nologin.*
rm $RPM_BUILD_ROOT%{_mandir}/man3/getspnam.*
@ -203,11 +180,8 @@ rm $RPM_BUILD_ROOT%{_mandir}/*/man8/faillog.*
# Remove PAM service files we don't use.
rm $RPM_BUILD_ROOT%{_pam_confdir}/chfn
rm $RPM_BUILD_ROOT%{_pam_confdir}/chpasswd
rm $RPM_BUILD_ROOT%{_pam_confdir}/chsh
rm $RPM_BUILD_ROOT%{_pam_confdir}/groupmems
rm $RPM_BUILD_ROOT%{_pam_confdir}/login
rm $RPM_BUILD_ROOT%{_pam_confdir}/newusers
rm $RPM_BUILD_ROOT%{_pam_confdir}/su
find $RPM_BUILD_ROOT%{_mandir} -depth -type d -empty -delete
@ -234,6 +208,8 @@ rm -f $RPM_BUILD_ROOT/%{_libdir}/libsubid.a
%license gpl-2.0.txt shadow-bsd.txt
%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/login.defs
%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/default/useradd
%config(noreplace) %{_pam_confdir}/chpasswd
%config(noreplace) %{_pam_confdir}/newusers
%config(noreplace) %{_pam_confdir}/passwd
%{_bindir}/sg
%attr(4755,root,root) %{_bindir}/chage
@ -288,9 +264,26 @@ rm -f $RPM_BUILD_ROOT/%{_libdir}/libsubid.a
%{_libdir}/libsubid.so
%changelog
* Wed Apr 29 2026 Iker Pedrosa <ipedrosa@redhat.com> - 2:4.19.0-7
* Wed Jul 29 2026 Iker Pedrosa <ipedrosa@redhat.com> - 2:4.20.0-1
- Rebase to version 4.20.0
* Wed Jul 22 2026 Iker Pedrosa <ipedrosa@redhat.com> - 2:4.20.0-rc3-1
- Rebase to version 4.20.0-rc3
* Fri Jul 17 2026 Fedora Release Engineering <releng@fedoraproject.org> - 2:4.19.3-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild
* Wed May 27 2026 Iker Pedrosa <ipedrosa@redhat.com> - 2:4.19.3-3
- Enable use of PAM for chpasswd and newusers
Resolves: #2461179 and #2283963
* Thu Apr 23 2026 Iker Pedrosa <ipedrosa@redhat.com> - 2:4.19.3-2
- btrfs: simplify checks improve useradd behavior for non-btrfs
* Wed Feb 11 2026 Debarshi Ray <rishi@fedoraproject.org> - 2:4.19.3-1
- Rebase to version 4.19.3
Resolves: #2426288
* Tue Jan 27 2026 Adam Williamson <awilliam@redhat.com> - 2:4.19.0-6
- chkhash.c: fix escaping in SHA-256 / SHA-512 / MD5 regexes

View file

@ -1,2 +1,2 @@
SHA512 (shadow-4.19.0.tar.xz) = 0846c34f426101213ab6277bbcdc58c28b3a54df2f68d1c1addcc8c5aa79ce3e15d5d8dfcb9cc6dbd1b758e0cd8676c68ca65268f5a037d4c24aa7d358c9a390
SHA512 (shadow-4.19.0.tar.xz.asc) = 34c1a764d06a5d24533cbb57310de7462eb632c1de904901699d48f44401b1b1f4751db35f2b0e2b9a057cf1a777ccddbae4bc6b19a6db865ebc3e30fde0d060
SHA512 (shadow-4.20.0.tar.xz) = 0b8afded372e4d37a78f38cb972c0ab877870ef6356cdd1c45be3c708af3d1496c6f87de6bf6a5b1d217d4d52d7e2d15c28b26530949cc6de0231a4028930406
SHA512 (shadow-4.20.0.tar.xz.asc) = e4b134543768f323df30a1e450c22086d013020928d62bd3207db7f633beea3587b67802da5b575d5a794e8c2bebd7a0abeba6ed69d44bf08adc37df3bf352ac

13
tests/mhc-fedora-ci.yaml Normal file
View file

@ -0,0 +1,13 @@
provisioned_topologies:
- shadow
domains:
- id: shadow
hosts:
- hostname: localhost
role: shadow
conn:
type: ssh
host: localhost
user: root
artifacts:
- /var/log/*