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=44daddbff72df601b9703259abe6e23804cefa71;hp=a1b149340ab0b7a49619aec251d3ffcb26a00ce5;hb=cf0ee9747debc62a3a979f3d259b25688a543955;hpb=2328518cb189663a9ff0ad72576edea90230834b diff --git a/sync-accounts/sync-accounts b/sync-accounts/sync-accounts index a1b1493..44daddb 100755 --- a/sync-accounts/sync-accounts +++ b/sync-accounts/sync-accounts @@ -1,156 +1,109 @@ #!/usr/bin/perl -# usage: sync-accounts [-n] [-C] [ ...] -# options: -# -n do not really do anything -# -C alternative config file (default is /etc/sync-accounts) -# if no host(s) specified, does all +# This is part of sync-accounts, a tool for synchronising UN*X password data. # -# The config file consists of directives, one per line. Leading and -# trailing whitespace, blank lines and lines starting # are ignored. +# sync-accounts is +# Copyright 1999-2000,2002 Ian Jackson +# Copyright 2000-2001 nCipher Corporation Ltd # -# Some config file directives apply globally and should appear first: +# sync-accounts 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. # -# lockpasswd -# lockgroup -# 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 `/'. +# sync-accounts 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. # -# logfile -# Append log messages to instead of stdout. -# Errors still go to stderr. +# 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. # -# 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: -# -# uidmin -# uidmax -# homebase -# When an account is to be created, a uid/gid will be chosen -# which is one higher than the highest currently in use (except -# that ids outside the range - are ignored and will -# never be used). The default home directory location is -# /. -# -# sameuid -# nosameuid -# Specifies whether uids are supposed to match. The default is -# nosameuid. When sameuid is on, it is an error for the uid or -# gid of a local account not to match the corresponding remote -# account, and new local accounts will get the remote accounts' -# ids. -# -# usergroups -# nousergroups -# Specifies whether local accounts are supposed to have -# corresponding groups. If this is set then when a new account -# is created, the corresponding per-user group will be created as -# well, and per-user groups are created for existing accounts if -# necessary (if account creation is enabled). If the gid or -# group name for a per-user group is already taken for a -# different group name or gid this will be logged, and processing -# of that account will be inhibited, but it is not a fatal -# error. The default is `usergroups'. -# -# createuser -# createuser -# nocreateuser -# Specifies whether accounts found on the remote host should be -# created if necessary, and what command to run to do the -# creation (eg, setup of home directory). The default is -# nocreateuser. If createuser is specified without a commandname -# then sync-accounts-createuser is used. The command is found on -# the PATH if necessary. Either sameuid, or both uidmin and -# uidmax, must be specified, if accounts are to be created. -# -# group -# nogroup -# Specifies that the membership of the local groups specified -# should be adjusted or not adjusted whenever account data for a -# particular user is copied, so that the account will be a member -# of the affected group locally iff it is a member of the same -# group on the remote host. The most recently-encountered -# glob-pattern for a particular group takes effect. The default -# is `nogroups *'. -# -# Some config file directives are per-host, and should appear before -# any directives which actually modify accounts: -# -# host -# Starts a host's section. This resets the per-host parameters -# to the defaults. The shorthostname need not be the host's -# official name in any sense. If sync-accounts is invoked with -# host names on the command line they are compared with the -# shorthostnames. -# -# getpasswd -# getgroup -# Commands to run on the local host to get the passwd, shadow and -# group data for the host in question. getpasswd must be -# specified if user data is to be transferred; getgroup must be -# specified if group data is to be transferred. -# -# getshadow -# Specifies that shadow file data is to be used (by default, -# password information is found from the output of getpasswd). -# The command should emit shadow data in the format specified by -# shadow(5) on Linux. getshadow should not be specified without -# getpasswd. -# -# 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: -# -# user [remote=] -# Specifies that account data should be copied for local user -# from the remote account (assumed to -# be the same as if not specified). The account -# password, comment field, and shell will be copied -# unconditionally. If sameuid is specified the uid will be -# checked. -# -# users - -# Specifies that all remote users whose uid is in the given range -# are to be copied to corresponding local user accounts. -# -# nouser -# Specifies that data for is _not_ to be copied, even -# if subsequent user or users directives suggest that it should -# be. -# -# (A note is made when a `user', `users' or `nouser' directive is -# encountered for a particular account, and no subsequent directives -# for that account will take effect.) -# -# addhere -# This directive has no effect on `sync-accounts'. However, it -# is used as a placeholder by `grab-account': new accounts for -# creation are inserted just before `addhere'. -# -# Finally, the config file must finish with: -# -# end +# $Id: sync-accounts,v 1.22 2002-07-14 22:21:29 ianmdlvl Exp $ use POSIX; $configfile= '/etc/sync-accounts'; $def_createuser= 'sync-accounts-createuser'; $ch_homebase= '/home'; -$opt_usergroups= 1; +$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; @@ -159,11 +112,17 @@ while ($ARGV[0] =~ m/^-/) { $configfile= $'; } elsif (m/^-n$/) { $no_act= 1; + $display= 0; + } elsif (m/^-q$/) { + $no_act= 1; + $display= 1; } else { die "unknown option $_\n"; } } +die "hosts must not be specified with -q\n" if @ARGV && $display; + for $h (@ARGV) { $wanthost{$h}= 1; } open CF,"< $configfile" or die "$configfile: $!"; @@ -181,21 +140,75 @@ 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) ]; + if (m/^\#/ || !m/\S/) { + $record= $_; + } else { + $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 ($) { @@ -207,7 +220,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 // $@"; @@ -215,20 +228,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"; @@ -237,8 +254,9 @@ 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])"); + next unless ref $e; + if ($e->[$PW_USER] ne $foruser && $e->[$PW_UID] == $useuid) { + diag("uid clash with $e->[$PW_USER] (uid $e->[$PW_UID])"); return 0; } } @@ -267,13 +285,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 () { @@ -281,21 +300,24 @@ 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 ($$) { my ($lu,$luid) = @_; - return 1 if !$opt_usergroups; + return 1 if $defaultgid != -1; #print STDERR "syncusergroup($lu,$luid)\n"; $ugfound=0; for $e (@owngroup) { - $samename= $e->[0] eq $lu; - $sameid= $e->[2] eq $luid; + next unless ref $e; + $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) { @@ -311,54 +333,87 @@ 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; } - + +sub hosthead ($) { + my ($th) = @_; + return if $hostheaddone eq $th; + print "\n\n" or die $! if length $hostheaddone; + print "==== $th ====\n" or die $!; + $hostheaddone= $th; +} + 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; - $diagstr= "user $lu from $ch_name!$ru"; fetchown(); + + if ($display) { + for $e (@ownpasswd) { + next unless ref $e && $e->[$PW_USER] eq $lu; + hosthead("from $ch_name"); + print ($lu eq $ru ? " $lu" : " $lu($ru)") or die $!; + print "" if $displaydone{$lu}++; + } + return; + } + + $diagstr= "user $lu from $ch_name!$ru"; + #print STDERR "syncuser($lu,$ru) doing\n"; 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(ref $_ && $_->[$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]; + $useuid= $rempasswd{$ru}->[$REM_UID]; + $usegid= $rempasswd{$ru}->[$REM_GID]; } else { - length $ch_uidmin or die "no uidmin specified, cannot create users"; - length $ch_uidmax or die "no uidmax specified, cannot create users"; - $ch_uidmin<$ch_uidmax or die "uidmin>=uidmax specified, cannot create users"; + die "nousergroups specified, cannot create users\n" if $defaultgid==-2; + length $ch_uidmin or die "no uidmin specified, cannot create users\n"; + length $ch_uidmax or die "no uidmax specified, cannot create users\n"; + $ch_uidmin<$ch_uidmax or die "uidmin>=uidmax, cannot create users\n"; $useuid= $ch_uidmin; - for $e (@ownpasswd, @owngroup) { - $tuid= $e->[2]; next if $tuid<$useuid || $tuid>$ch_uidmax; + for $e ($defaultgid==-1 ? (@ownpasswd, @owngroup) : (@ownpasswd)) { + next unless ref $e; + $tuid= $e->[$PW_UID]; next if $tuid<$useuid || $tuid>$ch_uidmax; if ($tuid==$ch_uidmax) { - diag("uid/gid $ch_uidmax used, cannot create users"); + diag("uid (or gid?) $ch_uidmax used, cannot create users"); return; } $useuid= $tuid+1; } + $usegid= $defaultgid==-1 ? $useuid : $defaultgid; } - @newpwent= ($lu,'x',$useuid,$useuid,$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) { @@ -368,9 +423,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"; } @@ -381,48 +437,58 @@ 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 ref $e && $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 ref $e && $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(ref $_ && $_->[$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]; + next unless ref $e; + $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"; @@ -431,8 +497,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; @@ -441,7 +507,7 @@ sub syncuser ($$) { } else { next; } - $e->[3]= $cusers; + $e->[$GR_USERS]= $cusers; $modifiedgroup= 1; } } @@ -454,33 +520,69 @@ sub banner () { } sub finish () { + my ($record); + for $h (keys %wanthost) { die "host $h not in config file\n" if $wanthost{$h}; } + + if ($display) { +#print STDERR "\n\nfinish display=$display pw=$pw\n\n"; + for $e (@ownpasswd) { + next unless ref $e; + $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 ref $e2 && $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 $!; + 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 $!; + $record= ref $e ? join(':',@$e) : $e; + print NF $record,"\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; @@ -488,8 +590,8 @@ sub finish () { while () { chomp; - next if m/^\#/ || !m/\S/; s/^\s*//; s/\s*$//; + next if m/^\#/ || !m/\S/; finish() if m/^end$/; if (m/^host\s+(\S+)$/) { $ch_name= $1; @@ -503,11 +605,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 $@; @@ -518,13 +627,20 @@ while () { } elsif (m/^createuser\s+(\S+)$/) { $opt_createuser= $1; } elsif (m/^logfile\s+(.*\S)$/) { - if ($no_act) { - print "would log to $1\n" or die $!; - } else { + 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 $!; } - } elsif (m/^(no|)(sameuid|usergroups)$/) { + } elsif (m/^(no|)(sameuid)$/) { eval "\$opt_$2= ".($1 eq 'no' ? 0 : 1)."; 1;" or die $@; + } elsif (m/^usergroups$/) { + $defaultgid= -1; + } elsif (m/^nousergroups$/) { + $defaultgid= -2; + } elsif (m/^defaultgid\s+(\d+)$/) { + $defaultgid= $1; } elsif (m/^(no|)group\s+([-+.0-9a-zA-Z*?]+)$/) { $yes= $1 eq 'no' ? 0 : 1; $_= $2;