###
### Copyright 2000-2007 University of Illinois Board of Trustees
### All rights reserved.
###
### Users.pm - user account module for psgconf
###
### Campus Information Technologies and Educational Services
### University of Illinois at Urbana-Champaign
###
package PSGConf::Control::Users;
use strict;
use PSGConf::Action::GenerateFile::etc_passwd;
use PSGConf::Action::GenerateFile::etc_group;
use PSGConf::Action::GenerateFile::etc_shadow;
use PSGConf::Action::GenerateFile::etc_security_passwd;
use PSGConf::Action::GenerateFile::etc_master_passwd;
use PSGConf::Action::GenerateFile::etc_user_attr;
use PSGConf::Action::ModifyFile;
use PSGConf::Action::HomeDir;
use PSGConf::Action::Symlink;
use PSGConf::Data::Boolean;
use PSGConf::Data::Hash;
use PSGConf::Data::Integer;
use PSGConf::Util;
use User::pwent;
use User::grent;
###############################################################################
### sorting function
###############################################################################
# need to use a prototype here because this function will be
# called from other packages
# (see comment in "sort" entry in perlfunc(1) man page)
sub _uid_sort($$)
{
my ($a, $b) = @_;
### first entry should always be root
return -1
if ($a->{'name'} eq 'root');
return 1
if ($b->{'name'} eq 'root');
### sort identical uids by name
return ($a->{'name'} cmp $b->{'name'})
if ($a->{'uid'} == $b->{'uid'});
### otherwise, sort by uid
return ($a->{'uid'} <=> $b->{'uid'});
}
###############################################################################
### PSGConf::Action::ModifyFile plugin for /etc/security/user
###############################################################################
sub _modify_etc_security_user
{
my ($action, $infh, $fh, $psgconf) = @_;
my (@text, $text, $entry, $user, $attr);
### read the existing /etc/security/user file
@text = <$infh>;
### save the comment banner at the top of the file
while ($text[0] =~ m/^\*/)
{
print $fh shift(@text);
}
while ($text[0] eq "\n")
{
shift(@text);
}
### print out block for default settings
$text = join('', @text);
foreach $entry (split(/\s*\n\n\s*/, $text))
{
$user = (split(':', $entry))[0];
if ($user eq 'default')
{
print $fh "\n$entry\n\n";
last;
}
}
### now we write out the new /etc/security/user file
foreach $entry (sort _uid_sort values %{$psgconf->data_obj('user_info')->get()})
{
print $fh "$entry->{'name'}:\n";
foreach $attr (sort keys %{$entry->{'attributes'}})
{
print $fh "\t$attr = $entry->{'attributes'}->{$attr}\n";
}
print $fh "\n";
}
return 1;
}
###############################################################################
### utility function to read /etc/security/user
###############################################################################
sub _read_etc_security_user
{
my ($self, $psgconf) = @_;
my (@text, $text, $user, $entry, $line, $attrs, $attr, $value);
### read the existing /etc/security/user file
if (!open(SECUSER, '</etc/security/user'))
{
warn "\n\t!!! can't open '/etc/security/user': $!\n";
return -1;
}
@text = <SECUSER>;
close SECUSER;
### skip the comment banner at the top of the file
while ($text[0] =~ m/^\*/)
{
shift(@text);
}
while ($text[0] eq "\n")
{
shift(@text);
}
### parse the existing file
$text = join('', @text);
foreach $entry (split(/\s*\n\n\s*/, $text))
{
($user, $entry) = split(':', $entry);
next
if ($user eq 'default'
|| !defined $psgconf->data_obj('user_info')->find($user));
###
### Load up the temp hash $attrs with all the values
### we need to insert into the user_info hash.
###
$attrs = undef;
foreach $line (grep /\S/, split(/\n/, $entry))
{
$line =~ s/^\s*//;
$line =~ s/\s*$//;
($attr, $value) = split(/\s*=\s*/, $line);
$attrs->{$attr} = $value;
}
$psgconf->data_obj('user_info')->insert(
{ $user => {'attributes' => $attrs }}
) if (defined $psgconf->data_obj('user_info')->find($user));
}
}
###############################################################################
### utility function to read /etc/user_attr (Solaris)
###############################################################################
sub _read_etc_user_attr
{
my ($self, $psgconf) = @_;
my (@text, $line);
local *FP;
### read the existing /etc/user_attr file
if (!open(FP, '</etc/user_attr'))
{
warn "\n\t!!! can't open '/etc/user_attr': $!\n";
return -1;
}
@text = <FP>;
close FP;
foreach $line ( @text ) {
chomp $line;
$line =~ s/#.*$//;
next
if (!length($line));
my (@fields) = split (/:/, $line);
# Currently, only the first and 4th fields are being
# used, man user_attr(4)
$psgconf->data_obj('user_info')->insert(
{ $fields[0] => {'attributes' => { 'attr' => $fields[4] }}}
) if (defined $psgconf->data_obj('user_info')->find($fields[0]));
}
}
###############################################################################
### policy methods
###############################################################################
sub _policy_merge_existing
{
my ($self, $psgconf) = @_;
my ($group_info, $user_info, $pwent, $grent, $field);
return
if ($psgconf->data_obj('create_accounts')->equals('false'));
$user_info = $psgconf->data_obj('user_info')->get();
$group_info = $psgconf->data_obj('group_info')->get();
### merge in existing users
setpwent;
while ($pwent = getpwent)
{
$user_info->{$pwent->name}->{added_from_passwd} = 1 if (!exists($user_info->{$pwent->name}));
foreach $field ( 'name', 'passwd', 'uid', 'gid', 'gecos', 'dir', 'shell' )
{
### Check first to see if someone did not put the values
### in the structure via a user_info directive.
if ( ! defined $user_info->{$pwent->name}->{$field} )
{
### The home directory is the only field that does not
### have the same name in the pwent strucutre as it does
### in the user_info structure :(
if ( $field eq 'dir') {
$user_info->{$pwent->name}->{'home'} = $pwent->$field;
} else {
$user_info->{$pwent->name}->{$field} = $pwent->$field;
}
}
}
}
endpwent;
$self->_read_etc_security_user($psgconf)
if ($psgconf->data_obj('platform')->match('aix'));
$self->_read_etc_user_attr($psgconf)
if ($psgconf->data_obj('platform')->match('solaris'));
### merge in existing groups
setgrent;
while ($grent = getgrent)
{
### Check first to see if someone did not put the values
### in the structure via a group_info directive.
foreach $field ( 'name', 'gid', 'passwd', 'members' ) {
$group_info->{$grent->name}->{$field} = $grent->$field
if ( ! defined $group_info->{$grent->name}->{$field} );
}
### work around User::grent bug under AIX
$group_info->{$grent->name}->{gid} = 4294967294
if ($group_info->{$grent->name}->{gid} == -2
&& $psgconf->data_obj('platform')->match('aix'));
}
endgrent;
}
sub _policy_validate_groups
{
my ($self, $psgconf) = @_;
my ($group, $group_info, $last_gid, @gids);
return
if ($psgconf->data_obj('create_accounts')->equals('false'));
$group_info = $psgconf->data_obj('group_info')->get();
### start assigning free gids here
@gids = sort map { $group_info->{$_}->{gid} } sort keys %$group_info;
$last_gid = $gids[-1];
foreach $group (keys %$group_info)
{
### assign gid if missing
if (!exists($group_info->{$group}->{gid}))
{
$group_info->{$group}->{gid} = ++$last_gid;
}
### save group name in name field
$group_info->{$group}->{name} = $group;
}
}
sub _policy_validate_group_members
{
my ($self, $psgconf) = @_;
my ($group, $group_info, $user_info, @members);
return
if ($psgconf->data_obj('create_accounts')->equals('false'));
$user_info = $psgconf->data_obj('user_info')->get();
$group_info = $psgconf->data_obj('group_info')->get();
foreach $group (keys %$group_info)
{
### weed out non-existent users from member list
@members = grep { exists($user_info->{$_}) }
@{$group_info->{$group}->{members}};
### sort members list by uid
@members = map { $user_info->{$_} } @members;
@members = sort _uid_sort @members;
@members = map { $_->{name}; } @members;
### save new value
$group_info->{$group}->{members} = [ @members ];
}
}
sub _policy_validate_users
{
my ($self, $psgconf) = @_;
my ($user, $user_info, $group_info, $last_uid, @uids, $default_gid);
return
if ($psgconf->data_obj('create_accounts')->equals('false'));
$user_info = $psgconf->data_obj('user_info')->get();
$group_info = $psgconf->data_obj('group_info')->get();
### start assigning free uids here
@uids = sort map { $user_info->{$_}->{uid} } sort keys %$user_info;
$last_uid = $uids[-1];
### find default gid
$default_gid = (exists($group_info->{users})
? $group_info->{users}->{gid}
: 100);
foreach $user (keys %$user_info)
{
### assign uid if missing
if (!exists($user_info->{$user}->{uid}))
{
$user_info->{$user}->{uid} = ++$last_uid;
}
### assign group if missing
if (!exists($user_info->{$user}->{gid}))
{
if (exists($user_info->{$user}->{group}))
{
$user_info->{$user}->{gid} = $group_info->{$user_info->{$user}->{group}}->{gid};
}
else
{
### otherwise, assign the default gid
$user_info->{$user}->{gid} = $default_gid;
}
}
### assign home directory
$user_info->{$user}->{home} = "/home/$user"
if (!exists($user_info->{$user}->{home}));
### save login in name field
$user_info->{$user}->{name} = $user;
### Assign uid/gid/modes for the home directory.
### But we do not want to change the value for '/'
if ( $user_info->{$user}->{home} ne '/' ) {
$user_info->{$user}->{home_uid} =
$user_info->{$user}->{uid}
if (!exists($user_info->{$user}->{home_uid}));
$user_info->{$user}->{home_gid} =
$user_info->{$user}->{gid}
if (!exists($user_info->{$user}->{home_gid}));
$user_info->{$user}->{home_mode} =
$psgconf->data_obj('default_home_mode')->get()
if (!exists($user_info->{$user}->{home_mode}));
} else {
$user_info->{$user}->{home_uid} = -1
if (!exists($user_info->{$user}->{home_uid}));
$user_info->{$user}->{home_gid} = -1
if (!exists($user_info->{$user}->{home_gid}));
$user_info->{$user}->{home_mode} = -1
if (!exists($user_info->{$user}->{home_mode}));
}
}
}
###############################################################################
### decide() method
###############################################################################
sub decide
{
my ($self, $psgconf) = @_;
my ($user, $user_info, $group_info, $mode, $entry, @members);
$mode = PSGConf::Data::Integer->new();
return
if ($psgconf->data_obj('create_accounts')->equals('false'));
$user_info = $psgconf->data_obj('user_info')->get();
$group_info = $psgconf->data_obj('group_info')->get();
### add actions
if (! $psgconf->data_obj('platform')->match('freebsd'))
{
$psgconf->register_actions(
PSGConf::Action::GenerateFile::etc_passwd->new(
'name' => '/etc/passwd',
'comment_str' => undef,
'mode' => 0444,
'user_info' => $user_info,
'sort_func' => \&_uid_sort,
'passwd_token' => $psgconf->data_obj('passwd_token')->get(),
'add_passwords' => ($psgconf->data_obj('platform')->match('hpux'))
),
);
}
$psgconf->register_actions(
PSGConf::Action::GenerateFile::etc_group->new(
'name' => '/etc/group',
'comment_str' => undef,
'group_info' => $group_info
)
);
if ($psgconf->data_obj('platform')->match('hpux'))
{
$psgconf->register_actions(
PSGConf::Action::Symlink->new(
name => '/etc/logingroup',
link_to => '/etc/group',
)
);
}
elsif ($psgconf->data_obj('platform')->match('aix'))
{
$psgconf->register_actions(
PSGConf::Action::GenerateFile::etc_security_passwd->new(
'name' => '/etc/security/passwd',
'comment_str' => undef,
'gid' => (getgrnam('security'))[2],
'mode' => 0600,
'user_info' => $user_info,
'sort_func' => \&_uid_sort,
),
PSGConf::Action::ModifyFile->new(
'name' => '/etc/security/user',
'modify_func' => \&_modify_etc_security_user,
'gid' => (getgrnam('security'))[2],
'mode' => 0640
)
);
}
elsif ($psgconf->data_obj('platform')->match('freebsd'))
{
$psgconf->register_actions(
PSGConf::Action::GenerateFile::etc_master_passwd->new(
'name' => '/etc/master.passwd',
'mode' => 0600,
'user_info' => $user_info,
'sort_func' => \&_uid_sort,
)
);
}
else
{
$psgconf->register_actions(
PSGConf::Action::GenerateFile::etc_shadow->new(
'name' => '/etc/shadow',
'comment_str' => undef,
'mode' => 0400,
'user_info' => $user_info,
'sort_func' => \&_uid_sort,
)
);
}
if ($psgconf->data_obj('platform')->match('solaris'))
{
$psgconf->register_actions(
PSGConf::Action::GenerateFile::etc_user_attr->new(
'name' => '/etc/user_attr',
'mode' => 0644,
'uid' => 0,
'gid' => 3,
'user_info' => $user_info
)
);
}
return
if ($psgconf->data_obj('create_home_dirs')->equals('false'));
foreach $user (sort keys %$user_info)
{
next
if ($user_info->{$user}->{'home'} eq ''
|| ! $user_info->{$user}->{'create_home_dir'});
$mode->set($user_info->{$user}->{home_mode});
$psgconf->register_actions(
PSGConf::Action::HomeDir->new(
user_info => $user_info->{$user},
name => $user_info->{$user}->{home},
uid => $user_info->{$user}->{home_uid},
gid => $user_info->{$user}->{home_gid},
mode => $mode->get()
)
);
}
}
###############################################################################
### Constructor
###############################################################################
sub new
{
my ($class, $psgconf) = @_;
my ($self);
$self = {};
bless($self, $class);
$self->{name} = 'Users';
$psgconf->register_data(
'create_accounts' => PSGConf::Data::Boolean->new(
value => 'false'
),
'create_home_dirs' => PSGConf::Data::Boolean->new(
value => 'false'
),
'user_info' => PSGConf::Data::Hash->new(
'value_type' => 'HASH',
'value_optional' => 1
),
'default_home_mode' => PSGConf::Data::Integer->new(
value => -1
),
'group_info' => PSGConf::Data::Hash->new(
'value_type' => 'HASH',
'value_optional' => 1
),
passwd_token => PSGConf::Data::String->new(
value => 'x'
),
);
$psgconf->register_policy($self,
users_merge_existing => '_policy_merge_existing',
validate_groups => '_policy_validate_groups',
validate_users => '_policy_validate_users',
validate_group_members => '_policy_validate_group_members'
);
return $self;
}
###############################################################################
### cleanup() method
###############################################################################
sub cleanup
{
my ($self, $psgconf) = @_;
unlink($self->{tmpfile})
if ( -e $self->{tmpfile} &&
$psgconf->data_obj('platform')->match('freebsd') &&
$psgconf->{rm_tmpfiles} );
}
###############################################################################
### documentation
###############################################################################
1;
__END__
=head1 NAME
PSGConf::Control::Users - psgconf control class for user accounts
=head1 SYNOPSIS
In F<psgconf_modules>:
Control PSGConf::Control::Users
=head1 DESCRIPTION
The B<PSGConf::Control::Users> module provides a B<psgconf> control object
for maintaining user account information. It supports the following methods:
=over 4
=item new()
The constructor. Its parameter is a reference to the B<PSGConf>
object.
The constructor registers the following data objects:
=over 4
=item I<create_accounts>
A B<PSGConf::Data::Boolean> object that indicates whether we should be
generating files that contain account information (i.e., F</etc/passwd>
and friends).
=item I<create_home_dirs>
A B<PSGConf::Data::Boolean> object that indicates whether we should
create home directories for new users.
=item I<default_home_mode>
A B<PSGConf::Data::Integer> object that sets the default mode for the
user's home dir. Defaults to C<-1> which means do not change the current
mode.
=item I<user_info>
A B<PSGConf::Data::Hash> object that contains information about users to
create. The hash key is the login of the user, and the value is a
reference to a hash of user attributes.
=item I<group_info>
A B<PSGConf::Data::Hash> object that contains information about groups to
create. The hash key is the name of the group, and the value is a
reference to a hash of group attributes.
=item I<passwd_token>
A B<PSGConf::Data::String> object that contains the value to use in
F</etc/passwd> as the password place holder. Defaults to C<x>.
=back
The constructor also registers the following policy methods:
=over 4
=item I<users_merge_existing>
If I<create_accounts> is enabled, seeds the I<user_info> and I<group_info>
data objects with the system's existing user and group information.
=item I<validate_groups>
For each entry in I<group_info> that does not have the I<gid> attribute
set, assign an available gid.
=item I<validate_users>
For each entry in I<user_info>, fill in any missing fields.
If the I<uid> attribute is not set, assign an available uid.
If the I<gid> attribute is not set, but the I<group> attribute is set,
set the I<gid> attribute to the gid of the group named by the I<group>
attribute. If the I<group> attribute is not set, set the I<gid> attribute
to the gid of group C<users>. If group C<users> does not exist, assign
gid C<100>.
If the I<home> attribute is not set, set it to C</home/> followed by the
user's login.
If the I<home_uid> attribute is not set, set it to C<uid> if the home
directory is not C</>.
If the I<home_gid> attribute is not set, set it to C<gid> if the home
directory is not C</>.
If the I<home_mode> attribute is not set, set it to C<default_home_mode>
if the home directory is not C</>.
=item I<validate_group_members>
For each entry in I<group_info>, delete any non-existant users from the
group's I<members> list. Also sorts the I<members> list by the uid of
the users.
=back
=item decide()
If I<create_accounts> is enabled, instantiates and registers action
objects, as follows:
=over 4
=item *
Registers a B<PSGConf::Action::GenerateFile::etc_passwd> action object to
create F</etc/passwd>.
=item *
Registers a B<PSGConf::Action::GenerateFile::etc_group> action object to
create F</etc/group>.
=item *
Under HP-UX, registers a B<PSGConf::Action::Symlink> action object to
create a symlink from F</etc/logingroup> to F</etc/group>.
=item *
Under AIX, registers a
B<PSGConf::Action::GenerateFile::etc_security_passwd> action object to
create F</etc/security/passwd>.
=item *
On platforms other than AIX, registers a
B<PSGConf::Action::GenerateFile::etc_shadow> action object to create
F</etc/shadow>.
=item *
Under AIX, registers a B<PSGConf::Action::ModifyFile> action object to
update F</etc/security/user>.
=item *
If I<create_home_dirs> is enabled, registers B<PSGConf::Action::HomeDir>
action objects to create the home directory for each user with the
I<create_home_dir> attribute enabled.
=back
=back
=head1 SEE ALSO
L<perl>
passwd(4)
group(4)
shadow(4)
L<PSGConf>
L<PSGConf::Action::GenerateFile::etc_passwd>
L<PSGConf::Action::GenerateFile::etc_group>
L<PSGConf::Action::GenerateFile::etc_shadow>
L<PSGConf::Action::GenerateFile::etc_security_passwd>
L<PSGConf::Action::GenerateFile::etc_user_attr>
L<PSGConf::Action::ModifyFile>
L<PSGConf::Action::HomeDir>
L<PSGConf::Action::Symlink>
L<PSGConf::Data::Boolean>
L<PSGConf::Data::Hash>
L<PSGConf::Data::Integer>
L<psgconf-intro>
=cut
syntax highlighted by Code2HTML, v. 0.9.1