#-*- 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)
#
# 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");
# enum like for verbose group array positions
my $ID = 0;
my $LOGIN = 1;
my $PASSWD = 2;
my $UID = 3;
my $GID = 4;
my $COMMENT = 5;
my $HOME = 6;
my $SHELL = 7;
%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-3.0' => $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_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 do_get_use_md5
{
my ($file) = @_;
my ($fh, @line, $i, $use_md5);
my $fh = &Utils::File::open_read_from_names ("/etc/pam.d/$file");
return 0 if (!$fh);
$use_md5 = 0;
while (<$fh>)
{
next if &Utils::Util::ignore_line ($_);
chomp;
@line = split /[ \t]+/;
if ($line[0] eq "\@include")
{
$use_md5 = &do_get_use_md5 ($line[1]);
}
elsif ($line[0] eq "password")
{
foreach $i (@line)
{
$use_md5 = 1 if ($i eq "md5");
}
}
}
close $fh;
return $use_md5;
}
sub get_use_md5
{
return &do_get_use_md5 ("passwd");
}
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);
my (@line, @users, $counter);
# Find the passwd file.
$counter = 1;
$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;
unshift @line, $counter;
$counter++;
$login = $line[$LOGIN];
@comment = split ',', $line[$COMMENT], 5;
# we need to make sure that there are 5 elements
push @comment, "" while (scalar (@comment) < 5);
$line[$COMMENT] = [@comment];
$users_hash{$login} = [@line];
}
&Utils::File::close_file ($ifh);
$ifh = &Utils::File::open_read_from_names(@shadow_names);
if ($ifh)
{
while (<$ifh>)
{
chomp;
# FreeBSD allows comments in the shadow passwd file.
next if &Utils::Util::ignore_line ($_);
@line = split ':', $_, -1;
$login = shift @line;
$passwd = shift @line;
# do not add data if the user isn't present
next if (!exists $users_hash{$login});
$users_hash{$login}[$PASSWD] = $passwd;
# FIXME: add the rest of the fields?
#push @{$$users_hash{$login}}, @line;
}
&Utils::File::close_file ($ifh);
}
# transform the hash into an array
foreach $login (keys %users_hash)
{
push @users, $users_hash{$login};
}
return \@users;
}
sub get_files
{
my @arr;
push @arr, @passwd_names;
push @arr, @shadow_names;
return \@arr;
}
sub del_user
{
my ($user) = @_;
my ($command);
if ($Utils::Backend::tool{"system"} eq "FreeBSD")
{
$command = "$cmd_pw userdel -n \'" . $$user[$LOGIN] . "\' -r ";
}
else
{
if ($cmd_deluser)
{
$command = "$cmd_deluser '". $$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);
}
# modifies /etc/shadow directly, not good practice,
# but better than passing clean passwords around
sub modify_shadow_password
{
my ($login, $password) = @_;
my ($buffer, $i, @arr);
$buffer = &Utils::File::load_buffer (@shadow_names);
return if (!$buffer);
$i = 0;
while ($$buffer[$i])
{
@arr = split ':', $$buffer[$i], -1;
if ($arr[0] eq $login)
{
$arr[1] = $password;
$$buffer[$i] = join (':', @arr) . "\n";
last;
}
$i++;
}
&Utils::File::save_buffer ($buffer, @shadow_names);
}
sub add_user
{
my ($user) = @_;
my ($home_parents, $tool_mkdir);
$tool_mkdir = &Utils::File::locate_tool ("mkdir");
if ($Utils::Backend::tool{"system"} eq "FreeBSD")
{
my $pwdpipe;
my $home;
my $user;
$home = $$user[$HOME];
$user = $$user[$LOGIN];
$command = "$cmd_pw useradd " .
" -n \'" . $$user[$LOGIN] . "\'" .
" -u \'" . $$user[$UID] . "\'" .
" -d \'" . $$user[$HOME] . "\'" .
" -g \'" . $$user[$GID] . "\'" .
" -s \'" . $$user[$SHELL] . "\'" .
" -m -h 0"; # pw(8) reads password from STDIN
$pwdpipe = &Utils::File::run_pipe_write ($command);
print $pwdpipe $$user[$PASSWD];
&Utils::File::close_file ($pwdpipe);
&Utils::File::run ("chown -R $user $home");
}
elsif ($Utils::Backend::tool{"system"} eq "SunOS")
{
$home_parents = $$user[$HOME];
$home_parents =~ s/\/+[^\/]+\/*$//;
&Utils::File::run ("$tool_mkdir -p $home_parents");
$command = "$cmd_useradd" .
" -d \'" . $$user[$HOME] . "\'" .
" -g \'" . $$user[$GID] . "\'" .
" -s \'" . $$user[$SHELL] . "\'" .
" -u \'" . $$user[$UID] . "\'" .
" \'" . $$user[$LOGIN] . "\'";
&Utils::File::run ($command);
&modify_shadow_password ($$user[$LOGIN], $$user[$PASSWD]);
}
else
{
$home_parents = $$user[$HOME];
$home_parents =~ s/\/+[^\/]+\/*$//;
&Utils::File::run ("$tool_mkdir -p $home_parents");
if ($cmd_adduser &&
$Utils::Backend::tool{"platform"} !~ /^slackware/ &&
$Utils::Backend::tool{"platform"} !~ /^archlinux/)
{
# 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" .
" --home \'" . $$user[$HOME] . "\'" .
" --gid \'" . $$user[$GID] . "\'" .
" --shell \'" . $$user[$SHELL] . "\'" .
" --uid \'" . $$user[$UID] . "\'" .
" \'" . $$user[$LOGIN] . "\'";
&Utils::File::run ($command);
# password can't be set in non-interactive
# mode with adduser, call usermod instead
$command = "$cmd_usermod " .
" -p '" . $$user[$PASSWD] . "' " . $$user[$LOGIN];
&Utils::File::run ($command);
}
else
{
# fallback to useradd
$command = "$cmd_useradd -m" .
" -d \'" . $$user[$HOME] . "\'" .
" -g \'" . $$user[$GID] . "\'" .
" -p \'" . $$user[$PASSWD] . "\'" .
" -s \'" . $$user[$SHELL] . "\'" .
" -u \'" . $$user[$UID] . "\'" .
" \'" . $$user[$LOGIN] . "\'";
&Utils::File::run ($command);
}
}
&change_user_chfn ($$user[$LOGIN], undef, $$user[$COMMENT]);
}
sub change_user
{
my ($old_user, $new_user) = @_;
if ($Utils::Backend::tool{"system"} eq "FreeBSD")
{
my $pwdpipe;
$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] . "\'" .
" -h 0"; # pw(8) reads password from STDIN
$pwdpipe = &Utils::File::run_pipe_write ($command);
print $pwdpipe $$new_user[$PASSWD];
&Utils::File::close_file ($pwdpipe);
}
elsif ($Utils::Backend::tool{"system"} eq "SunOS")
{
$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);
&modify_shadow_password ($$new_user[$LOGIN], $$new_user[$PASSWD]);
}
else
{
$command = "$cmd_usermod" .
" -d \'" . $$new_user[$HOME] . "\'" .
" -g \'" . $$new_user[$GID] . "\'" .
" -l \'" . $$new_user[$LOGIN] . "\'" .
" -p \'" . $$new_user[$PASSWD] . "\'" .
" -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]);
}
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 set
{
my ($config) = @_;
my ($old_config, %users);
my (%config_hash, %old_config_hash);
if ($config)
{
# Make backups manually, otherwise they don't get backed up.
&Utils::File::do_backup ($_) foreach (@passwd_names);
&Utils::File::do_backup ($_) foreach (@shadow_names);
$old_config = &get ();
foreach $i (@$config)
{
$users{$$i[0]} |= 1;
$config_hash{$$i[0]} = $i;
}
foreach $i (@$old_config)
{
$users{$$i[0]} |= 2;
$old_config_hash{$$i[0]} = $i;
}
# Delete all groups that only appeared in the old configuration
foreach $i (sort (keys (%users)))
{
$state = $users{$i};
if ($state == 1)
{
# Groups with state 1 have been added to the config
&add_user ($config_hash{$i});
}
elsif ($state == 2)
{
# Groups with state 2 have been deleted from the config
&del_user ($old_config_hash{$i});
}
elsif (($state == 3) &&
(!Utils::Util::struct_eq ($config_hash{$i}, $old_config_hash{$i})))
{
&change_user ($old_config_hash{$i}, $config_hash{$i});
}
}
}
}
1;
syntax highlighted by Code2HTML, v. 0.9.1