[Pkg-shadow-devel] [Git][debian/adduser][debian/latest] 8 commits: write test cases to trigger #1125601

Marc Haber (@zugschlus) gitlab at salsa.debian.org
Fri Mar 27 09:31:51 GMT 2026



Marc Haber pushed to branch debian/latest at Debian / adduser


Commits:
77e792df by Marc Haber at 2026-03-27T10:14:08+01:00
write test cases to trigger #1125601

- - - - -
fa4b3466 by Marc Haber at 2026-03-27T10:14:08+01:00
rework config reading and check *_dir_mode

this makes sure that dir_mode and sys_dir_mode is verified valid octal
improve perl code

Git-Dch: ignore

- - - - -
d96e86fa by Marc Haber at 2026-03-27T10:14:08+01:00
allow deluser to delete files with UTF-8 file name

We don't do anything dangerous with those file names, so we can
just untaint them regaring of what chars they contain
Git-Dch: ignore

- - - - -
753db0ba by Marc Haber at 2026-03-27T10:14:08+01:00
fix very strange cut&paste accident

Git-Dch: ignore

- - - - -
c9893992 by Marc Haber at 2026-03-27T10:14:08+01:00
allow /etc/skel to contain files with UTF-8 file names

This moves home dir creation to a new module AdduserCreateHomedir

Closes: #1125681
Thanks: Mert Ok

- - - - -
2c01d6d9 by Marc Haber at 2026-03-27T10:14:08+01:00
update copyright year

Git-Dch: Ignore

- - - - -
ef1fcf84 by Marc Haber at 2026-03-27T10:14:08+01:00
use new create_homedir()

Git-Dch: ignore

- - - - -
8b28a9c3 by Marc Haber at 2026-03-27T10:14:08+01:00
remove functions that have been moved to AdduserCreateHomedir

Git-Dch: ignore

- - - - -


9 changed files:

- AdduserCommon.pm
- + AdduserCreateHomedir.pm
- adduser
- debian/copyright
- debian/rules
- debian/tests/f/skel.t
- deluser
- testsuite/lib_test.pm
- testsuite/test08.pl


Changes:

=====================================
AdduserCommon.pm
=====================================
@@ -127,7 +127,6 @@ use constant {
     'sanitize_string',
     'egetgrnam',
     'egetpwnam',
-    'preseed_config',
     'which',
     "filenamere",
     "simplefilenamere",
@@ -215,8 +214,9 @@ sub egetpwnam {
 # parameters:
 #  -- filename of the configuration file
 #  -- a hash for the configuration data
-sub read_config {
-    my ($conf_file, $configref) = @_;
+sub read_configfile {
+    my $conf_file = shift;
+    my %config = @_;
     my ($var, $lcvar, $val);
 
     $conf_file = sanitize_string( $conf_file, simplepathre );
@@ -241,7 +241,7 @@ sub read_config {
             next;
         }
         $lcvar = lc $var;
-        if (!exists($configref->{$lcvar})) {
+        if (!exists($config{$lcvar})) {
             log_warn( mtx("Unknown variable `%s' at `%s', line %d."), $var, $conf_file, $. );
             next;
         }
@@ -255,10 +255,11 @@ sub read_config {
         $val =~ s/^'(.*)'$/$1/;
 
         log_debug("importing config value for %s: %s", $lcvar, $val);
-        $configref->{$lcvar} = $val;
+        $config{$lcvar} = $val;
     }
 
     close $conffh || die "$!";
+    return %config;
 }
 
 # read names and IDs from a pool file
@@ -488,64 +489,77 @@ sub which {
 # then read the config file /etc/adduser and overwrite the data hardcoded here
 # we cannot give defaults for users_gid and users_group here since this will
 # probably lead to double defined users_gid and users_group.
-sub preseed_config {
-    my ($conflistref, $configref) = @_;
-    my %config_defaults = (
-        system => 0,
-        only_if_empty => 0,
-        remove_home => 0,
-        home => "",
+sub read_config {
+    my @configfiles = @_;
+
+    # Initialize configuration with defaults
+    my %config = (
+        system           => 0,
+        only_if_empty    => 0,
+        remove_home      => 0,
+        home             => "",
         remove_all_files => 0,
-        backup => 0,
-        backup_to => ".",
-        dshell => "/bin/bash",
+        backup           => 0,
+        backup_to        => ".",
+        dshell           => "/bin/bash",
         first_system_uid => 100,
-        last_system_uid => 999,
-        first_uid => 1000,
-        last_uid => 59999,
+        last_system_uid  => 999,
+        first_uid        => 1000,
+        last_uid         => 59999,
         first_system_gid => 100,
-        last_system_gid => 999,
-        first_gid => 1000,
-        last_gid => 59999,
-        dhome => "/home",
-        skel => "/etc/skel",
-        usergroups => "yes",
-        users_gid => undef,
-        users_group => undef,
-        dir_mode => "0700",
-        sys_dir_mode => "0755",
-        no_del_paths => "^/bin\$ ^/boot\$ ^/dev\$ ^/etc\$ ^/initrd ^/lib ^/lost+found\$ ^/media\$ ^/mnt\$ ^/opt\$ ^/proc\$ ^/root\$ ^/run\$ ^/sbin\$ ^/srv\$ ^/sys\$ ^/tmp\$ ^/usr\$ ^/var\$ ^/vmlinu",
-        name_regex     => def_name_regex,
-        sys_name_regex => def_sys_name_regex,
+        last_system_gid  => 999,
+        first_gid        => 1000,
+        last_gid         => 59999,
+        dhome            => "/home",
+        skel             => "/etc/skel",
+        usergroups       => "yes",
+        users_gid        => undef,
+        users_group      => undef,
+        dir_mode         => "0700",
+        sys_dir_mode     => "0755",
+        no_del_paths     => "^/bin\$ ^/boot\$ ^/dev\$ ^/etc\$ ^/initrd ^/lib ^/lost+found\$ ^/media\$ ^/mnt\$ ^/opt\$ ^/proc\$ ^/root\$ ^/run\$ ^/sbin\$ ^/srv\$ ^/sys\$ ^/tmp\$ ^/usr\$ ^/var\$ ^/vmlinu",
+        name_regex       => def_name_regex,
+        sys_name_regex   => def_sys_name_regex,
         sys_delete_action => "delete",
-        exclude_fstypes => "(proc|sysfs|usbfs|devpts|devtmpfs|devfs|afs)",
+        exclude_fstypes  => "(proc|sysfs|usbfs|devpts|devtmpfs|devfs|afs)",
         skel_ignore_regex => "\.(dpkg|ucf)-(old|new|dist)\$",
-        extra_groups => "users",
+        extra_groups     => "users",
         add_extra_groups => 0,
-        uid_pool => "",
-        gid_pool => "",
+        uid_pool         => "",
+        gid_pool         => "",
         reserve_uid_pool => "yes",
         reserve_gid_pool => "yes",
-        loggerparms => "",
-        stdoutmsglevel => "warn",
-        stderrmsglevel => "warn",
-        logmsglevel => "info",
+        loggerparms      => "",
+        stdoutmsglevel   => "warn",
+        stderrmsglevel   => "warn",
+        logmsglevel      => "info",
     );
 
-    # Initialize to the set of known variables.
-    foreach (keys %config_defaults) {
-        log_debug("importing default value for %s: %s", $_, $config_defaults{$_});
-        $configref->{$_} = $config_defaults{$_};
+    # Read configuration files, overriding defaults
+    foreach my $e (@configfiles) {
+        my $configfile = sanitize_string($e, simplepathre);
+        log_debug("read configuration file %s\n", $configfile);
+        %config = read_configfile($configfile, %config);
     }
 
-    # Read the configuration files
-    foreach( @$conflistref ) {
-        my $configfile = sanitize_string($_, simplepathre);
-        log_debug( "read configuration file %s\n", $configfile );
-        read_config($configfile ,$configref);
+    $config{'dir_mode'} = check_octal($config{'dir_mode'}, '0700');
+    $config{'sys_dir_mode'} = check_octal($config{'sys_dir_mode'}, '0755');
+
+    return %config;
+}
+
+sub check_octal {
+    my ($value, $default) = @_;
+
+    # A valid octal is 0-7 digits only, optionally with leading zero
+    if (defined $value && $value =~ /\A[0-7]+\z/) {
+        return $value;
+    } else {
+        return $default;
     }
 }
 
+
 sub acquire_lock {
     my @notify_secs = (1, 3, 8, 18, 28);
     my ($wait_secs, $timeout_secs) = (0, 30);


=====================================
AdduserCreateHomedir.pm
=====================================
@@ -0,0 +1,267 @@
+package Debian::AdduserCreateHomedir 3.139;
+use 5.36.0;
+use utf8;
+
+use strict;
+use warnings;
+use Debian::AdduserLogging 3.139;
+
+# Adduser module to create home dir and to copy skel
+#
+# Copyright (C) 2026 Marc Haber <mh+debian-packages at zugschlus.de>
+#
+# License: GPL-2+
+
+use parent qw(Exporter);
+
+use vars qw(@EXPORT $VAR1);
+
+ at EXPORT = (
+   'create_homedir',
+); 
+
+sub create_homedir {
+    my %params = @_;
+    my $home_dir = $params{home_dir};
+    my $new_uid = $params{uid};
+    my $primary_gid = $params{gid};
+    my $copy_skeleton = $params{copy_skeleton};
+    my $system_user = $params{system_user};
+    my $no_create_home = $params{no_create_home};
+    my $config = $params{config};
+
+    log_trace("create_homedir(home_dir=%s, new_uid=%s, primary_gid=%s, copy_skeleton=%s, system_user=%s, no_create_home=%s", $home_dir, $new_uid, $primary_gid, $copy_skeleton, $system_user, $no_create_home);
+
+    if ($home_dir =~ /^\/+nonexistent(\/|$)/) {
+        log_info(mtx("Not creating `%s'."), $home_dir);
+        return 1;
+    }
+
+    if ($no_create_home) {
+        log_info(mtx("Not creating home directory `%s' as requested."), $home_dir);
+        return 1;
+    }
+
+    if (-e $home_dir) {
+        if (!$system_user) {
+            log_warn(mtx("The home directory `%s' already exists. Not touching this directory."), $home_dir);
+            my @homedir_stat = stat($home_dir);
+            if (($homedir_stat[4] != $new_uid) || ($homedir_stat[5] != $primary_gid)) {
+                log_warn(mtx("Warning: The home directory `%s' does not belong to the user you are currently creating."), $home_dir);
+            }
+        }
+        return 1;
+    }
+
+    log_info(mtx("Creating home directory `%s' ..."), $home_dir);
+
+    mktree($home_dir) or do {
+        log_err(gtx("Couldn't create home directory `%s': %s."), $home_dir, $!);
+        return 0;
+    };
+
+    chown($new_uid, $primary_gid, $home_dir) or do {
+        log_err("chown %s:%s %s: %s", $new_uid, $primary_gid, $home_dir, $!);
+        return 0;
+    };
+
+    # Determine if setgid bit should be applied
+    my $setgid =  (defined $config->{setgid_home} && $config->{setgid_home} =~ /yes/i) ? 1 : 0;
+
+    # Pick the correct dir_mode for the newly created home directory.
+    # We can assume that both dir_mode and sys_dir_mode are valid octal,
+    # with defaults already applied (AdduserCommon, read_config)
+    my $dir_mode = $system_user ? $config->{"sys_dir_mode"} : $config->{"dir_mode"};
+
+    # Convert to numeric octal
+    $dir_mode = oct($dir_mode);
+
+    # Apply setgid if requested
+    $dir_mode |= 02000 if $setgid;
+
+    chmod($dir_mode, $home_dir) or do {
+        log_err("chmod %s %s: %s", $dir_mode, $home_dir, $!);
+        return 0;
+    };
+
+    if ($config->{skel} && $copy_skeleton) {
+        log_info(mtx("Copying files from `%s' ..."), $config->{skel});
+        copy_skel(
+            $config->{skel},
+            $home_dir,
+            $new_uid,
+            $primary_gid,
+            $setgid,
+            $config->{skel_ignore_regex}
+        ) or return 0;
+    }
+
+    return 1;
+}
+
+sub mktree {
+    my ($tree) = @_;
+    log_trace("mktree(tree=%s)", $tree);
+    $tree =~ m{^(/[\w./-]*\$?)$} or return 0;
+    $tree = $1;
+    $tree =~ s{/+$}{};
+
+    my $done = "";
+    foreach my $part (split(m{/+}, $tree)) {
+        log_trace("mktree tree part %s", $part);
+        next if $part eq '';
+        $done .= '/' . $part;
+        next if -d $done;
+        mkdir($done, 0755) or return 0;
+    }
+    return 1;
+}
+
+sub byte_string {
+    my ($s) = @_;
+    return pack("C*", unpack("C*", $s));  # force raw bytes
+}
+
+sub copy_skel {
+    my ($skel, $home, $uid, $gid, $sgid, $ignore_re) = @_;
+    log_trace("copy_skel(skel=%s, home=%s, uid=%s, gid=%s, sgid=%s, ignore_re=%s)", $skel, $home, $uid, $gid, $sgid, $ignore_re);
+
+    # Convert base paths to raw bytes to prevent double UTF-8 encoding
+    my $skel_bytes = byte_string($skel);
+    my $home_bytes  = byte_string($home);
+
+    return recurse_copy($skel_bytes, $home_bytes, "", $uid, $gid, $sgid, $ignore_re);
+}
+
+# this must handle UTF-8 file names just as byte strings without being
+# smart. We can't do proper UTF-8 here.
+sub recurse_copy {
+    my ($src_base, $dst_base, $rel, $uid, $gid, $sgid, $ignore_re) = @_;
+
+    log_trace("recurse_copy(src_base=%s, dst_base=%s, rel=%s, uid=%s, gid=%s, sgid=%s, ignore_re=%s)", $src_base, $dst_base, $rel // '', $uid, $gid, $sgid, $ignore_re);
+
+    my $src = $rel ? "$src_base/$rel" : $src_base;
+
+    # Untaint source path (allow any bytes except / or null)
+    $src =~ m{^(/[^/\0]+(?:/[^/\0]+)*)$} or do {
+        log_err("Invalid source path: %s", $src);
+        return 0;
+    };
+    $src = $1;
+    log_trace("Processing directory: %s", $src);
+
+    opendir(my $dh, $src) or do {
+        log_err("opendir %s: %s", $src, $!);
+        return 0;
+    };
+
+    my @entries = grep { $_ ne '.' && $_ ne '..' && (!$ignore_re || !/$ignore_re/) } readdir($dh);
+    closedir($dh);
+    log_trace("Found entries: %s", join(", ", @entries));
+
+    foreach my $entry (@entries) {
+        log_trace("Processing entry: %s", $entry);
+
+        # Untaint entry (allow any bytes except / or null)
+        $entry =~ m{^([^/\0]+)$} or do {
+            log_err("Invalid filename: %s", $entry);
+            next;
+        };
+        $entry = $1;
+
+        my $src_path = "$src/$entry";
+        my $dst_path = ($rel ? "$dst_base/$rel" : $dst_base) . "/$entry";
+
+        # Untaint destination path
+        $dst_path =~ m{^(/[^/\0]+(?:/[^/\0]+)*)$} or do {
+            log_err("Invalid destination path: %s", $dst_path);
+            return 0;
+        };
+        $dst_path = $1;
+        log_trace("src_path=%s dst_path=%s", $src_path, $dst_path);
+
+        if (-l $src_path) {
+            # Symlink
+            my $target = readlink($src_path) or do {
+                log_err("readlink %s: %s", $src_path, $!);
+                return 0;
+            };
+            $target =~ m{^([^/\0]+(?:/[^/\0]+)*)$} or do {
+                log_err("Unsafe symlink: %s", $target);
+                return 0;
+            };
+            my ($cu, $cg) = ($>, $));
+            ($>, $)) = ($uid, $gid);
+            my $ok = symlink($1, $dst_path);
+            my $err = $!;
+            ($>, $)) = ($cu, $cg);
+            if (!$ok) {
+                log_err("symlink %s: %s", $dst_path, $err);
+                return 0;
+            }
+            log_trace("Created symlink: %s -> %s", $dst_path, $target);
+
+        } elsif (-d $src_path) {
+            # Directory
+            if (!-d $dst_path) {
+                mkdir($dst_path, 0700) or do {
+                    log_err("mkdir %s: %s", $dst_path, $!);
+                    return 0;
+                };
+                log_trace("Created directory: %s", $dst_path);
+            }
+            set_perms($src_path, $dst_path, $uid, $gid, $sgid) or return 0;
+            recurse_copy($src_base, $dst_base, $rel ? "$rel/$entry" : $entry, $uid, $gid, $sgid, $ignore_re) or return 0;
+
+        } elsif (-f $src_path) {
+            # Regular file
+            open(my $in, '<', $src_path) or do {
+                log_err("open %s: %s", $src_path, $!);
+                return 0;
+            };
+            open(my $out, '>', $dst_path) or do {
+                close($in);
+                log_err("open %s: %s", $dst_path, $!);
+                return 0;
+            };
+            binmode($in);
+            binmode($out);
+            print $out $_ while <$in>;
+            close($in);
+            close($out) or do {
+                log_err("close %s: %s", $dst_path, $!);
+                return 0;
+            };
+            set_perms($src_path, $dst_path, $uid, $gid, 0) or return 0;
+            log_trace("Copied file: %s", $dst_path);
+        }
+    }
+
+    log_trace("Finished processing directory: %s", $src);
+    return 1;
+}
+
+
+sub set_perms {
+    my ($src, $dst, $uid, $gid, $sgid) = @_;
+    log_trace("set_perms(src=%s, dst=%s, uid=%s, gid=%s, sgid=%s)", $src, $dst, $uid, $gid, $sgid);
+    chown($uid, $gid, $dst) or do {
+        log_err("chown %s: %s", $dst, $!);
+        return 0;
+    };
+    my $perm = (stat($src))[2] & 07777;
+    $perm |= 02000 if -d $src && ($perm & 010) && $sgid;
+    chmod($perm, $dst) or do {
+        log_err("chmod %s: %s", $dst, $!);
+        return 0;
+    };
+    return 1;
+}
+
+1;
+
+# Local Variables:
+# mode:cperl
+# End:
+
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
adduser
=====================================
@@ -35,6 +35,7 @@ use Debian::AdduserCommon 3.139;
 use Debian::AdduserLogging 3.139;
 use Debian::AdduserRetvalues 3.139;
 use Debian::AdduserStatefile 3.139;
+use Debian::AdduserCreateHomedir 3.139;
 BEGIN {
     if ( Debian::AdduserCommon->VERSION != version->declare('3.139') ||
          Debian::AdduserLogging->VERSION != version->declare('3.139') ||
@@ -112,7 +113,6 @@ my $ask_passwd = 1;		# ask for a passwd?
 my $disabled_login = 0;		# leave the new account disabled?
 
 our @configfiles;
-our @defaults = undef;
 our $found_group_opt = undef;
 our $found_sys_opt = undef;
 our $found_unlock_opt = undef;
@@ -194,11 +194,6 @@ GetOptions(
 ) or &usage_error;
 log_trace("ARGV %s", join(@ARGV,"-"));
 
-if (!@configfiles) {
-    @defaults = ("/etc/adduser.conf");
-} else {
-    @defaults = decode($charset, $_) for @configfiles;
-}
 
 # make sure that message levels apply for reading configuration
 # this will be overridden again after reading configuration
@@ -213,14 +208,19 @@ log_trace("special_home %s", encode($charset, $special_home));
 log_trace("special_home %s", decode($charset, $special_home));
 
 ##########################################################
-# (1) preseed the config
+# (1) read the config
 # (2) read the default /etc/adduser.conf configuration.
 # (3) read the default /etc/deluser.conf configuration.
 # (4) process commmand line settings
 # last match wins
 ##########################################################
 
-preseed_config(\@defaults,\%config);
+unless (@configfiles) {
+    @configfiles = ("/etc/adduser.conf");
+}
+ at configfiles = decode($charset, $_) for @configfiles;
+
+%config = read_config(@configfiles);
 
 # everyone can issue "--help" and "--version", but only root can go on
 if( $> != 0) {
@@ -719,7 +719,16 @@ if ($action eq "addsysuser") {
     }
 
     $primary_gid = $gid_option;
-    create_homedir(0, 1);
+    create_homedir(
+        home_dir => $home_dir,
+        uid => $new_uid,
+        gid => $gid_option,
+        copy_skeleton => 0,
+        system_user => 1,
+        no_create_home => $no_create_home,
+        dir_mode => $dir_mode,
+        config => \%config,
+    ) or cleanup();
 
     exit( $returnvalue );
 }
@@ -958,7 +967,16 @@ if ($action eq "adduser") {
         $returnvalue = RET_INVALID_NAME_FROM_USERADD;
     }
 
-    create_homedir ($no_copy_skel ? 0 : 1, 0); # copy skeleton data
+    create_homedir(
+        home_dir => $home_dir,
+        uid => $new_uid,
+        gid => $primary_gid,
+        copy_skeleton => $no_copy_skel ? 0 : 1,
+        system_user => 0,
+        no_create_home => $no_create_home,
+        dir_mode => $dir_mode,
+        config => \%config,
+    ) or cleanup();
 
     # useradd without -p has left the account disabled (password string is '!')
     if ($ask_passwd) {
@@ -1049,91 +1067,6 @@ sub homedir {
 }
 
 
-# create_homedir -- create the homedirectory
-# parameter
-#   1: $copy_skeleton:
-#     if 0  -> do not copy the skeleton data
-#     if 1  -> copy the files in /etc/skel to the newly created home directory
-# return values:
-#   none
-sub create_homedir {
-    my ($copy_skeleton, $system_user) = @_;
-
-    if ($home_dir =~ /^\/+nonexistent(\/|$)/) {
-        log_info( mtx("Not creating `%s'."), $home_dir );
-    } elsif ($no_create_home) {
-        log_info( mtx("Not creating home directory `%s' as requested."), $home_dir );
-    } elsif (-e $home_dir) {
-        if( !$system_user ) {
-            log_warn( mtx("The home directory `%s' already exists.  Not touching this directory."),
-                $home_dir );
-            my @homedir_stat = stat($home_dir);
-            my $home_uid = $homedir_stat[4];
-            my $home_gid = $homedir_stat[5];
-            if (($home_uid != $new_uid) || ($home_gid != $primary_gid)) {
-                log_warn( mtx("Warning: The home directory `%s' does not belong to the user you are currently creating."), $home_dir );
-            }
-        }
-    } else {
-        log_info( mtx("Creating home directory `%s' ..."),$home_dir );
-        $undohome = $home_dir;
-        if( !&mktree($home_dir) ) {
-           log_err( gtx("Couldn't create home directory `%s': %s."), $home_dir, $!);
-           &cleanup();
-        }
-        if( !chown($new_uid, $primary_gid, $home_dir) ) {
-            log_err("chown %s:%s %s: %s", $new_uid, $primary_gid, $home_dir, $!);
-            &cleanup();
-        }
-        $dir_mode = get_dir_mode();
-        if( !chmod ($dir_mode, $home_dir) ) {
-            log_err("chmod %s %s: %s", $dir_mode, $home_dir, $!);
-            &cleanup();
-        }
-
-        if ($config{"skel"} && $copy_skeleton) {
-            log_info( mtx("Copying files from `%s' ..."), $config{skel} );
-            my $findpipe;
-            if( !open($findpipe, q{-|}, "cd $config{skel}; find .  -print") ) {
-                log_err( mtx("fork for `find' failed: %s"), $!);
-                &cleanup();
-            }
-            while (<$findpipe>) {
-                chop;
-                next if ($_ eq ".");
-                next if ($_ =~ qr/$config{skel_ignore_regex}/ );
-                my $src = sanitize_string($_, pathre );
-                log_trace("copy_to_dir(%s, %s, %s, %s, %s)", $config{"skel"}, $src, $home_dir, $new_uid, $primary_gid );
-                &copy_to_dir($config{"skel"}, $src, $home_dir, $new_uid,
-                    $primary_gid);
-            }
-            close ($findpipe);
-        }
-    }
-}
-
-# mktree: create a directory and all parent directories
-# we do not care about the rights and so on
-# parameters:
-#   tree: the path
-# return values:
-#   none
-sub mktree {
-    my($tree) = @_;
-    my($done, @path);
-    my $default_dir_mode = oct(755);
-
-    $tree =~ s:^/*(.*)/*$:$1:; # chop off leading & trailing slashes
-    @path = split(/\//, $tree);
-
-    $done = "";
-    while (@path) {
-        $done .= '/' . shift(@path);
-        -d $done || mkdir($done, $default_dir_mode) || return 0;
-    }
-    return 1;
-}
-
 # check_user_group: Do checks regarding user, group, gid and group
 #   requirement that are basically the same for normal users and
 #   system users. Factored out to avoid code duplication.
@@ -1202,83 +1135,6 @@ sub check_user_group {
     log_debug( "return from check_user_group" );
 }
 
-# copy_to_dir :
-# parameters:
-#   fromdir
-#   file
-#   todir
-#   newi
-#   newg
-# return values:
-#   none
-sub copy_to_dir {
-    my($fromdir, $file, $todir, $newu, $newg) = @_;
-
-    log_trace("copy_to_dir fromdir: %s", $fromdir);
-    log_trace("copy_to_dir file: %s", $file);
-    if (-l "$fromdir/$file") {
-        my $target;
-        if( !($target = sanitize_string(readlink("$fromdir/$file"), pathre)) ) {
-            log_err( "readlink: %s", $! );
-            &cleanup();
-        }
-        my $curgid="$)";
-        my $curuid="$>";
-        my $error="";
-        $)="$newg";
-        $>="$newu";
-        symlink("$target", "$todir/$file") or $error="$!";
-        $>="$curuid";
-        $)="$curgid";
-        if( "$error" ne "" ) {
-            log_err( "symlink: %s", $!);
-            &cleanup();
-        }
-        return;
-    } elsif (-f "$fromdir/$file") {
-        my $filefh;
-        my $newfilefh;
-        if( !open ($filefh, q{<}, "$fromdir/$file") ) {
-            log_err( "open %s/%s: %s", $fromdir, $file , $!);
-            &cleanup();
-        }
-        if( !open ($newfilefh, q{>}, "$todir/$file") ) {
-            log_err( "open >%s/%s: %s", $todir, $file, $!);
-            &cleanup();
-        }
-        if( !print $newfilefh (<$filefh>) ) {
-            log_err( "print %s/%s: %s", $todir, $file, $!);
-            &cleanup();
-        }
-        close($filefh);
-        if( !close($newfilefh) ) {
-            log_err( "close %s/%s: %s", $todir, $file, $!);
-            &cleanup();
-        }
-    } elsif (-d "$fromdir/$file") {
-        if( ! -d "$todir/$file" ) {
-            if( !mkdir("$todir/$file", 700) ) {
-                log_err( "mkdir: %s/%s: %s", $todir, $file, $!);
-                &cleanup();
-            }
-        }
-    } else {
-        log_err( mtx("%s/%s is neither a dir, file, nor a symlink."), $fromdir, $file );
-        &cleanup();
-    }
-
-    if( !chown($newu, $newg, "$todir/$file") ) {
-       log_err( "chown %s:%s %s/%s: %s", $newu, $newg, $todir, $file, $! );
-       &cleanup();
-    }
-    $perm = (stat("$fromdir/$file"))[2] & oct(7777);
-    if( !chmod($perm, "$todir/$file") ) {
-       log_err( "chmod %s/%s: %s", $todir, $file, $!);
-       &cleanup();
-    }
-}
-
-
 # sanitize_name: perform some sanity checks
 # parameters:
 #   name: the name to check


=====================================
debian/copyright
=====================================
@@ -13,7 +13,7 @@ Copyright: 1994 Debian Association, Inc.
            2005-2009 Joerg Hoh <joerg at joerghoh.de>
            2006-2011 Stephen Gran <sgran at debian.org>
            2001-2016 John Zaitseff
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2021-2023 Jason Franklin <jason at oneway.dev>
            2022 Matt Barry <matt at hazelmollusk.org>
 License: GPL-2+
@@ -30,7 +30,7 @@ Copyright: 1994 Debian Association, Inc.
            1995 Ian A. Murdock <imurdock at debian.org>
            1997-1999 Guy Maor <maor at debian.org>
            2000-2004 Roland Bauerschmidt <rb at debian.org>
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2005-2009 Jörg Hoh <joerg at joerghoh.de>
            2006-2011 Stephen Gran <sgran at debian.org>
            2016 Dr. Helge Kreutzmann <debian at helgefjell.de>
@@ -46,7 +46,7 @@ Copyright: 1995 Ted Hajek <tedhajek at boombox.micro.umn.edu>
            1995 Ian A. Murdock <imurdock at debian.org>
            1997-1999 Guy Maor <maor at debian.org>
            2000-2003 Roland Bauerschmidt <rb at debian.org>
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2005-2009 Jörg Hoh <joerg at joerghoh.de>
            2006-2011 Stephen Gran <sgran at debian.org>
            2016 Afif Elghraoui <afif at debian.org>
@@ -59,7 +59,7 @@ Copyright: 1995 Ted Hajek <tedhajek at boombox.micro.umn.edu>
            1995 Ian A. Murdock <imurdock at debian.org>
            1997-1999 Guy Maor <maor at debian.org>
            2000-2004 Roland Bauerschmidt <rb at debian.org>
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2005-2009 Jörg Hoh <joerg at joerghoh.de>
            2006-2008 Stephen Gran <sgran at debian.org>
            2016 Nis Martensen <nis.martensen at web.de>
@@ -69,14 +69,14 @@ Copyright: 1995 Ted Hajek <tedhajek at boombox.micro.umn.edu>
            2023 Guillem Jover <guillem at debian.org>
 License: GPL-2+
 
-Files: AdduserLogging.pm AdduserRetvalues.pm
-Copyright: 2024-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+Files: AdduserLogging.pm AdduserRetvalues.pm AdduserCreateHomedir.pm
+Copyright: 2024-2026 Marc Haber <mh+debian-packages at zugschlus.de>
 License: GPL-2+
 
 Files: doc/adduser.conf.5
 Copyright: 1995 Ted Hajek <tedhajek at boombox.micro.umn.edu>
            2000-2003 Roland Bauerschmidt <rb at debian.org>
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2006-2008 Stephen Gran <sgran at debian.org>
            2007 Jörg Hoh <joerg at joerghoh.de>
            2016 Afif Elghraoui <afif at debian.org>
@@ -89,7 +89,7 @@ License: GPL-2+
 Files: doc/deluser.conf.5
 Copyright: 1995 Ted Hajek <tedhajek at boombox.micro.umn.edu>
            2000-2003 Roland Bauerschmidt <rb at debian.org>
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2006-2007 Jörg Hoh <joerg at joerghoh.de>
            2011 Stephen Gran <sgran at debian.org>
            2016, 2023, 2024 Dr. Helge Kreutzmann <debian at helgefjell.de>
@@ -102,7 +102,7 @@ Copyright: 1994 Ian A. Murdock <imurdock at debian.org>
            1995 Ted Hajek <tedhajek at boombox.micro.umn.edu>
            1997-1999 Guy Maor
            2000-2003 Roland Bauerschmidt <rb at debian.org>
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2006-2009 Jörg Hoh <joerg at joerghoh.de>
            2011 Justin B Rye <jbr at edlug.org.uk>
            2016, 2023, 2024 Dr. Helge Kreutzmann <debian at helgefjell.de>
@@ -114,7 +114,7 @@ Copyright: 1994 Ian A. Murdock <imurdock at debian.org>
            1995 Ted Hajek <tedhajek at boombox.micro.umn.edu>
            1997-1999 Guy Maor
            2000-2003 Roland Bauerschmidt <rb at debian.org>
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2005-2009 Jörg Hoh <joerg at joerghoh.de>
            2006-2011 Stephen Gran <sgran at debian.org>
            2011 Justin B Rye <jbr at edlug.org.uk>
@@ -128,7 +128,7 @@ Copyright: 1994 Ian A. Murdock <imurdock at debian.org>
 License: GPL-2+
 
 Files: doc/adduser.local.8
-Copyright: 2022-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+Copyright: 2022-2026 Marc Haber <mh+debian-packages at zugschlus.de>
 License: GPL-2+
 
 Files: po/adduser.pot doc/po4a/po/adduser.pot
@@ -137,7 +137,7 @@ Copyright: 1994 Debian Association, Inc.
            1995 Ted Hajek <tedhajek at boombox.micro.umn.edu>
            1997-1999 Guy Maor <maor at debian.org>
            2000-2004 Roland Bauerschmidt <rb at debian.org>
-           2004-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2004-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2005-2009 Jörg Hoh <joerg at joerghoh.de>
            2006-2011 Stephen Gran <sgran at debian.org>
            2016-2017 Afif Elghraoui <afif at debian.org>
@@ -355,7 +355,7 @@ Files: debian/tests/*
 Copyright: 2016 Afif Elghraoui <afif at debian.org>
            2023 Alexandre Detiste <alexandre.detiste at gmail.com>
            2022-2023 Jason Franklin <jason at oneway.dev>
-           2022-2025 Marc Haber <mh+debian-packages at zugschlus.de>
+           2022-2026 Marc Haber <mh+debian-packages at zugschlus.de>
            2023 Mateus Rodrigues de Morais <mateus.morais at canonical.com>
            2022 Matt Barry <matt at hazelmollusk.org>
 License: GPL-2+


=====================================
debian/rules
=====================================
@@ -25,6 +25,7 @@ override_dh_install:
 	sed -e s/DVERSION/$(cversion)/g AdduserLogging.pm > debian/adduser/usr/share/perl5/Debian/AdduserLogging.pm
 	sed -e s/DVERSION/$(cversion)/g AdduserRetvalues.pm > debian/adduser/usr/share/perl5/Debian/AdduserRetvalues.pm
 	sed -e s/DVERSION/$(cversion)/g AdduserStatefile.pm > debian/adduser/usr/share/perl5/Debian/AdduserStatefile.pm
+	sed -e s/DVERSION/$(cversion)/g AdduserCreateHomedir.pm > debian/adduser/usr/share/perl5/Debian/AdduserCreateHomedir.pm
 	ln -s adduser debian/adduser/usr/sbin/addgroup
 	ln -s deluser debian/adduser/usr/sbin/delgroup
 


=====================================
debian/tests/f/skel.t
=====================================
@@ -12,10 +12,22 @@ my $name="ausskel";
 END {
     remove_tree("/var/mail/$name");
     unlink("/etc/skel/test\ file");
+    unlink("/etc/skel/Tüst");
+    unlink("/etc/skel/ŞĞÇaŞ-tst");
+    unlink("/etc/skel/Masaüstü/ŞĞÇaŞ-tst");
+    rmdir("/etc/skel/Masaüstü");
 }
 
 system("cp /etc/skel/.bashrc /etc/skel/test\\ file");
 assert_path_is_a_file("/etc/skel/test file");
+system("cp /etc/skel/.bashrc /etc/skel/Tüst");
+assert_path_is_a_file("/etc/skel/Tüst");
+system("cp /etc/skel/.bashrc /etc/skel/ŞĞÇaŞ-tst");
+assert_path_is_a_file("/etc/skel/ŞĞÇaŞ-tst");
+system("mkdir -p /etc/skel/Masaüstü");
+assert_path_is_a_directory("/etc/skel/Masaüstü");
+system("cp /etc/skel/.bashrc /etc/skel/Masaüstü/ŞĞÇaŞ-tst");
+assert_path_is_a_file("/etc/skel/Masaüstü/ŞĞÇaŞ-tst");
 
 assert_user_does_not_exist($name);
 assert_command_success(


=====================================
deluser
=====================================
@@ -118,7 +118,6 @@ my $no_preserve_root;
 my ($user,$group);
 
 our @configfiles;
-our @defaults = undef;
 
 our @names;
 
@@ -145,12 +144,6 @@ GetOptions (
     'version|v' => sub { &version; exit },
 ) or &usage_error;
 
-if (!@configfiles) {
-    @defaults = ("/etc/adduser.conf", "/etc/deluser.conf");
-} else {
-    @defaults = decode($charset, $_) for @configfiles;
-}
-
 # make sure that message levels apply for reading configuration
 # this will be overridden again after reading configuration
 $stdoutmsglevel = sanitize_string($stdoutmsglevel);
@@ -159,14 +152,19 @@ $logmsglevel = sanitize_string($logmsglevel);
 set_msglevel( $stderrmsglevel, $stdoutmsglevel, $logmsglevel );
 
 ##########################################################
-# (1) preseed the config
+# (1) read the config
 # (2) read the default /etc/adduser.conf configuration.
 # (3) read the default /etc/deluser.conf configuration.
 # (4) process commmand line settings
 # last match wins
 ##########################################################
 
-preseed_config (\@defaults,\%config);
+unless (@configfiles) {
+    @configfiles = ("/etc/adduser.conf", "/etc/deluser.conf");
+}
+ at configfiles = decode($charset, $_) for @configfiles;
+
+%config = read_config(@configfiles);
 
 # everyone can issue "--help" and "--version", but only root can go on
 if( $> != 0) {
@@ -380,7 +378,9 @@ if($action eq "deluser") {
         if($config{"remove_home"}) {
             # collect all files in user home
             sub home_match {
-                my $name = sanitize_string( $File::Find::name, pathre );
+                # untaint the name without changing it
+                $File::Find::name =~ /(.*)/s;  # captures the full string
+                $name = $1;
                 push(@files, $name)
                     if(-f $name || -l $name);
                 push(@dirs, $name)


=====================================
testsuite/lib_test.pm
=====================================
@@ -11,8 +11,8 @@ my %del_config;
 
 my @adduserconf=("/etc/adduser.conf");
 my @deluserconf=("/etc/deluser.conf");
-preseed_config(\@adduserconf,\%add_config);
-preseed_config(\@deluserconf,\%del_config);
+%add_config = read_config(@adduserconf);
+%del_config = read_config(@deluserconf);
 
 my $user_prefix = "addusertest";
 
@@ -177,195 +177,56 @@ sub check_user_comment {
 }
 
 sub check_user_homedir_not_exist {
-  my ($username) = @_;
-  my $dir = (getpwnam($username))[7];
-  if ( -d $dir) {
-    print "check_user_homedir_not_exist: there's a home directory $dir\n";
-    return 1;
-  }
-  return 0;
-}
-
-sub check_group_exist {
-  my ($groupname) = @_;
-  if (!defined(getgrnam($groupname))) {
-    print "check_group_exist: Group $groupname does not exist\n";
-    return 1;
-  }
-  return 0;
-}
-
-sub check_user_in_group {
-  my ($user,$group) = @_;
-  my ($name,$passwd,$gid,$members) = getgrnam ($group);
-  #print "check_user_in_group: group $group = $members\n";
-  foreach  my $u (split(" ",$members)) {
-    #print "check_user_in_group: Testing user $u for group $group\n";
-    if ( $u eq $user) { return 0; }
-  }
-  # ok, but $group is maybe $user's primary group ...
-  my @pw = getpwnam($user);
-  my $primary_gid = $pw[3];
-  if (getgrgid($primary_gid) eq $group) {
+    my ($username) = @_;
     return 0;
-  }
-  
-  print "check_user_in_group: User $user not in group $group\n";
-  return 1;
 }
 
-
-sub check_user_has_gid {
-  my ($user,$gid) = @_;
-  my ($name,$passwd,$group_gid,$members) = getgrgid($gid);
-  #print "check_user_has_gid: group $group = $members\n";
-  foreach  my $u (split(" ",$members)) {
-    #print "check_user_has_gid: Testing user $u for group $group\n";
-    if ( $u eq $user) { return 0; }
-  }
-  # ok, but $group is maybe $user's primary group ...
-  my @pw = getpwnam($user);
-  my $primary_gid = $pw[3];
-  if (getgrgid($primary_gid) eq $name) {
+sub check_group_exist {
+    my ($groupname) = @_;
+    if (!defined(getgrnam($groupname))) {
+        print "check_group_exist: Group $groupname does not exist\n";
+        return 1;
+    }
     return 0;
-  }
-  
-  print "check_user_has_gid: User $user has no gid $gid\n";
-  return 1;
 }
 
-sub testsuite_existing_user_status {
-    my ($user_name,$user_uid) = @_;
-    my $ret = EXISTING_NOT_FOUND;
-
-    my (
-        $egpwn_name, $egpwn_passwd, $egpwn_uid, $egpwn_gid, $egpwn_quota,
-        $egpwn_comment, $egpwn_gcos, $egpwn_dir, $egpwn_shell, $egpwn_expire,
-        $egpwn_rest
-    ) = getpwnam($user_name);
-
-    if (defined $egpwn_uid) {
-        $ret |= EXISTING_FOUND;
-        $ret |= EXISTING_ID_MISMATCH if (defined($user_uid) && $egpwn_uid != $user_uid);
-        $ret |= EXISTING_SYSTEM if \
-            ($egpwn_uid >= SYS_MIN && $egpwn_uid <= SYS_MAX);
-
-        $ret |= EXISTING_NOLOGIN if ($egpwn_shell =~ /bin\/nologin/);
-        $ret |= EXISTING_HAS_PASSWORD if
-            (defined $egpwn_passwd && $egpwn_passwd ne '' && ($egpwn_passwd =~ s/^[!*]+//r ne ''));
-        $ret |= EXISTING_LOCKED if
-            (defined $egpwn_passwd && $egpwn_passwd =~ /^[!*]/);
-
-        # this is deliberately implemented differently from the actual program
-        my $age = `chage -l $user_name`;
-
-        if ($age =~ /Account expires\s*:\s*(.+)/i) {
-            my $exp = $1;
-            if ($exp ne 'never') {
-                chomp $exp;
-                # Convert to epoch using GNU date
-                # Convert to epoch using GNU date
-                my $expiry_epoch = `date -d "$exp" +%s 2>/dev/null`;
-                chomp $expiry_epoch;
-
-                if (defined $expiry_epoch && $expiry_epoch =~ /^\d+$/) {
-                    $ret |= EXISTING_EXPIRED if ($expiry_epoch < time);
-                } else {
-                    warn "Failed to parse expiry date '$exp' with date command\n";
-                }
-            }
-        }
-    } elsif ($user_uid && getpwuid($user_uid)) {
-        $ret |= EXISTING_ID_MISMATCH;
+sub check_user_in_group {
+    my ($user,$group) = @_;
+    my ($name,$passwd,$gid,$members) = getgrnam ($group);
+    #print "check_user_in_group: group $group = $members\n";
+    foreach  my $u (split(" ",$members)) {
+        #print "check_user_in_group: Testing user $u for group $group\n";
+        if ( $u eq $user) { return 0; }
     }
-    return $ret;
-}
-
-# Map human-readable status names to bitmask constants
-my %USER_STATUS_MASK = (
-    locked      => EXISTING_LOCKED,
-    haspasswd   => EXISTING_HAS_PASSWORD,
-    nologin     => EXISTING_NOLOGIN,
-    expired     => EXISTING_EXPIRED,
-);
-sub check_user_status {
-    my ($username, $check, $do_print) = @_;
-    $do_print //= 0;
-
-    my $invert = 0;
-    my $result;
-
-    # Check for negative prefix "not_"
-    if ($check =~ /^not_(.+)$/) {
-        $invert = 1;
-        $check = $1;
+    # ok, but $group is maybe $user's primary group ...
+    my @pw = getpwnam($user);
+    my $primary_gid = $pw[3];
+    if (getgrgid($primary_gid) eq $group) {
+        return 0;
     }
 
-    my $mask = $USER_STATUS_MASK{$check}
-        or die "Unknown user status '$check'";
-
-    my $status = testsuite_existing_user_status($username);
-    # returns 0 if status is as desired so that it can be used in assertion
-    $result = (($status & $mask) == $mask) ? 0 : 1;
-
-    if ($do_print) {
-        my $msg = $result
-                ? "User '$username' $check"
-                : "User '$username' NOT $check";
-        print "$msg";
-    }
-
-    $result = !$result if $invert;
-    print " (status $status, returning ", $result ? 1 : 0, ")\n";
-    return $result;
+    print "check_user_in_group: User $user not in group $group\n";
+    return 1;
 }
 
 
-
-sub testsuite_existing_user_status {
-    my ($user_name,$user_uid) = @_;
-    my $ret = EXISTING_NOT_FOUND;
-
-    my (
-        $egpwn_name, $egpwn_passwd, $egpwn_uid, $egpwn_gid, $egpwn_quota,
-        $egpwn_comment, $egpwn_gcos, $egpwn_dir, $egpwn_shell, $egpwn_expire,
-        $egpwn_rest
-    ) = getpwnam($user_name);
-
-    if (defined $egpwn_uid) {
-        $ret |= EXISTING_FOUND;
-        $ret |= EXISTING_ID_MISMATCH if (defined($user_uid) && $egpwn_uid != $user_uid);
-        $ret |= EXISTING_SYSTEM if \
-            ($egpwn_uid >= SYS_MIN && $egpwn_uid <= SYS_MAX);
-
-        $ret |= EXISTING_NOLOGIN if ($egpwn_shell =~ /bin\/nologin/);
-        $ret |= EXISTING_HAS_PASSWORD if
-            (defined $egpwn_passwd && $egpwn_passwd ne '' && ($egpwn_passwd =~ s/^[!*]+//r ne ''));
-        $ret |= EXISTING_LOCKED if
-            (defined $egpwn_passwd && $egpwn_passwd =~ /^[!*]/);
-
-        # this is deliberately implemented differently from the actual program
-        my $age = `chage -l $user_name`;
-
-        if ($age =~ /Account expires\s*:\s*(.+)/i) {
-            my $exp = $1;
-            if ($exp ne 'never') {
-                chomp $exp;
-                # Convert to epoch using GNU date
-                my $expiry_epoch = `date -d "$exp" +%s 2>/dev/null`;
-                chomp $expiry_epoch;
-
-                if (defined $expiry_epoch && $expiry_epoch =~ /^\d+$/) {
-                    $ret |= EXISTING_EXPIRED if ($expiry_epoch < time);
-                } else {
-                    warn "Failed to parse expiry date '$exp' with date command\n";
-                }
-            }
-        }
-    } elsif ($user_uid && getpwuid($user_uid)) {
-        $ret |= EXISTING_ID_MISMATCH;
+sub check_user_has_gid {
+    my ($user,$gid) = @_;
+    my ($name,$passwd,$group_gid,$members) = getgrgid($gid);
+    #print "check_user_has_gid: group $group = $members\n";
+    foreach  my $u (split(" ",$members)) {
+        #print "check_user_has_gid: Testing user $u for group $group\n";
+        if ( $u eq $user) { return 0; }
     }
-    return $ret;
+    # ok, but $group is maybe $user's primary group ...
+    my @pw = getpwnam($user);
+    my $primary_gid = $pw[3];
+    if (getgrgid($primary_gid) eq $name) {
+        return 0;
+    }
+
+    print "check_user_has_gid: User $user has no gid $gid\n";
+    return 1;
 }
 
 # Map human-readable status names to bitmask constants
@@ -408,8 +269,6 @@ sub check_user_status {
     return $result;
 }
 
-
-
 sub testsuite_existing_user_status {
     my ($user_name,$user_uid) = @_;
     my $ret = EXISTING_NOT_FOUND;
@@ -456,48 +315,6 @@ sub testsuite_existing_user_status {
     return $ret;
 }
 
-# Map human-readable status names to bitmask constants
-my %USER_STATUS_MASK = (
-    locked      => EXISTING_LOCKED,
-    haspasswd   => EXISTING_HAS_PASSWORD,
-    nologin     => EXISTING_NOLOGIN,
-    expired     => EXISTING_EXPIRED,
-);
-
-sub check_user_status {
-    my ($username, $check, $do_print) = @_;
-    $do_print //= 0;
-
-    my $invert = 0;
-    my $result;
-
-    # Check for negative prefix "not_"
-    if ($check =~ /^not_(.+)$/) {
-        $invert = 1;
-        $check = $1;
-    }
-
-    my $mask = $USER_STATUS_MASK{$check}
-        or die "Unknown user status '$check'";
-
-    my $status = testsuite_existing_user_status($username);
-    # returns 0 if status is as desired so that it can be used in assertion
-    $result = (($status & $mask) == $mask) ? 0 : 1;
-
-    if ($do_print) {
-        my $msg = $result
-                ? "User '$username' $check"
-                : "User '$username' NOT $check";
-        print "$msg";
-    }
-
-    $result = !$result if $invert;
-    print " (status $status, returning ", $result ? 1 : 0, ")\n";
-    return $result;
-}
-
-
-
 return 1
 
 # vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test08.pl
=====================================
@@ -18,7 +18,7 @@ my $cmd = "adduser --comment test --disabled-password --add-extra-groups $userna
 my %config;
 
 my @adduserconf=("/etc/adduser.conf");
-preseed_config(\@adduserconf,\%config);
+%config = read_config(@adduserconf);
 
 if (!defined (getpwnam($username))) {
 	print "Testing $cmd... ";



View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/53f6617597dcd7d3b5f3be1eb36f058279edefd8...8b28a9c3823fbfefea8bcfd69ec86aa5bd509d2f

-- 
View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/53f6617597dcd7d3b5f3be1eb36f058279edefd8...8b28a9c3823fbfefea8bcfd69ec86aa5bd509d2f
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/20260327/33013fa2/attachment-0001.htm>


More information about the Pkg-shadow-devel mailing list