X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=chiark-utils.git;a=blobdiff_plain;f=sync-accounts%2Fsync-accounts;h=80d22269605bced7ffba50bdda7c5a2edd783d0f;hp=6acfe4bf7e3625145761a72d63045008fca38c4a;hb=9a16c546a87fb36e09cb1f1822739c11012fcd6c;hpb=53dfb827b51b510f92c975cb6fd32afb02fdf209 diff --git a/sync-accounts/sync-accounts b/sync-accounts/sync-accounts index 6acfe4b..80d2226 100755 --- a/sync-accounts/sync-accounts +++ b/sync-accounts/sync-accounts @@ -1,27 +1,62 @@ #!/usr/bin/perl -# $Id: sync-accounts,v 1.9 1999-01-03 01:56:57 ian Exp $ +# $Id: sync-accounts,v 1.17 2001-03-06 18:35:09 ian Exp $ +# +# Copyright (C)1999-2000 Ian Jackson +# Copyright (C)2000-2001 nCipher Corporation Ltd +# +# This is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any later +# version. +# +# This 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 General Public License +# for more details. +# +# You should already have a copy of the GNU General Public License. +# If not, write to the Free Software Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# # usage: sync-accounts [-n] [-C] [ ...] # options: # -n do not really do anything # -C alternative config file (default is /etc/sync-accounts) # -q display accounts synched, not synched, etc. # if no host(s) specified, does all +# host(s) may not be specified with -q # # The config file consists of directives, one per line. Leading and # trailing whitespace, blank lines and lines starting # are ignored. # # Some config file directives apply globally and should appear first: # -# lockpasswd -# lockgroup +# lockpasswd link +# lockgroup link # Specifies the lockfile suffix or pathname to use when editing # the passwd and group files. The value is a suffix if it does # not start with `/'. If set to /dev/null no locking is done. # +# lockpasswd runvia +# lockgroup runvia +# Lock by reinvoking ourselves via a program as EDITOR. +# ( would typically be vipw or vigr.) +# +# lockpasswd none +# lockgroup none +# Do not lock. +# # logfile # Append log messages to instead of stdout. # Errors still go to stderr. # +# localformat bsd|std +# Specifies the local password file is in the relevant format: +# `std' is the standard V7 password file (with a SysV- style +# /etc/shadow if one is detected at run-time); `bsd' is the weird +# BSD4.4 master.passwd format, and should be used only with +# `lockpasswd runvia vipw'. The default is `std'. +# # Some config file directives set options which may be different at # different points in the file. The most-recently-seen value is used # at each point: @@ -76,12 +111,12 @@ # the new account's home directory. The passwd and group entries # will not have been set up. The following environment variables # will be set, giving details about the account to be created: -# SYNCACCOUNT_CREATE_USER -# SYNCACCOUNT_CREATE_UID -# SYNCACCOUNT_CREATE_GID -# SYNCACCOUNT_CREATE_COMMENT -# SYNCACCOUNT_CREATE_HOME -# SYNCACCOUNT_CREATE_SHELL +# SYNCUSER_CREATE_USER +# SYNCUSER_CREATE_UID +# SYNCUSER_CREATE_GID +# SYNCUSER_CREATE_COMMENT +# SYNCUSER_CREATE_HOME +# SYNCUSER_CREATE_SHELL # If it chooses, the script may modify the password entry which # will be added to the system, by outputting a replacement # password file entry. (The password field of that is ignored.) @@ -98,6 +133,11 @@ # glob-pattern for a particular group takes effect. The default # is `nogroups *'. # +# defaultshell +# If, when creating an account, the remote account's shell is not +# available on the local system, this value will be used. The +# default is /bin/sh. +# # Some config file directives are per-host, and should appear before # any directives which actually modify accounts: # @@ -122,6 +162,13 @@ # shadow(5) on Linux. getshadow should not be specified without # getpasswd. # +# remoteformat std|bsd +# Specifies the format of the output of `getpasswd'. `std' is +# standard V7 passwd file format (optionally augmented by the use +# of a shadow file fetched with getshadow). `bsd' is the weird +# BSD4.4 master.passwd format (and getshadow should not normally +# be used with `remoteformat bsd'). The default is `std'. +# # Some configuration file directives specify that account data is to # transferred from the current host. They should appear as the last # thing(s) in a host section: @@ -161,20 +208,84 @@ use POSIX; $configfile= '/etc/sync-accounts'; $def_createuser= 'sync-accounts-createuser'; $ch_homebase= '/home'; +$ch_defaultshell= '/bin/sh'; $defaultgid= -1; # -1 => usergroups; -2 => nousergroups @groupglobs= [ '.*', 0 ]; regroupglobs(); +$file{'passwd','std'}= 'passwd'; +$file{'shadow','std'}= 'shadow'; +$file{'group','std'}= 'group'; + +$file{'passwd','bsd'}= 'master.passwd'; +$file{'shadow','bsd'}= 'shadow-non-existent'; +$file{'group','bsd'}= 'group'; + +@fields_pw_std= qw(USER PW UID GID COMMENT HOME SHELL); +@fields_pw_bsd= qw(USER PW UID GID CLASS CHANGE EXPIRE COMMENT HOME SHELL); +fields_fmt('PW','std'); +fields('GR',qw(GROUP PW GID USERS)); +fields('SP',qw(USER PW DAYSCHGD DAYSFIX DAYSEXP DAYSWARN DAYSEXPDIS DAYSDISD RESERVED)); +# The name field had better always be field 0 ! + END { - for $x (@unlocks) { - unlink $x or warn "unable to unlock by removing $x: $!\n"; + foreach $x (@unlocks) { + ($fn, $style, $arg) = @$x; + &{ "unlock_$style" } ( $fn,$arg ); } } +sub fields { + my ($pfx,@l) = @_; + my ($i, $v, $vn); + foreach $v (@l) { $vn= "${pfx}_$v"; $$vn = $i++; } + $vn= "${pfx}_fields"; $$vn= $i; +} + +sub fields_fmt ($$) { + my ($pfx,$fmt) = @_; + my ($vn); + $vn= "fields_pw_$fmt"; + die "unknown format $fmt\n" unless defined @$vn; + fields($pfx,@$vn); + $vn= "${pfx}_format"; + $$vn= $fmt; +} + +sub newentry { + my ($pfx,$name,@field_val_list) = @_; + my (@rv, $vn, $fn, $v, $i); + @rv= (); + $vn= "${pfx}_fields"; + for ($i=0; $i<$$vn; $i++) { $rv[$i]= ''; } + die "@field_val_list ?" if @field_val_list % 2; + $rv[0] = $name; + while (@field_val_list) { + ($fn,$v,@field_val_list) = @field_val_list; + $vn= "${pfx}_$fn"; +#print STDERR ">$fn|$v|$vn|$$vn<\n"; + $rv[$$vn]= $v; + } + return @rv; +} + $|=1; $cdays= int(time/86400); -@envs_passwd= qw(USER x UID GID COMMENT HOME SHELL); +@envs_createuser= qw(USER UID GID COMMENT HOME SHELL); + +if (@ARGV == 1 && length $ENV{'SYNC_ACCOUNTS_RUNVIA_INFO'}) { + @na= map { + s/\%([0-9a-f][0-9a-f])/ pack("C", hex $1) /ge; + $_; + } split(/\:/, $ENV{'SYNC_ACCOUNTS_RUNVIA_INFO'}); + delete $ENV{'SYNC_ACCOUNTS_RUNVIA_INFO'}; + $ta= shift @na; + $ENV{"SYNC_ACCOUNTS_RUNVIA_LOCKEDGOT_$ta"} = $ARGV[0]; + @ARGV= @na; +} + +@orgargv= @ARGV; while ($ARGV[0] =~ m/^-/) { $_= shift @ARGV; @@ -192,6 +303,8 @@ while ($ARGV[0] =~ m/^-/) { } } +die "hosts must not be specified with -q\n" if @ARGV && $display; + for $h (@ARGV) { $wanthost{$h}= 1; } open CF,"< $configfile" or die "$configfile: $!"; @@ -209,21 +322,71 @@ sub fetchfile (\%$) { close G; $? and die "$ch_name: $get_str: exit code $?\n"; } -sub fetchownfile (\@$$) { - my ($ary_ref,$fn_str,$lock_str) = @_; - die "$configfile:$.: lockfile name for $fn_str not". - " defined (use lockpasswd/lockgroup)\n" unless length $lock_str; - if ($lock_str ne '/dev/null' && !$no_act) { - $lock_str= "$fn_str$lock_str" unless $lock_str =~ m,^/,; - link $fn_str,$lock_str or die "cannot lock $fn_str by creating $lock_str: $!\n"; - push @unlocks,$lock_str; +sub lockstyle_ ($$) { + my ($fn,$lock) = @_; + + die "$configfile:$.: locking mechanism for $fn not". + " defined (use lockpasswd/lockgroup)\n"; +} + +sub abslock (@) { + my ($fn,$lock) = @_; + + $fn= "/etc/$fn"; + $lock= "$fn$lock" unless $lock =~ m,^/,; + return ($fn,$lock); +} + +sub lock_none ($$) { return (abslock(@_))[0]; } +sub unlock_none ($$) { } + +sub lock_link ($$) { + my ($fn,$lock) = abslock(@_); + link $fn,$lock or die "cannot lock $fn by creating $lock: $!\n"; + return $fn; +} +sub unlock_link ($$) { + my ($fn,$lock) = abslock(@_); + unlink $lock or warn "unable to unlock by removing $lock: $!\n"; +} + +sub lock_runvia ($$) { + my ($fn,$lock) = @_; + my ($evn); + $evn= "SYNC_ACCOUNTS_RUNVIA_LOCKEDGOT_$fn"; + return $ENV{$evn} if exists $ENV{$evn}; + + @na= map { + s/\W/ sprintf("%%%02x", unpack("C", $&)) /ge; + $_; + } ($fn,@orgargv); + $ENV{'SYNC_ACCOUNTS_RUNVIA_INFO'}= join(":", @na); + $ENV{'EDITOR'}= $0; + delete $ENV{'VISUAL'}; + exec $lock; die "cannot lock $fn by executing $lock: $!\n"; +} +sub unlock_runvia ($$) { } + +sub fetchownfile (\@$$$$) { + my ($ary_ref,$fn_str,$nfields,$style,$lock_arg) = @_; + my ($fn_use, $record, $fn_emsg); + $fn_emsg= $fn_str; + if (!$no_act) { + $fn_use= &{ "lock_$style" } ($fn_str, $lock_arg); + push @unlocks, [ $fn_str, $style, $lock_arg ]; + $savebackto{$fn_str}= $fn_use; + } else { + $fn_use= $fn_emsg= "/etc/".$file{$fn_str,$PW_format}; } - open O,"$fn_str" or die "$fn_str: $!"; + open O,"$fn_use" or die "$fn_use ($fn_str): $!"; while () { chomp; - push @$ary_ref, [ split(/\:/,$_,-1) ]; + $record= [ split(/\:/,$_,-1) ]; + die "$fn_emsg:$.:wrong number of fields:\`$_'\n" + unless @$record == $nfields; + push @$ary_ref, $record; } - close O or die "$fn_str: $!"; + close O or die "$fn_use ($fn_str): $!"; } sub diag ($) { @@ -235,7 +398,7 @@ sub regroupglobs () { $groupglobs[0]->[0] eq '.*' && !$groupglobs[0]->[1]); $ggfunc= "sub wantsyncgroup {\n \$_= \$_[0];\n return\n"; - for $e (@groupglobs) { $ggfunc.= " m/^$e->[0]\$/ ? $e->[1] :\n"; } + for $g (@groupglobs) { $ggfunc.= " m/^$g->[0]\$/ ? $g->[1] :\n"; } $ggfunc.= " die;\n};\n1;\n"; #print STDERR "$ggfunc\n"; eval $ggfunc or die "$ggfunc // $@"; @@ -243,20 +406,24 @@ sub regroupglobs () { sub fetchown () { if (!$own_fetchedpasswd) { - fetchownfile(@ownpasswd,'/etc/passwd',$ch_lockpasswd); - if (defined stat('/etc/shadow')) { +#print STDERR ">$PW_fields<\n"; + fetchownfile(@ownpasswd,'passwd', + $PW_fields, $ch_lockstyle_passwd, $ch_lock_passwd); + $shadowfile= $file{'shadow',$PW_format}; + if (stat("/etc/$shadowfile")) { $own_haveshadow= 1; $own_fetchedshadow= 1; - fetchownfile(@ownshadow,'/etc/shadow','/dev/null'); + fetchownfile(@ownshadow,'shadow',$SP_fields,'none',''); } elsif ($! == &ENOENT) { $own_haveshadow= 0; } else { - die "unable to check for /etc/shadow: $!\n"; + die "unable to check for /etc/$shadowfile: $!\n"; } $own_fetchedpasswd= 1; } if (!$own_fetchedgroup) { - fetchownfile(@owngroup,'/etc/group',$ch_lockgroup); + fetchownfile(@owngroup,'group',$GR_fields, + $ch_lockstyle_group, $ch_lock_group); $own_fetchedgroup= 1; } #print STDERR "fetchown() -> $#ownpasswd $#owngroup\n"; @@ -265,8 +432,8 @@ sub fetchown () { sub checkuid ($$) { my ($useuid,$foruser) = @_; for $e (@ownpasswd) { - if ($e->[0] ne $foruser && $e->[2] == $useuid) { - diag("uid clash with $e->[0] (uid $e->[2])"); + if ($e->[$PW_USER] ne $foruser && $e->[$PW_UID] == $useuid) { + diag("uid clash with $e->[$PW_USER] (uid $e->[$PW_UID])"); return 0; } } @@ -295,13 +462,14 @@ sub fetchpasswd () { if (length $ch_getshadow) { fetchfile(%remshadow,"$ch_getshadow |"); for $k (keys %rempasswd) { - $rempasswd{$k}->[1]= 'xx' unless length $rempasswd{$k}->[1]; + $rempasswd{$k}->[$REM_PW]= 'xx' unless length $rempasswd{$k}->[$REM_PW]; } for $k (keys %remshadow) { next unless exists $rempasswd{$k}; - $rempasswd{$k}->[1]= $remshadow{$k}->[1]; + $rempasswd{$k}->[$REM_PW]= $remshadow{$k}->[$SP_PW]; } } + $ch_fetchedpasswd= 1; } sub fetchgroup () { @@ -309,6 +477,7 @@ sub fetchgroup () { die "$configfile:$.: getgroup not specified for host $ch_name\n" unless length $ch_getgroup; fetchfile(%remgroup,"$ch_getgroup |"); + $ch_fetchedgroup= 1; } sub syncusergroup ($$) { @@ -319,11 +488,12 @@ sub syncusergroup ($$) { $ugfound=0; for $e (@owngroup) { - $samename= $e->[0] eq $lu; - $sameid= $e->[2] eq $luid; + $samename= $e->[$GR_GROUP] eq $lu; + $sameid= $e->[$GR_GID] eq $luid; next unless $samename || $sameid; if (!$samename || !$sameid) { - diag("local group $e->[0] ($e->[2]) mismatch vs. local user $lu ($luid)"); + diag("local group $e->[$GR_GROUP] ($e->[$GR_GID]) mismatch vs.". + " local user $lu ($luid)"); return 0; } if ($ugfound) { @@ -339,7 +509,9 @@ sub syncusergroup ($$) { diag("account creation not enabled, not creating per-user group"); return 0; } - push @owngroup, [ $lu,'x',$luid,'' ]; + push @owngroup, [ newentry('GR', $lu, + 'PW', 'x', + 'GID', $luid) ]; $modifiedgroup= 1; return 1; } @@ -354,17 +526,18 @@ sub hosthead ($) { sub syncuser ($$) { my ($lu,$ru) = @_; + my ($vn); #print STDERR "syncuser($lu,$ru)\n"; - next unless $ch_doinghost; return if $doneuser{$lu}++; + next unless $ch_doinghost; return if !length $ru; fetchown(); if ($display) { for $e (@ownpasswd) { - next unless $e->[0] eq $lu; + next unless $e->[$PW_USER] eq $lu; hosthead("from $ch_name"); print ($lu eq $ru ? " $lu" : " $lu($ru)") or die $!; print "" if $displaydone{$lu}++; @@ -378,18 +551,19 @@ sub syncuser ($$) { fetchpasswd(); if (!$rempasswd{$ru}) { diag("no remote entry"); return; } - if (length $ch_getshadow && exists $remshadow{$ru} && length $remshadow{$ru}->[7]) { + if (length $ch_getshadow && exists $remshadow{$ru} && + length $remshadow{$ru}->[$SP_DAYSDISD]) { diag("remote account disabled in shadow"); return; } - if (!grep($_->[0] eq $lu, @ownpasswd)) { + if (!grep($_->[$PW_USER] eq $lu, @ownpasswd)) { if (!length $opt_createuser) { diag("account creation not enabled"); return; } if ($no_act) { diag("-n specified; not creating account"); return; } if ($opt_sameuid) { - $useuid= $rempasswd{$ru}->[2]; - $usegid= $rempasswd{$ru}->[3]; + $useuid= $rempasswd{$ru}->[$REM_UID]; + $usegid= $rempasswd{$ru}->[$REM_GID]; } else { die "nousergroups specified, cannot create users\n" if $defaultgid==-2; length $ch_uidmin or die "no uidmin specified, cannot create users\n"; @@ -398,7 +572,7 @@ sub syncuser ($$) { $useuid= $ch_uidmin; for $e ($defaultgid==-1 ? (@ownpasswd, @owngroup) : (@ownpasswd)) { - $tuid= $e->[2]; next if $tuid<$useuid || $tuid>$ch_uidmax; + $tuid= $e->[$PW_UID]; next if $tuid<$useuid || $tuid>$ch_uidmax; if ($tuid==$ch_uidmax) { diag("uid (or gid?) $ch_uidmax used, cannot create users"); return; @@ -408,8 +582,13 @@ sub syncuser ($$) { $usegid= $defaultgid==-1 ? $useuid : $defaultgid; } - @newpwent= ($lu,'x',$useuid,$usegid,$rempasswd{$ru}->[4], - "$ch_homebase/$lu",$rempasswd{$ru}->[6]); + @newpwent= newentry('PW', $lu, + 'PW', 'x', + 'UID', $useuid, + 'GID', $usegid, + 'COMMENT', $rempasswd{$ru}->[$REM_COMMENT], + 'HOME', "$ch_homebase/$lu", + 'SHELL', $ch_defaultshell); defined($c= open CU,"-|") or die $!; if (!$c) { @@ -419,9 +598,10 @@ sub syncuser ($$) { print STDOUT join(':',@newpwent),"\n" or die $!; exit 0; } - for ($i=0; $i<@envs_passwd; $i++) { - next if $envs_passwd[$i] eq 'x'; - $ENV{"SYNCUSER_CREATE_$envs_passwd[$i]"}= $newpwent[$i]; + for ($i=0; $i<@envs_createuser; $i++) { + $vn= "PW_$envs_createuser[$i]"; +#print STDERR ">$i|$vn|$$vn|$newpwent[$$vn]<\n"; + $ENV{"SYNCUSER_CREATE_$envs_createuser[$i]"}= $newpwent[$$vn]; } exec $opt_createuser; die "$configfile:$.: ($lu): $opt_createuser: $!\n"; } @@ -432,48 +612,57 @@ sub syncuser ($$) { if ($newpwent !~ m/\:/) { diag("creation script demurred"); return; } @newpwent= split(/\:/,$newpwent,-1); } - die "$opt_createuser: bad output: $_\n" if @newpwent!=7 or $newpwent[0] ne $lu; - checkuid($newpwent[2],$lu) or return; + die "$opt_createuser: bad result: \`".join(':',@newpwent)."\'\n" + if @newpwent != $PW_fields or $newpwent[$PW_USER] ne $lu; + checkuid($newpwent[$PW_UID],$lu) or return; if ($own_haveshadow) { - push(@ownshadow,[ $lu, $newpwent[1], $cdays, 0,99999,7,'','','' ]); - $newpwent[1]= 'x'; + push @ownshadow, [ newentry('SP', $lu, + 'PW', 'x', + 'DAYSCHGD', $cdays, + 'DAYSFIX', 0, + 'DAYSEXP', 99999, + 'DAYSEXPDIS', 7) ]; $modifiedshadow= 1; } - syncusergroup($lu,$newpwent[2]) or return; + syncusergroup($lu,$newpwent[$PW_UID]) or return; push @ownpasswd,[ @newpwent ]; $modifiedpasswd= 1; } for $e (@ownpasswd) { - next unless $e->[0] eq $lu; - syncusergroup($lu,$e->[2]) or return; + next unless $e->[$PW_USER] eq $lu; + syncusergroup($lu,$e->[$PW_UID]) or return; } - $ruid= $rempasswd{$ru}->[2]; - $rgid= $rempasswd{$ru}->[3]; + $ruid= $rempasswd{$ru}->[$REM_UID]; + $rgid= $rempasswd{$ru}->[$REM_GID]; if ($opt_sameuid && checkuid($ruid,$lu)) { for $e (@ownpasswd) { - next unless $e->[0] eq $lu; - $luid= $e->[2]; $lgid= $e->[3]; + next unless $e->[$PW_USER] eq $lu; + $luid= $e->[$PW_UID]; $lgid= $e->[$PW_GID]; die "$diagstr: local uid $luid, remote uid $ruid\n" if $luid ne $ruid; die "$diagstr: local gid $lgid, remote gid $rgid\n" if $lgid ne $rgid; } } #print STDERR "syncuser($lu,$ru) exists $own_haveshadow\n"; - if ($own_haveshadow && grep($_->[0] eq $lu, @ownshadow)) { -#print STDERR "syncuser($lu,$ru) shadow $rempasswd{$ru}->[1]\n"; - copyfield('shadow',$lu,1, $rempasswd{$ru}->[1]); + if ($own_haveshadow && grep($_->[$PW_USER] eq $lu, @ownshadow)) { +#print STDERR "syncuser($lu,$ru) shadow $rempasswd{$ru}->[$REM_PW]\n"; + copyfield('shadow',$lu,$SP_PW, $rempasswd{$ru}->[$REM_PW]); } else { -#print STDERR "syncuser($lu,$ru) passwd $rempasswd{$ru}->[1]\n"; - copyfield('passwd',$lu,1, $rempasswd{$ru}->[1]); +#print STDERR "syncuser($lu,$ru) passwd $rempasswd{$ru}->[$REM_PW]\n"; + copyfield('passwd',$lu,$PW_PW, $rempasswd{$ru}->[$REM_PW]); } - copyfield('passwd',$lu,4, $rempasswd{$ru}->[4]); # comment - copyfield('passwd',$lu,6, $rempasswd{$ru}->[6]); # shell + copyfield('passwd',$lu,$PW_COMMENT, $rempasswd{$ru}->[$REM_COMMENT]); + + $newsh= $rempasswd{$ru}->[$REM_SHELL]; + $oksh= $checkedshell{$newsh}; + if (!length $oksh) { $checkedshell{$newsh}= $oksh= (-x $newsh) ? 1 : 0; } + copyfield('passwd',$lu,$PW_SHELL, $newsh) if $oksh; if (!$nogroups) { for $e (@owngroup) { - $tgroup= $e->[0]; + $tgroup= $e->[$GR_GROUP]; #print STDERR "syncuser($lu,$ru) group $tgroup\n"; next unless &wantsyncgroup($tgroup); #print STDERR "syncuser($lu,$ru) group $tgroup yes\n"; @@ -482,8 +671,8 @@ sub syncuser ($$) { diag("group $tgroup: not on remote host"); next; } - $inremote= grep($_ eq $ru, split(/\,/,$remgroup{$tgroup}->[3])); - $cusers= $e->[3]; $inlocal= grep($_ eq $lu, split(/\,/,$cusers)); + $inremote= grep($_ eq $ru, split(/\,/,$remgroup{$tgroup}->[$GR_USERS])); + $cusers= $e->[$GR_USERS]; $inlocal= grep($_ eq $lu, split(/\,/,$cusers)); if ($inremote && !$inlocal) { $cusers.= ',' if length $cusers; $cusers.= $lu; @@ -492,7 +681,7 @@ sub syncuser ($$) { } else { next; } - $e->[3]= $cusers; + $e->[$GR_USERS]= $cusers; $modifiedgroup= 1; } } @@ -510,51 +699,60 @@ sub finish () { } if ($display) { - for $pw (1,-1,0) { #print STDERR "\n\nfinish display=$display pw=$pw\n\n"; - for $e (@ownpasswd) { - $tu= $e->[0]; - next if $displaydone{$tu}; - $tpw= $e->[1]; - for $e2 (@ownshadow) { - next unless $e2->[0] eq $tu; - $tpw= $e2->[1]; last; - } - $tpw= length($tpw)==13 ? 1 : length($tpw) ? -1 : 0; - next unless $pw == $tpw; - hosthead($pw == 1 ? "unsynched normal account" : - $pw == 0 ? "unsynched, passwordless" : - "unsynched, no logins"); - print " $e->[0]" or die $!; + for $e (@ownpasswd) { + $tu= $e->[$PW_USER]; + $tuid= $e->[$PW_UID]; + next if $displaydone{$tu}; + $tpw= $e->[$PW_PW]; +#print STDERR ">$tu|$tpw<\n"; + for $e2 (@ownshadow) { + next unless $e2->[$SP_USER] eq $tu; + $tpw= $e2->[$SP_PW]; last; } + $tpw= length($tpw)>=13 ? 1 : length($tpw) ? -1 : 0; + $head= ($tpw == 1 ? "unsynched" : + $tpw == 0 ? "unsynched, passwordless" : + "unsynched, no-logins"). + ($tuid < 100 ? " system account" : " normal user"); + $unsynch_type{$head} .= " $e->[$PW_USER]"; + } + foreach $head (sort keys %unsynch_type) { + hosthead($head); + print $unsynch_type{$head} or die $!; } - print "\n\n" or die $! if $hostheaddone; + print "\n\n" or die $!; exit 0; } umask 077; for $file (qw(passwd shadow group)) { + $realfile= $file{$file,$PW_format}; eval "\$modified= \$modified$file; \$data_ref= \\\@own$file;". " \$fetched= \$own_fetched$file; 1;" or die $@; next if !$modified; die $file unless $fetched; banner(); - $newfile= $no_act ? "$file.new" : "/etc/$file.new"; - open NF,"> $newfile"; + if ($no_act) { + $newfileinst= "/etc/$realfile"; + $newfile= "$realfile.new"; + } else { + $newfileinst= $savebackto{$file}; + $newfile= "$newfileinst.new"; + } + open NF,"> $newfile" or die "$newfile: $!"; for $e (@$data_ref) { print NF join(':',@$e),"\n" or die $!; } close NF or die $!; - system "diff -U0 /etc/$file $newfile"; $?==256 or die $?; + system 'diff','-U0','--label',$realfile,$newfileinst, + '--label',"$realfile.new",$newfile; + $?==256 or die $?; if (!$no_act) { - if ($file eq 'shadow') { - system "chown root.shadow $newfile"; $? and die $?; - chmod 0640, $newfile or die $!; - } else { - chown 0,0, $newfile or die $!; - chmod 0644, $newfile or die $!; - } - rename $newfile, "/etc/$file" or die $!; + (@stats= stat $newfileinst) or die "$file: $!"; + chown $stats[4],$stats[5], $newfile or die $!; + chmod $stats[2] & 07777, $newfile or die $!; + rename $newfile, $newfileinst or die $!; } } exit 0; @@ -577,11 +775,18 @@ while () { } else { $ch_doinghost= 0; } + fields_fmt('REM','std'); } elsif (m/^(getpasswd|getshadow|getgroup)\s+(.*\S)$/) { eval "\$ch_$1= \$2; 1;" or die $@; - } elsif (m/^(lockpasswd|lockgroup)\s+(\S+)$/) { - eval "\$ch_$1= \$2; 1;" or die $@; - } elsif (m,^(homebase)\s+(/\S+)$,) { + } elsif (m/^(local|remote)format\s+(\w+)$/) { + fields_fmt($1 eq 'local' ? 'PW' : + $1 eq 'remote' ? 'REM' : die, + $2); + } elsif (m/^lock(passwd|group)\s+(runvia|link)\s+(\S+)$/) { + eval "\$ch_lock_$1= \$3; \$ch_lockstyle_$1= \$2; 1;" or die $@; + } elsif (m/^lock(passwd|group)\s+(none)$/) { + eval "\$ch_lockstyle_$1= \$2; 1;" or die $@; + } elsif (m,^(homebase|defaultshell)\s+(/\S+)$,) { eval "\$ch_$1= \$2; 1;" or die $@; } elsif (m/^(uidmin|uidmax)\s+(\d+)$/ && $2>0) { eval "\$ch_$1= \$2; 1;" or die $@; @@ -594,6 +799,7 @@ while () { } elsif (m/^logfile\s+(.*\S)$/) { if (!$no_act) { open STDOUT,">> $1" or die "$1: $!"; $|=1; + $!=0; system 'date'; $? and die "date: $! $?\n"; } elsif (!$display) { print "would log to $1\n" or die $!; }