Hi,

I'm using a perl script to read a file from AFS with a list of users in
passwd format and append those users to /etc/passwd. The problem is that
about once every few months, the systems account, like root,  vanish
from the passwd file, but the backup passwd file, passwd- where I store
the system accounts still exists.

I suspect that something else is messing with the passwd file at the
same time that I am, but I don't know what.

I have attached my perl script for reference. how can I safely update
the /etc/passwd and /etc/shadow files?

What is the best practice? I don't want to set up an LDAP server because
of internal politics and the extra work involved.

Thanks,
Jason
#!/usr/bin/perl

# update-passwd
# written by Jason Edgecombe

# This script updates the password file. it takes the AFS password
# file and combines it with the local password file It reads the
# current passwd file and saves any accounts with non-afs home
# directories it then combines the local account list with the afs
# account list to produce the new password file.

# changelog

# Aug 7, 2008 - jwe - added extra sanity checks for root user in newly
# written files.

# silently fail if AFS isn't available
exit if !( -r "/afs/example.com/common" );


use Fcntl qw(:flock);
use English;
use IO::File;
use Carp;
use Getopt::Long;
use File::Copy;
use Pod::Usage;
#use Data::Dumper;
use warnings;
use strict;

# check for command-line options
my $option_help;
my $result = GetOptions( "help" => \$option_help );

if ($option_help) {
    pod2usage(-message => "",
              -exitval => 2,
              -verbose => 3,
              -output  => \*STDERR);
    exit;
}


umask 022;

my $PASSWD        = "/etc/passwd";

# for solaris, backup_passwd if passwd.orig, for linux, it's passwd-
my $AFS_PASSWD    = "/afs/example.com/common/etc/passwd";
my $ROOT_ENTRY;
my $BACKUP_PASSWD;
my $SHADOW        = "/etc/shadow";
my $BACKUP_SHADOW = "/etc/shadow.orig";

if( $OSNAME eq 'linux' ) {
    #linux
    $ROOT_ENTRY    = "root:x:0:0:root:/root:/bin/bash";
    $BACKUP_PASSWD = "/etc/passwd-";
} else {
    # solaris
    $ROOT_ENTRY    = "root:x:0:1:Super-User:/:/sbin/sh";
    $BACKUP_PASSWD = "/etc/passwd.orig";
}

my %backup_users = (
                    "root" => $ROOT_ENTRY,
                    );

if( $OSNAME eq 'linux' ) {
    $backup_users{"lwsadm"} = "lwsadm:x:0:0:root:/lwsadm:/bin/bash"
}

# the mother of all sanity checks
# if we can't read the passwd file out in AFS, abort
croak "can't read $AFS_PASSWD: $OS_ERROR\n" if !( -r $AFS_PASSWD );

my %users = ();

# read in the backup password file into a hash if it exists
if ( -r $BACKUP_PASSWD ) {
    passwd2hash( $BACKUP_PASSWD, \%users );
}



# read the main system passwd file into a hash
passwd2hash( $PASSWD, \%users );

# on solaris, only trust the sacred backup user list
if( $OSNAME eq 'linux' ) {
    # add any non-afs accounts to the backup_users hash
    foreach my $key ( keys %users ) {
        my @columns = split /:/, $users{$key};
        
        next if $columns[5] =~ /^\/afs\//;
        
        $backup_users{$key} = $users{$key};
    }
}

my @new_backup_user_values = sort { sort_passwd( $a, $b ) }
    ( values %backup_users );

# save the new list of backup users to the backup passwd file
array2file( $BACKUP_PASSWD . ".new", [EMAIL PROTECTED], 0 );

# read back the new password file to verify that the root user is still there.
my %verify_users=();
passwd2hash( $BACKUP_PASSWD . ".new", \%verify_users );
croak "root user not in passwd-.new...aborting\n"
    if ( $ROOT_ENTRY ne $verify_users{"root"} );


if ( ( -s $BACKUP_PASSWD . ".new" ) < 300 ) {
    croak "New Backup passwd file is too small!";
}

# Solaris doesn't update the sacred list of system users
if( $OSNAME eq 'linux' ) {
    copy( $BACKUP_PASSWD . ".new", $PASSWD . ".new" )
        or croak "Can't copy $BACKUP_PASSWD: $OS_ERROR";
    
    move( $BACKUP_PASSWD . ".new", $BACKUP_PASSWD )
        or croak "Can't move $BACKUP_PASSWD: $OS_ERROR";
} else {
    move( $BACKUP_PASSWD . ".new", $PASSWD . ".new" )
        or croak "Can't MOVE $BACKUP_PASSWD: $OS_ERROR";
    
}

my $afs_passwd_handle = IO::File->new( $AFS_PASSWD, '<' )
    or croak "Can't open '$AFS_PASSWD': $OS_ERROR";

flock( $afs_passwd_handle, LOCK_SH )
    or croak "Can't lock '$AFS_PASSWD': $OS_ERROR";

my @afs_passwd_lines = <$afs_passwd_handle>;

$afs_passwd_handle->close()
    or croak "Can't close '$PASSWD': $OS_ERROR";

chomp @afs_passwd_lines;

if( $OSNAME eq 'linux' ) {

    my @linux_afs_passwd_lines = map {
        $_ =~ s|:/bin/csh|/linux:/bin/bash|; $_;
    } @afs_passwd_lines;
    
    array2file( $PASSWD . ".new", [EMAIL PROTECTED], 1);
} else {
    #solaris uses csh
    array2file( $PASSWD . ".new", [EMAIL PROTECTED], 1);
}

# read back the new password file to verify that the root user is still there.
%verify_users=();
passwd2hash( $PASSWD . ".new", \%verify_users );
croak "root user not in passwd.new...aborting\n"
    if ( $ROOT_ENTRY ne $verify_users{"root"} );

move( $PASSWD . ".new", $PASSWD )
    or croak "Can't move $PASSWD\n";

# verify that the passwd file was written correctly.
%verify_users=();
passwd2hash( $PASSWD, \%verify_users );

# restore backup passwd to passwd if no root user in new passwd
copy( $BACKUP_PASSWD, $PASSWD )
    if ( $ROOT_ENTRY ne $verify_users{"root"} );
    
# fix the selinux permissions
if( $OSNAME eq 'linux' ) {
    system( qw( /usr/bin/chcon -t etc_t /etc/passwd /etc/passwd- ) );
    croak "Failed to set selinux context on /etc/passwd*: $OS_ERROR\n "
        if ( $CHILD_ERROR );

    system( qw( /usr/bin/chcon -t shadow_t /etc/shadow /etc/shadow- ) );
    croak "Failed to set selinux context on /etc/shadow*: $OS_ERROR\n "
        if ( $CHILD_ERROR );
}


croak "Aborting... root user not in newly written passwd file\n!"
    if ( $ROOT_ENTRY ne $verify_users{"root"} );


# make a new shadow file. On solaris, a user cannot login even with
# kerberos if there is not an entry in the shadow file. On Linux, a
# user can login with kerberos without having an entry in the shadow
# file.
if( $OSNAME ne 'linux' ) {
    # solaris

    my %backup_shadow_users;
    # slurp the blessed shadow file into a hash
    passwd2hash( $BACKUP_SHADOW, \%backup_shadow_users );

    # removed any shadow users from the afs passwd list to avoid
    # conflicts
    for my $blessed_user ( keys %backup_shadow_users ) {
        delete $users{ $blessed_user };
    }
    
    copy( $BACKUP_SHADOW, $SHADOW . ".new" )
        or croak "Can't copy $BACKUP_SHADOW to $SHADOW.new: $OS_ERROR";
    
    my @afs_shadow_lines = map {
        $_ . ":NP:::::::"
    }  sort keys %users;

    array2file( $SHADOW . ".new", [EMAIL PROTECTED], 1);
    
    move( $SHADOW . ".new", $SHADOW )
        or croak "Can't move $SHADOW.new to $SHADOW";
}


# dump an array of strings to a file
sub array2file {
    my $filename  = $_[0];
    my $lines_ref = $_[1];
    my $append    = $_[2] || 0;
    chomp @{$lines_ref};
    
    my $handle = IO::File->new( $filename, $append ? ">>": ">" )
        or croak "Can't open '$filename': $OS_ERROR";
    
    flock( $handle, LOCK_EX )
        or croak "Can't lock '$filename': $OS_ERROR";
    
    print {$handle} map { $_ . "\n" } @{$lines_ref};
    
    $handle->close()
        or croak "Can't close '$filename': $OS_ERROR";
}

# read in a passwd-formatted file into a hash { user => passwd_line }
sub passwd2hash {
    my ( $filename, $hash_ref ) = @_;
    
    my $handle = IO::File->new( $filename, '<' )
        or croak "Can't open '$filename': $OS_ERROR";
    
    flock( $handle, LOCK_SH )
        or croak "Can't lock '$filename': $OS_ERROR";
    
    while ( my $line = <$handle> ) {
        chomp $line;
        my @columns = split /:/, $line;
        $hash_ref->{$columns[0]} = $line;
    }
    
    $handle->close()
        or croak "Can't close '$filename': $OS_ERROR";

    return 0;
}

# sort the passwords based on username
sub sort_passwd {
    my ( $a, $b ) = @_;
    my @a_columns = split /:/, $a;
    my @b_columns = split /:/, $b;
    
    return -1 if $a_columns[0] eq "root";
    return  1 if $b_columns[0] eq "root";
    return $a_columns[2] <=> $b_columns[2]
        || $a_columns[0] cmp $b_columns[0];
}

0;
__END__

=head1 NAME
 
update-passwd - Update the passwd file on a Linux or Solaris machine
 
=head1 VERSION
 
This documentation refers to update-passwd version 1.1
 
=head1 USAGE
 
update-passwd
 
=head1 REQUIRED ARGUMENTS

This program takes no required arguments or options.

=head1 OPTIONS

-h - Print this help page
 
=head1 DESCRIPTION

update-passwd takes the AFS passwd file located in
/afs/example.com/common/etc/passwd and updates the files /etc/passwd and
/etc/shadow (/etc/shadow is only updated on solaris). It does this by
reading in a blessed backup file that lists the required entries and
appends the contents of the afs passwd file after some massaging of
the data.
    
On Linux, any account whose home directory does NOT live in /afs is
added to the blessed backup passwd file.

=head1 DIAGNOSTICS

This program reads, copes and moves files. If any file operation
fails, then the program will abort with a descriptive error message
which tells which files was being worked on.
 
=head1 CONFIGURATION AND ENVIRONMENT
 
All of the configurable variables are at the top of the script.
  
 
=head1 DEPENDENCIES

This program depends on the following modules:
   Carp,
   English,
   Fcntl ':flock',
    File::Copy, Getopt::Long, Pod::Usage
   IO::File

=head1 HISTORY

This script was oringally written for Linux and then adapted to
replace the compiled C program /usr/local/bin/update_passwd_file
on solaris, which was written by Robert Gaddy in the 1990's.

=head1 LIMITATIONS

This script must be run as root.

=head1 AUTHOR

Jason Edgecombe <[EMAIL PROTECTED]> 2008, UNC-Charlotte
_______________________________________________
rhelv5-list mailing list
rhelv5-list@redhat.com
https://www.redhat.com/mailman/listinfo/rhelv5-list

Reply via email to