[Pkg-shadow-devel] [Git][debian/adduser][master] 43 commits: add trace output to sanitize_string

Marc Haber (@zugschlus) gitlab at salsa.debian.org
Tue Feb 18 14:44:33 GMT 2025



Marc Haber pushed to branch master at Debian / adduser


Commits:
da92f716 by Marc Haber at 2025-02-18T15:25:26+01:00
add trace output to sanitize_string

- - - - -
d2b0dc56 by Marc Haber at 2025-02-18T15:25:26+01:00
remove po/Makefile, see discussion in #1031081

- - - - -
62ef2079 by Marc Haber at 2025-02-18T15:25:26+01:00
modernize default use clauses at the beginning

- - - - -
5c9c3388 by Marc Haber at 2025-02-18T15:25:26+01:00
use Encode and define $charset, set binmode for standard output and error

- - - - -
7b5d9e5e by Marc Haber at 2025-02-18T15:25:26+01:00
define and export egetgrnam and egetpwnam

- - - - -
f1fb125a by Marc Haber at 2025-02-18T15:25:26+01:00
more debug output in sanitize_string

- - - - -
cb5b231e by Marc Haber at 2025-02-18T15:25:26+01:00
more logging

Git-Dch: ignore

- - - - -
aaa779cc by Marc Haber at 2025-02-18T15:25:26+01:00
use eget(gr|pw)nam in Tests

Git-Dch: ignore

- - - - -
0fea87ed by Marc Haber at 2025-02-18T15:25:26+01:00
encode username according to charset

Git-Dch: ignore

- - - - -
43b09b35 by Marc Haber at 2025-02-18T15:25:26+01:00
use more speaking names than "foo", yielding readable logs

Git-Dch: ignore

- - - - -
e7b37199 by Marc Haber at 2025-02-18T15:25:26+01:00
encode UTF-8 data handed in from external

- - - - -
27b69c30 by Marc Haber at 2025-02-18T15:25:26+01:00
use eget(gr|pw)nam in adduser and deluser

- - - - -
c1148cd3 by Marc Haber at 2025-02-18T15:25:26+01:00
sanitize $PATH

Git-Dch: ignore

- - - - -
1d6219b3 by Marc Haber at 2025-02-18T15:25:26+01:00
sanitize command line data

- - - - -
13b29e4c by Marc Haber at 2025-02-18T15:25:26+01:00
modernize perl (new operator), better logging

Git-Dch: ignore

- - - - -
8aa14ddf by Marc Haber at 2025-02-18T15:25:26+01:00
move validity checks of parameters to a different place

Git-Dch: ignore

- - - - -
3563d31e by Marc Haber at 2025-02-18T15:25:26+01:00
decode configuration data

Git-Dch: ignore

- - - - -
9832fabc by Marc Haber at 2025-02-18T15:25:26+01:00
decode data read from pool

Git-Dch: ignore

- - - - -
4e16c60c by Marc Haber at 2025-02-18T15:25:26+01:00
use eget(pw|gr)nam in AdduserCommon

Git-Dch: ignore

- - - - -
be974297 by Marc Haber at 2025-02-18T15:25:26+01:00
decode file names read from directory

Git-Dch: ignore

- - - - -
8d94169c by Marc Haber at 2025-02-18T15:25:26+01:00
allow namere to be used in sanitizing

Git-Dch: ignore

- - - - -
d8575ba8 by Marc Haber at 2025-02-18T15:25:26+01:00
introduce anynamere which is used at the beginning of sanitation

Git-Dch: ignore

- - - - -
d8736525 by Marc Haber at 2025-02-18T15:25:26+01:00
sanitize name against anynamere

Git-Dch: ignore

- - - - -
be0b05b3 by Marc Haber at 2025-02-18T15:25:26+01:00
improve logging around name sanitazion

Git-Dch: ignore

- - - - -
fb0bad58 by Marc Haber at 2025-02-18T15:25:26+01:00
sanitize_name explicitly returns "" if sanitazion fails

Git-Dch: ignore

- - - - -
bda2076d by Marc Haber at 2025-02-18T15:25:26+01:00
add invalid name test that would traverse directories

- - - - -
71d1081d by Marc Haber at 2025-02-18T15:25:26+01:00
adapt valid_username.t to new useradd behavior

Git-Dch: ignore

The old settings so that they are at least preserved in git history.

- - - - -
21da6fab by Marc Haber at 2025-02-18T15:25:26+01:00
remove commented out tests again

Git-Dch: ignore

- - - - -
0c9431b2 by Marc Haber at 2025-02-18T15:25:26+01:00
intermediate commit of regexp constants that work

Git-Dch: ignore

- - - - -
2e95b4e2 by Marc Haber at 2025-02-18T15:25:26+01:00
add last touched date to README

Git-Dch: ignore

- - - - -
db8c01d7 by Marc Haber at 2025-02-18T15:25:26+01:00
fix typos in README

Git-Dch: ignore

- - - - -
7a5687ee by Marc Haber at 2025-02-18T15:25:26+01:00
reword logging paragraph

- - - - -
a1bd8b5a by Marc Haber at 2025-02-18T15:25:26+01:00
make it clear that we want you to join the team

Git-Dch: ignore

- - - - -
f6151d43 by Marc Haber at 2025-02-18T15:25:26+01:00
re-word the declarative paragraph

Git-Dch: ignore

- - - - -
85b3eea5 by Marc Haber at 2025-02-18T15:25:26+01:00
make clear that we consider directory service code to be gone

Git-Dch: ignore

- - - - -
7cc13cf3 by Marc Haber at 2025-02-18T15:25:26+01:00
re-word the DIR_MODE paragraph

Git-Dch: ignore

- - - - -
4094ae95 by Marc Haber at 2025-02-18T15:25:26+01:00
Add UTF-8 paragraph

Git-Dch: ignore

- - - - -
a06166b6 by Marc Haber at 2025-02-18T15:25:26+01:00
update the Security paragraph

Git-Dch: ignore

- - - - -
854efaa3 by Marc Haber at 2025-02-18T15:25:26+01:00
update code age

Git-Dch: ignore

- - - - -
7a435db2 by Marc Haber at 2025-02-18T15:25:26+01:00
add more docs paragraph

Git-Dch: ignore

- - - - -
94e7df9c by Marc Haber at 2025-02-18T15:25:26+01:00
add versioned dependency on new passwd

that's the version we have been testing with

- - - - -
6a980715 by Marc Haber at 2025-02-18T15:25:26+01:00
send useradd --badname's warning to /dev/null in test

Git-Dch: ignore

- - - - -
28f610f9 by Marc Haber at 2025-02-18T15:25:26+01:00
don't run testsuite with shadow off, no longer supported upstream

- - - - -


14 changed files:

- AdduserCommon.pm
- AdduserLogging.pm
- AdduserRetvalues.pm
- adduser
- debian/README
- debian/control
- debian/rules
- debian/tests/f/cronjack.t
- debian/tests/f/deluser_files.t
- debian/tests/f/valid_username.t
- debian/tests/lib/AdduserTestsCommon.pm
- deluser
- − po/Makefile
- testsuite/runsuite.sh


Changes:

=====================================
AdduserCommon.pm
=====================================
@@ -1,7 +1,8 @@
 package Debian::AdduserCommon 3.138;
-use 5.32.0;
-use strict;
-use warnings;
+use 5.40.0;
+use utf8;
+use Encode;
+use I18N::Langinfo qw(langinfo CODESET);
 
 # Subroutines shared by the "adduser" and "deluser" utilities.
 #
@@ -39,6 +40,7 @@ BEGIN {
 }
 
 use vars qw(@EXPORT $VAR1);
+my $charset = langinfo(CODESET);
 
 BEGIN {
     local $ENV{PERL_DL_NONLAZY}=1;
@@ -57,13 +59,14 @@ my $lockfile;
 my $lockfile_path = '/run/adduser';
 
 use constant {
-    filenamere => qr/[-_\.+!\$%&()\]\[;0-9a-zA-Z]*/,
-    simplefilenamere => qr/[-_\.0-9a-zA-Z]*/,
-    pathre => qr/[-_\.+!\$%&()\]\[;0-9a-zA-Z\/{}>*'@]*/,
-    simplepathre => qr/[-_\.0-9a-zA-Z\/]*/,
+    filenamere => qr/[-_\.+!\$%&()\]\[;0-9a-zA-Z]+/,
+    simplefilenamere => qr/[-_\.0-9a-zA-Z]+/,
+    pathre => qr/[-\p{Graph}_\.+!\$%&()\]\[;0-9a-zA-Z\/{}>*'@]+/,
+    simplepathre => qr/[-_\.0-9a-zA-Z\/]+/,
     commentre => qr/["-_\.+!\$%&()\]\[;0-9a-zA-Z\/ ]*/,
     numberre => qr/[0-9]+/,
-    namere => qr/[^-+~:,\s\/][^:,\s\/]*/,
+    namere => qr/^([^-+~:,\s\/][^:,\s\/]*)$/aa,
+    anynamere => qr/^([^-+~:,\s\/][^:,\s\/]*)$/aa,
 };
 
 @EXPORT = (
@@ -77,9 +80,12 @@ use constant {
     'acquire_lock',
     'release_lock',
     'sanitize_string',
+    'egetgrnam',
+    'egetpwnam',
     'preseed_config',
     'which',
     'namere',
+    'anynamere',
     "filenamere",
     "simplefilenamere",
     "pathre",
@@ -94,15 +100,33 @@ sub sanitize_string {
     # Set a default pattern to allow alphanumeric characters,
     # spaces, and underscores.
     $pattern //= qr/[a-zA-Z0-9 _]*/;
+    log_trace("sanitize_string %s against %s", $input, $pattern);
 
-    # If the input matches the pattern, extract and return the untainted value.
+    # If the input matches the pattern,
+    # extract and return the untainted value.
     if ($input =~ qr/^($pattern)$/ ) {
+        log_trace("sanitize_string returning %s", "$1");
         return $1;  # $1 is the captured, untainted portion of the string.
     } else {
-        die "Input $input contains invalid characters and could not be untainted.";
+        die "invalid characters in $input";
     }
 }
 
+
+sub egetgrnam {
+    my ($name) = @_;
+    log_trace("egetgrnam called with %s", $name);
+    $name = encode($charset, $name);
+    return getgrnam($name);
+}
+
+sub egetpwnam {
+    my ($name) = @_;
+    log_trace("egetpwnam called with %s", $name);
+    $name = encode($charset, $name);
+    return getpwnam($name);
+}
+
 # parse the configuration file
 # parameters:
 #  -- filename of the configuration file
@@ -124,6 +148,7 @@ sub read_config {
     }
     while (<$conffh>) {
         chomp;
+        $_ = decode($charset, $_);
         next if /^#/ || /^\s*$/;
 
         log_trace("read from config file: %s", $_);
@@ -162,6 +187,7 @@ sub read_pool {
     my %ids = ();
     my %new;
 
+    $pool_file = decode($charset, $pool_file);
     if (-d $pool_file) {
         my $dir;
         unless( opendir( $dir, $pool_file) ) {
@@ -190,6 +216,7 @@ sub read_pool {
     }
     while (<$pool>) {
         chomp;
+        $_ = decode($charset, $_);
         next if /^#/ || /^\s*$/;
 
         my $new;
@@ -263,8 +290,8 @@ sub get_group_members
 
     my @members;
 
-    foreach my $member (split(/ /, (getgrnam($group))[3])) {
-        push(@members, $member) if defined(getpwnam($member));
+    foreach my $member (split(/ /, (egetgrnam($group))[3])) {
+        push(@members, $member) if defined(egetpwnam($member));
     }
 
     return @members;


=====================================
AdduserLogging.pm
=====================================
@@ -1,7 +1,6 @@
 package Debian::AdduserLogging 3.138;
-use 5.32.0;
-use strict;
-use warnings;
+use 5.40.0;
+use utf8;
 
 # Adduser logging Subroutines
 #


=====================================
AdduserRetvalues.pm
=====================================
@@ -1,7 +1,6 @@
 package Debian::AdduserRetvalues 3.138;
-use 5.32.0;
-use strict;
-use warnings;
+use 5.40.0;
+use utf8;
 
 # Adduser definitions of return values
 #


=====================================
adduser
=====================================
@@ -22,9 +22,9 @@
 #
 # License: GPL-2+
 
-use 5.32.0;
-use strict;
-use warnings;
+use 5.40.0;
+use utf8;
+use Encode;
 
 use Getopt::Long;
 
@@ -40,7 +40,6 @@ BEGIN {
 }
 
 my $version = "DVERSION";
-$ENV{"PATH"} = sanitize_string( $ENV{"PATH"}, qr/[-_a-zA-Z0-9_:.\/]*/ );
 
 BEGIN {
     local $ENV{PERL_DL_NONLAZY}=1;
@@ -49,7 +48,7 @@ BEGIN {
     ## no critic
     eval "
         require POSIX;
-        import POSIX qw(setlocale);
+        POSIX->import(qw(setlocale));
     ";
     ## use critic
     if ($@) {
@@ -60,7 +59,7 @@ BEGIN {
     ## no critic
     eval "
         require I18N::Langinfo;
-        import I18N::Langinfo qw(langinfo YESEXPR NOEXPR);
+        I18N::Langinfo->import(qw(langinfo CODESET YESEXPR NOEXPR))
     ";
     ## use critic
     if ($@) {
@@ -71,10 +70,13 @@ BEGIN {
 }
 
 my $yesexpr = langinfo(YESEXPR());
+my $charset = langinfo(CODESET);
+binmode(STDOUT, ":encoding($charset)");
+binmode(STDERR, ":encoding($charset)");
 
 my %config;			# configuration hash
 
-my $nogroup_id = getgrnam("nogroup") || 65534;
+my $nogroup_id = egetgrnam("nogroup") || 65534;
 $0 =~ s+.*/++;
 
 our $action;
@@ -129,6 +131,7 @@ my %reserved_gid_pool;
 
 our @names;
 
+log_trace("ARGV %s", join(@ARGV,"-"));
 GetOptions(
     'add-extra-groups' => \$add_extra_groups,
     'add_extra_groups' => \$add_extra_groups_old,
@@ -162,11 +165,12 @@ GetOptions(
     'verbose' => sub { $verbose = 1; },
     'version|v' => sub { &version; exit },
 ) or &usage_error;
+log_trace("ARGV %s", join(@ARGV,"-"));
 
 if (!@configfiles) {
     @defaults = ("/etc/adduser.conf");
 } else {
-    @defaults = (@configfiles);
+    @defaults = decode($charset, $_) for @configfiles;
 }
 
 # make sure that message levels apply for reading configuration
@@ -175,6 +179,10 @@ $stdoutmsglevel = sanitize_string($stdoutmsglevel);
 $stderrmsglevel = sanitize_string($stderrmsglevel);
 $logmsglevel = sanitize_string($logmsglevel);
 set_msglevel( $stderrmsglevel, $stdoutmsglevel, $logmsglevel );
+log_trace("ARGV %s", join(@ARGV,"-"));
+log_trace("special_home %s", $special_home);
+log_trace("special_home %s", encode($charset, $special_home));
+log_trace("special_home %s", decode($charset, $special_home));
 
 ##########################################################
 # (1) preseed the config
@@ -225,11 +233,15 @@ $ENV{"IFS"}=" \t\n";
 # checks related to @names #
 ############################
 
+log_trace("ARGV %s", join(@ARGV,"-"));
 while (defined(my $arg = shift(@ARGV))) {
-  if (defined($names[0]) && $arg =~ /^--/) {
+    $arg = decode($charset, $arg);
+    log_trace("process argument %s", $arg);
+    if (defined($names[0]) && $arg =~ /^--/) {
         log_fatal( mtx("No options allowed after names.") );
         exit( RET_INVALID_CALL );
     } else {                    # it's a username
+        log_trace("add argument %s to \@names", $arg);
         push (@names, $arg);
     }
 }
@@ -255,6 +267,7 @@ if (@names == 2) {	# must be addusertogroup
 }
 else { # 1 parameter, must be adduser
     $new_name = sanitize_name( shift (@names) );
+    log_trace("sanitized new_name %s", $new_name);
 }
 
 undef(@names);
@@ -282,21 +295,6 @@ if ($action ne "addgroup" &&
 }
 
 
-if ((defined($special_home)) && ($special_home !~ m+^/+ )) {
-  log_fatal( mtx("The home dir must be an absolute path.") );
-  exit( RET_INVALID_HOME_DIRECTORY );
-}
-
-if (defined($special_home)) {
-    if (!defined($no_create_home) && -d $special_home) {
-        log_warn( mtx("The home dir %s you specified already exists.\n"), $special_home );
-    }
-    if (defined($no_create_home) && ! -d $special_home) {
-        log_warn( mtx("The home dir %s you specified can't be accessed: %s\n"), $special_home, $! );
-    }
-}
-
-
 if ($found_group_opt) {
     if ($action eq "addsysuser") {
         $make_group_also = 1;
@@ -324,50 +322,71 @@ if ($config{"gid_pool"}) {
     }
 }
 
-if( defined $new_name ) {
-    $new_name = sanitize_name($new_name);
-}
-
 if( defined $ingroup_name ) {
-    $ingroup_name = sanitize_name($ingroup_name);
+    log_trace("sanitize ingroup_name");
+    $ingroup_name = sanitize_name( decode($charset, $ingroup_name));
 }
 
 if( defined $special_home ) {
-    $special_home = sanitize_string($special_home, pathre);
+    log_trace("sanitize special_home %s", $special_home);
+    $special_home = sanitize_string( decode($charset, $special_home), pathre);
 }
 
 if( defined $special_shell ) {
-    $special_shell = sanitize_string($special_shell, simplepathre);
+    log_trace("sanitize special_shell");
+    $special_shell = sanitize_string( decode($charset, $special_shell), simplepathre);
 }
 
 if( defined $new_comment ) {
-    $new_comment = sanitize_string($new_comment, commentre);
+    log_trace("sanitize new_comment");
+    $new_comment = sanitize_string( decode($charset, $new_comment), commentre);
 }
 
 if( defined $new_firstgid ) {
+    log_trace("sanitize new_firstgid");
     $new_firstgid = sanitize_string($new_firstgid, numberre);
 }
 
 if( defined $new_lastgid ) {
+    log_trace("sanitize new_lastgid");
     $new_lastgid = sanitize_string($new_lastgid, numberre);
 }
 
 if( defined $new_firstuid ) {
+    log_trace("sanitize new_firstuid");
     $new_firstuid = sanitize_string($new_firstuid, numberre);
 }
 
 if( defined $new_lastuid ) {
+    log_trace("sanitize new_lastgud");
     $new_lastuid = sanitize_string($new_lastuid, numberre);
 }
 
 if( defined $new_uid ) {
+    log_trace("sanitize new_uid");
     $new_uid = sanitize_string($new_uid, numberre);
 }
 
 if( defined $gid_option ) {
+    log_trace("sanitize gid_option");
     $gid_option = sanitize_string($gid_option, numberre);
 }
 
+if ((defined($special_home)) && ($special_home !~ m+^/+ )) {
+  log_fatal( mtx("The home dir must be an absolute path.") );
+  exit( RET_INVALID_HOME_DIRECTORY );
+}
+
+if (defined($special_home)) {
+    if (!defined($no_create_home) && -d $special_home) {
+        log_warn( mtx("The home dir %s you specified already exists.\n"), $special_home );
+    }
+    if (defined($no_create_home) && ! -d $special_home) {
+        log_warn( mtx("The home dir %s you specified can't be accessed: %s\n"), $special_home, $! );
+    }
+}
+
+
 $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = 'handler';
 
 #####
@@ -451,7 +470,7 @@ if ($action eq "addsysgroup") {
 ##############
 if ($action eq "addgroup") {
     acquire_lock();
-    if (defined getgrnam($new_name)) {
+    if (defined egetgrnam(encode($charset, $new_name))) {
         log_fatal( mtx("The group `%s' already exists."), $new_name);
         exit( RET_OBJECT_EXISTS );
     }
@@ -493,11 +512,11 @@ if ($action eq "addgroup") {
 ####################
 if ($action eq 'addusertogroup') {
 
-    if (!defined getpwnam($existing_user)) {
+    if (!defined egetpwnam($existing_user)) {
         log_fatal( mtx("The user `%s' does not exist."), $existing_user );
         exit( RET_OBJECT_DOES_NOT_EXIST );
     }
-    if (!defined getgrnam($existing_group)) {
+    if (!defined egetgrnam($existing_group)) {
         log_fatal( mtx("The group `%s' does not exist."), $existing_group );
         exit( RET_OBJECT_DOES_NOT_EXIST );
     }
@@ -524,7 +543,7 @@ if ($action eq "addsysuser") {
     if (existing_user_ok($new_name, $new_uid) == 1) {
 
         # a user with this name already exists; it's a problem when it's not a system user
-        my $tmp_u = getpwnam($new_name);
+        my $tmp_u = egetpwnam($new_name);
         if (($tmp_u >= $config{"first_system_uid"}) and ($tmp_u <= $config{"last_system_uid"})) {
             log_fatal( mtx("The system user `%s' already exists. Exiting.\n"), $new_name );
             exit( RET_OK );
@@ -572,7 +591,7 @@ if ($action eq "addsysuser") {
         if (defined($gid_option)) {
             $ingroup_name = getgrgid($gid_option);
         } elsif ($ingroup_name) {
-            $gid_option = getgrnam($ingroup_name);
+            $gid_option = egetgrnam($ingroup_name);
         } else {
             log_fatal( mtx("Neither ingroup option nor gid given.") );
             exit( RET_NO_PRIMARY_GROUP );
@@ -582,7 +601,7 @@ if ($action eq "addsysuser") {
         if (defined($gid_option)) {
             $ingroup_name = getgrgid($gid_option);
         } elsif ($ingroup_name) {
-            $gid_option = getgrnam($ingroup_name);
+            $gid_option = egetgrnam($ingroup_name);
         } elsif ($make_group_also) {
             $gid_option=$new_uid; $ingroup_name=$new_name;
         } else {
@@ -593,7 +612,7 @@ if ($action eq "addsysuser") {
     log_info( mtx("Adding system user `%s' (UID %d) ..."), $new_name, $new_uid );
 
     # if we reach this point, and the group does already exist, we can use it.
-    if ($make_group_also && !getgrnam($new_name)) {
+    if ($make_group_also && !egetgrnam($new_name)) {
         log_info( mtx("Adding new group `%s' (GID %d) ..."), $new_name, $gid_option );
         $undogroup = $new_name;
         my $groupadd = &which('groupadd');
@@ -662,7 +681,7 @@ if ($action eq "adduser") {
         }
         if( $ingroup_name ) {
             $make_group_also = 0;
-            $primary_gid = getgrnam($ingroup_name);
+            $primary_gid = egetgrnam($ingroup_name);
             log_trace( "ingroup_name defined %s, make_group_also 0, primary_gid %s", $gid_option, $primary_gid );
         }
         log_trace( "make_group_also %s, primary_gid %s", $make_group_also, $primary_gid );
@@ -679,7 +698,7 @@ if ($action eq "adduser") {
                 if (defined($config{"users_group"})) {
                     $primary_group=$config{"users_group"};
                 }
-                $primary_gid=getgrnam($primary_group);
+                $primary_gid=egetgrnam($primary_group);
                 log_debug( "set primary_gid to users_group %s %s", $primary_gid, $primary_group);
             }
         } else {
@@ -702,13 +721,13 @@ if ($action eq "adduser") {
     } else {
         log_debug( "config usergroups != yes code path" );
         if( defined($ingroup_name) ) {
-            $primary_gid=getgrnam($ingroup_name);
+            $primary_gid=egetgrnam($ingroup_name);
         } elsif (defined($config{"users_gid"})) {
             log_trace( "primary_gid = users_gid = %d", $primary_gid );
             $primary_gid = $config{"users_gid"};
         } else {
             if (defined($config{"users_group"})) {
-                my @grgid=getgrnam($config{"users_group"});
+                my @grgid=egetgrnam($config{"users_group"});
                 my $grgid=$grgid[2];
                 log_trace( "primary_gid = users_group %s %s", $config{"users_group"}, $grgid);
                 $primary_gid = $grgid;
@@ -775,7 +794,7 @@ if ($action eq "adduser") {
             $primary_gid = $gid_option;
             log_debug( "gid_option defined and not -1, ingroup_name %s, primary_gid %d", $ingroup_name, $primary_gid );
         } elsif ($ingroup_name) {
-            $primary_gid = getgrnam($ingroup_name);
+            $primary_gid = egetgrnam($ingroup_name);
             log_debug( "ingroup_name defined %s, primary_gid %d", $ingroup_name, $primary_gid );
         } elsif ( defined( $primary_gid ) ) {
             $ingroup_name = getgrgid($primary_gid);
@@ -800,7 +819,7 @@ if ($action eq "adduser") {
             $ingroup_name = getgrgid($primary_gid);
             log_debug( "primary_gid defined %s, ingroup_name %s", $primary_gid, $ingroup_name );
         } elsif ($ingroup_name) {
-            $primary_gid = getgrnam($ingroup_name);
+            $primary_gid = egetgrnam($ingroup_name);
             log_debug( "ingroup_name defined %s, primary_gid %s", $ingroup_name, $primary_gid );
         } elsif ($make_group_also) {
             $primary_gid=$new_uid; $ingroup_name=$new_name;
@@ -822,7 +841,7 @@ if ($action eq "adduser") {
         } else {
            log_info( mtx("Adding new group `%s' (new group ID) ..."), $new_name);
            &systemcall($groupadd, $new_name);
-           $primary_gid = getgrnam($new_name);
+           $primary_gid = egetgrnam($new_name);
            log_info( mtx("new group '%s' created with GID %d"), $new_name, $primary_gid );
         }
     }
@@ -838,7 +857,7 @@ if ($action eq "adduser") {
     } else {
         $shell = $special_shell || $uid_pool{$new_name}{'shell'} || "/usr/sbin/nologin";
     }
-    log_debug( "creating new user with home_dir %s and shell %s", $home_dir, $shell );
+    log_debug( "creating new user %s with home_dir %s and shell %s", $new_name, $home_dir, $shell );
     $undouser = $new_name;
     my $useradd = &which('useradd');
     &systemcall($useradd, '-d', $home_dir, '-g', $primary_gid, '-s',
@@ -913,7 +932,7 @@ if ($action eq "adduser") {
         log_info( mtx("Adding new user `%s' to supplemental / extra groups `%s' ..."), $new_name, join(", ", @supplemental_groups) );
         foreach my $newgrp ( @supplemental_groups ) {
             log_trace( "newgrp %s", $newgrp );
-            if (!defined getgrnam($newgrp)) {
+            if (!defined egetgrnam($newgrp)) {
                 log_warn( mtx("The group `%s' does not exist."), $newgrp);
                 next;
             }
@@ -1056,8 +1075,10 @@ sub mktree {
 #   2 if the user already exists, but $new_uid does not match its uid
 sub existing_user_ok {
     my($new_name,$new_uid) = @_;
+    log_trace( "existing_user_ok called with new_name %s, new_uid %s", $new_name, $new_uid );
     my ($dummy1,$dummy2,$uid);
-    if (($dummy1,$dummy2,$uid) = getpwnam($new_name)) {
+    log_trace( "new_name %s", $new_name);
+    if (($dummy1,$dummy2,$uid) = egetpwnam($new_name)) {
         if( defined($new_uid) && $uid == $new_uid ) {
             return 1;
         }
@@ -1088,7 +1109,7 @@ sub existing_user_ok {
 sub existing_group_ok {
     my($new_name,$new_gid) = @_;
     my ($dummy1,$dummy2,$gid);
-    if (($dummy1,$dummy2,$gid) = getgrnam($new_name)) {
+    if (($dummy1,$dummy2,$gid) = egetgrnam($new_name)) {
 
         # TODO: is this check required? There should not  be any
         # gid outside of our allowed range anyways ...
@@ -1122,7 +1143,7 @@ sub check_user_group {
     my ($system) = @_;
     log_debug( "check_user_group %s called, make_group_also %s", $system, $make_group_also );
     if( !$system || !existing_user_ok($new_name, $new_uid) ) {
-        if( defined getpwnam($new_name) ) {
+        if( defined egetpwnam($new_name) ) {
             if( $system ) {
                 log_fatal( mtx("The user `%s' already exists, and is not a system user."), $new_name);
                 exit( RET_WRONG_OBJECT_PROPERTIES );
@@ -1139,7 +1160,7 @@ sub check_user_group {
     if ($make_group_also) {
         log_trace( "make_group_also 1, new_name %s, new_uid %s", $new_name, $new_uid );
         if( !$system || !existing_group_ok($new_name, $new_uid) ) {
-            if (defined getgrnam($new_name)) {
+            if (defined egetgrnam($new_name)) {
                 log_fatal( mtx("The group `%s' already exists."),$new_name );
                 exit( RET_OBJECT_EXISTS );
             }
@@ -1149,7 +1170,7 @@ sub check_user_group {
             }
         }
     } else {
-        if ($ingroup_name && !defined(getgrnam($ingroup_name))) {
+        if ($ingroup_name && !defined(egetgrnam($ingroup_name))) {
             log_fatal( mtx("The group `%s' does not exist."), $ingroup_name);
             exit( RET_OBJECT_DOES_NOT_EXIST );
         }
@@ -1266,7 +1287,7 @@ sub sanitize_name {
     }
 
     log_trace("sanitize_name testing > 32 chars");
-    if (length $name > 32) {
+    if (length( encode($charset, $name) ) > 32) {
         # this check cannot be turned off
         log_err( mtx("Usernames must be no more than 32 bytes in length;
             note that if you are using Unicode characters, the character
@@ -1311,11 +1332,13 @@ sub sanitize_name {
         exit( RET_INVALID_CHARS_IN_NAME );
     }
 
-    log_trace("sanitize_name checking %s against .*", $name);
-    if ($name =~ qr/(.*)/) {
+    log_trace("sanitize_name checking %s against %s", $name, anynamere);
+    if ($name =~ anynamere) {
         log_trace("sanitize_name passed. returning %s", $1);
         return $1;
     };
+    log_fatal("sanitize_name failed. invalid user name %s", $name);
+    return "";
 }
 
 # first_avail_uid: return the first available uid in given range
@@ -1411,7 +1434,7 @@ sub ch_comment {
 # user is member of group?
 sub user_is_member {
     my($user, $group) = @_;
-    for (split(/ /, (getgrnam($group))[3])) {
+    for (split(/ /, (egetgrnam($group))[3])) {
         return 1 if ($user eq $_);
     }
     return 0;


=====================================
debian/README
=====================================
@@ -1,3 +1,5 @@
+This document was last touched in Februar 2025.
+
 adduser in Debian
 =================
 
@@ -59,10 +61,10 @@ even log in as your user and configure it appropriately.
 
 Adduser's logging subsystem has been re-worked since the release of
 Debian 12. Adduser should now be silent especially in maintainer scripts
-if everthing regarding the new user is reasonably correct. If you want
-report of what adduser does, please consider changing the log levels in
-/etc/adduser.conf. You can set different log levels for the system log,
-for standard output and standard error.
+if everything regarding the new user is reasonably correct. If you want
+adduser to report what it does, please consider changing the log levels
+in /etc/adduser.conf. You can set different log levels for the system
+log, for standard output and standard error.
 
 It might be advisable to not delete your users on purge of your package
 since there might still be files belonging to the user. Instead, lock
@@ -105,24 +107,25 @@ find an explanation to only give these limited choices in debconf. To
 be consistent, we would have to add many more questions. This does not
 seem to be a realistic endeavor given the available personpower.
 
-Consequently, debconf support was removed complete, making
+Consequently, debconf support was removed completely, making
 /etc/adduser.conf a regular dpkg-conffile.
 
 We might be willing to reintroduce more elaborate debconf support in the
 future if somebody volunteers to write the code, documentation and
 tests, and to actually maintain it for the foreseeable future. Please
-get in touch with the adduser team if you're willing to help.
+get in touch with the adduser team if you're willing to help and join
+the team.
 
 
 
 Why not declarative?
 --------------------
-Adduser is intended to be used in maintainer scripts. While there are
+Adduser is intended to be used in maintainer scripts. There are
 approaches in Debian to move more and more functionality away from
-maintainer scripts to a more declarative approach. The current form of
-adduser is used in hundreds of packages.
+maintainer scripts to a more declarative approach. However, the current
+form of adduser is used in hundreds of packages.
 
-It is fine to use a more declarative approach to define system users in
+It is fine to use the declarative approach to define system users in
 Debian packages. There is nothing that forces package maintainers to use
 adduser to create their package-related users. The packages dh-sysuser,
 opensysusers, and systemd-sysusers already offer a declarative approach
@@ -140,11 +143,10 @@ allowing adduser to be used to create users in a directory service such
 as NIS, NIS+ and LDAP, for more than two decades. It is not realistic to
 expect this to happen any time soon.
 
-For the time being, any support to have adduser write to directory
-services that might still exist in the code is official deprecated. An
-arbitrary server in an organization that uses a directory service is
-unlikely to have enough privileges to write to a directory service
-anyway.
+Adduser does not support writing to directory services. The only
+interface to any user database is the use of the tools that the passwd
+package offers. Please file a bug if you find any code that still tries
+to support directory services. We might have forgotten to remove it.
 
 It might be possible, to add the desired support via an adduser.local
 hook. If you need more hooks to locally implement what you need, let us
@@ -154,7 +156,8 @@ might be implemented in the nearer future.
 We might be willing to reintroduce support for directory services in the
 future if somebody volunteers to write the code, documentation and
 tests, and to actually maintain it for the foreseeable future. Please
-get in touch with the adduser team if you're willing to help.
+get in touch with the adduser team if you're willing to help and join
+the team.
 
 
 
@@ -179,29 +182,31 @@ has oscillated back and forth in adduser multiple times since the 1990s,
 because both ways to set this bit by default have advantages and
 disadvantages.  After a preliminary request for comment (see
 https://lists.debian.org/debian-devel/2022/03/msg00098.html), the
-default value for DIR_MODE was changed to 2700 in Debian 12.  Sadly,
-though the technical reasoning for NOT setting the bit did largely not
-survive the last two decades, some use cases that we were not fully
-aware of were adversely impacted by this change.
+default value for DIR_MODE was eventually changed to 2700 at some point.
+Sadly, the technical reasoning for NOT setting the bit did largely not
+survive the last two decades. However, some use cases that we were not
+fully aware of were adversely impacted by this change.
 
 Promptly, #1014901 was filed, requesting that DIR_MODE be changed to
 0700, effectively causing home directories of non-system users to be
-created without the setgid bit. The biggest point in the reasoning is that
-having the setgid bit set will need special measures to keep the home
-directory's group ownership from propagating to file system images,
+created without the setgid bit. The biggest point in the reasoning is
+that having the setgid bit set will need special measures to keep the
+home directory's group ownership from propagating to file system images,
 chroots, and archives, causing wrong file ownership/permissions in those
-entities, which in turn might propagate to different systems and cause
-security-related effects there.  The bug report gives instructions to
-reproduce the behavior.
-
-System administrators who run multi-user environments which require
-shared workspaces have tools at their disposal to change the default
-behavior as their individual needs require, and likely are aware of how
-to work around any issues that arise as part of that configuration; it
-is also very possible that such systems may be managed using
-configuration management software. In an age of general purpose use on
-one end, and single purpose containers on the other, this is unlikely to
-be the majority of newly installed systems.
+collections of files, which in turn might propagate to different systems
+and cause security-related effects there. The bug report gives
+instructions to reproduce the behavior.
+
+System administrators who run multi-user environments might appreciate
+the sgid bit set on home directories since this makes it easier to
+manage file ownership in collaborative environments such as shared
+workspaces. Those administrators have tools at their disposal to change
+the default behavior as their individual needs require, and likely are
+aware of how to work around any issues that arise as part of that
+configuration. It is also very possible that such systems may be managed
+using configuration management software. In an age of general purpose
+use on one end, and single purpose containers on the other, this is
+unlikely to be the majority of newly installed systems.
 
 So what remains is the decision to provide a sane default for a system
 that is installed by an end-user, who may not understand or be aware of
@@ -218,7 +223,7 @@ flipping the default for the setgid bit one last time to the value we
 had for the majority of Debian's existence period. With this change,
 Debian is re-joining ranks again with ALL other major Linux
 distributions, none of which setting the setgid bit on home directories
-to 1 (research done in July 2022).
+to 1 (research done in July 2022, prove us wrong today if you want to).
 
 This primarily affects the one user that can be created in the Installer
 before there is any possibility to configure adduser. Those users will
@@ -236,12 +241,24 @@ discussion period.
 
 
 
+UTF-8 User Names
+----------------
+It used to be possible to create UTF-8 encoded user names with adduser
+and passwd. While you have possibly considered this a feeature, it never
+was intended to be a feature, just an accident. Debian had a discussion
+about UTF-8 user names in November 2024, while shadow/passwd upstream
+decided to explicitly disallow non-ASCII characters in user names. This
+version of adduser follows along, so UTF-8 encoded user names cannot be
+created any more with adduser. It never fully worked anyway, and
+introuced some nasty side effects, possibly security relevant. So you
+might want to revisit any non-ASCII user names you have ever created.
+
+
+
 Security
 --------
-We habe beem working on adduser being able to run with the -T switch
-set. This has made it impossible to test for strange unicode characters.
-If you know how to write a perl regexp that includes possibly all
-printable unicode characters, please file a bug and help us.
+Adduser now runs with perl in tainted mode. This has introduced a
+significant raise in security.
 
 
 
@@ -249,7 +266,8 @@ Rewrite
 -------
 adduser needs a rewrite. The code base was started in the 1990s and
 this fact can easily be seen. The introduction of our test suite has
-made changes to adduser much easier, but the code is still 25 years old.
+made changes to adduser much easier, but the code is still more than 25
+years old.
 
 We support any effort for a rewrite, but we strongly believe that this
 should be done in a dedicated project with a new name, such as
@@ -290,6 +308,18 @@ characters per level, using spaces, not tabs.
 
 
 
+More Docs?
+----------
+See:
+  * RFC8264 "PRECIS Framework: Preparation, Enforcement, and Comparison of
+             Internationalized Strings in Application Protocols"
+  * RFC8265 "PRECIS Representing Usernames and Passwords"
+  * https://systemd.io/USER_NAMES/
+  * https://wiki.debian.org/UserAccounts
+  * https://wiki.debian.org/Unicode
+
+
+
 Credits
 -------
 This document was compiled by the adduser maintainers. Ximon Eighteen


=====================================
debian/control
=====================================
@@ -14,7 +14,7 @@ Package: adduser
 Architecture: all
 Multi-Arch: foreign
 Pre-Depends: ${misc:Pre-Depends}
-Depends: passwd, ${misc:Depends}
+Depends: passwd (>= 1:4.17.2-5), ${misc:Depends}
 Suggests: liblocale-gettext-perl, perl, cron, quota
 Description: add and remove users and groups
  This package includes the 'adduser' and 'deluser' commands for creating


=====================================
debian/rules
=====================================
@@ -17,9 +17,6 @@ override_dh_auto_clean:
 	cd doc/po4a && po4a --keep 95 --previous --rm-translations po4a.conf
 	rm -f po/*.mo
 
-override_dh_auto_install:
-	$(MAKE) -C po install DESTDIR=`pwd`/debian/adduser
-
 override_dh_install:
 	dh_install
 	sed -e s/DVERSION/$(version)/g adduser > debian/adduser/usr/sbin/adduser


=====================================
debian/tests/f/cronjack.t
=====================================
@@ -15,7 +15,7 @@ END {
     remove_tree('/home/bob');
 }
 
-assert_command_success('/usr/sbin/useradd', '--badname', '-d', '/home/bob', '-m', 'bob;>/hacked');
+assert_command_success('sh', '-c', q{/usr/sbin/useradd --badname -d /home/bob -m 'bob;>/hacked' 2>/dev/null});
 
 assert_path_does_not_exist('/hacked');
 


=====================================
debian/tests/f/deluser_files.t
=====================================
@@ -13,8 +13,8 @@ sub create_files_in_homedir{
     mkdir ("/home/$acct/mnt", 0777);
     mkdir ("/home/$acct/dir", 0777);
     mkdir ("/tmp/$acct", 0777);
-    unlink("/tmp/foo.txt");
-    for ("/home/$acct/extra.txt", "/tmp/$acct/extra2.txt", "/tmp/foo.txt") {
+    unlink("/tmp/deluserfiles.txt");
+    for ("/home/$acct/extra.txt", "/tmp/$acct/extra2.txt", "/tmp/deluserfiles.txt") {
         open (XTRA, '>', $_) || die ("could not open file $_: $!");
         print XTRA "extra file";
         close (XTRA) || die ('could not close file!');
@@ -26,31 +26,31 @@ sub create_files_in_homedir{
         "/tmp/$acct/extra2.txt",
         "/home/$acct/mnt",
         "/home/$acct/dir",
-        "/tmp/foo.txt",
+        "/tmp/deluserfiles.txt",
         "/home/$acct/pipe");
     assert_command_success('mount','-o','bind',"/tmp/$acct","/home/$acct/mnt");
 }
 
 END {
-    # remove_tree('/home/foo');
-    # remove_tree('/var/mail/foo');
-    system("umount /home/foo-extra/mnt >/dev/null 2>/dev/null");
-    remove_tree('/home/foo-extra');
-    remove_tree('/tmp/foo-extra');
-    unlink('/tmp/foo.tar.gz'); 
-    unlink('/tmp/foo.txt');
+    # remove_tree('/home/deluserfiles');
+    # remove_tree('/var/mail/deluserfiles');
+    system("umount /home/deluserfiles-extra/mnt >/dev/null 2>/dev/null");
+    remove_tree('/home/deluserfiles-extra');
+    remove_tree('/tmp/deluserfiles-extra');
+    unlink('/tmp/deluserfiles.tar.gz'); 
+    unlink('/tmp/deluserfiles.txt');
 }
 
-assert_user_does_not_exist('foo');
+assert_user_does_not_exist('deluserfiles');
 assert_command_success('/usr/sbin/adduser',
     '--stdoutmsglevel=error', '--stderrmsglevel=error',
     '--system', 
-    '--home', '/home/foo',
-    'foo');
-assert_user_exists('foo');
+    '--home', '/home/deluserfiles',
+    'deluserfiles');
+assert_user_exists('deluserfiles');
 
-my ($login, $pass, $uid, $gid) = getpwnam('foo');
-create_files_in_homedir("foo-extra", $uid, $gid);
+my ($login, $pass, $uid, $gid) = getpwnam('deluserfiles');
+create_files_in_homedir("deluserfiles-extra", $uid, $gid);
 
 assert_command_success('/usr/sbin/deluser',
     '--stdoutmsglevel=error', '--stderrmsglevel=error',
@@ -58,74 +58,78 @@ assert_command_success('/usr/sbin/deluser',
     '--backup-suffix', 'gz',
     '--remove-all-files',
     '--backup-to', '/tmp',
-    'foo');
-system("umount /home/foo-extra/mnt >/dev/null 2>/dev/null");
-assert_user_does_not_exist('foo');
-assert_path_does_not_exist('/home/foo');
-assert_path_does_not_exist('/home/foo-extra/extra.txt');
-assert_path_does_not_exist('/home/foo-extra/pipe');
+    'deluserfiles');
+system("umount /home/deluserfiles-extra/mnt >/dev/null 2>/dev/null");
+assert_user_does_not_exist('deluserfiles');
+assert_path_does_not_exist('/home/deluserfiles');
+assert_path_does_not_exist('/home/deluserfiles-extra/extra.txt');
+assert_path_does_not_exist('/home/deluserfiles-extra/pipe');
 #FIXME
-#assert_path_does_not_exist('/tmp/foo-extra/extra2.txt');
-assert_path_exists('/tmp/foo.txt');
-assert_path_exists('/home/foo-extra/mnt');
-assert_path_does_not_exist('/home/foo-extra/dir');
+#assert_path_does_not_exist('/tmp/deluserfiles-extra/extra2.txt');
+assert_path_exists('/tmp/deluserfiles.txt');
+assert_path_exists('/home/deluserfiles-extra/mnt');
+assert_path_does_not_exist('/home/deluserfiles-extra/dir');
 
 # check backup archive
-assert_path_exists('/tmp/foo.tar.gz');
-my $tar_files = `tar tf /tmp/foo.tar.gz`;
+assert_path_exists('/tmp/deluserfiles.tar.gz');
+my $tar_files = `tar tf /tmp/deluserfiles.tar.gz`;
 is($? >> 8, 0, 'successfully listed backup files');
-like($tar_files, qr{home/foo-extra/extra.txt}, 'archive contains expected file: extra.txt');
+like($tar_files, qr{home/deluserfiles-extra/extra.txt}, 'archive contains expected file: extra.txt');
 
+# create new user and delete again, backing up to /nonexistent
+# this succeeds when there are no files to back up
 assert_command_success('/usr/sbin/adduser',
     '--stdoutmsglevel=error', '--stderrmsglevel=error',
     '--system', 
-    '--home', '/home/foo',
-    'foo');
-assert_user_exists('foo', $uid, $gid);
+    '--home', '/home/deluserfiles',
+    'deluserfiles');
+assert_user_exists('deluserfiles', $uid, $gid);
 assert_command_success('/usr/sbin/deluser',
     '--stdoutmsglevel=error', '--stderrmsglevel=error',
     '--system',
     '--remove-all-files',
     '--backup-to', '/nonexistent',
-    'foo');
+    'deluserfiles');
+# create new user, put files in and delete again, backing up to /nonexistent
+# this must fail
 assert_command_success('/usr/sbin/adduser',
     '--stdoutmsglevel=error', '--stderrmsglevel=error',
     '--system', 
-    '--home', '/home/foo',
-    'foo');
-assert_user_exists('foo', $uid, $gid);
-($login, $pass, $uid, $gid) = getpwnam('foo');
-create_files_in_homedir("foo-extra", $uid, $gid);
+    '--home', '/home/deluserfiles',
+    'deluserfiles');
+assert_user_exists('deluserfiles', $uid, $gid);
+($login, $pass, $uid, $gid) = getpwnam('deluserfiles');
+create_files_in_homedir("deluserfiles-extra", $uid, $gid);
 assert_command_failure_silent('/usr/sbin/deluser',
     '--stdoutmsglevel=error', '--stderrmsglevel=error',
     '--system',
     '--remove-all-files',
     '--backup-to', '/nonexistent',
-    'foo');
-assert_user_exists('foo');
-assert_path_exists('/home/foo');
-assert_path_exists('/home/foo-extra/extra.txt');
-assert_path_exists('/home/foo-extra/pipe');
+    'deluserfiles');
+assert_user_exists('deluserfiles');
+assert_path_exists('/home/deluserfiles');
+assert_path_exists('/home/deluserfiles-extra/extra.txt');
+assert_path_exists('/home/deluserfiles-extra/pipe');
 #FIXME
-#assert_path_does_not_exist('/tmp/foo-extra/extra2.txt');
-assert_path_exists('/tmp/foo.txt');
-assert_path_exists('/home/foo-extra/mnt');
-assert_path_exists('/home/foo-extra/dir');
+#assert_path_does_not_exist('/tmp/deluserfiles-extra/extra2.txt');
+assert_path_exists('/tmp/deluserfiles.txt');
+assert_path_exists('/home/deluserfiles-extra/mnt');
+assert_path_exists('/home/deluserfiles-extra/dir');
 assert_command_success('/usr/sbin/deluser',
     '--stdoutmsglevel=error', '--stderrmsglevel=error',
     '--system',
     '--remove-all-files',
     '--backup-to', '/tmp',
-    'foo');
-system("umount /home/foo-extra/mnt >/dev/null 2>/dev/null");
-assert_user_does_not_exist('foo');
-assert_path_does_not_exist('/home/foo');
-assert_path_does_not_exist('/home/foo-extra/extra.txt');
-assert_path_does_not_exist('/home/foo-extra/pipe');
+    'deluserfiles');
+system("umount /home/deluserfiles-extra/mnt >/dev/null 2>/dev/null");
+assert_user_does_not_exist('deluserfiles');
+assert_path_does_not_exist('/home/deluserfiles');
+assert_path_does_not_exist('/home/deluserfiles-extra/extra.txt');
+assert_path_does_not_exist('/home/deluserfiles-extra/pipe');
 #FIXME
-#assert_path_does_not_exist('/tmp/foo-extra/extra2.txt');
-assert_path_exists('/tmp/foo.txt');
-assert_path_exists('/home/foo-extra/mnt');
-assert_path_does_not_exist('/home/foo-extra/dir');
+#assert_path_does_not_exist('/tmp/deluserfiles-extra/extra2.txt');
+assert_path_exists('/tmp/deluserfiles.txt');
+assert_path_exists('/home/deluserfiles-extra/mnt');
+assert_path_does_not_exist('/home/deluserfiles-extra/dir');
 
 # vim: tabstop=4 shiftwidth=4 expandtab


=====================================
debian/tests/f/valid_username.t
=====================================
@@ -5,8 +5,16 @@
 use diagnostics;
 use strict;
 use warnings;
+use utf8;
+use Encode;
+use I18N::Langinfo qw(langinfo CODESET);
+
+my $charset = langinfo(CODESET);
+binmode(STDOUT, ":encoding($charset)");
+binmode(STDERR, ":encoding($charset)");
 
 use AdduserTestsCommon;
+ok("$charset", "charset $charset");
 
 #       from the useradd manual
 #       --
@@ -65,26 +73,34 @@ test_name('user$', PASS,
 test_name('_', PASS,
             $pat{ieee}, PASS,
             $pat{min}, PASS);
+# directory traversal
+test_name('../bin/foo', FAIL,
+            $pat{ieee}, FAIL,
+            $pat{min}, FAIL);
 # this test is to see what backslash does
 # unfortunately unpredictable
-test_name("\}a", FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, PASS|ALL,
-            $pat{min}, PASS);
+# useradd 4.17.2 doesn't accept } any more
+test_name("\}a", FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL,
+            $pat{min}, FAIL);
 test_name("\\}b", FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL,
             $pat{min}, FAIL);
 # windows conventions
 test_name('machine$', PASS,
             $pat{ieee}, PASS);
-# useradd doesnt accept \ at this time, #1074306
-#test_name('DOMAIN\user', FAIL, FAIL|BAD, FAIL|FBAD, FAIL|ABAD, PASS|ALL,
-#         $pat{min}, PASS);
-test_name('user at email.example.com', FAIL, PASS|BAD, PASS|FBAD, PASS|ABAD, PASS|ALL,
-            $pat{min}, PASS);
+# useradd 4.17.2 doesnt accept \ any more
+test_name('DOMAIN\user', FAIL, FAIL|BAD, FAIL|FBAD, FAIL|ABAD, FAIL|ALL,
+         $pat{min}, FAIL);
+
+# useradd 4.17.2 doesn't accept @ any more
+test_name('user at email.example.com', FAIL, FAIL|BAD, FAIL|FBAD, FAIL|ABAD, FAIL|ALL,
+            $pat{min}, FAIL);
 
 # test alternate escaping
-test_name("'duke'", FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, PASS|ALL);
+# useradd 4.17.2 does not accept ' any more
+test_name("'duke'", FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL);
 # the worst username possible
-# useradd doesn't accept that any more, #1086225
-#test_name("'c4\$h\\M0n3y\"'==>@", FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, PASS|ALL);
+# useradd doesn't accept that any more
+test_name("'c4\$h\\M0n3y\"'==>@", FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL);
 
 # should always fail any regex
 my @fails = ('12345', 'root:root', 'test space', "test\nwhite\tspace",
@@ -92,20 +108,26 @@ my @fails = ('12345', 'root:root', 'test space', "test\nwhite\tspace",
 test_name($_, FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL,
             $pat{all}, FAIL, FAIL|ALL) for @fails;
 
-# imho this should continue to fail; it may need
+# #1086785 imho this should continue to fail; it may need
 # special casing in creating home dirs if allowed
 test_name("test/slash", FAIL,
             $pat{min}, FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL);
 
 # some stranger choices
-test_name('*',          FAIL|BAD, FAIL|ABAD, FAIL|FBAD, PASS|ALL, $pat{min}, PASS);
-test_name('3>6',  FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, PASS|ALL, $pat{min}, PASS);
+# useradd 4.17.2 does not acccept *, > and % any more
+test_name('*',          FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL, $pat{min}, FAIL);
+test_name('3>6',  FAIL, FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL, $pat{min}, FAIL);
 test_name('.com', FAIL, PASS|BAD, PASS|ABAD, PASS|FBAD, PASS|ALL, $pat{min}, PASS);
-test_name('user%',      FAIL|BAD, FAIL|ABAD, FAIL|FBAD, PASS|ALL, $pat{min}, PASS);
+test_name('user%',      FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL, $pat{min}, FAIL);
 # comment those because I don't know how to include unicode characters
 # in a perl regexp. If you know how to do this, please file a bug.
-# test_name("ÿar",        FAIL|BAD, FAIL|ABAD, FAIL|FBAD, PASS|ALL, $pat{min}, PASS);
-# test_name('˄ʙɄȘ˳',      FAIL|BAD, FAIL|ABAD, FAIL|FBAD, PASS|ALL, $pat{min}, PASS);
+# useradd 4.17.2 does not accept Unicode any more
+test_name("ÿar",        FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL, $pat{min}, FAIL);
+test_name('˄ʙɄȘ˳',      FAIL|BAD, FAIL|ABAD, FAIL|FBAD, FAIL|ALL, $pat{min}, FAIL);
+# how would I test for invalid UTF-8?
+# perl -e 'print "\xc0\n";' | tee /dev/fd/2 | hexdump
+# 0xFFFE is not supposed to be in input streams, 0xFEFF is BOM which we dont accept
+# \N{WHITE SMILING FACE}
 
 # system users with leading underscores
 test_name('_foo', PASS);
@@ -130,7 +152,7 @@ sub mode_string {
 # The mode defaults to FAIL. The regex defaults to the adduser default for
 # the SYS_NAME_REGEX setting.
 sub test_name {
-    my $username = shift;
+    my $username = Encode::encode($charset, shift);
     my $regex = (@_ && $_[0] !~ /^\d+$/) ? shift : undef;
     my $username_esc = $username;
     my $homedir = qq{/home/$username};


=====================================
debian/tests/lib/AdduserTestsCommon.pm
=====================================
@@ -1,7 +1,9 @@
 use diagnostics;
 use strict;
 use warnings;
-
+use Encode;
+use utf8;
+use I18N::Langinfo qw(langinfo CODESET);
 use File::Path qw(remove_tree);
 use Test::More qw(no_plan);
 
@@ -28,6 +30,22 @@ END {
     if (-f '/var/cache/adduser/tests/state.tar');
 }
 
+my $charset = langinfo(CODESET);
+binmode(STDOUT, ":encoding($charset)");
+binmode(STDERR, ":encoding($charset)");
+
+sub egetgrnam {
+    my ($name) = @_;
+    $name = encode($charset, $name);
+    return getgrnam($name);
+}
+
+sub egetpwnam {
+    my ($name) = @_;
+    $name = encode($charset, $name);
+    return getpwnam($name);
+}
+
 sub assert_command_success {
     system(@_);
     is($? >> 8, 0, "command success: @_");
@@ -59,7 +77,7 @@ sub assert_command_match_output {
 
 sub assert_group_does_not_exist {
     my $group = shift;
-    is(getgrnam($group), undef, "group does not exist: $group");
+    is(egetgrnam($group), undef, "group does not exist: $group");
 }
 
 sub assert_gid_does_not_exist {
@@ -69,7 +87,7 @@ sub assert_gid_does_not_exist {
 
 sub assert_group_exists {
     my $group = shift;
-    isnt(getgrnam($group), undef, "group exists: $group");
+    isnt(egetgrnam($group), undef, "group exists: $group");
 }
 
 sub assert_gid_exists {
@@ -85,8 +103,8 @@ sub assert_group_gid_exists {
 sub group_gid_exists {
     my ($group, $gid) = @_;
 
-    isnt(getgrnam($group), undef, "group exists: $group");
-    my @group_info = getgrnam($group);
+    isnt(egetgrnam($group), undef, "group exists: $group");
+    my @group_info = egetgrnam($group);
 
     if (defined($group_info[2])) {
         return 1 if $group_info[2] == $gid;
@@ -108,8 +126,8 @@ sub assert_group_membership_exists {
 sub group_membership_exists {
     my ($user, $group) = @_;
 
-    my @user_info = getpwnam($user);
-    my @group_info = getgrnam($group);
+    my @user_info = egetpwnam($user);
+    my @group_info = egetgrnam($group);
 
     if (defined($user_info[3]) && defined($group_info[2])) {
         return 1 if $user_info[3] == $group_info[2];
@@ -132,8 +150,8 @@ sub assert_primary_group_membership_exists {
 sub primary_group_membership_exists {
     my ($user, $group) = @_;
 
-    my @user_info = getpwnam($user);
-    my @group_info = getgrnam($group);
+    my @user_info = egetpwnam($user);
+    my @group_info = egetgrnam($group);
 
     if (defined($user_info[3]) && defined($group_info[2])) {
         return 1 if $user_info[3] == $group_info[2];
@@ -155,7 +173,7 @@ sub assert_supplementary_group_membership_does_not_exist {
 sub supplementary_group_membership_exists {
     my ($user, $group) = @_;
 
-    my @group_info = getgrnam($group);
+    my @group_info = egetgrnam($group);
 
     if (defined($group_info[3])) {
         foreach (split(/ /, $group_info[3])) {
@@ -239,12 +257,12 @@ sub assert_path_is_an_empty_directory {
 
 sub assert_user_does_not_exist {
     my $user = shift;
-    is(getpwnam($user), undef, "user does not exist: $user");
+    is(egetpwnam($user), undef, "user does not exist: $user");
 }
 
 sub assert_user_exists {
     my $user = shift;
-    isnt(getpwnam($user), undef, "user exists: $user");
+    isnt(egetpwnam($user), undef, "user exists: $user");
 }
 
 sub assert_uid_does_not_exist {
@@ -265,8 +283,8 @@ sub assert_user_uid_exists {
 sub user_uid_exists {
     my ($user, $uid) = @_;
 
-    isnt(getpwnam($user), undef, "user exists: $user");
-    my @user_info = getpwnam($user);
+    isnt(egetpwnam($user), undef, "user exists: $user");
+    my @user_info = egetpwnam($user);
 
     if (defined($user_info[2])) {
         return 1 if $user_info[2] == $uid;
@@ -299,13 +317,13 @@ sub assert_user_has_disabled_password {
 
 sub assert_user_has_home_directory {
     my ($user, $home) = @_;
-    is((getpwnam($user))[7], $home, "user has home directory: ~$user is $home");
+    is((egetpwnam($user))[7], $home, "user has home directory: ~$user is $home");
 }
 
 sub assert_user_has_comment {
     my ($user, $comment) = @_;
     $comment .= ',,,';
-    is((getpwnam($user))[6], $comment, "user has comment: ~$user is $comment");
+    is((egetpwnam($user))[6], $comment, "user has comment: ~$user is $comment");
 }
 
 sub assert_dir_group_owner {
@@ -316,15 +334,15 @@ sub assert_dir_group_owner {
 
 sub assert_user_has_login_shell {
     my ($user, $shell) = @_;
-    is((getpwnam($user))[8], $shell, "user has login shell: $user runs $shell");
+    is((egetpwnam($user))[8], $shell, "user has login shell: $user runs $shell");
 }
 
 sub assert_user_has_uid {
     my ($user, $uid) = @_;
-    if (getpwnam($user)) {
-        my @pwnam=getpwnam($user);
+    if (egetpwnam($user)) {
+        my @pwnam=egetpwnam($user);
         my $isuid=$pwnam[2];
-        is(getpwnam($user), $uid, "user has uid: uid of $user is $isuid (expected $uid)");
+        is(egetpwnam($user), $uid, "user has uid: uid of $user is $isuid (expected $uid)");
     } else {
         fail( "user has uid: user $user does not exist" );
     }
@@ -332,10 +350,10 @@ sub assert_user_has_uid {
 
 sub assert_group_has_gid {
     my ($group, $gid) = @_;
-    if (getgrnam($group)) {
-        my @grnam=getgrnam($group);
+    if (egetgrnam($group)) {
+        my @grnam=egetgrnam($group);
         my $isgid=$grnam[2];
-        is(getgrnam($group), $gid, "group has gid: gid of $group is $isgid (expected $gid)");
+        is(egetgrnam($group), $gid, "group has gid: gid of $group is $isgid (expected $gid)");
     } else {
         fail( "group has gid: group $group does not exist" );
     }


=====================================
deluser
=====================================
@@ -20,9 +20,10 @@
 #
 # License: GPL-2+
 
-use 5.32.0;
-use strict;
-use warnings;
+use 5.40.0;
+use utf8;
+use Encode;
+use I18N::Langinfo qw(langinfo CODESET);
 
 use Getopt::Long;
 
@@ -38,7 +39,6 @@ BEGIN {
 }
 
 my $version = "DVERSION";
-$ENV{"PATH"} = sanitize_string( $ENV{"PATH"}, qr/[-_a-zA-Z0-9_:.\/]*/ );
 
 my $install_more_packages;
 
@@ -64,7 +64,7 @@ BEGIN {
     ## no critic
     eval "
         require POSIX;
-        import POSIX qw(setlocale);
+        POSIX->import(qw(setlocale));
     ";
     ## use critic
     if ($@) {
@@ -74,6 +74,10 @@ BEGIN {
     }
 }
 
+my $charset = langinfo(CODESET);
+binmode(STDOUT, ":encoding($charset)");
+binmode(STDERR, ":encoding($charset)");
+
 our $action;
 our $verbose;
 our $stdoutmsglevel = "warn";
@@ -113,7 +117,7 @@ GetOptions (
 if (!@configfiles) {
     @defaults = ("/etc/adduser.conf", "/etc/deluser.conf");
 } else {
-    @defaults = (@configfiles);
+    @defaults = decode($charset, $_) for @configfiles;
 }
 
 # make sure that message levels apply for reading configuration
@@ -157,8 +161,6 @@ if( defined $verbose ) {
 # detect the operation mode
 $action = $0 =~ /delgroup$/ ? "delgroup" : "deluser";
 
-log_trace("action $action");
-
 # explicitly set PATH, because super (1) cleans up the path and makes deluser unusable;
 # this is also a good idea for sudo (which doesn't clean up)
 $ENV{"PATH"}="/bin:/usr/bin:/sbin:/usr/sbin";
@@ -169,7 +171,9 @@ $ENV{"IFS"}=" \t\n";
 ############################
 
 while (defined(my $arg = shift(@ARGV))) {
-  if (defined($names[0]) && $arg =~ /^--/) {
+    $arg = decode($charset, $arg);
+    log_trace("process names %s", $arg);
+    if (defined($names[0]) && $arg =~ /^--/) {
         log_fatal( mtx("No options allowed after names.") );
         exit( RET_INVALID_CALL );
     } else {                    # it's a username
@@ -195,6 +199,8 @@ if(@names == 2) {      # must be deluserfromgroup
     }
 }
 
+log_trace("action $action");
+
 undef(@names);
 
 # TODO: map this to log levels
@@ -202,7 +208,7 @@ $ENV{"VERBOSE"} = $verbose;
 $ENV{"DEBUG"}   = $verbose;
 
 foreach(keys(%pconfig)) {
-    $config{$_} = $pconfig{$_} if ($pconfig{$_});
+    $config{$_} = decode($charset, $pconfig{$_}) if ($pconfig{$_});
 }
 
 if (($config{remove_home} || $config{remove_all_files} || $config{backup}) && ($install_more_packages)) {
@@ -211,20 +217,17 @@ if (($config{remove_home} || $config{remove_all_files} || $config{backup}) && ($
 }
 
 
-my ($pw_uid, $pw_gid, $pw_homedir, $gr_gid, $maingroup);
+my ($name, $passwd, $pw_uid, $pw_gid, $quota, $comment, $gcos, $pw_homedir, $shell, $expire, $maingroup);
 
 if(defined($user)) {
-    my @passwd = getpwnam($user);
-    $pw_uid = $passwd[2];
-    $pw_gid = $passwd[3];
-    $pw_homedir = $passwd[7];
+    ($name, $passwd, $pw_uid, $pw_gid, $quota, $comment, $gcos, $pw_homedir, $shell, $expire) = egetpwnam(encode($charset, $user));
+    $pw_homedir = encode($charset, $pw_homedir);
 
-    $maingroup = $pw_gid ? getgrgid($pw_gid) : "";
+    $maingroup = $pw_gid ? decode($charset, getgrgid($pw_gid)) : "";
 }
+log_trace("from getpwnam: pw_homedir %s, maingroup %s", $pw_homedir, $maingroup);
 if(defined($group)) {
-    #($gr_name,$gr_passwd,$gr_gid,$gr_members) = getgrnam($group);
-    my @group = getgrnam($group);
-    $gr_gid = $group[2];
+    my ($name, $passwd, $uid, $gr_gid, $quota, $comment, $gcos, $dir, $shell, $expire) = egetgrnam(encode($charset, $group));
 }
 
 # arguments are processed:
@@ -241,13 +244,13 @@ if(defined($group)) {
 
 
 if($action eq "deluser") {
-    my($dummy1,$dummy2,$uid);
+    my($name, $passwd, ,$uid, $rest);
 
     # Don't allow a non-system user to be deleted when --system is given
     # Also, "user does not exist" is only a warning with --system, but an
     # error without --system.
     if( $config{"system"} ) {
-        if( ($dummy1,$dummy2,$uid) = getpwnam($user) ) {
+        if( ($name, $passwd, $uid, $rest) = egetpwnam(encode($charset, $user)) ) {
             if ( ($uid < $config{"first_system_uid"} ||
                 $uid > $config{"last_system_uid" } ) ) {
                 log_warn( mtx("The user `%s' is not a system user. Exiting."), $user);
@@ -273,10 +276,12 @@ if($action eq "deluser") {
     # consistency check
     # if --backup-to is specified, --backup should be set too
     if ($pconfig{"backup_to"}) {
+        log_trace("backup_to is set, setting backup as well");
         $config{"backup"} = 1;
     }
 
     if($config{"remove_home"} || $config{"remove_all_files"}) {
+        log_trace( mtx("remove_home or remove_all_files beginning") );
         log_info( mtx("Looking for files to backup/remove ...") );
         my @mountpoints;
         my $exclude_fstypes = $config{"exclude_fstypes"};
@@ -321,7 +326,7 @@ if($action eq "deluser") {
             sub find_match {
                 my ($dev,$ino,$mode,$nlink,$uid,$gid) = lstat;
                 log_trace("find_match %s", $_);
-                my $name = sanitize_string( $File::Find::name, simplepathre );
+                my $name = sanitize_string( decode($charset, $File::Find::name), pathre );
                 log_trace("find_match sanitized %s", $name);
                 foreach my $mount (@mountpoints) {
                     if( $name eq $mount ) {
@@ -337,7 +342,7 @@ if($action eq "deluser") {
                       return;
                     }
                 }
-                log_trace("uid %s, pw_uid %s", $uid, $pw_uid);
+                log_trace("name %s, uid %s, pw_uid %s", $name, $uid, $pw_uid);
                 (defined($uid) && ($uid == $pw_uid)) &&
                     (
                         (-f $name && push(@files, $name)) ||
@@ -346,7 +351,6 @@ if($action eq "deluser") {
                         (-S $name && push(@files, $name)) ||
                         (-p $name && push(@files, $name))
                 );
-                log_trace("names: %s", $#names);
                 if ( -b $name || -c $name ) {
                     log_warn( mtx("Cannot handle special file %s"), $name );
                 }
@@ -355,9 +359,10 @@ if($action eq "deluser") {
             File::Find::find({wanted => \&find_match, untaint => 1, no_chdir => 1}, '/');
         }
 
+        log_trace("%d files in list to backup/remove", scalar(@files) );
         if($config{"backup"} && (@files)) {
-            log_info( mtx("Backing up files to be removed to %s ..."), $config{"backup_to"} );
-            my $filesfile = new File::Temp(TEMPLATE=>"deluser.XXXXX", DIR=>"/tmp");
+            log_info( mtx("Backing up %d files to be removed to %s ..."), scalar(@files), $config{"backup_to"} );
+            my $filesfile = File::Temp->new(TEMPLATE=>"deluser.XXXXX", DIR=>"/tmp");
             my $filesfilename = $filesfile->filename;
             my $backup_name = sanitize_string( $config{"backup_to"} . "/$user.tar", simplepathre );
             log_trace("filesfilename %s, backup_to %s", $filesfilename, $config{"backup_to"});
@@ -422,7 +427,7 @@ if ($action eq 'delgroup') {
     }
 
     my($dummy,$gid,$members);
-    unless( (($dummy, $dummy, $gid, $members ) = getgrnam($group)) ) {
+    unless( (($dummy, $dummy, $gid, $members ) = egetgrnam($group)) ) {
         log_fatal( mtx("getgrnam `%s' failed: %s. This shouldn't happen."), $group, $! );
         exit( RET_SYSTEM_ERROR );
     }
@@ -537,12 +542,12 @@ sub usage_error {
 
 sub exist_user {
     my $exist_user = shift;
-    return(defined getpwnam($exist_user));
+    return(defined egetpwnam($exist_user));
 }
 
 sub exist_group {
     my $exist_group = shift;
-    return(defined getgrnam($exist_group));
+    return(defined egetgrnam($exist_group));
 }
 
 sub check_backup_suffix {


=====================================
po/Makefile deleted
=====================================
@@ -1,39 +0,0 @@
-XGETTEXT = xgettext
-MSGFMT = msgfmt
-MSGMERGE = msgmerge
-
-LOCALEDIR = /usr/share/locale
-
-.SUFFIXES: .po .mo .pot
-
-%.mo: %.po
-	$(MSGFMT) -o $@ $<
-
-PO = $(wildcard *.po)
-LANG = $(basename $(PO))
-MO = $(addsuffix .mo,$(LANG))
-SOURCES = ../adduser ../deluser ../AdduserCommon.pm ../AdduserLogging.pm ../AdduserRetvalues.pm
-
-all: update $(MO)
-update: adduser.pot
-	- at for po in $(PO); do \
-	echo -n "Updating $$po"; \
-	$(MSGMERGE) --previous -U $$po adduser.pot; \
-	done;
-
-adduser.pot: $(SOURCES)
-	$(XGETTEXT) -c -L Perl -kgtx -kmtx \
-	--msgid-bugs-address=adduser at packages.debian.org \
-	-o $@ $(SOURCES)
-
-install: all
-	for i in $(MO) ; do \
-	  t=$(DESTDIR)/$(LOCALEDIR)/`basename $$i .mo`/LC_MESSAGES ;\
-	  install -d $$t ;\
-	  install -m 644 $$i $$t/adduser.mo ;\
-	done
-
-clean:
-	$(RM) $(MO) *~
-
-.PHONY: update


=====================================
testsuite/runsuite.sh
=====================================
@@ -12,7 +12,7 @@ fi
 
 cp /etc/passwd $PASSWD_BAK
 
-for a in off on; do
+for a in on; do
   for i in ./test*.pl ; do
     if ! shadowconfig $a > /dev/null; then
       echo "shadowconfig $a failed"



View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/43a40e9daef87300ebcf822bc55e3d287b38dcb6...28f610f9f2ed243992fedfcf21ff9ce6ee76c5fd

-- 
View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/43a40e9daef87300ebcf822bc55e3d287b38dcb6...28f610f9f2ed243992fedfcf21ff9ce6ee76c5fd
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-shadow-devel/attachments/20250218/7172f06b/attachment-0001.htm>


More information about the Pkg-shadow-devel mailing list