[Pkg-shadow-devel] [Git][debian/adduser][wip/various-random-things] 16 commits: po: Add Georgian translation

Marc Haber (@zugschlus) gitlab at salsa.debian.org
Fri Jan 16 06:59:30 GMT 2026



Marc Haber pushed to branch wip/various-random-things at Debian / adduser


Commits:
3c3ce5fc by Temuri Doghonadze at 2026-01-15T19:57:32+01:00
po: Add Georgian translation

Thanks: Temuri Doghonadze

- - - - -
d5820aed by Marc Haber at 2026-01-15T20:14:14+01:00
rework EXISTING_ and existing_user_status

this redefines the EXISTING variables
simplifies the EXISTING states (they were overengineered and redundant)
reworks existing_user_status, improving logic and output

The new variable values are also read by the test suites and need to be
in sync

Thanks: Matt Barry

- - - - -
7411b944 by Marc Haber at 2026-01-15T20:14:14+01:00
move Marc's test10.pl to test11.pl to make Room

Git-Dch: ignore

- - - - -
6fd5b276 by Marc Haber at 2026-01-15T20:14:14+01:00
add vim helper line to testsuite files

The files need to be reflowed when working on them next time

Git-Dch: ignore

- - - - -
85ab6531 by Marc Haber at 2026-01-15T20:14:14+01:00
prepare upstream testsuite for EXISTING_

Git-Dch: ignore

- - - - -
a564b059 by Marc Haber at 2026-01-15T20:14:14+01:00
various fixes in lib_test.pm

Git-Dch: ignore

- - - - -
b4d7296f by Marc Haber at 2026-01-15T20:14:14+01:00
rework runsuite.sh

This now allows running a single test, and cleans up better

Git-Dch: ignore

- - - - -
dc304334 by Marc Haber at 2026-01-15T20:14:14+01:00
make more clear that failure was expected

Git-Dch: ignore

- - - - -
d8b8b515 by Marc Haber at 2026-01-15T20:14:14+01:00
fix testsuite warning in test08.pl

Git-Dch: ignore

- - - - -
51c81c3e by Marc Haber at 2026-01-16T07:59:20+01:00
improve log level handling

Thanks: Matt Barry
Git-Dch: ignore

- - - - -
b7725726 by Marc Haber at 2026-01-16T07:59:20+01:00
clarify documentation of exit value 31

Git-Dch: ignore

- - - - -
571f8054 by Marc Haber at 2026-01-16T07:59:20+01:00
rename to RET_INVALID_CHARS_IN_INPUT, apply to comment as well

That was RET_INVALID_CHARS_IN_INPUT previously. The check is now
applied to the comment as well and the error message adapted.

Git-Dch: ignore

- - - - -
783d4d09 by Marc Haber at 2026-01-16T07:59:20+01:00
move interactive command loops to a function

This is more streamlined and handles running on no terminal better

- - - - -
8ec9dd8a by Marc Haber at 2026-01-16T07:59:20+01:00
re-work logic around remove-home etc

Git-Dch: ignore

- - - - -
ab1adc30 by Marc Haber at 2026-01-16T07:59:20+01:00
prepare testsuite libraries to properly handle EXISTING_

this brings the simplifications to the test suite libraries

Git-Dch: ignore

- - - - -
67c314d0 by Marc Haber at 2026-01-16T07:59:20+01:00
add assert_path_is_a_file to debian/tests/lib

Git-Dch: ignore

- - - - -


19 changed files:

- AdduserCommon.pm
- AdduserRetvalues.pm
- adduser
- debian/tests/lib/AdduserTestsCommon.pm
- deluser
- doc/adduser.8
- + po/ka.po
- testsuite/lib_test.pm
- testsuite/runsuite.sh
- testsuite/test01.pl
- testsuite/test02.pl
- testsuite/test03.pl
- testsuite/test04.pl
- testsuite/test05.pl
- testsuite/test06.pl
- testsuite/test07.pl
- testsuite/test08.pl
- testsuite/test09.pl
- testsuite/test10.pl → testsuite/test11.pl


Changes:

=====================================
AdduserCommon.pm
=====================================
@@ -102,6 +102,14 @@ use constant {
     EXISTING_ID_MISMATCH => 4,
     EXISTING_LOCKED => 8,
     EXISTING_HAS_PASSWORD => 16,
+    EXISTING_EXPIRED => 32,
+    EXISTING_NOLOGIN => 64,
+};
+
+use constant {
+    STDOUTDEFLEVEL => "warn",
+    STDERRDEFLEVEL => "warn",
+    LOGMSGDEFLEVEL => "info",
 };
 
 @EXPORT = (
@@ -138,6 +146,11 @@ use constant {
     'EXISTING_ID_MISMATCH',
     'EXISTING_LOCKED',
     'EXISTING_HAS_PASSWORD',
+    'EXISTING_EXPIRED',
+    'EXISTING_NOLOGIN',
+    'STDOUTDEFLEVEL',
+    'STDERRDEFLEVEL',
+    'LOGMSGDEFLEVEL',
     'existing_user_status',
     'existing_group_status',
 );
@@ -590,35 +603,47 @@ END {
 #   new_name: the name of the user to check
 #   new_uid : the UID of the user
 # return value:
-#   bitwise combination of these constants:
-#       EXISTING_NOT_FOUND => 0
-#       EXISTING_FOUND => 1
-#       EXISTING_SYSTEM => 2
-#       EXISTING_ID_MISMATCH => 4
-#       EXISTING_LOCKED => 8
-#       EXISTING_HAS_PASSWORD => 16
-#   e.g. if the requested account name exists as a locked system user,
-#   return 8|2|1 == 11
+#   bitwise combination of the EXISTING_ constants
 sub existing_user_status {
-    my ($config, $new_name,$new_uid) = @_;
-    my ($dummy1,$pw,$uid);
+    my ($config, $user_name,$user_uid) = @_;
     my $ret = EXISTING_NOT_FOUND;
-    log_trace( "existing_user_status called with new_name %s, new_uid %s, first_system_uid %s, last_system_uid %s", $new_name, $new_uid, $config->{"first_system_uid"}, $config->{"last_system_uid"} );
-    if (($dummy1,$pw,$uid) = egetpwnam($new_name)) {
+    log_trace( "existing_user_status called with user_name %s, user_uid %s, first_system_uid %s, last_system_uid %s", $user_name, $user_uid, $config->{"first_system_uid"}, $config->{"last_system_uid"} );
+
+    # collect user data
+    my (
+        $egpwn_name, $egpwn_passwd, $egpwn_uid, $egpwn_gid, $egpwn_quota,
+        $egpwn_comment, $egpwn_gcos, $egpwn_dir, $egpwn_shell, $egpwn_expire,
+        $egpwn_rest
+    ) = egetpwnam($user_name);
+    my $shadow_line = `getent shadow $user_name`;
+    chomp $shadow_line;
+    my @shadow_fields = split /:/, $shadow_line;
+    if (defined $egpwn_uid) {
         # user with the name exists
-        log_trace( "egetpwnam(%s) returns %s, %s, %s", $new_name, $dummy1, $pw, $uid );
+        log_trace( "egetpwnam(%s) returns %s, %s, %s, %s", $user_name, $egpwn_passwd, $egpwn_uid, $egpwn_dir, $egpwn_shell );
         $ret |= EXISTING_FOUND;
-        $ret |= EXISTING_ID_MISMATCH if (defined($new_uid) && $uid != $new_uid);
+        $ret |= EXISTING_ID_MISMATCH if (defined($user_uid) && $egpwn_uid != $user_uid);
         $ret |= EXISTING_SYSTEM if
-            (($uid >= $config->{"first_system_uid"}) && ($uid <= $config->{"last_system_uid"}));
+            (($egpwn_uid >= $config->{"first_system_uid"}) && ($egpwn_uid <= $config->{"last_system_uid"}));
         $ret |= EXISTING_HAS_PASSWORD if
-            (defined $pw && $pw ne '' && $pw ne '!' && $pw !~ /^\*/);
-        $ret |= EXISTING_LOCKED if (substr($pw,0,1) eq "!");  # TODO: also check expiry?
-    } elsif ($new_uid && getpwuid($new_uid)) {
+            (defined $egpwn_passwd && $egpwn_passwd ne '' && ($egpwn_passwd =~ s/^[!*]+//r ne ''));
+
+        my $password_field = $shadow_fields[1] // '';
+        $ret |= EXISTING_LOCKED if $password_field =~ /^[!*]/;
+
+        $ret |= EXISTING_NOLOGIN if ($egpwn_shell =~ /bin\/nologin/);
+
+        my $acct_exp = $shadow_fields[7] // '';
+        if ($acct_exp && $acct_exp > 0) {
+            my $today_days = int(time / 86400);
+            $ret |= EXISTING_EXPIRED if $acct_exp < $today_days;
+        }
+
+    } elsif (defined($user_uid) && getpwuid($user_uid)) {
         # user with the uid exists
         $ret |= EXISTING_ID_MISMATCH;
     }
-    log_trace( "existing_user_status( %s, %s ) returns %s", $new_name, $new_uid, $ret );
+    log_trace( "existing_user_status( %s, %s ) returns %s (%s)", $user_name, $user_uid, $ret, existing_value_desc($ret) );
     return $ret;
 }
 
@@ -634,23 +659,37 @@ sub existing_user_status {
 #       EXISTING_ID_MISMATCH => 4
 sub existing_group_status {
     my ($config, $new_name,$new_gid) = @_;
-    my ($dummy1,$dummy2,$gid);
+    my ($gid);
     my $ret = EXISTING_NOT_FOUND;
     log_trace( "existing_group_status called with new_name %s, new_gid %s", $new_name, $new_gid );
-    if (($dummy1,$dummy2,$gid) = egetgrnam($new_name)) {
+    if ((undef,undef,$gid) = egetgrnam($new_name)) {
         # group with the name exists
         log_trace("egetgrnam %s returned successfully, gid = %s", $new_name, $gid);
         $ret |= EXISTING_FOUND;
         $ret |= EXISTING_ID_MISMATCH if (defined($new_gid) && $gid != $new_gid);
         $ret |= EXISTING_SYSTEM if
             (($gid >= $config->{"first_system_gid"}) && ($gid <= $config->{"last_system_gid"}));
-    } elsif ($new_gid && getgrgid($new_gid)) {
+    } elsif (defined($new_gid) && getgrgid($new_gid)) {
         $ret |= EXISTING_ID_MISMATCH;
     }
-    log_trace( "existing_group_status( %s, %s ) returns %s", $new_name, $new_gid, $ret );
+    log_trace( "existing_group_status( %s, %s ) returns %s (%s)", $new_name, $new_gid, $ret, existing_value_desc($ret) );
     return $ret;
 }
 
+sub existing_value_desc {
+    my ($val) = @_;
+    my @flags = ();
+    push @flags, "found" if ($val & EXISTING_FOUND);
+    push @flags, "wrongid" if $val & EXISTING_ID_MISMATCH;
+    push @flags, "system" if ($val & EXISTING_SYSTEM);
+    push @flags, "locked" if $val & EXISTING_LOCKED;
+    push @flags, "haspass" if $val & EXISTING_HAS_PASSWORD;
+    push @flags, "nologin" if $val & EXISTING_NOLOGIN;
+    push @flags, "expired" if $val & EXISTING_EXPIRED;
+    push @flags, "notfound" unless $#flags > 0;
+    return join '|', at flags
+}
+
 1;
 
 # Local Variables:


=====================================
AdduserRetvalues.pm
=====================================
@@ -20,7 +20,7 @@ use vars qw(@EXPORT $VAR1);
     'RET_ID_IN_USE',
     'RET_NO_ID_IN_RANGE',
     'RET_NO_PRIMARY_GID',
-    'RET_INVALID_CHARS_IN_NAME',
+    'RET_INVALID_CHARS_IN_INPUT',
     'RET_INVALID_HOME_DIRECTORY',
     'RET_INVALID_NAME_FROM_USERADD',
     'RET_GROUP_NOT_EMPTY',
@@ -61,8 +61,8 @@ use constant RET_NO_PRIMARY_GID => 23; # requested primary GID does not exist
 
 # object name errors
 
-use constant RET_INVALID_CHARS_IN_NAME => 31; # the provided name contains invalid characters
-use constant RET_INVALID_HOME_DIRECTORY => 32; # the provided name contains invalid characters
+use constant RET_INVALID_CHARS_IN_INPUT => 31; # provided input (name/comment) contains invalid characters
+use constant RET_INVALID_HOME_DIRECTORY => 32; # the provided home directory is invalid
 use constant RET_INVALID_NAME_FROM_USERADD => 32; # useradd returned 19 "invalid user or group name"
 
 # group membership errors


=====================================
adduser
=====================================
@@ -103,9 +103,9 @@ $0 =~ s+.*/++;
 our $action;
 our $verbose;		# should we be verbose?
 my $name_check_level = 0;		# should we allow bad names?
-our $stdoutmsglevel = "warn";
-our $stderrmsglevel = "warn";
-our $logmsglevel = "info";
+our $stdoutmsglevel = undef;
+our $stderrmsglevel = undef;
+our $logmsglevel = undef;
 my $allow_badname = 0;		# should we allow bad names?
 my $ask_passwd = 1;		# ask for a passwd?
 my $disabled_login = 0;		# leave the new account disabled?
@@ -199,10 +199,11 @@ if (!@configfiles) {
 
 # make sure that message levels apply for reading configuration
 # this will be overridden again after reading configuration
-$stdoutmsglevel = sanitize_string($stdoutmsglevel);
-$stderrmsglevel = sanitize_string($stderrmsglevel);
-$logmsglevel = sanitize_string($logmsglevel);
-set_msglevel( $stderrmsglevel, $stdoutmsglevel, $logmsglevel );
+set_msglevel( 
+    sanitize_string($stderrmsglevel or STDERRDEFLEVEL),
+    sanitize_string($stdoutmsglevel or STDOUTDEFLEVEL),
+    sanitize_string($logmsglevel or LOGMSGDEFLEVEL),
+);
 log_trace("ARGV %s", join(@ARGV,"-"));
 log_trace("special_home %s", $special_home);
 log_trace("special_home %s", encode($charset, $special_home));
@@ -223,8 +224,13 @@ if( $> != 0) {
     log_fatal( mtx("Only root may add a user or group to the system.") );
     exit( RET_ROOT_NEEDED );
 }
-
-# TODO: Handle configuration file input, allow bare input there.
+# ARGV > adduser.conf > (default)
+$stdoutmsglevel //= $config{'stdoutmsglevel'};
+$stdoutmsglevel //= STDOUTDEFLEVEL;
+$stderrmsglevel //= $config{'stderrmsglevel'}; 
+$stderrmsglevel //= STDERRDEFLEVEL;
+$logmsglevel    //= $config{'logmsglevel'};
+$logmsglevel    //= LOGMSGDEFLEVEL;
 $stdoutmsglevel = sanitize_string($stdoutmsglevel);
 $stderrmsglevel = sanitize_string($stderrmsglevel);
 $logmsglevel = sanitize_string($logmsglevel);
@@ -235,6 +241,7 @@ if( defined $verbose ) {
     } elsif( $verbose == 1 ) {
         set_msglevel( $stderrmsglevel, "info", $logmsglevel );
     } elsif( $verbose == 2 ) {
+        set_msglevel( $stdoutmsglevel, "debug", $logmsglevel );
         set_msglevel( $stderrmsglevel, "debug", $logmsglevel );
     }
 }
@@ -364,7 +371,8 @@ if ( defined $comment_tainted ) {
     log_trace("check comment %s for unwanted chars", $special_home);
     # do not sanitize, can't be done without libperl
     if ( $comment_tainted !~ qr/^([^\x00-\x1F\x7F:]*)$/ ) {
-        die( "unwanted chars in comment" );
+        log_fatal( mtx("unwanted chars in comment") );
+        exit( RET_INVALID_CHARS_IN_INPUT );
     }
 }
 if( defined $special_shell ) {
@@ -403,8 +411,8 @@ if( defined $gid_option ) {
 }
 
 if ((defined($special_home)) && ($special_home !~ m+^/+ )) {
-  log_fatal( mtx("The home dir must be an absolute path.") );
-  exit( RET_INVALID_HOME_DIRECTORY );
+    log_fatal( mtx("The home dir must be an absolute path.") );
+    exit( RET_INVALID_HOME_DIRECTORY );
 }
 
 
@@ -942,49 +950,20 @@ if ($action eq "adduser") {
     create_homedir ($no_copy_skel ? 0 : 1, 0); # copy skeleton data
 
     # useradd without -p has left the account disabled (password string is '!')
-    my $yesexpr = langinfo(YESEXPR());
-    my $noexpr = langinfo(NOEXPR());
     if ($ask_passwd) {
-        PASSWD: for (;;) {
-            my $passwd = which('passwd');
-            my $ok = systemcall_or_warn($passwd, $new_name);
-            $ok = $ok >> 8;
-            log_debug( "systemcall_or_warn %s %s return value %s", $passwd, $new_name, $ok);
-            if ($ok != 0) {
-                my $answer;
-                # hm, error, should we break now?
-                if ($ok == 1) {
-                    log_warn( mtx("Permission denied"));
-                } elsif ($ok == 2) {
-                    log_warn( mtx("invalid combination of options"));
-                } elsif ($ok == 3) {
-                    log_warn( mtx("unexpected failure, nothing done"));
-                } elsif ($ok == 4) {
-                    log_warn( mtx("unexpected failure, passwd file missing"));
-                } elsif ($ok == 5) {
-                    log_warn( mtx("passwd file busy, try again"));
-                } elsif ($ok == 6) {
-                    log_warn( mtx("invalid argument to option"));
-                } elsif ($ok == 10) {
-                    log_warn( mtx("wrong password given or password retyped incorrectly"));
-                } else {
-                    log_warn( mtx("unexpected return code %s given from passwd"), $ok );
-                }
+        my %passwd_errors = (
+            1  => "Permission denied",
+            2  => "Invalid combination of options",
+            3  => "Unexpected failure, nothing done",
+            4  => "Passwd file missing",
+            5  => "Passwd file busy, try again",
+            6  => "Invalid argument to option",
+            10 => "Wrong password given or retyped incorrectly",
+        );
 
-                # Translators: [y/N] has to be replaced by values defined in your
-                # locale.  You can see by running "locale noexpr" which regular
-                # expression will be checked to find positive answer.
-                PROMPT: for (;;) {
-                    print (gtx("Try again? [y/N] "));
-                    chop ($answer=<STDIN>);
-                    last PROMPT if ($answer =~ m/$yesexpr/o);
-                    last PASSWD if ($answer =~ m/$noexpr/o);
-                    last PASSWD if (!$answer);
-                }
-            } else {
-                last; ## passwd ok
-            }
-        }
+        interactive_command_with_confirmation(
+            'passwd', $new_name, \%passwd_errors
+        );
     }
 
     if (defined($comment_tainted)) {
@@ -992,22 +971,14 @@ if ($action eq "adduser") {
     } elsif ($uid_pool{$new_name}{'comment'}) {
         ch_comment($new_name, $uid_pool{$new_name}{'comment'});
     } else {
-        my $noexpr = langinfo(NOEXPR());
-        my $yesexpr = langinfo(YESEXPR());
-        CHFN: for (;;) {
-            my $chfn = &which('chfn');
-            systemcall($chfn, $new_name);
-            # Translators: [y/N] has to be replaced by values defined in your
-            # locale.  You can see by running "locale yesexpr" which regular
-            # expression will be checked to find positive answer.
-            PROMPT: for (;;) {
-                print (gtx("Is the information correct? [Y/n] "));
-                chop (my $answer=<STDIN>);
-                last PROMPT if ($answer =~ m/$noexpr/o);
-                last CHFN if ($answer =~ m/$yesexpr/o);
-                last CHFN if (!$answer);
-            }
-        }
+        my %chfn_errors = (
+            1 => "Permission denied",
+            2  => "Invalid combination of options",
+        );
+
+        interactive_command_with_confirmation(
+            'chfn', $new_name, \%chfn_errors, 1
+        );
     }
 
     if ( ( $add_extra_groups || $config{"add_extra_groups"} ) && defined($config{"extra_groups"}) ) {
@@ -1309,21 +1280,21 @@ sub sanitize_name {
         # this check cannot be turned off
         log_err( mtx("To avoid ambiguity with numerical UIDs, usernames which" .
             "resemble numbers or negative numbers are not allowed.") );
-        exit( RET_INVALID_CHARS_IN_NAME );
+        exit( RET_INVALID_CHARS_IN_INPUT );
     }
 
     log_trace("sanitize_name testing single or double period");
     if ( $name =~ qr/^\.\.?$/ ) {
         # this check cannot be turned off
         log_err( mtx("Usernames must not be a single or a double period.") );
-        exit( RET_INVALID_CHARS_IN_NAME );
+        exit( RET_INVALID_CHARS_IN_INPUT );
     }
 
     log_trace("sanitize_name testing > 32 chars");
     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.") );
-        exit( RET_INVALID_CHARS_IN_NAME );
+        exit( RET_INVALID_CHARS_IN_INPUT );
     }
 
     log_trace("sanitize_name testing %s against insane chars %s", $name, def_min_regex);
@@ -1333,7 +1304,7 @@ sub sanitize_name {
             "dash, plus sign, or tilde, and it must not contain any of the" .
             "following: colon, comma, slash, or any whitespace characters" .
             "including spaces, tabs, and newlines.") );
-        exit( RET_INVALID_CHARS_IN_NAME );
+        exit( RET_INVALID_CHARS_IN_INPUT );
     }
 
     log_trace("sanitize_name checking %s against %s (%s)", $name, $name_regex, $name_regex_var);
@@ -1350,7 +1321,7 @@ sub sanitize_name {
             "compatibility with Samba machine accounts, \$ is also supported" .
             "at the end of the username.  (Use the `--allow-all-names' option" .
             "to bypass this restriction.)") );
-        exit( RET_INVALID_CHARS_IN_NAME );
+        exit( RET_INVALID_CHARS_IN_INPUT );
     }
 
     if ($name_check_level) {
@@ -1360,7 +1331,7 @@ sub sanitize_name {
             "configured via the %s configuration variable.  Use the" .
             "`--allow-bad-names' option to relax this check or reconfigure" .
             "%s in configuration."), $name_regex_var, $name_regex_var );
-        exit( RET_INVALID_CHARS_IN_NAME );
+        exit( RET_INVALID_CHARS_IN_INPUT );
     }
 
     log_trace("sanitize_name checking %s against %s", $name, anynamere);
@@ -1466,6 +1437,64 @@ sub user_is_member {
 }
 
 
+sub interactive_command_with_confirmation {
+    my ($cmd, $user_name, $error_map, $always_confirm) = @_;
+
+    $always_confirm //= 0;   # default to false if not provided
+    return unless -t STDIN;  # skip non-interactive
+
+    my $yesexpr = langinfo(YESEXPR);
+    my $noexpr  = langinfo(NOEXPR);
+
+    my $retry = 1;
+
+    while ($retry) {
+        $retry = 0;
+
+        my $full_cmd = which($cmd);
+        my $ok = systemcall_or_warn($full_cmd, $user_name);
+        $ok = $ok >> 8;  # extract exit code
+
+        log_debug("systemcall_or_warn %s %s return value %s", $full_cmd, $user_name, $ok);
+
+        if ($ok != 0 && $error_map) {
+            if (exists $error_map->{$ok}) {
+                log_warn($error_map->{$ok});
+            } else {
+                log_warn("Unexpected return code %s from %s", $ok, $cmd);
+            }
+        }
+
+        # Prompt for confirmation if requested
+        if ($always_confirm) {
+            my $answer;
+            do {
+                print gtx("Is the information correct? [Y/n] ");
+                $answer = <STDIN>;
+
+                unless (defined $answer) {
+                    warn "No input available, assuming 'yes'\n";
+                    last;
+                }
+
+                chomp($answer);
+                $answer =~ s/^\s+|\s+$//g;
+
+                if ($answer =~ m/$yesexpr/o || $answer eq '') {
+                    $retry = 0;  # accepted
+                    last;
+                } elsif ($answer =~ m/$noexpr/o) {
+                    $retry = 1;  # retry command
+                } else {
+                    print "Please answer yes or no.\n";
+                    $answer = undef;  # repeat prompt
+                }
+
+            } while (!defined $answer);
+        }
+    }
+}
+
 sub cleanup {
     if ($undohome) {
         log_info( mtx("Removing directory `%s' ..."), $undohome);


=====================================
debian/tests/lib/AdduserTestsCommon.pm
=====================================
@@ -30,6 +30,21 @@ END {
     if (-f '/var/cache/adduser/tests/state.tar');
 }
 
+use constant {
+    EXISTING_NOT_FOUND => 0,
+    EXISTING_FOUND => 1,
+    EXISTING_SYSTEM => 2,
+    EXISTING_ID_MISMATCH => 4,
+    EXISTING_LOCKED => 8,
+    EXISTING_HAS_PASSWORD => 16,
+    EXISTING_EXPIRED => 32,
+    EXISTING_NOLOGIN => 64,
+    SYS_MIN => 100,
+    SYS_MAX => 999,
+    USER_MIN => 1000,
+    USER_MAX => 9999,
+};
+
 my $charset = langinfo(CODESET);
 binmode(STDOUT, ":encoding($charset)");
 binmode(STDERR, ":encoding($charset)");
@@ -293,7 +308,7 @@ sub assert_path_has_ownership {
 
 sub assert_path_is_a_file {
     my $path = shift;
-    ok(-f $path, "path is a file $path");
+    ok(-f $path, "path is a file: $path");
 }
 
 sub assert_path_is_a_directory {
@@ -459,6 +474,83 @@ sub apply_config_hash {
     close(CONF);
 }
 
+sub assert_user_status {
+    my ($username, $mask, $desc, $invert) = @_;
+
+    # Determine inversion from description if it starts with 'NOT '
+    if ($desc =~ /^NOT\s+(.*)/i) {
+        die "Cannot set invert=1 if description starts with NOT" if $invert;
+        $desc = $1;      # remove 'NOT ' prefix
+        $invert = 1;     # automatically invert
+    }
+
+    $invert //= 0;       # default to positive assertion
+
+    my $status = existing_user_status($username);
+    my $ok = ($status & $mask) == $mask;
+    $ok = !$ok if $invert;
+
+    my $message = $invert
+        ? "User '$username' is NOT $desc (status $status)"
+        : "User '$username' $desc (status $status)";
+
+    ok($ok, $message);
+}
+
+
+sub 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 =~ /^[!*]/);
+
+        my $age = `chage -l $user_name`;
+        if ($age =~ /Account expires\s*:\s*(.+)/i) {
+            my $exp = $1;
+            use POSIX qw(strftime);
+            use Time::Local;
+            if ($exp ne 'never') {
+                my $expiry_epoch = eval { `date -d "$exp" +%s` };
+                my $now = time;
+                $ret |= EXISTING_EXPIRED if ($expiry_epoch < $now);
+            }
+        }
+    } elsif ($user_uid && getpwuid($user_uid)) {
+        $ret |= EXISTING_ID_MISMATCH;
+    }
+    return $ret;
+}
+
+sub existing_group_status {
+    my ($group_name,$group_gid) = @_;
+    my $gid;
+    my $ret = EXISTING_NOT_FOUND;
+    if ((undef,undef,$gid) = egetgrnam($group_name)) {
+        $ret |= EXISTING_FOUND;
+        $ret |= EXISTING_ID_MISMATCH if (defined($group_gid) && $gid != $group_gid);
+        $ret |= EXISTING_SYSTEM if \
+            ($gid >= SYS_MIN && $gid <= SYS_MAX);
+    } elsif ($group_gid && getgrgid($group_gid)) {
+        $ret |= EXISTING_ID_MISMATCH;
+    }
+    return $ret;
+}
+
 1;
 
 # vim: tabstop=4 shiftwidth=4 expandtab


=====================================
deluser
=====================================
@@ -265,21 +265,28 @@ if(defined($group)) {
 if($action eq "deluser") {
     my($name, $passwd, ,$uid, $rest);
 
+    if (($config{remove_home} || $config{remove_all_files} || $config{backup}) && ($install_more_packages)) {
+        log_warn( mtx("In order to use the --remove-home, --remove-all-files, and --backup features, you need to install the `perl' package. To accomplish that, run apt-get install perl.") );
+        $config{remove_home}=undef;
+        $config{remove_all_files}=undef;
+        $config{backup}=undef;
+        $config{backup_to}=undef;
+        exit ( RET_MORE_PACKAGES) unless( $config{"system"} );
+    }
+
+    my (
+        $egpwn_name, $egpwn_passwd, $egpwn_uid, $egpwn_gid, $egpwn_quota,
+        $egpwn_comment, $egpwn_gcos, $egpwn_dir, $egpwn_shell, $egpwn_expire,
+        $egpwn_rest
+    ) = egetpwnam(encode($charset, $user));
+
     # 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 (($config{remove_home} || $config{remove_all_files} || $config{backup}) && ($install_more_packages)) {
-            log_warn( mtx("In order to use the --remove-home, --remove-all-files, and --backup features, you need to install the `perl' package. To accomplish that, run apt-get install perl.") );
-            $config{remove_home}=undef;
-            $config{remove_all_files}=undef;
-            $config{backup}=undef;
-            $config{backup_to}=undef;
-        }
-
-        if( ($name, $passwd, $uid, $rest) = egetpwnam(encode($charset, $user)) ) {
-            if ( ($uid < $config{"first_system_uid"} ||
-                $uid > $config{"last_system_uid" } ) ) {
+        if( defined $egpwn_uid ) {
+            if ( ($egpwn_uid < $config{"first_system_uid"} ||
+                $egpwn_uid > $config{"last_system_uid" } ) ) {
                 log_warn( mtx("The user `%s' is not a system user. Exiting."), $user);
                 exit( RET_WRONG_OBJECT_PROPERTIES );
             }
@@ -287,14 +294,9 @@ if($action eq "deluser") {
             log_info( mtx("The user `%s' does not exist, but --system was given. Exiting."), $user);
             exit( RET_OK );
         }
-    } else {
-        if (($config{remove_home} || $config{remove_all_files} || $config{backup}) && ($install_more_packages)) {
-            log_fatal( mtx("In order to use the --remove-home, --remove-all-files, and --backup features, you need to install the `perl' package. To accomplish that, run apt-get install perl.") );
-            exit( RET_MORE_PACKAGES );
-        }
     }
 
-    unless(exist_user($user)) {
+    unless(defined $egpwn_uid) {
         log_fatal( mtx("The user `%s' does not exist."), $user );
         exit( RET_OBJECT_DOES_NOT_EXIST );
     }


=====================================
doc/adduser.8
=====================================
@@ -613,8 +613,8 @@ There is no group with the requested GID for the primary group
 for a new user.
 .TP
 .B 31
-The chosen name for a new user or a new group does not conform to
-the selected naming rules.
+The chosen name or comment for a new user or a new group
+does not conform to the selected naming rules.
 .TP
 .B 32
 The home directory of a new user must be an absolute path.


=====================================
po/ka.po
=====================================
@@ -0,0 +1,905 @@
+# Translation of adduser program into LANGUAGE
+# Files: po/adduser.pot doc/po4a/po/adduser.pot
+# Copyright: 1994 Debian Association, Inc.
+#            1995 Ian A. Murdock <imurdock at debian.org>
+#            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>
+#            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>
+#            2016, 2023 Dr. Helge Kreutzmann <debian at helgefjell.de>
+#            2016 Nis Martensen <nis.martensen at web.de>
+#            2021-2022 Jason Franklin <jason at oneway.dev>
+#            2022 Matt Barry <matt at hazelmollusk.org>
+#            2023 Guillem Jover <guillem at debian.org>
+# License: GPL-2+
+#
+# This file is distributed under the same license as the adduser package.
+# Temuri Doghonadze <temuri.doghonadze at gmail.com>, 2026.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: adduser 3.153~\n"
+"Report-Msgid-Bugs-To: adduser at packages.debian.org\n"
+"POT-Creation-Date: 2025-09-14 13:07+0200\n"
+"PO-Revision-Date: 2026-01-15 12:44+0100\n"
+"Last-Translator: Temuri Doghonadze <temuri.doghonadze at gmail.com>\n"
+"Language-Team: Georgian <(nothing)>\n"
+"Language: ka\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.8\n"
+
+#: adduser:229
+msgid "Only root may add a user or group to the system."
+msgstr ""
+"სისტემაში მომხმარებლებისა და ჯგუფების დამატება, მხოლოდ, root-ს შეუძლია."
+
+#: adduser:266 deluser:200
+msgid "No options allowed after names."
+msgstr "სახელების შემდეგ პარამეტრები დაშვებული არა."
+
+#: adduser:275 deluser:208
+msgid "Only one or two names allowed."
+msgstr "დაშვებულია, მხოლოდ, ერთი, ან ორი სახელი."
+
+#: adduser:282
+msgid "Specify only one name in this mode."
+msgstr "ამ რეჟიმში, მხოლოდ, ერთი სახელის მითითებაა შესაძლებელი."
+
+#: adduser:286
+msgid "addgroup with two arguments is an unspecified operation."
+msgstr "addgroup ორი არგუმენტით მიუთითებელი ოპერაციაა."
+
+#: adduser:318
+msgid "The --group, --ingroup, and --gid options are mutually exclusive."
+msgstr "პარამეტრები --group, --ingroup და --gid ურთიერთგამომრიცხავია."
+
+#: adduser:403
+msgid "The home dir must be an absolute path."
+msgstr "საწყისი საქაღალდე აბსოლუტური ბილიკი უნდა იყოს."
+
+#: adduser:445
+#, perl-format
+msgid "The group `%s' already exists, but has a different GID. Exiting."
+msgstr "ჯგუფი '%s' უკვე არსებობს, მაგრამ სხვა GID-ით. გასვლა."
+
+#: adduser:451
+#, perl-format
+msgid "The group `%s' already exists as a system group."
+msgstr "ჯგუფი '%s' უკვე არსებობს სისტემური ჯგუფის სახით."
+
+#: adduser:454
+#, perl-format
+msgid "The group `%s' already exists and is not a system group. Exiting."
+msgstr "ჯგუფი '%s' უკვე არსებობს და სისტემური სისტემური ჯგუფი არაა."
+
+#: adduser:459 adduser:499
+#, perl-format
+msgid "The GID `%s' is already in use."
+msgstr "GID '%s' უკვე გამოიყენება."
+
+#: adduser:470
+#, perl-format
+msgid "No GID is available in the range %d-%d (FIRST_SYS_GID - LAST_SYS_GID)."
+msgstr "შუალედში %d-%d (FIRST_SYS_GID - LAST_SYS_GID) GID ხელმისაწვდომი არაა."
+
+#: adduser:472 adduser:518
+#, perl-format
+msgid "The group `%s' was not created."
+msgstr "ჯგუფი `%s' არ შექმნილა."
+
+#: adduser:478 adduser:523
+#, perl-format
+msgid "Adding group `%s' (GID %d) ..."
+msgstr "ჯგუფის `%s' (GID %d) დამატება..."
+
+#: adduser:495 adduser:1232
+#, perl-format
+msgid "The group `%s' already exists."
+msgstr "ჯგუფი '%s' უკვე არსებობს."
+
+#: adduser:516
+#, perl-format
+msgid "No GID is available in the range %d-%d (FIRST_GID - LAST_GID)."
+msgstr "შუალედში %d-%d ((FIRST_GID - LAST_GID) GID ხელმისაწვდომი არაა."
+
+#: adduser:540 deluser:296
+#, perl-format
+msgid "The user `%s' does not exist."
+msgstr "მომხმარებელი '%s' არ არსებობს."
+
+#: adduser:544 adduser:1009 adduser:1242 deluser:456 deluser:459
+#, perl-format
+msgid "The group `%s' does not exist."
+msgstr "ჯგუფი \"%s\" არ არსებობს."
+
+#: adduser:549 adduser:1013
+#, perl-format
+msgid "The user `%s' is already a member of `%s'."
+msgstr "მომხმარებელი '%s' უკვე არის '%s'-ის წევრი."
+
+#: adduser:553 adduser:1019
+#, perl-format
+msgid "Adding user `%s' to group `%s' ..."
+msgstr "მომხმარებლის '%s' ჩამატება ჯგუფში '%s'..."
+
+#: adduser:571
+#, perl-format
+msgid "The user `%s' already exists, but is not a system user. Exiting."
+msgstr ""
+"მომხმარებელი %s უკვე არსებობს, მაგრამ სისტემური მომხმარებელი არაა. გასვლა."
+
+#: adduser:575
+#, perl-format
+msgid "The user `%s' already exists with a different UID. Exiting."
+msgstr "მომხმარებელი '%s' უკვე არსებობს, მაგრამ სხვა UID-ით. გასვლა."
+
+#: adduser:579
+#, perl-format
+msgid "The system user `%s' already exists. Exiting.\n"
+msgstr "სისტემური მომხმარებელი '%s' უკვე არსებობს.\n"
+
+#: adduser:593
+#, perl-format
+msgid ""
+"No UID/GID pair is available in the range %d-%d (FIRST_SYS_UID - "
+"LAST_SYS_UID)."
+msgstr ""
+"შუალედში %d-%d (FIRST_SYS_UID - LAST_SYS_UID) UID/GID წყვილები ხელმისაწვდომი "
+"არაა."
+
+#: adduser:596 adduser:612 adduser:712 adduser:826 adduser:832
+#, perl-format
+msgid "The user `%s' was not created."
+msgstr "მომხმარებელი \"%s\" არ შექმნილა."
+
+#: adduser:609
+#, perl-format
+msgid "No UID is available in the range %d-%d (FIRST_SYS_UID - LAST_SYS_UID)."
+msgstr ""
+"შუალედში %d-%d (FIRST_SYS_UID - LAST_SYS_UID) UID-ები ხელმისაწვდომი არაა."
+
+#: adduser:620
+msgid "Neither ingroup option nor gid given."
+msgstr "მითითებულია არც პარამეტრი ingroup, არც GID."
+
+#: adduser:632
+msgid "Neither ingroup option nor gid given and make_group_also unset."
+msgstr ""
+"მითითებულია არც პარამეტრი ingroup, არც GID და make_group_also დაყენებული "
+"არაა."
+
+#: adduser:636
+#, perl-format
+msgid "Adding system user `%s' (UID %d) ..."
+msgstr "ემატება სისტემური მომხმარებელი `%s' (UID %d) ..."
+
+#: adduser:640
+#, perl-format
+msgid "Adding new group `%s' (GID %d) ..."
+msgstr "ემატება ახალი ჯგუფი `%s' (GID %d) ..."
+
+#: adduser:651 adduser:897
+#, perl-format
+msgid "The home dir %s you specified already exists.\n"
+msgstr "საწყისი საქაღალდე %s, რომელიც მიუთითეთ, უკვე არსებობს.\n"
+
+#: adduser:654 adduser:900
+#, perl-format
+msgid "The home dir %s you specified can't be accessed: %s\n"
+msgstr "საწყის საქაღალდესთან %s, რომელიც მიუთითეთ, წვდომა შეუძლებელია: %s\n"
+
+#: adduser:658
+#, perl-format
+msgid "Adding new user `%s' (UID %d) with group `%s' ..."
+msgstr "ემატება ახალი მომხმარებელი `%s' (UID %d) ჯგუფით `%s' ..."
+
+#: adduser:711
+msgid ""
+"USERS_GID and USERS_GROUP both given in configuration. This is an error."
+msgstr ""
+"კონფიგურაციაში მითითებულია ორივე, USERS_GID და USERS_GROUP. ეს შეცდომაა."
+
+#: adduser:802
+#, perl-format
+msgid "Adding user `%s' ..."
+msgstr "ემატება მომხმარებელი `%s' ..."
+
+#: adduser:825
+#, perl-format
+msgid "No UID/GID pair is available in the range %d-%d (FIRST_UID - LAST_UID)."
+msgstr ""
+"შუალედში %d-%d (FIRST_UID - LAST_UID) UID/GID წყვილები ხელმისაწვდომი არაა."
+
+#: adduser:831
+msgid ""
+"USERGROUPS=no, USER_GID=-1 and USERS_GROUP empty. A user needs a primary "
+"group!"
+msgstr ""
+"USERGROUPS=no, USER_GID=-1 და USERS_GROUP ცარიელია. მომხმარებელს ძირითადი "
+"ჯგუფი სჭირდება!"
+
+#: adduser:871
+msgid "Internal error interpreting parameter combination"
+msgstr "პარამეტრების კომბინაციის ინტერპრეტაციის შიდა შეცდომა"
+
+#: adduser:882
+#, perl-format
+msgid "Adding new group `%s' (%d) ..."
+msgstr "ემატება ახალი ჯგუფი `%s' (%d) ..."
+
+#: adduser:885
+#, perl-format
+msgid "Adding new group `%s' (new group ID) ..."
+msgstr "ემატება ახალი ჯგუფი `%s' (ახალი ჯგუფის ID) ..."
+
+#: adduser:888
+#, perl-format
+msgid "new group '%s' created with GID %d"
+msgstr "შეიქმნა ახალი ჯგუფი '%s' GID-ით %d"
+
+#: adduser:907
+#, perl-format
+msgid "Adding new user `%s' (%d) with group `%s (%d)' ..."
+msgstr "ემატება ახალი მომხმარებელი `%s' (%d) ჯგუფით `%s (%d)' ..."
+
+#: adduser:944
+msgid "Permission denied"
+msgstr "წვდომა აკრძალულია"
+
+#: adduser:946
+msgid "invalid combination of options"
+msgstr "პარამეტრების არასწორი კომბინაცია"
+
+#: adduser:948
+msgid "unexpected failure, nothing done"
+msgstr "მოულოდნელი ჩავარდნა. არაფერი გაკეთებულა"
+
+#: adduser:950
+msgid "unexpected failure, passwd file missing"
+msgstr "მოულოდნელი ჩავარდნა. ფაილი passwd აღმოჩენილი არაა"
+
+#: adduser:952
+msgid "passwd file busy, try again"
+msgstr "ფაილი passwd დაკავებულია. კიდევ სცადეთ"
+
+#: adduser:954
+msgid "invalid argument to option"
+msgstr "არასწორი არგუმენტი პარამეტრისთვის"
+
+#: adduser:956
+msgid "wrong password given or password retyped incorrectly"
+msgstr "გადაცემული პაროლი არასწორია, ან პაროლი გამეორებით არასწორად აკრიფეთ"
+
+#: adduser:958
+#, perl-format
+msgid "unexpected return code %s given from passwd"
+msgstr "passwd-დან დაბრუნდა მოულოდნელი დაბრუნების კოდი %s"
+
+#: adduser:965
+msgid "Try again? [y/N] "
+msgstr "ვცადო კიდევ ერთხელ? [y(დიახ)/N(არა)] "
+
+#: adduser:991
+msgid "Is the information correct? [Y/n] "
+msgstr "ეს ინფორმაცია სწორია? [Y/n] "
+
+#: adduser:1005
+#, perl-format
+msgid "Adding new user `%s' to supplemental / extra groups `%s' ..."
+msgstr "მომხმარებლის `%s' ჩამატება დამატებით ჯგუფებში `%s' ..."
+
+#: adduser:1029
+#, perl-format
+msgid "Setting quota for user `%s' to values of user `%s' ..."
+msgstr ""
+"კვოტის დაყენება მომხმარებლისთვის `%s' მნიშვნელობებზე მომხმარებლიდან`%s' ..."
+
+#: adduser:1067
+#, perl-format
+msgid "Not creating `%s'."
+msgstr "`%s' არ შეიქმნება."
+
+#: adduser:1069
+#, perl-format
+msgid "Not creating home directory `%s' as requested."
+msgstr "როგორც მოთხოვნილია, საწყისი საქაღალდე `%s' არ შეიქმნება."
+
+#: adduser:1072
+#, perl-format
+msgid "The home directory `%s' already exists.  Not touching this directory."
+msgstr "საწყისის საქაღალდე '%s' უკვე არსებობს.  საქაღალდეს ხელი არ დაედება."
+
+#: adduser:1078
+#, perl-format
+msgid ""
+"Warning: The home directory `%s' does not belong to the user you are "
+"currently creating."
+msgstr ""
+"გაფრთხილება; საწყისი საქაღალდე '%s' არ ეკუთვნის მომხმარებელს, რომელიც ახლა "
+"იქმნება."
+
+#: adduser:1082
+#, perl-format
+msgid "Creating home directory `%s' ..."
+msgstr "იქმნება საწყისი საქაღალდე `%s' ..."
+
+#: adduser:1085
+#, perl-format
+msgid "Couldn't create home directory `%s': %s."
+msgstr "ვერ შევქმენი საწყისი საქაღალდე '%s': %s."
+
+#: adduser:1099
+#, perl-format
+msgid "Copying files from `%s' ..."
+msgstr "`%s'-დან ფაილების კოპირება ..."
+
+#: adduser:1102
+#, perl-format
+msgid "fork for `find' failed: %s"
+msgstr "fork() ბრძანებისთვის `find' ჩავარდა: %s"
+
+#: adduser:1214
+#, perl-format
+msgid "The user `%s' already exists, and is not a system user."
+msgstr "მომხმარებელი '%s' უკვე არსებობს, მაგრამ სისტემური მომხმარებელი არაა."
+
+#: adduser:1223
+#, perl-format
+msgid "The user `%s' already exists."
+msgstr "მომხმარებელი '%s' უკვე არსებობს."
+
+#: adduser:1236
+#, perl-format
+msgid "The GID %d is already in use."
+msgstr "GID %d უკვე გამოიყენება."
+
+#: adduser:1246
+#, perl-format
+msgid "No group with GID %d found."
+msgstr "ჯგუფი GID-ით %d აღმოჩენილი არაა."
+
+#: adduser:1315
+#, perl-format
+msgid "%s/%s is neither a dir, file, nor a symlink."
+msgstr "%s/%s არც საქაღალდეა, არც ფაილი, არც სიმბმული."
+
+#: adduser:1348
+msgid ""
+"To avoid ambiguity with numerical UIDs, usernames whichresemble numbers or "
+"negative numbers are not allowed."
+msgstr ""
+"რიცხვით UID-ებთან გაურკვევლობის თავიდან ასაცილებლად მომხმარებლის სახელები, "
+"რომლებიც რიცხვებს, ან უარყოფით რიცხვებს წააგავს, დაშვებული არაა."
+
+#: adduser:1356
+msgid "Usernames must not be a single or a double period."
+msgstr "მომხმარებლის სახელები ერთი ან ორი ჰარე არ უნდა იყოს."
+
+#: adduser:1363
+msgid "Usernames must be no more than 32 bytes in length."
+msgstr "მომხმარებლის სახელები სიგრძეში 32 ბაიტზე მეტი არ უნდა იყოს."
+
+#: adduser:1370
+msgid ""
+"To avoid problems, the username must not start with adash, plus sign, or "
+"tilde, and it must not contain any of thefollowing: colon, comma, slash, or "
+"any whitespace charactersincluding spaces, tabs, and newlines."
+msgstr ""
+"პრობლემების თავიდან ასაცილებლად, მომხმარებლის სახელი არ უნდა იწყებოდეს "
+"ტირეთი, პლუსით, ან ტილდათი და ის არ უნდა შეიცავდეს შემდეგ სიმბოლოებს: "
+"ორწერტილი, მძიმე, დახრილი ხაზი და ნებისმიერ გამოტოვების სიმბოლოს, "
+"როგორებიცაა, ჰარე, ტაბულაცია და ხაზის გადატანა."
+
+#: adduser:1385
+msgid ""
+"To avoid problems, the username should consist only ofletters, digits, "
+"underscores, periods, at signs and dashes, andnot start with a dash (as "
+"defined by IEEE Std 1003.1-2001). Forcompatibility with Samba machine "
+"accounts, $ is also supportedat the end of the username.  (Use the `--allow-"
+"all-names' optionto bypass this restriction.)"
+msgstr ""
+"პრობლემების თავიდან ასაცილებლად მომხმარებლის სახელი უნდა შეიცავდეს ლათინურ "
+"ასოებს, ციფრებს, ქვედა ტირეებს, წერტილებს, @ ნიშნებს და ტირეებს და არ უნდა "
+"იწყებოდნენ ტირეთი (აღწერილია სტანდარტში IEEE 1003.1-2001). Samba-ის მანქანის "
+"ანგარიშებთან თავსებადობისთვის $ ასევე მხარდაჭერილია მომხმარებლის სახელის "
+"ბოლოში.   (ამ შეზღუდვის თავიდან ასაცილებლად გამოიყენეთ პარამეტრი '--allow-"
+"all-names.)"
+
+#: adduser:1395
+msgid "Allowing use of questionable username."
+msgstr "საეჭვო მომხმარებლის სახელის გამოყენების დაშვება."
+
+#: adduser:1397
+#, perl-format
+msgid ""
+"Please enter a username matching the regular expressionconfigured via the %s "
+"configuration variable.  Use the`--allow-bad-names' option to relax this "
+"check or reconfigure%s in configuration."
+msgstr ""
+"შეიყვანეთ მომხმარებლის სახელი, რომელიც ემთხვევა რეგულარულ გამოსახულებას, "
+"რომელიც მორგებულია %s კონფიგურაციის ცვლადით.   გამოიყენეთ პარამეტრი '--allow-"
+"bad-names', რომ მოადუნოთ ეს შემოწმება, ან შეცვალეთ %s კონფიგურაციაში."
+
+#: adduser:1426
+#, perl-format
+msgid "Selecting UID from range %d to %d ...\n"
+msgstr "UID-ის არჩევა შუალედიდან %d - %d ...\n"
+
+#: adduser:1449
+#, perl-format
+msgid "Selecting GID from range %d to %d ..."
+msgstr "GID-ის არჩევა შუალედიდან %d - %d ..."
+
+#: adduser:1473
+#, perl-format
+msgid "Selecting UID/GID from range %d to %d ..."
+msgstr "UID/GID-ის არჩევა შუალედიდან %d - %d ..."
+
+#: adduser:1509
+#, perl-format
+msgid "Removing directory `%s' ..."
+msgstr "იშლება საქაღალდე `%s' ..."
+
+#: adduser:1513 deluser:441
+#, perl-format
+msgid "Removing user `%s' ..."
+msgstr "იშლება მომხმარებელი `%s' ..."
+
+#: adduser:1517 deluser:483
+#, perl-format
+msgid "Removing group `%s' ..."
+msgstr "იშლება ჯგუფი `%s' ..."
+
+#: adduser:1527
+#, perl-format
+msgid "Caught a SIG%s."
+msgstr "Caught a SIG%s."
+
+#: adduser:1533
+#, perl-format
+msgid ""
+"adduser version %s\n"
+"\n"
+msgstr ""
+"adduser ვერსია %s\n"
+"\n"
+
+#: adduser:1534
+msgid ""
+"Adds a user or group to the system.\n"
+"\n"
+"For detailed copyright information, please refer to\n"
+"/usr/share/doc/adduser/copyright.\n"
+"\n"
+msgstr ""
+"ამატებს მომხმარებელს ან ჯგუფს სისტემაში.\n"
+"\n"
+"საავტორო უფლებების შესახებ დეტალური ინფორმაციისთვის იხილეთ ბმული\n"
+"/usr/share/doc/adduser/copyright.\n"
+"\n"
+
+#: adduser:1540 deluser:543
+msgid ""
+"This program is free software; you can redistribute it and/or modify\n"
+"it under the terms of the GNU General Public License as published by\n"
+"the Free Software Foundation; either version 2 of the License, or (at\n"
+"your option) any later version.\n"
+"\n"
+"This program is distributed in the hope that it will be useful, but\n"
+"WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
+"General Public License, /usr/share/common-licenses/GPL, for more details.\n"
+msgstr ""
+"This program is free software; you can redistribute it and/or modify\n"
+"it under the terms of the GNU General Public License as published by\n"
+"the Free Software Foundation; either version 2 of the License, or (at\n"
+"your option) any later version.\n"
+"\n"
+"This program is distributed in the hope that it will be useful, but\n"
+"WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
+"General Public License, /usr/share/common-licenses/GPL, for more details.\n"
+
+#: adduser:1554
+msgid ""
+"adduser [--uid id] [--firstuid id] [--lastuid id]\n"
+"        [--gid id] [--firstgid id] [--lastgid id] [--ingroup group]\n"
+"        [--add-extra-groups] [--shell shell]\n"
+"        [--comment comment] [--home dir] [--no-create-home]\n"
+"        [--allow-all-names] [--allow-bad-names]\n"
+"        [--disabled-password] [--disabled-login]\n"
+"        [--conf file] [--quiet] [--verbose] [--debug]\n"
+"        user\n"
+"    Add a regular user\n"
+"\n"
+"adduser --system\n"
+"        [--uid id] [--group] [--ingroup group] [--gid id]\n"
+"        [--shell shell] [--comment comment] [--home dir] [--no-create-home]\n"
+"        [--conf file] [--quiet] [--verbose] [--debug]\n"
+"        user\n"
+"   Add a system user\n"
+"\n"
+"adduser --group\n"
+"        [--gid ID] [--firstgid id] [--lastgid id]\n"
+"        [--conf file] [--quiet] [--verbose] [--debug]\n"
+"        group\n"
+"addgroup\n"
+"        [--gid ID] [--firstgid id] [--lastgid id]\n"
+"        [--conf file] [--quiet] [--verbose] [--debug]\n"
+"        group\n"
+"   Add a user group\n"
+"\n"
+"addgroup --system\n"
+"        [--gid id]\n"
+"        [--conf file] [--quiet] [--verbose] [--debug]\n"
+"        group\n"
+"   Add a system group\n"
+"\n"
+"adduser USER GROUP\n"
+"   Add an existing user to an existing group\n"
+msgstr ""
+"adduser [--uid id] [--firstuid id] [--lastuid id]\n"
+"        [--gid id] [--firstgid id] [--lastgid id] [--ingroup group]\n"
+"        [--add-extra-groups] [--shell გარსი]\n"
+"        [--comment კომენტარი] [--home სქღლდ] [--no-create-home]\n"
+"        [--allow-all-names] [--allow-bad-names]\n"
+"        [--disabled-password] [--disabled-login]\n"
+"        [--conf ფაილი] [--quiet] [--verbose] [--debug]\n"
+"        მომხმარებელი\n"
+"    ჩვეულებრივი მომხმარებლის დამატება\n"
+"\n"
+"adduser --system\n"
+"        [--uid id] [--group] [--ingroup ჯგუფი] [--gid id]\n"
+"        [--shell გარსი] [--comment ჯგუფი] [--home სქღლდ] [--no-create-home]\n"
+"        [--conf ფაილი] [--quiet] [--verbose] [--debug]\n"
+"        მომხმარებელი\n"
+"   სისტემური მომხმარებლის დამატება\n"
+"\n"
+"adduser --group\n"
+"        [--gid ID] [--firstgid id] [--lastgid id]\n"
+"        [--conf ფაილი] [--quiet] [--verbose] [--debug]\n"
+"        ჯგუფი\n"
+"addgroup\n"
+"        [--gid ID] [--firstgid id] [--lastgid id]\n"
+"        [--conf ფაილი] [--quiet] [--verbose] [--debug]\n"
+"        ჯგუფი\n"
+"   მომხმარებლის ჯგუფის დამატება\n"
+"\n"
+"addgroup --system\n"
+"        [--gid id]\n"
+"        [--conf ფაილი] [--quiet] [--verbose] [--debug]\n"
+"        ჯგუფი\n"
+"   სისტემური ჯგუფის დამატება\n"
+"\n"
+"adduser მომხმარებელი ჯგუფი\n"
+"   არსებული მომხმარებლის დამატება არსებულ ჯგუფში\n"
+
+#: deluser:170
+msgid "Only root may remove a user or group from the system."
+msgstr "სისტემიდან მომხმარებლის, ან ჯგუფის წაშლა, მხოლოდ, root-ს შეუძლია."
+
+#: deluser:271 deluser:290
+msgid ""
+"In order to use the --remove-home, --remove-all-files, and --backup "
+"features, you need to install the `perl' package. To accomplish that, run "
+"apt-get install perl."
+msgstr ""
+"იმისათვის, რომ გამოიყენოთ ფუნქციები --remove-home, --remove-all-files და --"
+"backup, უნდა დააყენოთ პაკეტი 'perl'. ამისთვის გაუშვით ბრძანება apt-get "
+"install perl."
+
+#: deluser:281
+#, perl-format
+msgid "The user `%s' is not a system user. Exiting."
+msgstr "მომხმარებელი '%s' სისტემური მომხმარებელი არაა. გასვლა."
+
+#: deluser:285
+#, perl-format
+msgid "The user `%s' does not exist, but --system was given. Exiting."
+msgstr ""
+"მომხმარებელი `%s' არ არსებობს, მაგრამ მითითებულია პარამეტრი --system. გასვლა."
+
+#: deluser:302
+msgid ""
+"WARNING: You are just about to delete the root account (uid 0). Usually this "
+"is never required as it may render the whole system unusable. If you really "
+"want this, call deluser with parameter --no-preserve-root. Stopping now "
+"without having performed any action."
+msgstr ""
+"გაფრთხილება: აპირებთ, წაშალოთ root-ის ანგარიში (uid 0). ჩვეულებრივ, ეს "
+"არასდროსაა საჭირო და ამან, შეიძლება, მთელი სისტემა არასტაბილური გახადოს. თუ "
+"ამის გაკეთება მაინც გნებავთ, deluser გამოიძახეთ პარამეტრით --no-preserve-"
+"root. ახლა კი გავჩერდები. სისტემაში ქმედება არ განხორციელებულა."
+
+#: deluser:314
+msgid "remove_home or remove_all_files beginning"
+msgstr "remove_home-ის, ან remove_all_files-ის დამუშავების დასაწყისი"
+
+#: deluser:315
+msgid "Looking for files to backup/remove ..."
+msgstr "ფაილების ძებნა დასამარქაფებლად/წასაშლელად..."
+
+#: deluser:320
+#, perl-format
+msgid "failed to open /proc/mounts: %s"
+msgstr "/proc/mounts-ის გახსნა ჩავარდა: %s"
+
+#: deluser:333
+#, perl-format
+msgid "failed to close /proc/mounts: %s"
+msgstr "/proc/mounts-ის დახურვა ჩავარდა: %s"
+
+#: deluser:363
+#, perl-format
+msgid "Not backing up/removing `%s', it is a mount point."
+msgstr "`%s'-ის მარქაფი/წაშლა არ მოხდება, რადგან ის მიმაგრების წერტილია."
+
+#: deluser:370
+#, perl-format
+msgid "Not backing up/removing `%s', it matches %s."
+msgstr "`%s'-ის მარქაფი/წაშლა არ მოხდება, რადგან ის %s-ს ემთხვევა."
+
+#: deluser:385
+#, perl-format
+msgid "Cannot handle special file %s"
+msgstr "სპეციალური ფაილის %s დამუშავება შეუძლებელია"
+
+#: deluser:394
+#, perl-format
+msgid "Backing up %d files to be removed to %s ..."
+msgstr "იქმნება %d ფაილის მარქაფი %s-ში წაშლამდე..."
+
+#: deluser:417
+msgid "Removing files ..."
+msgstr "ფაილების წაშლა ..."
+
+#: deluser:426
+msgid "Removing crontab ..."
+msgstr "Crontab -ის წაშლა ..."
+
+#: deluser:433 deluser:436
+#, perl-format
+msgid ""
+"`%s' is not executable. Skipping crontab removal. Package `cron' required."
+msgstr ""
+"'%s' შესრულებადი არაა. crontab-ის წაშლა გამოტოვებული იქნება. პაკეტი 'cron' "
+"აუცილებელია."
+
+#: deluser:466
+#, perl-format
+msgid "getgrnam `%s' failed: %s. This shouldn't happen."
+msgstr "getgrnam `%s' ჩავარდა: %s. ეს არ უნდა მომხდარიყო."
+
+#: deluser:472
+#, perl-format
+msgid "The group `%s' is not a system group. Exiting."
+msgstr "ჯგუფი '%s' სისტემური ჯგუფი არაა. გასვლა."
+
+#: deluser:476
+#, perl-format
+msgid "The group `%s' is not empty!"
+msgstr "ჯგუფი '%s' ცარიელი არაა!"
+
+#: deluser:495
+#, perl-format
+msgid "The user `%s' does not exist.\n"
+msgstr "მომხმარებელი '%s' არ არსებობს.\n"
+
+#: deluser:499
+#, perl-format
+msgid "The group `%s' does not exist.\n"
+msgstr "ჯგუფი \"%s\" არ არსებობს.\n"
+
+#: deluser:503
+msgid "You may not remove the user from their primary group."
+msgstr "მომხმარებელს მისი ძირითადი ჯგუფიდან ვერ წაშლით."
+
+#: deluser:521
+#, perl-format
+msgid "The user `%s' is not a member of group `%s'."
+msgstr "მომხმარებელი '%s' ჯგუფის '%s' წევრი არაა."
+
+#: deluser:525
+#, perl-format
+msgid "Removing user `%s' from group `%s' ..."
+msgstr "იშლება მომხმარებელი `%s' ჯგუფიდან `%s' ..."
+
+#: deluser:536
+#, perl-format
+msgid ""
+"deluser version %s\n"
+"\n"
+msgstr ""
+"deluser ვერსია %s\n"
+"\n"
+
+#: deluser:537
+msgid ""
+"Removes users and groups from the system.\n"
+"\n"
+"For detailed copyright information, please refer to\n"
+"/usr/share/doc/adduser/copyright.\n"
+"\n"
+msgstr ""
+"შლის მომხმარებლებს და ჯგუფებს სისტემიდან.\n"
+"\n"
+"საავტორო უფლებების შესახებ დეტალური ინფორმაციისთვის იხილეთ ბმული\n"
+"/usr/share/doc/adduser/copyright.\n"
+"\n"
+
+#: deluser:557
+msgid ""
+"deluser [--system] [--remove-home] [--remove-all-files] [--backup]\n"
+"        [--backup-to dir] [--backup-suffix str] [--conf file]\n"
+"        [--quiet] [--verbose] [--debug] user\n"
+"\n"
+"  remove a regular user from the system\n"
+"\n"
+"deluser --group [--system] [--only-if-empty] [--conf file] [--quiet]\n"
+"        [--verbose] [--debug] group\n"
+"delgroup [--system] [--only-if-empty] [--conf file] [--quiet]\n"
+"         [--verbose] [--debug] group\n"
+"  remove a group from the system\n"
+"\n"
+"deluser [--conf file] [--quiet] [--verbose] [--debug] user group\n"
+"  remove the user from a group\n"
+msgstr ""
+"deluser [--system] [--remove-home] [--remove-all-files] [--backup]\n"
+"        [--backup-to dir] [--backup-suffix str] [--conf ფაილი]\n"
+"        [--quiet] [--verbose] [--debug] user\n"
+"\n"
+"  ჩვეულებრივი მომხმარებლის წაშლა სისტემიდან\n"
+"\n"
+"deluser --group [--system] [--only-if-empty] [--conf ფაილი] [--quiet]\n"
+"        [--verbose] [--debug] ჯგუფი\n"
+"delgroup [--system] [--only-if-empty] [--conf ფაილი] [--quiet]\n"
+"         [--verbose] [--debug] ჯგუფი\n"
+"  ჯგუფის წაშლა სისტემიდან\n"
+"\n"
+"deluser [--conf ფაილი] [--quiet] [--verbose] [--debug] მომხმარებელი ჯგუფი\n"
+"  მომხმარებლის წაშლა ჯგუფიდან\n"
+
+#: deluser:603 deluser:613
+#, perl-format
+msgid "Backup suffix %s unavailable, using gzip."
+msgstr "მარქაფის სუფიქსი %s ხელმისაწვდომი არაა. გამოიყენება gzip."
+
+#: AdduserCommon.pm:192
+#, perl-format
+msgid "`%s' does not exist. Using defaults."
+msgstr "`%s' არ არსებობს. ვიყენებ ნაგულისხმევ მნიშვნელობებს."
+
+#: AdduserCommon.pm:198
+#, perl-format
+msgid "cannot open configuration file %s: `%s'\n"
+msgstr "ვერ გავხსენი კონფიგურაციის ფაილი %s: `%s'\n"
+
+#: AdduserCommon.pm:208 AdduserCommon.pm:280 AdduserCommon.pm:308
+#, perl-format
+msgid "Couldn't parse `%s', line %d."
+msgstr "ვერ დავამუშავე`%s', ხაზი %d."
+
+#: AdduserCommon.pm:213
+#, perl-format
+msgid "Unknown variable `%s' at `%s', line %d."
+msgstr "უცნობი ცვლადი `%s' `%s'-ში, ხაზი %d."
+
+#: AdduserCommon.pm:246
+#, perl-format
+msgid "Cannot read directory `%s'"
+msgstr "საქაღალდის '%s' წაკითხვის შეცდომა"
+
+#: AdduserCommon.pm:261
+#, perl-format
+msgid "`%s' does not exist."
+msgstr "`%s' არ არსებობს."
+
+#: AdduserCommon.pm:266
+#, perl-format
+msgid "Cannot open pool file %s: `%s'"
+msgstr "ვერ გავხსენი პულის ფაილი %s: `%s'"
+
+#: AdduserCommon.pm:321
+#, perl-format
+msgid "Illegal pool type `%s' reading `%s'."
+msgstr "დაუშვებელი პულის ტიპი `%s' ფაილიდან `%s'."
+
+#: AdduserCommon.pm:325
+#, perl-format
+msgid "Duplicate name `%s' at `%s', line %d."
+msgstr "დუბლირებული სახელი `%s' ფაილში `%s', ხაზი %d."
+
+#: AdduserCommon.pm:329
+#, perl-format
+msgid "Duplicate ID `%s' at `%s', line %d."
+msgstr "გამეორებული ID '%s' ფაილში '%s' ხაზზე %d."
+
+#: AdduserCommon.pm:367
+#, perl-format
+msgid ""
+"`%s' refused the given user name, but --allow-all-names is given. "
+"Continueing."
+msgstr ""
+"`%s'-მა უარი განაცხადა მიწოდებულ მომხმარებლის სახელზე, მაგრამ მითითებულია "
+"პარამეტრი --allow-all-names. ვაგრძელებ."
+
+#: AdduserCommon.pm:370
+#, perl-format
+msgid ""
+"`%s' refused the given user name. This is a bug in adduser. Please file a "
+"bug report."
+msgstr ""
+"`%s'-მა უარი განაცხადა მიწოდებულ მომხმარებლის სახელზე. ეს adduser-ის "
+"შეცდომაა. შეავსეთ ანგარიში შეცდომის შესახებ."
+
+#: AdduserCommon.pm:374 AdduserCommon.pm:388
+#, perl-format
+msgid "`%s' returned error code %d. Exiting."
+msgstr "`%s'-მა დააბრუნა შეცდომის კოდი %d. გასვლა."
+
+#: AdduserCommon.pm:378 AdduserCommon.pm:391
+#, perl-format
+msgid "`%s' exited from signal %d. Exiting."
+msgstr "`%s' გავიდა სიგნალისგან %d. გასვლა."
+
+#: AdduserCommon.pm:403
+#, perl-format
+msgid "`%s' failed to execute. %s. Continuing."
+msgstr "`%s'-ის გაშვება ჩავარდა. %s. ვაგრძელებ."
+
+#: AdduserCommon.pm:405
+#, perl-format
+msgid "`%s' killed by signal %d. Continuing."
+msgstr "`%s' მოკვდა სიგნალით %d. ვაგრძელებ."
+
+#: AdduserCommon.pm:407
+#, perl-format
+msgid "`%s' failed with status %d. Continuing."
+msgstr "`%s' ჩავარდა სტატუსით %d. ვაგრძელებ."
+
+#: AdduserCommon.pm:449
+#, perl-format
+msgid "Could not find program named `%s' in $PATH."
+msgstr "$PATH-ში პროგრამა სახელით '%s' აღმოჩენილი არაა."
+
+#: AdduserCommon.pm:526
+#, perl-format
+msgid "could not open lock file %s!"
+msgstr "გახსნის შეცდომა ბლოკის ფაილისთვის %s!"
+
+#: AdduserCommon.pm:532
+msgid "Could not obtain exclusive lock, please try again shortly!"
+msgstr "ექსკლუზიური ბლოკის მიღება შეუძლებელია. ცოტა ხანში სცადეთ!"
+
+#: AdduserCommon.pm:535
+msgid "Waiting for lock to become available..."
+msgstr "ბლოკის ხელმისაწვდომობის მოლოდინი..."
+
+#: AdduserCommon.pm:542
+#, perl-format
+msgid "could not seek - %s!"
+msgstr "გადახვევა შეუძლებელია - %s!"
+
+#: AdduserCommon.pm:551
+msgid "could not find lock file!"
+msgstr "ბლოკის ფაილის აღმოჩენა შეუძლებელია!"
+
+#: AdduserCommon.pm:556
+#, perl-format
+msgid "could not unlock file %s: %s"
+msgstr "განბლოკვა შეუძლებელია ფაილისთვის %s: %s"
+
+#: AdduserCommon.pm:561
+#, perl-format
+msgid "could not close lock file %s: %s"
+msgstr "ვერ დავხურე ბლოკის ფაილი %s: %s"
+
+#: AdduserLogging.pm:162
+#, perl-format
+msgid "logging to syslog failed: command line %s returned error: %s\n"
+msgstr "syslog-ში ჩაწერის შეცდომა: ბრძანებამ %s დააბრუნა შეცდომა: %s\n"


=====================================
testsuite/lib_test.pm
=====================================
@@ -16,12 +16,15 @@ preseed_config(\@deluserconf,\%del_config);
 
 my $user_prefix = "addusertest";
 
-
+use constant {
+    SYS_MIN => 100,
+    SYS_MAX => 999,
+};
 
 sub assert {
   my ($cond) = @_;
   if ($cond) {
-    print "Test failed; aborting test suite\n";
+    print "Test failed\n";
     exit 1;
   }
 }
@@ -46,7 +49,7 @@ sub find_unused_uid {
   }
   else {
     print "Haven't found a unused uid in range ($low_uid - $high_uid)\nExiting ...\n";
-    exit 1;
+    return 1;
   }
 }
 
@@ -90,7 +93,7 @@ sub find_unused_gid {
   }
   else {
     print "Haven't found a unused gid in range ($low_gid - $high_gid)\nExiting ...\n";
-    exit 1;
+    return 1;
   }
 }
 
@@ -102,12 +105,13 @@ sub check_user_exist {
   my @ent = getpwnam ($username);
   if (!@ent) {
 	print "user $username does not exist\n";
-	exit 1;
+	return 1;
   }
   if (( defined($uid)) && ($ent[2] != $uid)) {
 	printf "uid $uid does not match %s\n",$ent[2];
 	return 1;
   }
+  print "user $username exists\n";
   return 0;
 }
 
@@ -115,8 +119,10 @@ sub check_user_not_exist {
   my ($username) = @_;
 
   if (defined(getpwnam($username))) {
+    print "user $username exists\n";
     return 1;
   }
+  print "user $username does not exist\n";
   return 0;
 }
 
@@ -228,5 +234,182 @@ sub check_user_has_gid {
   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;
+    }
+    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;
+}
+
+
+
+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;
+    }
+    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/runsuite.sh
=====================================
@@ -1,39 +1,63 @@
 #!/bin/bash
+set -euo pipefail
 
-FAILED=""
+# Backup directory
+BACKUP_DIR="./etc_backup"
+mkdir -p "$BACKUP_DIR"
 
-PASSWD_BAK="./passwd.backup"
+FILES=(passwd shadow group gshadow)
+FAILED=()
 
-
-if [ "$(id -u)" != "0" ]; then
-  echo "root needed"
+# Ensure root
+if [ "$(id -u)" -ne 0 ]; then
+  echo "root needed" >&2
   exit 1
 fi
 
-cp /etc/passwd $PASSWD_BAK
+# Backup critical files
+for f in "${FILES[@]}"; do
+  cp "/etc/$f" "$BACKUP_DIR/$f"
+done
 
+# Restore on exit
+cleanup() {
+  echo "Restoring original /etc files..."
+  for f in "${FILES[@]}"; do
+    cp "$BACKUP_DIR/$f" "/etc/$f"
+  done
+  rm -rf $BACKUP_DIR
+}
+trap cleanup EXIT
+
+# Determine tests to run
+if [ $# -ge 1 ]; then
+  TESTS=("$1")
+else
+  TESTS=(./test*.pl)
+fi
+
+# Run tests with shadowconfig set to "on" only
 for a in on; do
-  for i in ./test*.pl ; do
-    if ! shadowconfig $a > /dev/null; then
-      echo "shadowconfig $a failed"
-      exit 1
-    fi
+  if ! shadowconfig "$a" >/dev/null; then
+    echo "shadowconfig $a failed" >&2
+    exit 1
+  fi
+
+  for i in "${TESTS[@]}"; do
     echo
     echo "Starting $i (shadow $a)"
-    /usr/bin/perl -I. $i
-    if [ "$?" != "0" ]; then
-      FAILED="$FAILED $i($a)"
+    if ! perl -I. "$i" < /dev/null; then
+      FAILED+=("$i($a)")
     fi
   done
 done
 
-if [ -z "$FAILED" ]; then
+# Success/failure reporting
+if [ "${#FAILED[@]}" -eq 0 ]; then
   echo "All tests passed successfully"
-  rm $PASSWD_BAK
-  exit 0
 else
-  echo "tests $FAILED failed"
-  echo "see $PASSWD_BAK for a copy of /etc/passwd before starting"
+  echo "Tests failed: ${FAILED[*]}"
+  echo "Original /etc files were restored from $BACKUP_DIR"
   exit 1
 fi
 


=====================================
testsuite/test01.pl
=====================================
@@ -42,3 +42,4 @@ if (defined (getpwnam($username))) {
 	print "ok\n";
 }
 
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test02.pl
=====================================
@@ -127,9 +127,9 @@ if (defined (getpwnam($username))) {
             if( $error == 56 ) {
                 `deluser $username`;
             } else {
-                print "failed\n  deluser (file::find not present) returned an errorcode != 0/56 ($error)\n";
+                print "failed (expected)\n  deluser (file::find not present) returned an errorcode != 0/56 ($error)\n";
             }
-            print "failed\n  deluser (file::find not present) returned an errorcode != 0 ($error)\n";
+            print "failed (expected)\n  deluser (file::find not present) returned an errorcode != 0 ($error)\n";
             $error=0;
             `rm -rf $homedir`;
         }


=====================================
testsuite/test03.pl
=====================================
@@ -30,4 +30,5 @@ if (!defined (getpwnam($username))) {
 	assert(check_homedir_not_exist($homedir));	
 	print "ok\n";
 }
-  
+
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test04.pl
=====================================
@@ -30,4 +30,5 @@ if (!defined (getpwnam($username))) {
 	assert(check_user_in_group($username,$groupname));
 	print "ok\n";
 }
-  
+
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test05.pl
=====================================
@@ -29,4 +29,5 @@ if (!defined (getpwnam($username))) {
 	assert(check_user_in_group($username,$groupname));
 	print "ok\n";
 }
-  
+
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test06.pl
=====================================
@@ -28,4 +28,5 @@ if (!defined (getpwnam($username))) {
 	assert(check_user_has_gid($username,$want_gid));
 	print "ok\n";
 }
-  
+
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test07.pl
=====================================
@@ -30,4 +30,5 @@ if (!defined (getpwnam($username))) {
 	assert(check_user_homedir_not_exist ($username));
 	print "ok\n";
 }
-  
+
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test08.pl
=====================================
@@ -91,7 +91,7 @@ unless (!defined getgrnam($newgroup)) {
         print "ok\n";
 }
 
-my $newgroup = find_unused_name();
+$newgroup = find_unused_name();
 
 $cmd = "adduser --group $newgroup";
 unless (defined getgrnam($newgroup)) {
@@ -137,3 +137,5 @@ if (!defined (getpwnam($sysusername))) {
         }
 	print "ok\n";
 }
+
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test09.pl
=====================================
@@ -121,3 +121,4 @@ if ($output !~ /^fatal: The group `addusertest\d+' already exists\.$/ ) {
 }
 print "ok\n";
 
+# vim: tabstop=4 shiftwidth=4 expandtab


=====================================
testsuite/test10.pl → testsuite/test11.pl
=====================================



View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/56820425ea3a85db564ca95d1a540af1ed8a0e30...67c314d0a72efeb4bbc748f1fcd3f2f174aed853

-- 
View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/56820425ea3a85db564ca95d1a540af1ed8a0e30...67c314d0a72efeb4bbc748f1fcd3f2f174aed853
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/20260116/b712fdd2/attachment-0001.htm>


More information about the Pkg-shadow-devel mailing list