#!/usr/bin/perl -w sub usage { die "usage: rsync-passwd-group-shadow-gshadow \n"; } # apt-get install libfile-temp-perl libfile-slurp-perl # This script checks a remote /etc/passwd for users that do not # exist on the local host, ignoring accounts with UID < 1000 or UID # > 2000. It then copies the users entries from the remote files # {passwd,shadow,group,gshadow} to the corresponding local files, using # 'vipw' to do so. use File::Temp qw(tempdir); use File::Slurp; my @files = qw(passwd shadow group gshadow); $dir = tempdir( CLEANUP => 1 ) or die "tempdir: $!\n"; my $remote = shift or usage; my @rsync_sources = map({ "$remote:/etc/$_" } @files); system qw(rsync -a), @rsync_sources, $dir; sub parse_passwd { my $file = shift; my @lines = split /\n/, read_file $file or die "read_file($file): $!\n"; my %out; for (@lines) { my ($uname, @rest) = split /:/; $out{$uname} = \@rest; } \%out; } my (%local, %remote); for my $f (@files) { $remote{$f} = parse_passwd "$dir/$f"; $local{$f} = parse_passwd "/etc/$f"; } sub get_remote_line { my ($uname, $db) = @_; die unless exists $remote{$db}; join ':', $uname, @{$remote{$db}{$uname}} } sub magic_append { my ($file, @lines) = @_; my %cmd = (passwd => 'vipw', shadow => 'vipw -s', group => 'vigr', gshadow => 'vigr -s'); die unless exists $cmd{$file}; local $ENV{VISUAL} = sprintf q#sed -i "\$a\%s"#, join("\\\\"."\n", @lines); if (system($cmd{$file}) != 0) { if ($? == -1) { warn "Failed to execute 'vipw': $!. Was editing /etc/$file. Check for consistency!" } else { warn "Error executing 'vipw': $?. Was editing /etc/$file. Check for consistency!" } } } my %append; while (my ($uname, $fields) = each %{$remote{passwd}}) { my $uid = $fields->[2]; next if $uid < 1000 or $uid > 2000; next if $local{passwd}{$uname}; for (@files) { my $line = get_remote_line($uname, $_); push @{ $append{$_} }, $line; } } for (@files) { magic_append($_, @{$append{$_}}); }