912 lines
25 KiB
Perl
912 lines
25 KiB
Perl
|
|
#-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||
|
|
# Users account manager. Designed to be architecture and distribution independent.
|
||
|
|
#
|
||
|
|
# Copyright (C) 2000-2001 Ximian, Inc.
|
||
|
|
#
|
||
|
|
# Authors: Hans Petter Jansson <hpj@ximian.com>,
|
||
|
|
# Arturo Espinosa <arturo@ximian.com>,
|
||
|
|
# Tambet Ingo <tambet@ximian.com>.
|
||
|
|
# Grzegorz Golawski <grzegol@pld-linux.org> (PLD Support),
|
||
|
|
# Milan Bouchet-Valat <nalimilan@club.fr>.
|
||
|
|
#
|
||
|
|
# This program is free software; you can redistribute it and/or modify
|
||
|
|
# it under the terms of the GNU Library General Public License as published
|
||
|
|
# by the Free Software Foundation; either version 2 of the License, or
|
||
|
|
# (at your option) any later version.
|
||
|
|
#
|
||
|
|
# This program is distributed in the hope that it will be useful,
|
||
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
|
# GNU Library General Public License for more details.
|
||
|
|
#
|
||
|
|
# You should have received a copy of the GNU Library General Public License
|
||
|
|
# along with this program; if not, write to the Free Software
|
||
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
||
|
|
|
||
|
|
# Best viewed with 100 columns of width.
|
||
|
|
|
||
|
|
# Configuration files affected:
|
||
|
|
#
|
||
|
|
# /etc/passwd
|
||
|
|
# /etc/group
|
||
|
|
# /etc/shadow
|
||
|
|
# /etc/login.defs
|
||
|
|
# /etc/shells
|
||
|
|
# /etc/skel/
|
||
|
|
|
||
|
|
# NIS support will come later.
|
||
|
|
|
||
|
|
# Running programs affected/used:
|
||
|
|
#
|
||
|
|
# adduser: creating users.
|
||
|
|
# usermod: modifying user data.
|
||
|
|
# passwd: assigning or changing passwords. (Un)locking users.
|
||
|
|
# chfn: modifying finger information - Name, Office, Office phone, Home phone.
|
||
|
|
# pw: modifying users/groups and user/group data on FreeBSD.
|
||
|
|
|
||
|
|
package Users::Users;
|
||
|
|
|
||
|
|
use Utils::Util;
|
||
|
|
use Utils::Report;
|
||
|
|
use Utils::File;
|
||
|
|
use Utils::Backend;
|
||
|
|
use Utils::Replace;
|
||
|
|
|
||
|
|
# --- System config file locations --- #
|
||
|
|
|
||
|
|
# We list each config file type with as many alternate locations as possible.
|
||
|
|
# They are tried in array order. First found = used.
|
||
|
|
@passwd_names = ( "/etc/passwd" );
|
||
|
|
@shadow_names = ( "/etc/shadow", "/etc/master.passwd" );
|
||
|
|
@login_defs_names = ( "/etc/login.defs", "/etc/adduser.conf" );
|
||
|
|
@shell_names = ( "/etc/shells" );
|
||
|
|
@skel_dir = ( "/usr/share/skel", "/etc/skel" );
|
||
|
|
|
||
|
|
# Where are the tools?
|
||
|
|
$cmd_usermod = &Utils::File::locate_tool ("usermod");
|
||
|
|
$cmd_userdel = &Utils::File::locate_tool ("userdel");
|
||
|
|
$cmd_useradd = &Utils::File::locate_tool ("useradd");
|
||
|
|
|
||
|
|
$cmd_adduser = &Utils::File::locate_tool ("adduser");
|
||
|
|
$cmd_deluser = &Utils::File::locate_tool ("deluser");
|
||
|
|
|
||
|
|
$cmd_chfn = &Utils::File::locate_tool ("chfn");
|
||
|
|
$cmd_pw = &Utils::File::locate_tool ("pw");
|
||
|
|
|
||
|
|
$cmd_passwd = &Utils::File::locate_tool ("passwd");
|
||
|
|
$cmd_chpasswd = &Utils::File::locate_tool ("chpasswd");
|
||
|
|
|
||
|
|
# enum like for verbose group array positions
|
||
|
|
my $i = 0;
|
||
|
|
my $LOGIN = $i++;
|
||
|
|
my $PASSWD = $i++;
|
||
|
|
my $UID = $i++;
|
||
|
|
my $GID = $i++;
|
||
|
|
my $COMMENT = $i++;
|
||
|
|
my $HOME = $i++;
|
||
|
|
my $SHELL = $i++;
|
||
|
|
my $PASSWD_STATUS = $i++;
|
||
|
|
my $ENC_HOME = $i++;
|
||
|
|
my $HOME_FLAGS = $i++;
|
||
|
|
my $LOCALE = $i++;
|
||
|
|
my $LOCATION = $i++;
|
||
|
|
my $FACE = $i++;
|
||
|
|
|
||
|
|
%login_defs_prop_map = ();
|
||
|
|
%profiles_prop_map = ();
|
||
|
|
|
||
|
|
sub get_login_defs_prop_array
|
||
|
|
{
|
||
|
|
my @prop_array;
|
||
|
|
my @login_defs_prop_array_default =
|
||
|
|
(
|
||
|
|
"QMAIL_DIR", "qmail_dir",
|
||
|
|
"MAIL_DIR", "mailbox_dir",
|
||
|
|
"MAIL_FILE", "mailbox_file",
|
||
|
|
"PASS_MAX_DAYS", "pwd_maxdays",
|
||
|
|
"PASS_MIN_DAYS", "pwd_mindays",
|
||
|
|
"PASS_MIN_LEN", "pwd_min_length",
|
||
|
|
"PASS_WARN_AGE", "pwd_warndays",
|
||
|
|
"UID_MIN", "umin",
|
||
|
|
"UID_MAX", "umax",
|
||
|
|
"GID_MIN", "gmin",
|
||
|
|
"GID_MAX", "gmax",
|
||
|
|
"USERDEL_CMD", "del_user_additional_command",
|
||
|
|
"CREATE_HOME", "create_home",
|
||
|
|
"", "");
|
||
|
|
|
||
|
|
my @login_defs_prop_array_suse =
|
||
|
|
(
|
||
|
|
"QMAIL_DIR", "qmail_dir",
|
||
|
|
"MAIL_DIR", "mailbox_dir",
|
||
|
|
"MAIL_FILE", "mailbox_file",
|
||
|
|
"PASS_MAX_DAYS", "pwd_maxdays",
|
||
|
|
"PASS_MIN_DAYS", "pwd_mindays",
|
||
|
|
"PASS_MIN_LEN", "pwd_min_length",
|
||
|
|
"PASS_WARN_AGE", "pwd_warndays",
|
||
|
|
"UID_MIN", "umin",
|
||
|
|
"UID_MAX", "umax",
|
||
|
|
"SYSTEM_GID_MIN", "gmin",
|
||
|
|
"GID_MAX", "gmax",
|
||
|
|
"USERDEL_CMD", "del_user_additional_command",
|
||
|
|
"CREATE_HOME", "create_home",
|
||
|
|
"", "");
|
||
|
|
|
||
|
|
if ($Utils::Backend::tool{"platform"} =~ /^suse/)
|
||
|
|
{
|
||
|
|
@prop_array = @login_defs_prop_array_suse;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
@prop_array = @login_defs_prop_array_default;
|
||
|
|
}
|
||
|
|
|
||
|
|
for ($i = 0; $prop_array [$i] ne ""; $i += 2)
|
||
|
|
{
|
||
|
|
$login_defs_prop_map {$prop_array [$i]} = $prop_array [$i + 1];
|
||
|
|
$login_defs_prop_map {$prop_array [$i + 1]} = $prop_array [$i];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_profiles_prop_array
|
||
|
|
{
|
||
|
|
my @prop_array;
|
||
|
|
my @profiles_prop_array_default =
|
||
|
|
(
|
||
|
|
"NAME" , "name",
|
||
|
|
"COMMENT", "comment",
|
||
|
|
"LOGINDEFS", "login_defs",
|
||
|
|
"HOME_PREFFIX", "home_prefix",
|
||
|
|
"SHELL", "shell",
|
||
|
|
"GROUP", "group",
|
||
|
|
"SKEL_DIR", "skel_dir",
|
||
|
|
"QMAIL_DIR" , "qmail_dir",
|
||
|
|
"MAIL_DIR" , "mailbox_dir",
|
||
|
|
"MAIL_FILE" , "mailbox_file",
|
||
|
|
"PASS_RANDOM", "pwd_random",
|
||
|
|
"PASS_MAX_DAYS" , "pwd_maxdays",
|
||
|
|
"PASS_MIN_DAYS" , "pwd_mindays",
|
||
|
|
"PASS_MIN_LEN" , "pwd_min_length",
|
||
|
|
"PASS_WARN_AGE" , "pwd_warndays",
|
||
|
|
"UID_MIN" , "umin",
|
||
|
|
"UID_MAX" , "umax",
|
||
|
|
"GID_MIN" , "gmin",
|
||
|
|
"GID_MAX" , "gmax",
|
||
|
|
"USERDEL_CMD" , "del_user_additional_command",
|
||
|
|
"CREATE_HOME" , "create_home",
|
||
|
|
"", "");
|
||
|
|
|
||
|
|
my @profiles_prop_array_suse =
|
||
|
|
(
|
||
|
|
"NAME" , "name",
|
||
|
|
"COMMENT", "comment",
|
||
|
|
"LOGINDEFS", "login_defs",
|
||
|
|
"HOME_PREFFIX", "home_prefix",
|
||
|
|
"SHELL", "shell",
|
||
|
|
"GROUP", "group",
|
||
|
|
"SKEL_DIR", "skel_dir",
|
||
|
|
"QMAIL_DIR" , "qmail_dir",
|
||
|
|
"MAIL_DIR" , "mailbox_dir",
|
||
|
|
"MAIL_FILE" , "mailbox_file",
|
||
|
|
"PASS_RANDOM", "pwd_random",
|
||
|
|
"PASS_MAX_DAYS" , "pwd_maxdays",
|
||
|
|
"PASS_MIN_DAYS" , "pwd_mindays",
|
||
|
|
"PASS_MIN_LEN" , "pwd_min_length",
|
||
|
|
"PASS_WARN_AGE" , "pwd_warndays",
|
||
|
|
"UID_MIN" , "umin",
|
||
|
|
"UID_MAX" , "umax",
|
||
|
|
"GID_MIN" , "gmin",
|
||
|
|
"GID_MAX" , "gmax",
|
||
|
|
"USERDEL_CMD" , "del_user_additional_command",
|
||
|
|
"CREATE_HOME" , "create_home",
|
||
|
|
"", "");
|
||
|
|
|
||
|
|
if ($Utils::Backend::tool{"platform"} =~ /suse/)
|
||
|
|
{
|
||
|
|
@prop_array = @profiles_prop_array_suse;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
@prop_array = @profiles_prop_array_default;
|
||
|
|
}
|
||
|
|
|
||
|
|
for ($i = 0; $prop_array[$i] ne ""; $i += 2)
|
||
|
|
{
|
||
|
|
$profiles_prop_map {$prop_array [$i]} = $prop_array [$i + 1];
|
||
|
|
$profiles_prop_map {$prop_array [$i + 1]} = $prop_array [$i];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#FIXME: do not hardcode GIDs like that
|
||
|
|
my $rh_logindefs_defaults = {
|
||
|
|
'shell' => '/bin/bash',
|
||
|
|
'group' => -1,
|
||
|
|
'skel_dir' => '/etc/skel/',
|
||
|
|
};
|
||
|
|
|
||
|
|
my $gentoo_logindefs_defaults = {
|
||
|
|
'shell' => '/bin/bash',
|
||
|
|
'group' => 100,
|
||
|
|
'skel_dir' => '/etc/skel/',
|
||
|
|
};
|
||
|
|
|
||
|
|
my $freebsd_logindefs_defaults = {
|
||
|
|
'shell' => '/bin/sh',
|
||
|
|
'group' => -1,
|
||
|
|
'skel_dir' => '/etc/skel/',
|
||
|
|
};
|
||
|
|
|
||
|
|
my $logindefs_dist_map = {
|
||
|
|
'redhat-6.2' => $rh_logindefs_defaults,
|
||
|
|
'redhat-7.0' => $rh_logindefs_defaults,
|
||
|
|
'redhat-7.1' => $rh_logindefs_defaults,
|
||
|
|
'redhat-7.2' => $rh_logindefs_defaults,
|
||
|
|
'redhat-7.3' => $rh_logindefs_defaults,
|
||
|
|
'redhat-8.0' => $rh_logindefs_defaults,
|
||
|
|
'mandrake-9.0' => $rh_logindefs_defaults,
|
||
|
|
'pld-1.0' => $rh_logindefs_defaults,
|
||
|
|
'fedora-1' => $rh_logindefs_defaults,
|
||
|
|
'debian' => $rh_logindefs_defaults,
|
||
|
|
'vine-3.0' => $rh_logindefs_defaults,
|
||
|
|
'gentoo' => $gentoo_logindefs_defaults,
|
||
|
|
'archlinux' => $gentoo_logindefs_defaults,
|
||
|
|
'slackware-9.1.0' => $gentoo_logindefs_defaults,
|
||
|
|
'freebsd-5' => $freebsd_logindefs_defaults,
|
||
|
|
'suse-9.0' => $gentoo_logindefs_defaults,
|
||
|
|
'solaris-2.11' => $gentoo_logindefs_defaults,
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
# Add reporting table.
|
||
|
|
|
||
|
|
&Utils::Report::add ({
|
||
|
|
'users_read_profiledb_success' => ['info', 'Profiles read successfully.'],
|
||
|
|
'users_read_profiledb_fail' => ['warn', 'Profiles read failed.'],
|
||
|
|
'users_read_users_success' => ['info', 'Users read successfully.'],
|
||
|
|
'users_read_users_fail' => ['warn', 'Users read failed.'],
|
||
|
|
'users_read_users_invalid' => ['warn', 'Invalid user found while reading (missing fields).'],
|
||
|
|
'users_read_groups_success' => ['info', 'Groups read successfully.'],
|
||
|
|
'users_read_groups_fail' => ['warn', 'Groups read failed.'],
|
||
|
|
'users_read_shells_success' => ['info', 'Shells read successfully.'],
|
||
|
|
'users_read_shells_fail' => ['warn', 'Reading shells failed.'],
|
||
|
|
|
||
|
|
'users_write_profiledb_success' => ['info', 'Profiles written successfully.'],
|
||
|
|
'users_write_profiledb_fail' => ['warn', 'Writing profiles failed.'],
|
||
|
|
'users_write_users_success' => ['info', 'Users written successfully.'],
|
||
|
|
'users_write_users_fail' => ['warn', 'Writing users failed.'],
|
||
|
|
'users_write_groups_success' => ['info', 'Groups written successfully.'],
|
||
|
|
'users_write_groups_fail' => ['warn', 'Writing groups failed.'],
|
||
|
|
});
|
||
|
|
|
||
|
|
sub logindefs_add_defaults
|
||
|
|
{
|
||
|
|
# Common for all distros
|
||
|
|
my $logindefs = {
|
||
|
|
'home_prefix' => '/home/',
|
||
|
|
};
|
||
|
|
|
||
|
|
&get_profiles_prop_array ();
|
||
|
|
|
||
|
|
# Distro specific
|
||
|
|
my $dist_specific = $logindefs_dist_map->{$Utils::Backend::tool{"platform"}};
|
||
|
|
|
||
|
|
# Just to be 100% sure SOMETHING gets filled:
|
||
|
|
unless ($dist_specific)
|
||
|
|
{
|
||
|
|
$dist_specific = $rh_logindefs_defaults;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach my $key (keys %$dist_specific)
|
||
|
|
{
|
||
|
|
# Make sure there's no crappy entries
|
||
|
|
if (exists ($profiles_prop_map{$key}) || $key eq "groups")
|
||
|
|
{
|
||
|
|
$logindefs->{$key} = $dist_specific->{$key};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $logindefs;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_logindefs
|
||
|
|
{
|
||
|
|
my $logindefs;
|
||
|
|
|
||
|
|
&get_login_defs_prop_array ();
|
||
|
|
$logindefs = &logindefs_add_defaults ();
|
||
|
|
|
||
|
|
# Get new data in case someone has changed login_defs manually.
|
||
|
|
my $fh = &Utils::File::open_read_from_names (@login_defs_names);
|
||
|
|
|
||
|
|
if ($fh)
|
||
|
|
{
|
||
|
|
while (<$fh>)
|
||
|
|
{
|
||
|
|
next if &Utils::Util::ignore_line ($_);
|
||
|
|
chomp;
|
||
|
|
my @line = split /[ \t]+/;
|
||
|
|
|
||
|
|
if (exists $login_defs_prop_map{$line[0]})
|
||
|
|
{
|
||
|
|
$logindefs->{$login_defs_prop_map{$line[0]}} = $line[1];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
close $fh;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
# Put safe defaults for distros/OS that don't have any defaults file
|
||
|
|
$logindefs->{"umin"} = '1000';
|
||
|
|
$logindefs->{"umax"} = '60000';
|
||
|
|
$logindefs->{"gmin"} = '1000';
|
||
|
|
$logindefs->{"gmax"} = '60000';
|
||
|
|
}
|
||
|
|
|
||
|
|
return $logindefs;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get
|
||
|
|
{
|
||
|
|
my ($ifh, @users, %users_hash, $fd, @passwd_status);
|
||
|
|
my (@line, @users);
|
||
|
|
|
||
|
|
# Find the passwd file.
|
||
|
|
$ifh = &Utils::File::open_read_from_names(@passwd_names);
|
||
|
|
return unless ($ifh);
|
||
|
|
|
||
|
|
while (<$ifh>)
|
||
|
|
{
|
||
|
|
chomp;
|
||
|
|
# FreeBSD allows comments in the passwd file.
|
||
|
|
next if &Utils::Util::ignore_line ($_);
|
||
|
|
|
||
|
|
@line = split ':', $_, -1;
|
||
|
|
$login = $line[$LOGIN];
|
||
|
|
|
||
|
|
# skip invalid users, else they will create troubles
|
||
|
|
if (($line[$LOGIN] eq "") || ($line[$UID] eq ""))
|
||
|
|
{
|
||
|
|
&Utils::Report::do_report ("users_read_users_invalid");
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
|
||
|
|
@comment = split ',', $line[$COMMENT], 5;
|
||
|
|
|
||
|
|
# we need to make sure that there are 5 elements
|
||
|
|
push @comment, "" while (scalar (@comment) < 5);
|
||
|
|
$line[$COMMENT] = [@comment];
|
||
|
|
|
||
|
|
# always return empty string - anyway, passwd should be in /etc/shadow
|
||
|
|
$line[$PASSWD] = "";
|
||
|
|
|
||
|
|
$users_hash{$login} = [@line];
|
||
|
|
|
||
|
|
# Detect lock status of password
|
||
|
|
# We run 'passwd' instead of reading /etc/shadow directly
|
||
|
|
# to avoid leaving sensitive data in memory (hard to clear in perl)
|
||
|
|
$fd = &Utils::File::run_pipe_read ("passwd -S $login");
|
||
|
|
@passwd_status = split ' ', <$fd>;
|
||
|
|
&Utils::File::close_file ($fd);
|
||
|
|
|
||
|
|
if ($passwd_status[1] eq "P")
|
||
|
|
{
|
||
|
|
$users_hash{$login}[$PASSWD_STATUS] = 0;
|
||
|
|
}
|
||
|
|
elsif ($passwd_status[1] eq "NP")
|
||
|
|
{
|
||
|
|
$users_hash{$login}[$PASSWD_STATUS] = 1;
|
||
|
|
}
|
||
|
|
else # "L", means locked password
|
||
|
|
{
|
||
|
|
$users_hash{$login}[$PASSWD_STATUS] = 1 << 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# max value for an unsigned 32 bits integer means no main group
|
||
|
|
$users_hash{$login}[$GID] = 0xFFFFFFFF if (!$users_hash{$login}[$GID]);
|
||
|
|
|
||
|
|
# TODO: read actual values
|
||
|
|
$users_hash{$login}[$ENC_HOME] = 0;
|
||
|
|
$users_hash{$login}[$HOME_FLAGS] = 0;
|
||
|
|
$users_hash{$login}[$LOCALE] = "";
|
||
|
|
$users_hash{$login}[$LOCATION] = "";
|
||
|
|
$users_hash{$login}[$FACE] = "";
|
||
|
|
}
|
||
|
|
|
||
|
|
&Utils::File::close_file ($ifh);
|
||
|
|
|
||
|
|
# transform the hash into an array
|
||
|
|
foreach $login (keys %users_hash)
|
||
|
|
{
|
||
|
|
push @users, $users_hash{$login};
|
||
|
|
}
|
||
|
|
|
||
|
|
return \@users;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub del_user
|
||
|
|
{
|
||
|
|
my ($user) = @_;
|
||
|
|
my (@command, $remove_home);
|
||
|
|
|
||
|
|
$remove_home = $$user[$HOME_FLAGS] & 1;
|
||
|
|
|
||
|
|
if ($Utils::Backend::tool{"system"} eq "FreeBSD")
|
||
|
|
{
|
||
|
|
if ($remove_home)
|
||
|
|
{
|
||
|
|
@command = ($cmd_pw, "userdel", "-r", "-n", $$user[$LOGIN]);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
@command = ($cmd_pw, "userdel", "-n", $$user[$LOGIN]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
elsif ($cmd_deluser) # use deluser (preferred method)
|
||
|
|
{
|
||
|
|
if ($remove_home)
|
||
|
|
{
|
||
|
|
@command = ($cmd_deluser, "--remove-home", $$user[$LOGIN]);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
@command = ($cmd_deluser, $$user[$LOGIN]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else # use userdel
|
||
|
|
{
|
||
|
|
if ($remove_home)
|
||
|
|
{
|
||
|
|
@command = ($cmd_userdel, "--remove", $$user[$LOGIN]);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
@command = ($cmd_userdel, $$user[$LOGIN]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub change_user_chfn
|
||
|
|
{
|
||
|
|
my ($login, $old_comment, $comment) = @_;
|
||
|
|
my ($fname, $office, $office_phone, $home_phone);
|
||
|
|
my (@command, $str);
|
||
|
|
|
||
|
|
return if !$login;
|
||
|
|
|
||
|
|
# Compare old and new data
|
||
|
|
return if (Utils::Util::struct_eq ($old_comment, $comment));
|
||
|
|
$str = join (",", @$comment);
|
||
|
|
|
||
|
|
if ($Utils::Backend::tool{"system"} eq "FreeBSD")
|
||
|
|
{
|
||
|
|
@command = ($cmd_pw, "usermod", "-n", $login,
|
||
|
|
"-c", $str);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
@command = ($cmd_usermod, "-c", $str, $login);
|
||
|
|
}
|
||
|
|
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_passwd
|
||
|
|
{
|
||
|
|
my ($login, $password, $passwd_status) = @_;
|
||
|
|
my ($pwdpipe);
|
||
|
|
|
||
|
|
# handle empty password via passwd, as all tools don't support it
|
||
|
|
if ($passwd_status & 1)
|
||
|
|
{
|
||
|
|
&Utils::File::run ("passwd", "-d", $login);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($Utils::Backend::tool{"system"} eq "FreeBSD")
|
||
|
|
{
|
||
|
|
my ($command);
|
||
|
|
$command = "$cmd_pw usermod \'$login\' -h 0";
|
||
|
|
$pwdpipe = &Utils::File::run_pipe_write ($command);
|
||
|
|
print $pwdpipe $password;
|
||
|
|
&Utils::File::close_file ($pwdpipe);
|
||
|
|
}
|
||
|
|
elsif ($Utils::Backend::tool{"system"} eq "SunOS")
|
||
|
|
{
|
||
|
|
my ($command);
|
||
|
|
$command = "$cmd_passwd --stdin \'$login\'";
|
||
|
|
$pwdpipe = &Utils::File::run_pipe_write ($command);
|
||
|
|
print $pwdpipe $password;
|
||
|
|
&Utils::File::close_file ($pwdpipe);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
$pwdpipe = &Utils::File::run_pipe_write ($cmd_chpasswd);
|
||
|
|
print $pwdpipe "$login:$password";
|
||
|
|
&Utils::File::close_file ($pwdpipe);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Enable/disable password, only call if value has changed
|
||
|
|
sub set_lock
|
||
|
|
{
|
||
|
|
my ($login, $passwd_status) = @_;
|
||
|
|
my ($pwdpipe);
|
||
|
|
|
||
|
|
if ($passwd_status & (1 << 1))
|
||
|
|
{
|
||
|
|
&Utils::File::run ("passwd", "-l", $login);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
&Utils::File::run ("passwd", "-u", $login);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# This function allows empty values to be passed, in which cas
|
||
|
|
# the platform's tools will choose the default.
|
||
|
|
sub add_user
|
||
|
|
{
|
||
|
|
my ($user) = @_;
|
||
|
|
my ($tool_mkdir, $chown_home, $real_uid, $real_gid);
|
||
|
|
|
||
|
|
$tool_mkdir = &Utils::File::locate_tool ("mkdir");
|
||
|
|
|
||
|
|
# If directory is specified, ensure its parents exist.
|
||
|
|
# When using default prefix, we assume the directory exists.
|
||
|
|
if ($$user[$HOME])
|
||
|
|
{
|
||
|
|
my $home_parents, $erase_home;
|
||
|
|
|
||
|
|
$home_parents = $$user[$HOME];
|
||
|
|
$home_parents =~ s/\/+[^\/]+\/*$//;
|
||
|
|
&Utils::File::run ($tool_mkdir, "-p", $home_parents);
|
||
|
|
|
||
|
|
$erase_home = $$user[$HOME_FLAGS] & (1 << 3);
|
||
|
|
|
||
|
|
# Remove home if asked, it will be created from scratch by platform tools
|
||
|
|
if ($erase_home && -e $$user[$HOME] && $$user[$HOME] ne "/")
|
||
|
|
{
|
||
|
|
# Remove trailing slash(es) to avoid issues with rm on symlinks
|
||
|
|
$$user[$HOME] =~ s|/*$||;
|
||
|
|
|
||
|
|
@command = ("rm", "-Rf", $$user[$HOME]);
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# max value means default UID or GID here
|
||
|
|
$real_uid = ($$user[$UID] != 0xFFFFFFFF);
|
||
|
|
$real_gid = ($$user[$GID] != 0xFFFFFFFF);
|
||
|
|
|
||
|
|
if ($Utils::Backend::tool{"system"} eq "FreeBSD")
|
||
|
|
{
|
||
|
|
my $logindefs;
|
||
|
|
|
||
|
|
# FreeBSD doesn't create the home directory
|
||
|
|
if (!$$user[$HOME])
|
||
|
|
{
|
||
|
|
$logindefs = &get_logindefs ();
|
||
|
|
$$user[$HOME] = "$$logindefs{'home_prefix'}/$$user[$LOGIN]";
|
||
|
|
}
|
||
|
|
&Utils::File::run ($tool_mkdir, "-p", $$user[$HOME]);
|
||
|
|
|
||
|
|
@command = ($cmd_pw, "useradd", "-n", $$user[$LOGIN],
|
||
|
|
"-h", "-"); # disable login until password is set
|
||
|
|
|
||
|
|
push (@command, ("-s", $$user[$HOME])) if ($$user[$HOME]);
|
||
|
|
push (@command, ("-s", $$user[$SHELL])) if ($$user[$SHELL]);
|
||
|
|
push (@command, ("-u", $$user[$UID])) if ($real_uid);
|
||
|
|
push (@command, ("-g", $$user[$GID])) if ($real_gid);
|
||
|
|
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
elsif ($Utils::Backend::tool{"system"} eq "SunOS")
|
||
|
|
{
|
||
|
|
@command = ($cmd_useradd);
|
||
|
|
|
||
|
|
push (@command, ("-d", $$user[$HOME])) if ($$user[$HOME]);
|
||
|
|
push (@command, ("-s", $$user[$SHELL])) if ($$user[$SHELL]);
|
||
|
|
push (@command, ("-u", $$user[$UID])) if ($real_uid);
|
||
|
|
push (@command, ("-g", $$user[$GID])) if ($real_gid);
|
||
|
|
push (@command, $$user[$LOGIN]);
|
||
|
|
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if ($cmd_adduser &&
|
||
|
|
$Utils::Backend::tool{"platform"} !~ /^slackware/ &&
|
||
|
|
$Utils::Backend::tool{"platform"} !~ /^archlinux/ &&
|
||
|
|
$Utils::Backend::tool{"platform"} !~ /^redhat/ &&
|
||
|
|
$Utils::Backend::tool{"platform"} !~ /^gentoo/)
|
||
|
|
{
|
||
|
|
# use adduser if available and valid (slackware one is b0rk)
|
||
|
|
# set empty gecos fields and password, they will be filled out later
|
||
|
|
@command = ($cmd_adduser, "--gecos", "",
|
||
|
|
"--disabled-password");
|
||
|
|
|
||
|
|
push (@command, ("--home", $$user[$HOME])) if ($$user[$HOME]);
|
||
|
|
push (@command, ("--shell", $$user[$SHELL])) if ($$user[$SHELL]);
|
||
|
|
push (@command, ("--uid", $$user[$UID])) if ($real_uid);
|
||
|
|
push (@command, ("--gid", $$user[$GID])) if ($real_gid);
|
||
|
|
|
||
|
|
# Allow encrypted home if the tool is present
|
||
|
|
if ($$user[$ENC_HOME] && &Utils::File::locate_tool ("mount.ecryptfs"))
|
||
|
|
{
|
||
|
|
push (@command, "--encrypt-home");
|
||
|
|
}
|
||
|
|
|
||
|
|
push (@command, $$user[$LOGIN]);
|
||
|
|
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
# fallback to useradd
|
||
|
|
@command = ($cmd_useradd, "-m");
|
||
|
|
|
||
|
|
push (@command, ("-d", $$user[$HOME])) if ($$user[$HOME]);
|
||
|
|
push (@command, ("-s", $$user[$SHELL])) if ($$user[$SHELL]);
|
||
|
|
push (@command, ("-u", $$user[$UID])) if ($real_uid);
|
||
|
|
push (@command, ("-g", $$user[$GID])) if ($real_gid);
|
||
|
|
push (@command, $$user[$LOGIN]);
|
||
|
|
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
&change_user_chfn ($$user[$LOGIN], undef, $$user[$COMMENT]);
|
||
|
|
&set_passwd ($$user[$LOGIN], $$user[$PASSWD], $$user[$PASSWD_STATUS]);
|
||
|
|
&set_lock ($$user[$LOGIN], $$user[$PASSWD_STATUS]);
|
||
|
|
|
||
|
|
$chown_home = $$user[$HOME_FLAGS] & (1 << 1);
|
||
|
|
|
||
|
|
# update user to get values that were filled
|
||
|
|
$user = &get_user ($$user[$LOGIN]);
|
||
|
|
|
||
|
|
# ensure user owns its home dir if asked
|
||
|
|
if ($chown_home && $$user[$HOME] ne "/")
|
||
|
|
{
|
||
|
|
@command = ("chown", "-R", "$$user[$LOGIN]:$$user[$GID]", $$user[$HOME]);
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
|
||
|
|
# Return the new user with default values filled.
|
||
|
|
# Returns NULL if user doesn't exist, which means failure.
|
||
|
|
return $user;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub change_user
|
||
|
|
{
|
||
|
|
my ($old_user, $new_user) = @_;
|
||
|
|
my $chown_home, $move_home, $copy_home, $erase_home;
|
||
|
|
|
||
|
|
if ($Utils::Backend::tool{"system"} eq "FreeBSD")
|
||
|
|
{
|
||
|
|
@command = ($cmd_pw, "usermod", $$old_user[$LOGIN],
|
||
|
|
"-l", $$new_user[$LOGIN],
|
||
|
|
"-u", $$new_user[$UID],
|
||
|
|
"-d", $$new_user[$HOME],
|
||
|
|
"-g", $$new_user[$GID],
|
||
|
|
"-s", $$new_user[$SHELL]);
|
||
|
|
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
@command = ($cmd_usermod, "-d", $$new_user[$HOME],
|
||
|
|
"-g", $$new_user[$GID],
|
||
|
|
"-l", $$new_user[$LOGIN],
|
||
|
|
"-s", $$new_user[$SHELL],
|
||
|
|
"-u", $$new_user[$UID],
|
||
|
|
$$old_user[$LOGIN]);
|
||
|
|
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
|
||
|
|
&change_user_chfn ($$new_user[$LOGIN], $$old_user[$COMMENT], $$new_user[$COMMENT]);
|
||
|
|
&set_passwd ($$new_user[$LOGIN], $$new_user[$PASSWD], $$user[$PASSWD_STATUS]);
|
||
|
|
|
||
|
|
# Only change lock status if status has changed
|
||
|
|
if (($$new_user[$PASSWD_STATUS] & (1 << 1)) != ($$old_user[$PASSWD_STATUS] & (1 << 1)))
|
||
|
|
{
|
||
|
|
&set_lock ($$new_user[$LOGIN], $$new_user[$PASSWD_STATUS]);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
# Home directory handling
|
||
|
|
if ($$new_user[$HOME] ne $$old_user[$HOME])
|
||
|
|
{
|
||
|
|
# remove old home dir
|
||
|
|
$remove_home = $$new_user[$HOME_FLAGS] & (1 << 0);
|
||
|
|
# ensure user owns home dir
|
||
|
|
$chown_home = $$new_user[$HOME_FLAGS] & (1 << 1);
|
||
|
|
# copy old home files to new dir
|
||
|
|
$copy_home = $$new_user[$HOME_FLAGS] & (1 << 2);
|
||
|
|
# remove files present in path to new home
|
||
|
|
$erase_home = $$new_user[$HOME_FLAGS] & (1 << 3);
|
||
|
|
|
||
|
|
# Remove trailing slash(es) to avoid issues with rm on symlinks
|
||
|
|
# '/' becomes empty, which is easier to check for security below
|
||
|
|
$$new_user[$HOME] =~ s|/*$||;
|
||
|
|
$$old_user[$HOME] =~ s|/*$||;
|
||
|
|
|
||
|
|
if ($erase_home && $$new_user[$HOME] && -e $$new_user[$HOME])
|
||
|
|
{
|
||
|
|
@command = ("rm", "-Rf", $$new_user[$HOME]);
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($copy_home && $$new_user[$HOME] && $$old_user[$HOME])
|
||
|
|
{
|
||
|
|
# Remove new directory if present, to avoid troubles when merging.
|
||
|
|
# GUIs should ask the user before passing this flag anyway!
|
||
|
|
if (-e $$new_user[$HOME])
|
||
|
|
{
|
||
|
|
@command = ("rm", "-Rf", $$new_user[$HOME]);
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (-e $$old_user[$HOME])
|
||
|
|
{
|
||
|
|
if ($remove_home)
|
||
|
|
{
|
||
|
|
@command = ("mv", "-f", $$old_user[$HOME], $$new_user[$HOME]);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if ($Utils::Backend::tool{"system"} eq "SunOS")
|
||
|
|
{
|
||
|
|
@command = ("cp", "-RPpf", $$old_user[$HOME], $$new_user[$HOME]);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
@command = ("cp", "-af", $$old_user[$HOME], $$new_user[$HOME]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
elsif ($remove_home && $$old_user[$HOME] && -e $$old_user[$HOME] )
|
||
|
|
{
|
||
|
|
@command = ("rm", "-Rf", $$old_user[$HOME]);
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create home directory owned by user if not present
|
||
|
|
# If a file with this name exists, skip
|
||
|
|
if (!-e $$new_user[$HOME] && $$new_user[$HOME])
|
||
|
|
{
|
||
|
|
@command = ("mkdir", "-p", $$new_user[$HOME]);
|
||
|
|
&Utils::File::run (@command) if (!-d $$new_user[$HOME]);
|
||
|
|
|
||
|
|
@command = ("chown", "-f", "$$new_user[$LOGIN]:$$new_user[$GID]", $$new_user[$HOME]);
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
elsif ($chown_home && $$new_user[$HOME])
|
||
|
|
{
|
||
|
|
@command = ("chown", "-Rf", "$$new_user[$LOGIN]:$$new_user[$GID]", $$new_user[$HOME]);
|
||
|
|
&Utils::File::run (@command);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Erase password string to avoid it from staying in memory
|
||
|
|
$$new_user[$PASSWD] = '0' x length ($$new_user[$PASSWD]);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_logindefs
|
||
|
|
{
|
||
|
|
my ($config) = @_;
|
||
|
|
my ($logindefs, $key, $file);
|
||
|
|
|
||
|
|
return unless $config;
|
||
|
|
|
||
|
|
&get_login_defs_prop_array ();
|
||
|
|
|
||
|
|
foreach $key (@login_defs_names)
|
||
|
|
{
|
||
|
|
if (-f $key)
|
||
|
|
{
|
||
|
|
$file = $key;
|
||
|
|
last;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ($file)
|
||
|
|
{
|
||
|
|
&Utils::Report::do_report ("file_open_read_failed", join (", ", @login_defs_names));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach $key (keys (%$config))
|
||
|
|
{
|
||
|
|
# Write ONLY login.defs values.
|
||
|
|
if (exists ($login_defs_prop_map{$key}))
|
||
|
|
{
|
||
|
|
&Utils::Replace::split ($file, $login_defs_prop_map{$key}, "[ \t]+", $$config{$key});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_self
|
||
|
|
{
|
||
|
|
my ($uid) = @_;
|
||
|
|
my ($users) = &get ();
|
||
|
|
|
||
|
|
foreach $user (@$users)
|
||
|
|
{
|
||
|
|
next if ($uid != $$user[$UID]);
|
||
|
|
return ($$user[$COMMENT], $$user[$LOCALE], $$user[$LOCATION]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return ([""], "");
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_user
|
||
|
|
{
|
||
|
|
my ($login) = @_;
|
||
|
|
my ($users) = &get ();
|
||
|
|
|
||
|
|
foreach $user (@$users)
|
||
|
|
{
|
||
|
|
next if ($login ne $$user[$LOGIN]);
|
||
|
|
return $user;
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_user
|
||
|
|
{
|
||
|
|
my ($new_user) = @_;
|
||
|
|
my ($users) = &get ();
|
||
|
|
|
||
|
|
# Make backups manually, otherwise they don't get backed up.
|
||
|
|
&Utils::File::do_backup ($_) foreach (@passwd_names);
|
||
|
|
&Utils::File::do_backup ($_) foreach (@shadow_names);
|
||
|
|
|
||
|
|
foreach $user (@$users)
|
||
|
|
{
|
||
|
|
if ($$new_user[$LOGIN] eq $$user[$LOGIN])
|
||
|
|
{
|
||
|
|
&change_user ($user, $new_user);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_self
|
||
|
|
{
|
||
|
|
my ($uid, @comments, $locale, $location) = @_;
|
||
|
|
my ($users) = &get ();
|
||
|
|
|
||
|
|
# Make backups manually, otherwise they don't get backed up.
|
||
|
|
&Utils::File::do_backup ($_) foreach (@passwd_names);
|
||
|
|
&Utils::File::do_backup ($_) foreach (@shadow_names);
|
||
|
|
|
||
|
|
foreach $user (@$users)
|
||
|
|
{
|
||
|
|
if ($uid == $$user[$UID])
|
||
|
|
{
|
||
|
|
&change_user_chfn ($$user[$LOGIN], $$user[$COMMENT], @comments);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
# TODO: change locale and location
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
sub get_files
|
||
|
|
{
|
||
|
|
my ($arr);
|
||
|
|
|
||
|
|
push @$arr, @passwd_names;
|
||
|
|
push @$arr, @shadow_names;
|
||
|
|
|
||
|
|
return $arr;
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|