[Pkg-shadow-devel] [Git][debian/adduser][wip/feature-system-locks] 25 commits: po: Add Georgian translation
Marc Haber (@zugschlus)
gitlab at salsa.debian.org
Sat Jan 17 14:15:27 GMT 2026
Marc Haber pushed to branch wip/feature-system-locks 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
- - - - -
26e35d01 by Marc Haber at 2026-01-17T15:15:05+01:00
add AdduserStatefile.pm to maintain state file
- - - - -
9bf89c5d by Marc Haber at 2026-01-17T15:15:05+01:00
install AdduserStatefile, remove state file on postrm
Git-Dch: ignore
- - - - -
c462807a by Marc Haber at 2026-01-17T15:15:05+01:00
use AdduserStatefile
Git-Dch: ignore
- - - - -
1fa65cad by Marc Haber at 2026-01-17T15:15:05+01:00
fix EXISTING_HAS_PASSWORD to correctly handle !
This now also handles !something in the password field
- - - - -
223579fc by Matt Barry at 2026-01-17T15:15:05+01:00
deluser --lock [--system]
* Add --lock/--unlock options for dealing with disabled accounts.
See documentation. Should probably have a notice specifically
about behavioral changes (eg. adding/removing system accounts).
- - - - -
c23643a0 by Marc Haber at 2026-01-17T15:15:05+01:00
adduser --unlock [--system]
- - - - -
9e761834 by Matt Barry at 2026-01-17T15:15:05+01:00
the documentation commit
this contains documentation changes related to account locking.
might still need some work.
- - - - -
69283b89 by Matt Barry at 2026-01-17T15:15:05+01:00
add some basic tests
- - - - -
5937f07b by Matt Barry at 2026-01-17T15:15:05+01:00
locked.account.tests
- - - - -
30 changed files:
- AdduserCommon.pm
- AdduserRetvalues.pm
- + AdduserStatefile.pm
- adduser
- adduser.conf
- debian/postrm
- debian/rules
- + debian/tests/f/account_locks.t
- debian/tests/lib/AdduserTestsCommon.pm
- deluser
- deluser.conf
- doc/adduser.8
- doc/deluser.8
- doc/deluser.conf.5
- + notes.100808x.md
- + po/ka.po
- + testsuite/deluser-delete.conf
- 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
=====================================
@@ -31,6 +31,7 @@ my $codeset;
use Debian::AdduserLogging 3.139;
use Debian::AdduserRetvalues 3.139;
+use Debian::AdduserStatefile 3.139;
BEGIN {
if ( Debian::AdduserLogging->VERSION != version->declare('3.139') ||
Debian::AdduserRetvalues->VERSION != version->declare('3.139') ) {
@@ -102,6 +103,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 +147,11 @@ use constant {
'EXISTING_ID_MISMATCH',
'EXISTING_LOCKED',
'EXISTING_HAS_PASSWORD',
+ 'EXISTING_EXPIRED',
+ 'EXISTING_NOLOGIN',
+ 'STDOUTDEFLEVEL',
+ 'STDERRDEFLEVEL',
+ 'LOGMSGDEFLEVEL',
'existing_user_status',
'existing_group_status',
);
@@ -507,6 +521,7 @@ sub preseed_config {
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)",
skel_ignore_regex => "\.(dpkg|ucf)-(old|new|dist)\$",
extra_groups => "users",
@@ -590,35 +605,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 =~ /^[!*]/ && (get_state_value($user_name, "locked") // "") eq "1";
+
+ $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 +661,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
=====================================
AdduserStatefile.pm
=====================================
@@ -0,0 +1,293 @@
+package Debian::AdduserStatefile 3.139;
+use 5.36.0;
+use utf8;
+
+# AdduserStatefile.pm: Manage persistent state for deleted user accounts
+#
+# This module provides a simple key-value store for tracking information
+# about deleted user accounts. The state is stored in a text file at
+# /var/lib/adduser/state in a format similar to /etc/passwd.
+#
+# File format: username:key1=value1:key2=value2:key3=value3
+#
+# Copyright (C) 2026 Marc Haber
+#
+# License: GPL-2+
+
+use parent qw(Exporter);
+use vars qw(@EXPORT);
+
+ at EXPORT = (
+ 'set_state_value',
+ 'get_state_value',
+ 'get_state_user_data',
+ 'delete_state_user',
+);
+
+use strict;
+use warnings;
+
+our $STATE_DIR = '/var/lib/adduser';
+our $STATE_FILE = "$STATE_DIR/state";
+our $LOCK_FILE = "$STATE_DIR/state.lock";
+
+# init_state_file()
+#
+# Initialize the state file and its parent directory if they don't exist.
+# Creates /var/lib/adduser with mode 0755 and the state file with mode 0644.
+# Dies on error.
+sub init_state_file {
+ # Create the directory if it doesn't exist
+ unless (-d $STATE_DIR) {
+ mkdir($STATE_DIR, 0755) or die "Cannot create $STATE_DIR: $!";
+ }
+
+ # Create the state file if it doesn't exist
+ unless (-f $STATE_FILE) {
+ open(my $fh, '>', $STATE_FILE) or die "Cannot create $STATE_FILE: $!";
+ close($fh);
+ chmod(0644, $STATE_FILE);
+ }
+}
+
+# set_value($username, $key, $value)
+#
+# Set a key-value pair for the specified username in the state file.
+# If the username doesn't exist, it will be created.
+# If the key already exists for that user, it will be overwritten.
+# If the value is empty or undefined, the key will be deleted instead.
+#
+# Parameters:
+# $username - The username to store data for
+# $key - The key name (cannot contain '=', ':', or newlines)
+# $value - The value to store (cannot contain ':' or newlines)
+# If empty or undef, the key is deleted
+#
+# Dies if the username, key, or value contain invalid characters.
+sub set_state_value {
+ my ($username, $key, $value) = @_;
+
+ my $lock_fh = _acquire_lock();
+ my %data = _read_state();
+
+ $data{$username} ||= {};
+ if (!defined $value || $value eq '') {
+ delete $data{$username}{$key};
+ if (keys %{$data{$username}} == 0) {
+ delete $data{$username};
+ }
+ } else {
+ die "Invalid value" if $value =~ /[:\n]/;
+ $data{$username}{$key} = $value;
+ }
+
+ _write_state(\%data);
+ _release_lock($lock_fh);
+}
+
+# get_value($username, $key)
+#
+# Retrieve a value for a given username and key (or all values if key is undef).
+# This is a lock-free read operation. Due to atomic file replacement,
+# readers will always see a consistent (though possibly slightly stale)
+# version of the state file.
+#
+# Parameters:
+# $username - The username to look up
+# $key - The key name to retrieve (optional)
+#
+# Returns:
+# If $key is provided: the value if found, undef otherwise
+# If $key is undef: a hash reference containing all key-value pairs for the user
+sub get_state_value {
+ my ($username, $key) = @_;
+
+ # Return undef/empty hash if state file doesn't exist yet
+ return undef unless -f $STATE_FILE;
+
+ my %data = _read_state();
+
+ # Return undef/empty hash if user doesn't exist
+ if (!exists $data{$username}) {
+ return defined $key ? undef : {};
+ }
+
+ # Return specific key or all data
+ return defined $key ? $data{$username}{$key} : $data{$username};
+}
+
+# get_user_data($username)
+#
+# Retrieve all key-value pairs for a given username.
+# This is a convenience wrapper around get_value($username, undef).
+#
+# Parameters:
+# $username - The username to look up
+#
+# Returns:
+# A hash reference containing all key-value pairs for the user,
+# or an empty hash reference if the user doesn't exist
+sub get_state_user_data {
+ my ($username) = @_;
+ return get_value($username, undef);
+}
+
+# delete_user($username)
+#
+# Remove all data for a user from the state file.
+# This completely removes the user's entry from the state file.
+#
+# Parameters:
+# $username - The username to delete
+#
+# Returns silently if the state file doesn't exist or the user isn't found.
+sub delete_state_user {
+ my ($username) = @_;
+
+ # Nothing to do if state file doesn't exist
+ return unless -f $STATE_FILE;
+
+ # Acquire lock, read, modify, write, release lock
+ my $lock_fh = _acquire_lock();
+ my %data = _read_state();
+ delete $data{$username};
+ _write_state(\%data);
+ _release_lock($lock_fh);
+}
+
+# _acquire_lock()
+#
+# Internal function to acquire an exclusive lock on the state file.
+# Uses a separate lock file to avoid interfering with atomic renames.
+#
+# Returns:
+# File handle to the lock file (caller must pass to _release_lock)
+#
+# Dies if the lock file cannot be opened.
+sub _acquire_lock {
+ # Ensure the directory exists
+ unless (-d $STATE_DIR) {
+ mkdir($STATE_DIR, 0755) or die "Cannot create $STATE_DIR: $!";
+ }
+
+ # Open (or create) the lock file
+ open(my $lock_fh, '>>', $LOCK_FILE) or die "Cannot open $LOCK_FILE: $!";
+
+ # Acquire exclusive lock (blocks until available)
+ flock($lock_fh, 2) or die "Cannot acquire lock on $LOCK_FILE: $!"; # LOCK_EX
+
+ return $lock_fh;
+}
+
+# _release_lock($lock_fh)
+#
+# Internal function to release the lock and close the lock file.
+#
+# Parameters:
+# $lock_fh - File handle returned by _acquire_lock()
+sub _release_lock {
+ my ($lock_fh) = @_;
+
+ # Closing the file handle releases the flock automatically
+ close($lock_fh);
+}
+
+# _read_state()
+#
+# Internal function to read and parse the entire state file.
+# For write operations, this MUST be called while holding the lock.
+# For read-only operations, this can be called without a lock due to
+# atomic file replacement via rename().
+#
+# File format: Each line contains:
+# username:key1=value1:key2=value2:...
+#
+# Empty lines and lines starting with '#' are ignored.
+#
+# Returns:
+# A hash where keys are usernames and values are hash references
+# containing the key-value pairs for that user
+#
+# Dies if the file cannot be opened.
+sub _read_state {
+ my %data;
+
+ # Return empty hash if state file doesn't exist yet
+ return %data unless -f $STATE_FILE;
+
+ open(my $fh, '<', $STATE_FILE) or die "Cannot read $STATE_FILE: $!";
+
+ while (my $line = <$fh>) {
+ chomp $line;
+
+ # Skip empty lines and comments
+ next if $line =~ /^\s*$/;
+ next if $line =~ /^\s*#/;
+
+ # Split on colons - first field is username, rest are key=value pairs
+ # Untaint the line by validating it matches our expected format
+ next unless $line =~ /^([^:\n]+(?::[^:\n=]+=[^:\n]*)*?)$/;
+ $line = $1; # Now untainted
+ my @fields = split(/:/, $line);
+ next unless @fields >= 1;
+
+ my $username = shift @fields;
+ $data{$username} = {};
+
+ # Parse each key=value pair
+ foreach my $pair (@fields) {
+ my ($key, $value) = split(/=/, $pair, 2);
+ $data{$username}{$key} = $value if defined $key && defined $value;
+ }
+ }
+
+ close($fh);
+
+ return %data;
+}
+
+# _write_state(\%data)
+#
+# Internal function to write the entire state file atomically.
+# MUST be called while holding the lock via _acquire_lock().
+# Uses a temporary file and rename() to ensure atomicity.
+#
+# Parameters:
+# $data_ref - A hash reference in the format returned by _read_state()
+#
+# The output is sorted by username and by key within each username
+# for consistent, diff-friendly output.
+#
+# Dies if the file cannot be written or renamed.
+sub _write_state {
+ my ($data_ref) = @_;
+
+ # Use a process-specific temporary file name
+ my $temp_file = "$STATE_FILE.tmp.$$";
+
+ open(my $fh, '>', $temp_file) or die "Cannot write to $temp_file: $!";
+
+ # Write each user's data, sorted for consistency
+ foreach my $username (sort keys %$data_ref) {
+ my @pairs;
+
+ # Build key=value pairs, sorted by key
+ foreach my $key (sort keys %{$data_ref->{$username}}) {
+ my $value = $data_ref->{$username}{$key};
+ push @pairs, "$key=$value";
+ }
+
+ # Write the line: username:key1=value1:key2=value2:...
+ print $fh "$username:", join(':', @pairs), "\n";
+ }
+
+ close($fh);
+
+ # Atomically replace the old state file with the new one
+ # This happens while still holding the lock, so no race condition
+ rename($temp_file, $STATE_FILE) or die "Cannot rename $temp_file: $!";
+ chmod(0644, $STATE_FILE);
+}
+
+# Module must return true
+1;
=====================================
adduser
=====================================
@@ -34,6 +34,7 @@ use Getopt::Long;
use Debian::AdduserCommon 3.139;
use Debian::AdduserLogging 3.139;
use Debian::AdduserRetvalues 3.139;
+use Debian::AdduserStatefile 3.139;
BEGIN {
if ( Debian::AdduserCommon->VERSION != version->declare('3.139') ||
Debian::AdduserLogging->VERSION != version->declare('3.139') ||
@@ -103,9 +104,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?
@@ -114,6 +115,7 @@ our @configfiles;
our @defaults = undef;
our $found_group_opt = undef;
our $found_sys_opt = undef;
+our $found_unlock_opt = undef;
our $ingroup_name = undef;
our $new_firstgid = undef;
our $new_firstuid = undef;
@@ -186,6 +188,7 @@ GetOptions(
'shell=s' => \$special_shell,
'system' => \$found_sys_opt,
'uid=i' => \$new_uid,
+ 'unlock' => \$found_unlock_opt,
'verbose' => sub { $verbose = 1; },
'version|v' => sub { &version; exit },
) or &usage_error;
@@ -199,10 +202,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 +227,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 +244,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 );
}
}
@@ -313,7 +323,6 @@ if ($action ne "addgroup" &&
exit( RET_EXCLUSIVE_PARAMETERS );
}
-
if ($found_group_opt) {
if ($action eq "addsysuser") {
$make_group_also = 1;
@@ -326,6 +335,10 @@ if ($found_group_opt) {
}
}
+if ($found_unlock_opt) {
+ $action = "unlockuser";
+}
+
# $new_firstuid = $new_firstuid || $config{"first_uid"} || 1000;
# $new_lastuid = $new_lastuid || $config{"last_uid"} || 59999;
# $new_firstgid = $new_firstgid || $config{"first_gid"} || 1000;
@@ -364,7 +377,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 +417,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 );
}
@@ -431,6 +445,8 @@ $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = 'handler';
# $action = "addusertogroup"
# $existing_user - the user to be added
# $existing_group - the group to add her to
+# $action = "unlockuser"
+# $new_name - the user to be unlocked
#####
@@ -589,6 +605,9 @@ if ($action eq "addsysuser") {
exit( RET_WRONG_OBJECT_PROPERTIES );
}
if ($ret & EXISTING_FOUND) {
+ if ($ret & EXISTING_LOCKED) {
+ unlock_user($new_name, 1);
+ }
log_info( mtx("The system user `%s' already exists. Exiting.\n"), $new_name );
exit( RET_OK );
}
@@ -942,49 +961,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 +982,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"}) ) {
@@ -1051,6 +1033,15 @@ if ($action eq "adduser") {
exit( $returnvalue );
}
+if ($action eq "unlockuser") {
+ log_trace( "unlockuser %s", $new_name );
+ acquire_lock();
+ unlock_user($new_name, 0);
+ release_lock(0);
+
+ exit( $returnvalue );
+}
+
#
# we never go here
#
@@ -1171,6 +1162,13 @@ sub check_user_group {
log_debug( "check_user_group %s called, make_group_also %s", $system, $make_group_also );
my $ustat = existing_user_status(\%config, $new_name, $new_uid);
+ if ($ustat & EXISTING_FOUND) {
+ if ($ustat & EXISTING_LOCKED) {
+ # this must be a non-system user, addsysuser handles the locked case before we get called
+ log_err( mtx("User `%s' already exists and is locked. use adduser --unlock explicitly to unlock. Exiting.\n"), $new_name );
+ exit( RET_OBJECT_EXISTS );
+ }
+ }
if ($system) {
if (($ustat & EXISTING_FOUND) && !($ustat & EXISTING_SYSTEM)) {
log_fatal( mtx("The user `%s' already exists, and is not a system user."), $new_name);
@@ -1309,21 +1307,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 +1331,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 +1348,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 +1358,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);
@@ -1465,6 +1463,112 @@ sub user_is_member {
return 0;
}
+# unlock user
+sub unlock_user {
+ my ($user_name, $system) = @_;
+ log_debug( "unlock_user %s called, system %s", $user_name, $system );
+ my $ret = existing_user_status(\%config, $user_name, undef);
+ if ($ret & EXISTING_FOUND) {
+ if ($ret & EXISTING_LOCKED) {
+ my $default_shell = $system ? "/usr/sbin/nologin" : "/bin/bash";
+
+ # Get current shell state if available
+ my $shell = get_state_value($user_name, "shell") // $default_shell;
+
+ # Build usermod argument list
+ my @usermod_args = (
+ '-e', '-1', # set account expiry to never
+ '-s', $shell, # set shell
+ $user_name # username
+ );
+
+ # Add -U only if account has a password
+ if ($ret & EXISTING_HAS_PASSWORD) {
+ push @usermod_args, '-U';
+ } else {
+ log_info( mtx(
+ "Unlocking password `%s' would result in a passwordless account. Leaving password locked\n",
+ $user_name
+ ));
+ }
+
+ log_info( mtx("Unlocking user `%s' ...", $user_name) );
+ log_trace( "usermod `%s' ...", join(" ", @usermod_args) );
+ my $unlock_ret = systemcall_useradd($name_check_level, 'usermod', @usermod_args);
+
+ if( $unlock_ret == 0 ) {
+ log_info( mtx("user `%s' successfully unlocked.\n", $user_name) );
+ set_state_value($user_name, "shell", undef);
+ set_state_value($user_name, "locked", undef);
+ } else {
+ log_fatal( mtx("error %s while unlocking user `%s'. Exiting.\n", $unlock_ret, $user_name) );
+ exit( RET_SYSTEMCALL_ERROR );
+ }
+ }
+ log_info( mtx("User `%s' is already unlocked.\n"), $user_name );
+ } else {
+ log_fatal( mtx("User `%s' does not exist\n"), $user_name );
+ exit( RET_OBJECT_DOES_NOT_EXIST );
+ }
+}
+
+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) {
@@ -1523,6 +1627,11 @@ sub usage {
user
Add a regular user
+adduser --unlock
+ [--system]
+ user
+ Unlock an existing locked user account
+
adduser --system
[--uid id] [--group] [--ingroup group] [--gid id]
[--shell shell] [--comment comment] [--home dir] [--no-create-home]
=====================================
adduser.conf
=====================================
@@ -113,4 +113,3 @@
# EXTRA_GROUPS.
# Default: ADD_EXTRA_GROUPS=0
#ADD_EXTRA_GROUPS=0
-
=====================================
debian/postrm
=====================================
@@ -5,7 +5,7 @@ set -eu
case $1 in
purge)
- rm -fv /etc/adduser.conf /etc/adduser.conf.dpkg-save /etc/adduser.conf.update-old
+ rm -fv /etc/adduser.conf /etc/adduser.conf.dpkg-save /etc/adduser.conf.update-old /var/lib/adduser/state /var/lib/adduser
;;
esac
=====================================
debian/rules
=====================================
@@ -24,6 +24,7 @@ override_dh_install:
sed -e s/DVERSION/$(cversion)/g AdduserCommon.pm > debian/adduser/usr/share/perl5/Debian/AdduserCommon.pm
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
ln -s adduser debian/adduser/usr/sbin/addgroup
ln -s deluser debian/adduser/usr/sbin/delgroup
=====================================
debian/tests/f/account_locks.t
=====================================
@@ -0,0 +1,103 @@
+#! /usr/bin/perl -Idebian/tests/lib
+
+use diagnostics;
+use strict;
+use warnings;
+
+use AdduserTestsCommon;
+
+my $prefix = "lockedtest";
+my $un;
+
+END {
+ remove_tree("/home/$prefix-user");
+ remove_tree("/var/mail/$prefix-user");
+}
+
+## system user
+
+$un = "${prefix}-sys";
+
+assert_user_does_not_exist($un);
+assert_command_success('/usr/sbin/adduser',
+ '--stdoutmsglevel=error', '--stderrmsglevel=error',
+ '--disabled-password',
+ '--system',
+ $un);
+assert_user_exists($un);
+assert_user_status($un, EXISTING_LOCKED, "locked");
+assert_user_status($un, EXISTING_HAS_PASSWORD, "not has password");
+assert_user_status($un, EXISTING_NOLOGIN, "set to nologin");
+assert_user_status($un, EXISTING_EXPIRED, "not expired");
+assert_command_success('/usr/sbin/deluser',
+ '--stdoutmsglevel=error', '--stderrmsglevel=error',
+ '--system', "--lock",
+ $un);
+assert_user_exists($un);
+assert_user_status($un, EXISTING_LOCKED, "locked");
+assert_user_status($un, EXISTING_HAS_PASSWORD, "not has password");
+assert_user_status($un, EXISTING_NOLOGIN, "set to nologin");
+assert_user_status($un, EXISTING_EXPIRED, "expired");
+assert_command_success('/usr/sbin/adduser',
+ '--stdoutmsglevel=error', '--stderrmsglevel=error',
+ '--system',
+ $un);
+assert_user_status($un, EXISTING_LOCKED, "locked");
+assert_user_status($un, EXISTING_HAS_PASSWORD, "not has password");
+assert_user_status($un, EXISTING_NOLOGIN, "set to nologin");
+assert_user_status($un, EXISTING_EXPIRED, "not expired");
+assert_user_exists($un);
+assert_command_success('/usr/sbin/deluser',
+ '--stdoutmsglevel=error', '--stderrmsglevel=error',
+ '--system', '--force-delete',
+ $un);
+assert_user_does_not_exist($un);
+
+## normal user
+
+$un = "${prefix}-user";
+
+assert_user_does_not_exist($un);
+assert_command_success('/usr/sbin/adduser',
+ '--stdoutmsglevel=error', '--stderrmsglevel=error',
+ '--comment', "",
+ '--disabled-password',
+ $un);
+assert_user_exists($un);
+assert_user_status($un, EXISTING_LOCKED, "locked");
+assert_user_status($un, EXISTING_HAS_PASSWORD, "not has password");
+assert_user_status($un, EXISTING_NOLOGIN, "not set to nologin");
+assert_user_status($un, EXISTING_EXPIRED, "not expired");
+assert_command_success('/usr/sbin/deluser',
+ '--stdoutmsglevel=error', '--stderrmsglevel=error',
+ "--lock",
+ $un);
+assert_user_exists($un);
+assert_user_status($un, EXISTING_LOCKED, "locked");
+assert_user_status($un, EXISTING_HAS_PASSWORD, "not has password");
+assert_user_status($un, EXISTING_NOLOGIN, "set to nologin");
+assert_user_status($un, EXISTING_EXPIRED, "expired");
+assert_command_failure('/usr/sbin/adduser',
+ '--stdoutmsglevel=fatal', '--stderrmsglevel=fatal',
+ $un);
+assert_user_exists($un);
+assert_user_status($un, EXISTING_LOCKED, "locked");
+assert_user_status($un, EXISTING_HAS_PASSWORD, "not has password");
+assert_user_status($un, EXISTING_NOLOGIN, "set to nologin");
+assert_user_status($un, EXISTING_EXPIRED, "expired");
+assert_command_success('/usr/sbin/adduser',
+ '--stdoutmsglevel=error', '--stderrmsglevel=error',
+ '--unlock',
+ $un);
+assert_user_exists($un);
+assert_user_status($un, EXISTING_LOCKED, "locked");
+assert_user_status($un, EXISTING_HAS_PASSWORD, "not has password");
+assert_user_status($un, EXISTING_NOLOGIN, "not set to nologin");
+assert_user_status($un, EXISTING_EXPIRED, "not expired");
+assert_command_success('/usr/sbin/deluser',
+ '--stdoutmsglevel=error', '--stderrmsglevel=error',
+ '--force-delete', '--remove-home',
+ $un);
+assert_user_does_not_exist($un);
+
+# vim: tabstop=4 shiftwidth=4 expandtab
=====================================
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,82 @@ 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
=====================================
@@ -28,6 +28,7 @@ use Getopt::Long;
use Debian::AdduserCommon 3.139;
use Debian::AdduserLogging 3.139;
use Debian::AdduserRetvalues 3.139;
+use Debian::AdduserStatefile 3.139;
BEGIN {
if ( Debian::AdduserCommon->VERSION != version->declare('3.139') ||
Debian::AdduserLogging->VERSION != version->declare('3.139') ||
@@ -122,22 +123,24 @@ our @defaults = undef;
our @names;
GetOptions (
+ 'backup' => \$pconfig{'backup'},
+ 'backup-to=s' => \$pconfig{'backup_to'},
+ 'backup-suffix=s' => \$pconfig{'backup_suffix'},
'conf|c=s' => \@configfiles,
'debug' => sub { $verbose = 2; },
- 'stdoutmsglevel=s' => \$stdoutmsglevel,
- 'stderrmsglevel=s' => \$stderrmsglevel,
- 'logmsglevel=s' => \$logmsglevel,
- 'help|h' => sub { &usage(); exit 0; },
+ 'force-delete' => \$pconfig{'force_delete'},
'group' => sub { $action = 'delgroup'; },
- 'system' => \$pconfig{'system'},
+ 'help|h' => sub { &usage(); exit 0; },
+ 'lock|L' => \$pconfig{'lock'},
+ 'logmsglevel=s' => \$logmsglevel,
+ 'no-preserve-root' => \$no_preserve_root,
'only-if-empty' => \$pconfig{'only_if_empty'},
+ 'quiet|q' => sub { $verbose = 0; },
'remove-home' => \$pconfig{'remove_home'},
'remove-all-files' => \$pconfig{'remove_all_files'},
- 'backup' => \$pconfig{'backup'},
- 'backup-to=s' => \$pconfig{'backup_to'},
- 'backup-suffix=s' => \$pconfig{'backup_suffix'},
- 'no-preserve-root' => \$no_preserve_root,
- 'quiet|q' => sub { $verbose = 0; },
+ 'stdoutmsglevel=s' => \$stdoutmsglevel,
+ 'stderrmsglevel=s' => \$stderrmsglevel,
+ 'system' => \$pconfig{'system'},
'verbose' => sub { $verbose = 1; },
'version|v' => sub { &version; exit },
) or &usage_error;
@@ -265,21 +268,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,20 +297,15 @@ 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 );
}
# Warn in any case if you want to remove the root account
- if ((defined($pw_uid)) && ($pw_uid == 0) && (!defined($no_preserve_root))) {
+ if ((defined($egpwn_uid)) && ($egpwn_uid == 0) && (!defined($no_preserve_root))) {
log_fatal( mtx("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.") );
exit( RET_DONT_REMOVE_ROOT );
}
@@ -312,6 +317,42 @@ if($action eq "deluser") {
$config{"backup"} = 1;
}
+ # command line sys_delete_action action
+ # deluser lock lock
+ # deluser delete delete
+ # deluser --lock any lock
+ # deluser --force-delete any delete
+
+ # decide whether to lock or to delete the account
+ # command line takes precedence, otherwise sys_delete_action wins
+ my $do_lock = ($config{sys_delete_action} eq 'lock');
+ $do_lock = 1 if $config{lock};
+ $do_lock = 0 if $config{force_delete};
+ log_trace( "Config: sys_delete_action=%s, lock=%s, force_delete=%s → do_lock=%s", $config{sys_delete_action}, $config{lock}, $config{force_delete}, $do_lock );
+
+ # if account is to be locked (not deleted), leave files alone
+ if ($do_lock) {
+ if ($config{"backup"} or $config{"remove"} or $config{"remove_all_files"}) {
+ log_warn( mtx("Account will be locked; files will not be backed up or removed.") );
+ log_warn( mtx("Hint: Use --force-delete to delete the account as normal.") )
+ }
+
+ log_info( mtx("Locking user account `%s' ...", $user));
+ acquire_lock();
+ set_state_value($user, "shell", $egpwn_shell);
+ set_state_value($user, "locked", 1);
+ my $lock_ret = &systemcall('usermod', "-e", 1, "-f", 0, "-L", "-s", "/usr/sbin/nologin", $user);
+ if( $lock_ret == 0 ) {
+ log_info( mtx("user `%s' successfully locked.\n", $user) );
+ } else {
+ log_fatal( mtx("error %s while unlocking user `%s'. Exiting.\n", $lock_ret, $user) );
+ exit( RET_SYSTEMCALL_ERROR );
+ }
+ release_lock();
+
+ exit( RET_OK )
+ }
+
if($config{"remove_home"} || $config{"remove_all_files"}) {
log_trace( mtx("remove_home or remove_all_files beginning") );
log_info( mtx("Looking for files to backup/remove ...") );
@@ -444,6 +485,7 @@ if($action eq "deluser") {
acquire_lock();
systemcall('/usr/sbin/userdel', $user);
release_lock();
+ delete_state_user($user);
systemcall('/usr/local/sbin/deluser.local', $user, $pw_uid,
$pw_gid, $pw_homedir) if (-x "/usr/local/sbin/deluser.local");
@@ -495,8 +537,7 @@ if ($action eq 'delgroup') {
}
-if($action eq 'deluserfromgroup')
-{
+if($action eq 'deluserfromgroup') {
unless(exist_user($user)) {
log_fatal( mtx("The user `%s' does not exist.\n"), $user );
exit( RET_OBJECT_DOES_NOT_EXIST );
@@ -562,7 +603,7 @@ sub usage {
printf( gtx(
"deluser [--system] [--remove-home] [--remove-all-files] [--backup]
[--backup-to dir] [--backup-suffix str] [--conf file]
- [--quiet] [--verbose] [--debug] user
+ [--quiet] [--verbose] [--debug] [--lock] user
remove a regular user from the system
@@ -629,3 +670,4 @@ sub check_backup_suffix {
# End:
# vim: tabstop=4 shiftwidth=4 expandtab
+0
=====================================
deluser.conf
=====================================
@@ -39,3 +39,8 @@
# be excluded when looking for files of a user to be deleted.
# Default: EXCLUDE_FSTYPES = "(proc|sysfs|usbfs|devpts|tmpfs|afs)"
#EXCLUDE_FSTYPES = "(proc|sysfs|usbfs|devpts|tmpfs|afs)"
+
+# What action to take when deleting a system account
+# Options: lock|delete
+# Default: SYS_DELETE_ACTION=delete
+#SYS_DELETE_ACTION=delete
=====================================
doc/adduser.8
=====================================
@@ -65,6 +65,11 @@ adduser, addgroup \- add or manipulate users or groups
.B user
.YS
.SY adduser
+.B \-\-unlock
+.OP \-\-system
+.B user
+.YS
+.SY adduser
.B \-\-group
.OP \-\-conf file
.OP \-\-debug
@@ -162,6 +167,11 @@ that means a
in the sense of Debian Policy.
This is commonly referred to in \fBadduser\fP as a \fInon-system user.\fP
.PP
+If \fBadduser\fP is called with the \fB\-\-unlock\fP option, it will
+search for an existing account, and ensure it is unlocked. If the
+user is not found, \fBadduser\fP will return an error. See
+\fBUnlock an account\fP below for more details.
+.PP
\fBadduser\fP will choose the first available UID
from the range specified by
\fBFIRST_UID\fP and \fBLAST_UID\fP
@@ -224,6 +234,10 @@ often abbreviated as
\fIsystem user\fP
in the context of the \fBadduser\fP package.
.PP
+If the user already exists and is a system account, then
+\fBadduser\fP will ensure the account is unlocked and return
+successfully. See \fBUnlock a user account\fP below.
+.PP
\fBadduser\fP will choose the first available UID
from the range specified by
\fBFIRST_SYSTEM_UID\fP and \fBLAST_SYSTEM_UID\fP
@@ -266,6 +280,13 @@ Skeletal configuration files are not copied.
Other options will behave as for the creation of a regular user.
The files referenced by \fBUID_POOL\fP and \fBGID_POOL\fP are also honored.
+.SS "Unlock a user account"
+If called with the \fB\-\-unlock\fP option, \fBadduser\fP will
+unlock an existing, previously locked account. If found, the
+user's password will be re-enabled, and expiry restrictions will
+be removed. Any previous expiry settings will have to be
+added manually after unlocking the account.
+
.SS "Add a group"
If \fBadduser\fP is called with the \fB\-\-group\fP option and
without the \fB\-\-system\fP option, or
@@ -613,8 +634,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.
=====================================
doc/deluser.8
=====================================
@@ -27,6 +27,7 @@ deluser, delgroup \- remove a user or group from the system
.OP \-\-stdoutmsglevel prio
.OP \-\-stderrmsglevel prio
.OP \-\-logmsglevel prio
+.OP \-\-lock
.B user
.YS
@@ -44,6 +45,8 @@ deluser, delgroup \- remove a user or group from the system
.OP \-\-stdoutmsglevel prio
.OP \-\-stderrmsglevel prio
.OP \-\-logmsglevel prio
+.OP \-\-lock
+.OP \-\-force\-delete
.B user
.YS
@@ -115,6 +118,10 @@ If called with one non-option argument and
without the \fB\-\-group\fP option,
\fBdeluser\fP will remove a non-system user.
.PP
+Note: If \fBdeluser\fP is called with the \fB\-\-lock\fP option,
+the user account will be locked rather than removed as described
+here; see \fB\-\-lock\fP, below.
+.PP
By default,
\fBdeluser\fP will remove the user
without removing the home directory,
@@ -263,6 +270,19 @@ That allows the local admin to control \fBadduser\fP's chattiness
on the console and in the log independently, keeping probably confusing
information to itself while still leaving helpful information in the log.
.TP
+.B \-\-lock
+If \fBdeluser\fP is called with the \fB\-\-lock\fP option,
+the user account will be locked rather than removed. In this
+case, the user's password is invalidated and the account is
+set to expired; no other action is taken. A locked account may be
+subsequently unlocked using \fBadduser\fP \fB\-\-unlock\fP.
+
+System users may be set to lock upon delete by default by setting
+\fBSYS_DELETE_ACTION\fP=\fBlock\fP in \fB/etc/deluser.conf\fP.
+.B \-\-force\-delete
+If deleting a system user, and the default \fBSYS_DELETE_ACTION\fP
+would lock the account, instead delete it normally.
+.TP
.B \-\-version
Display version and copyright information.
=====================================
doc/deluser.conf.5
=====================================
@@ -78,6 +78,12 @@ Values may be 0 or 1. Defaults to \fI0\fP.
.B REMOVE_HOME
Removes the home directory and mail spool of the user to be removed.
Value may be 0 (don't delete) or 1 (do delete). Defaults to \fI0\fP.
+.TP
+.B SYS_DELETE_ACTION
+The default action to take when deleting system accounts. Valid options
+are "lock" and "delete" (the default). If set to "lock" and not
+overridden by \fB\-\-force\-delete\fP, behave as if \fB\-\-lock\fP were
+passed and lock the account. See \fBdeluser(8)\fP for specific details.
.SH FILES
.I /etc/deluser.conf
=====================================
notes.100808x.md
=====================================
@@ -0,0 +1,28 @@
+#1008082: deluser --system(?) --lock
+
+- leaves the account intact but makes login impossible (by setting an invalid
+password, leaving existing password recoverable, and setting shell to
+/usr/sbin/nologin)
+- only system accounts?
+- adding state (/var/lib/adduser)?
+ - or set to nologin, reset shell to default and WARN on unlock
+
+#1008083: deluser --system
+
+- /etc/deluser.conf: DELUSER_SYS_ACTION = (lock*|delete) *default
+- delgroup --system honors the above (lock == NOOP)
+
+- basically: extends --lock to /etc/deluser.conf for system users
+
+#1008084: adduser --system behavior if trying to create existing locked account
+ - if system account already exists, just unlock and set shell
+ - addgroup --system silently ignore existing also (?)
+
+#?: --homeless
+ - couldn't find a bug for this but saw it mentioned; is this something we
+ still want to do?
+
+blocked above:
+#1006912: is it time to have account deletion in policy?
+- anything relevant policy-wise? (i have not read the whole thread,
+ or any list discussion)
=====================================
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/deluser-delete.conf
=====================================
@@ -0,0 +1,47 @@
+# /etc/deluser.conf: `deluser' configuration.
+# See deluser(8) and deluser.conf(5) for full documentation.
+
+# A commented out setting indicates that this is the default in the
+# code. If you need to change those settings, remove the comment and
+# make your intended change.
+
+# Remove home directory and mail spool when user is removed
+# Default: REMOVE_HOME = 0
+#REMOVE_HOME = 0
+
+# Remove all files on the system owned by the user to be removed
+# Default: REMOVE_ALL_FILES = 0
+#REMOVE_ALL_FILES = 0
+
+# Backup files before removing them. This options has only an effect if
+# REMOVE_HOME or REMOVE_ALL_FILES is set.
+# Default: BACKUP = 0
+#BACKUP = 0
+
+# Target directory for the backup file
+# Default: BACKUP_TO = "."
+#BACKUP_TO = "."
+
+# Select compression (from tar --auto-compress) for backups
+# Default: BACKUP_SUFFIX = .gz
+#BACKUP_SUFFIX = .gz
+
+# Space-Separated list of regular expressions. Do not delete files
+# matching any of these.
+# Default: NO_DEL_PATHS="^/bin\$ ^/boot\$ ^/dev\$ ^/etc\$ ^/initrd ^/lib ^/lost+found\$ ^/media\$ ^/mnt\$ ^/opt\$ ^/proc\$ ^/root\$ ^/run\$ ^/sbin\$ ^/srv\$ ^/sys\$ ^/tmp\$ ^/usr\$ ^/var\$ ^/vmlinu"
+#NO_DEL_PATHS="^/bin\$ ^/boot\$ ^/dev\$ ^/etc\$ ^/initrd ^/lib ^/lost+found\$ ^/media\$ ^/mnt\$ ^/opt\$ ^/proc\$ ^/root\$ ^/run\$ ^/sbin\$ ^/srv\$ ^/sys\$ ^/tmp\$ ^/usr\$ ^/var\$ ^/vmlinu"
+
+# Only delete a group if there are no users belonging to this group.
+# Default: ONLY_IF_EMPTY = 0
+#ONLY_IF_EMPTY = 0
+
+# Single regular expression which describes filesystems types which should
+# be excluded when looking for files of a user to be deleted.
+# Default: EXCLUDE_FSTYPES = "(proc|sysfs|usbfs|devpts|tmpfs|afs)"
+#EXCLUDE_FSTYPES = "(proc|sysfs|usbfs|devpts|tmpfs|afs)"
+
+# What action to take when deleting a system account
+# Options: lock|delete
+# Default: SYS_DELETE_ACTION=delete
+#SYS_DELETE_ACTION=delete
+SYS_DELETE_ACTION=lock
=====================================
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,270 @@ 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;
+}
+
+
+
+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
=====================================
@@ -1,56 +1,447 @@
#!/usr/bin/perl -w
+# there is a deluser.conf in the same directory as the tests
+# that has SYS_DELETE_ACTION=lock so that we can choose the behavior
use strict;
+
use lib_test;
-my $username = find_unused_name();
-my $comment;
+my $error;
+my $output;
+
my $cmd;
+my $username;
+my $susername;
+my $num;
-sub testusercomment {
- my ($username, $comment, $fail_expected) = @_;
- $fail_expected ||= 0;
- $cmd = 'adduser --comment="'. $comment. '" --home=/nonexistent --disabled-password '. "$username";
- if (!defined (getpwnam($username))) {
- print "Testing $cmd... ";
- `$cmd`;
- my $error = ($?>>8);
- if( $fail_expected > 0 ) {
- assert(check_user_not_exist ($username));
- } else {
- if ($error) {
- print "failed\n adduser returned an errorcode != 0 ($error)\n";
- exit $error;
- }
- assert(check_user_exist ($username));
- assert(check_user_comment ($username, $comment));
- }
-
- }
-
- $cmd = "deluser $username";
- if (defined (getpwnam($username))) {
- print "Testing $cmd... ";
- `$cmd`;
- my $error = ($?>>8);
- if ($error) {
- print "failed\n adduser returned an errorcode != 0 ($error)\n";
- exit $error;
- }
- assert(check_user_not_exist ($username));
- print "ok\n";
- }
-}
-
-testusercomment($username, "Tom");
-testusercomment($username, "Tom Omalley");
-testusercomment($username, "Tom O\'Malley");
-testusercomment($username, "Tom O\'Mälléy");
-testusercomment($username, "Tomaß O\'Mälléy");
-testusercomment($username, "Éom O\'Mälléy");
-testusercomment($username, "Éoœm O\'Mälléy");
-testusercomment($username, "Tom:Malley", 1);
+$username = find_unused_name();
+$num = 0;
-# vim: tabstop=4 shiftwidth=4 expandtab
+assert(check_user_not_exist ($username));
+# unlock a non-existing account
+$cmd = "adduser --unlock $username";
+++$num && print "Testing failing (10.$num) $cmd... ";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+if (!$error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_not_exist ($username));
+
+# add the account
+$cmd = "adduser --no-create-home --comment '' --disabled-password $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "locked", 1));
+assert(check_user_status ($username, "not_haspasswd", 1));
+assert(check_user_status ($username, "not_nologin", 1));
+assert(check_user_status ($username, "not_expired", 1));
+
+# unlock the account
+$cmd = "adduser --unlock $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "locked", 1));
+assert(check_user_status ($username, "not_haspasswd", 1));
+assert(check_user_status ($username, "not_nologin", 1));
+assert(check_user_status ($username, "not_expired", 1));
+
+# lock the account
+$cmd = "deluser --lock $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "locked", 1));
+assert(check_user_status ($username, "not_haspasswd", 1));
+assert(check_user_status ($username, "nologin", 1));
+assert(check_user_status ($username, "expired", 1));
+
+# unlock the account
+$cmd = "adduser --unlock $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "locked", 1));
+assert(check_user_status ($username, "not_haspasswd", 1));
+assert(check_user_status ($username, "not_nologin", 1));
+assert(check_user_status ($username, "not_expired", 1));
+
+# set a password
+$cmd = "usermod --password \$y\$j9T\$6KrIYfSdT/O2rBLrkyzcF/\$pMxfrOqQgNn/jlZZVjSs1ELUZjpFRyjZ5ahXKZ84115 $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "not_locked", 1));
+assert(check_user_status ($username, "haspasswd", 1));
+assert(check_user_status ($username, "not_nologin", 1));
+assert(check_user_status ($username, "not_expired", 1));
+
+# unlock the account
+$cmd = "adduser --unlock $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "not_locked", 1));
+assert(check_user_status ($username, "haspasswd", 1));
+assert(check_user_status ($username, "not_nologin", 1));
+assert(check_user_status ($username, "not_expired", 1));
+
+# lock the account
+$cmd = "deluser --lock $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "locked", 1));
+assert(check_user_status ($username, "haspasswd", 1));
+assert(check_user_status ($username, "nologin", 1));
+assert(check_user_status ($username, "expired", 1));
+
+# add the account (should fail)
+$cmd = "adduser --no-create-home --comment '' --disabled-password $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing failing (10.$num) $cmd... ";
+if (!$error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "locked", 1));
+assert(check_user_status ($username, "haspasswd", 1));
+assert(check_user_status ($username, "nologin", 1));
+assert(check_user_status ($username, "expired", 1));
+
+# unlock the account
+$cmd = "adduser --unlock $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "not_locked", 1));
+assert(check_user_status ($username, "haspasswd", 1));
+assert(check_user_status ($username, "not_nologin", 1));
+assert(check_user_status ($username, "not_expired", 1));
+
+# deluser with SYS_DELETE_ACTION=lock
+$cmd = "deluser --conf ./deluser-delete.conf $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "locked", 1));
+assert(check_user_status ($username, "haspasswd", 1));
+assert(check_user_status ($username, "nologin", 1));
+assert(check_user_status ($username, "expired", 1));
+
+# unlock the account
+$cmd = "adduser --unlock $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_exist ($username));
+assert(check_user_status ($username, "not_locked", 1));
+assert(check_user_status ($username, "haspasswd", 1));
+assert(check_user_status ($username, "not_nologin", 1));
+assert(check_user_status ($username, "not_expired", 1));
+
+# regular deluser
+$cmd = "deluser $username";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok.\n";
+assert(check_user_not_exist ($username));
+
+
+
+#=======================
+# system user
+$susername = find_unused_name();
+assert(check_user_not_exist ($susername));
+# unlock a non-existing system ccount
+$cmd = "adduser --unlock --system $susername";
+++$num && print "Testing failing (10.$num) $cmd... ";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+if (!$error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+assert(check_user_not_exist ($susername));
+print "ok\n";
+
+# add system account
+$cmd = "adduser --system $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "locked", 1));
+assert(check_user_status ($susername, "not_haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "not_expired", 1));
+
+
+# unlock the account
+$cmd = "adduser --system --unlock $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing failing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "locked", 1));
+assert(check_user_status ($susername, "not_haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "not_expired", 1));
+
+# lock the account
+$cmd = "deluser --system --lock $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "locked", 1));
+assert(check_user_status ($susername, "not_haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "expired", 1));
+
+# unlock the account
+$cmd = "adduser --system --unlock $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "locked", 1));
+assert(check_user_status ($susername, "not_haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "not_expired", 1));
+
+# set a password
+$cmd = "usermod --password \$y\$j9T\$6KrIYfSdT/O2rBLrkyzcF/\$pMxfrOqQgNn/jlZZVjSs1ELUZjpFRyjZ5ahXKZ84115 $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "not_locked", 1));
+assert(check_user_status ($susername, "haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "not_expired", 1));
+
+# unlock the account
+$cmd = "adduser --system --unlock $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "not_locked", 1));
+assert(check_user_status ($susername, "haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "not_expired", 1));
+
+# lock the account
+$cmd = "deluser --system --lock $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "locked", 1));
+assert(check_user_status ($susername, "haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "expired", 1));
+
+# re-enable via add (fails, already exists and has a password)
+$cmd = "adduser --system $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if (!$error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "locked", 1));
+assert(check_user_status ($susername, "haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "expired", 1));
+
+# unlock the account
+$cmd = "adduser --system --unlock $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "not_locked", 1));
+assert(check_user_status ($susername, "haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "not_expired", 1));
+
+# unlock already-unlocked
+$cmd = "adduser --unlock $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "not_locked", 1));
+assert(check_user_status ($susername, "haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "not_expired", 1));
+
+# deluser with SYS_DELETE_ACTION=lock
+$cmd = "deluser --conf ./deluser-delete.conf --system $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+assert(check_user_exist ($susername));
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "locked", 1));
+assert(check_user_status ($susername, "haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "expired", 1));
+
+# unlock the account
+$cmd = "adduser --system --unlock $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_exist ($susername));
+assert(check_user_status ($susername, "not_locked", 1));
+assert(check_user_status ($susername, "haspasswd", 1));
+assert(check_user_status ($susername, "nologin", 1));
+assert(check_user_status ($susername, "not_expired", 1));
+
+# regular deluser
+$cmd = "deluser --system $susername";
+$output=`$cmd 2>&1`;
+$error = ($?>>8);
+++$num && print "Testing (10.$num) $cmd... ";
+if ($error) {
+ print "failed\n $cmd returned errorcode ($error)\n $output\n";
+ exit 1;
+}
+print "ok\n";
+assert(check_user_not_exist ($susername));
+
+# vim: tabstop=4 shiftwidth=4 expandtab
=====================================
testsuite/test11.pl
=====================================
@@ -0,0 +1,56 @@
+#!/usr/bin/perl -w
+
+
+use strict;
+use lib_test;
+
+my $username = find_unused_name();
+my $comment;
+my $cmd;
+
+sub testusercomment {
+ my ($username, $comment, $fail_expected) = @_;
+ $fail_expected ||= 0;
+ $cmd = 'adduser --comment="'. $comment. '" --home=/nonexistent --disabled-password '. "$username";
+ if (!defined (getpwnam($username))) {
+ print "Testing $cmd... ";
+ `$cmd`;
+ my $error = ($?>>8);
+ if( $fail_expected > 0 ) {
+ assert(check_user_not_exist ($username));
+ } else {
+ if ($error) {
+ print "failed\n adduser returned an errorcode != 0 ($error)\n";
+ exit $error;
+ }
+ assert(check_user_exist ($username));
+ assert(check_user_comment ($username, $comment));
+ }
+
+ }
+
+ $cmd = "deluser $username";
+ if (defined (getpwnam($username))) {
+ print "Testing $cmd... ";
+ `$cmd`;
+ my $error = ($?>>8);
+ if ($error) {
+ print "failed\n adduser returned an errorcode != 0 ($error)\n";
+ exit $error;
+ }
+ assert(check_user_not_exist ($username));
+ print "ok\n";
+ }
+}
+
+testusercomment($username, "Tom");
+testusercomment($username, "Tom Omalley");
+testusercomment($username, "Tom O\'Malley");
+testusercomment($username, "Tom O\'Mälléy");
+testusercomment($username, "Tomaß O\'Mälléy");
+testusercomment($username, "Éom O\'Mälléy");
+testusercomment($username, "Éoœm O\'Mälléy");
+testusercomment($username, "Tom:Malley", 1);
+
+# vim: tabstop=4 shiftwidth=4 expandtab
+
View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/4fbb40b2e3a9ef441b1cf7a0801a8a4b7dbced6e...5937f07b1cb4a3267fffe051c647c3e218a9fc2b
--
View it on GitLab: https://salsa.debian.org/debian/adduser/-/compare/4fbb40b2e3a9ef441b1cf7a0801a8a4b7dbced6e...5937f07b1cb4a3267fffe051c647c3e218a9fc2b
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/20260117/8b2fedf7/attachment-0001.htm>
More information about the Pkg-shadow-devel
mailing list