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