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=5e26a988e4371e60e075bda8b52b3f94dbd68823;hp=1a38d8d81c4cade754f290aacd819f56ac6dab7f;hb=d9d8b1a736946ae91c521133dc32d5e1d6b5e506;hpb=5a7de8d90c21fb1e230ef852a31437cdb8087a53 diff --git a/sync-accounts/sync-accounts b/sync-accounts/sync-accounts index 1a38d8d..5e26a98 100755 --- a/sync-accounts/sync-accounts +++ b/sync-accounts/sync-accounts @@ -1,206 +1,25 @@ #!/usr/bin/perl -# $Id: sync-accounts,v 1.16 2001-03-06 18:34:36 ian Exp $ +# This is part of sync-accounts, a tool for synchronising UN*X password data. # -# Copyright (C)1999 Ian Jackson +# sync-accounts is +# Copyright 1999-2000,2002 Ian Jackson +# Copyright 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. +# 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 3, 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. +# 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. # # 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 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: -# -# 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 -# defaultgid -# Specifies whether local accounts are supposed to have -# corresponding groups, or all be part of a particular group. If -# usergroups is set, 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. If defaultgid is used, -# then newly-created accounts will be made a part of that group, -# and the groups of existing accounts will be left alone. If -# nousergroups is specified then no new accounts can be created, -# and existing accounts' groups will be left alone. 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. -# -# The command (which will be run with sh -c) must at least create -# 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: -# 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.) -# If the script outputs a line which does not contain a : then -# the account will not be created after all. -# -# 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 *'. -# -# 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: -# -# 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. -# -# 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: -# -# 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.24 2007-09-21 20:59:02 ianmdlvl Exp $ use POSIX; @@ -380,9 +199,13 @@ sub fetchownfile (\@$$$$) { open O,"$fn_use" or die "$fn_use ($fn_str): $!"; while () { chomp; - $record= [ split(/\:/,$_,-1) ]; - die "$fn_emsg:$.:wrong number of fields:\`$_'\n" - unless @$record == $nfields; + 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_use ($fn_str): $!"; @@ -400,7 +223,7 @@ sub regroupglobs () { for $g (@groupglobs) { $ggfunc.= " m/^$g->[0]\$/ ? $g->[1] :\n"; } $ggfunc.= " die;\n};\n1;\n"; #print STDERR "$ggfunc\n"; - eval $ggfunc or die "$ggfunc // $@"; + must_eval($ggfunc); } sub fetchown () { @@ -431,6 +254,7 @@ sub fetchown () { sub checkuid ($$) { my ($useuid,$foruser) = @_; for $e (@ownpasswd) { + 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; @@ -439,16 +263,20 @@ sub checkuid ($$) { return 1; } +sub must_eval ($) { + eval $_[0] or die "$_[0] // $@"; +} + sub copyfield ($$$$) { my ($file,$entry,$field,$value) = @_; - eval "\$ary_ref= \\\@own$file; 1;" or die $@; + must_eval("\$ary_ref= \\\@own$file; 1;"); #print STDERR "copyfield($file,$entry,$field,$value)\n"; for $e (@$ary_ref) { #print STDERR "copyfield($file,$entry,$field,$value) $e->[0] $e->[field] ".join(':',@$e)."\n"; next unless $e->[0] eq $entry; next if $e->[$field] eq $value; $e->[$field]= $value; - eval "\$modified$file= 1; 1;" or die $@; + must_eval("\$modified$file= 1; 1;"); } } @@ -487,6 +315,7 @@ sub syncusergroup ($$) { $ugfound=0; for $e (@owngroup) { + next unless ref $e; $samename= $e->[$GR_GROUP] eq $lu; $sameid= $e->[$GR_GID] eq $luid; next unless $samename || $sameid; @@ -536,7 +365,7 @@ sub syncuser ($$) { if ($display) { for $e (@ownpasswd) { - next unless $e->[$PW_USER] eq $lu; + 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}++; @@ -556,7 +385,7 @@ sub syncuser ($$) { return; } - if (!grep($_->[$PW_USER] 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; } @@ -571,6 +400,7 @@ sub syncuser ($$) { $useuid= $ch_uidmin; 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 (or gid?) $ch_uidmax used, cannot create users"); @@ -629,7 +459,7 @@ sub syncuser ($$) { } for $e (@ownpasswd) { - next unless $e->[$PW_USER] eq $lu; + next unless ref $e && $e->[$PW_USER] eq $lu; syncusergroup($lu,$e->[$PW_UID]) or return; } @@ -637,7 +467,7 @@ sub syncuser ($$) { $rgid= $rempasswd{$ru}->[$REM_GID]; if ($opt_sameuid && checkuid($ruid,$lu)) { for $e (@ownpasswd) { - next unless $e->[$PW_USER] eq $lu; + 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; @@ -645,7 +475,7 @@ sub syncuser ($$) { } #print STDERR "syncuser($lu,$ru) exists $own_haveshadow\n"; - if ($own_haveshadow && grep($_->[$PW_USER] eq $lu, @ownshadow)) { + 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 { @@ -661,6 +491,7 @@ sub syncuser ($$) { if (!$nogroups) { for $e (@owngroup) { + next unless ref $e; $tgroup= $e->[$GR_GROUP]; #print STDERR "syncuser($lu,$ru) group $tgroup\n"; next unless &wantsyncgroup($tgroup); @@ -693,6 +524,8 @@ sub banner () { } sub finish () { + my ($record); + for $h (keys %wanthost) { die "host $h not in config file\n" if $wanthost{$h}; } @@ -700,13 +533,14 @@ sub finish () { 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 $e2->[$SP_USER] eq $tu; + next unless ref $e2 && $e2->[$SP_USER] eq $tu; $tpw= $e2->[$SP_PW]; last; } $tpw= length($tpw)>=13 ? 1 : length($tpw) ? -1 : 0; @@ -727,8 +561,8 @@ sub finish () { 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 $@; + must_eval("\$modified= \$modified$file; \$data_ref= \\\@own$file;". + " \$fetched= \$own_fetched$file; 1;"); next if !$modified; die $file unless $fetched; banner(); @@ -741,7 +575,8 @@ sub finish () { } 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','--label',$realfile,$newfileinst, @@ -759,8 +594,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; @@ -776,19 +611,19 @@ while () { } fields_fmt('REM','std'); } elsif (m/^(getpasswd|getshadow|getgroup)\s+(.*\S)$/) { - eval "\$ch_$1= \$2; 1;" or die $@; + must_eval("\$ch_$1= \$2; 1;"); } 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 $@; + must_eval("\$ch_lock_$1= \$3; \$ch_lockstyle_$1= \$2; 1;"); } elsif (m/^lock(passwd|group)\s+(none)$/) { - eval "\$ch_lockstyle_$1= \$2; 1;" or die $@; + must_eval("\$ch_lockstyle_$1= \$2; 1;"); } elsif (m,^(homebase|defaultshell)\s+(/\S+)$,) { - eval "\$ch_$1= \$2; 1;" or die $@; + must_eval("\$ch_$1= \$2; 1;"); } elsif (m/^(uidmin|uidmax)\s+(\d+)$/ && $2>0) { - eval "\$ch_$1= \$2; 1;" or die $@; + must_eval("\$ch_$1= \$2; 1;"); } elsif (m/^createuser$/) { $opt_createuser= $def_createuser; } elsif (m/^nocreateuser$/) { @@ -803,7 +638,7 @@ while () { print "would log to $1\n" or die $!; } } elsif (m/^(no|)(sameuid)$/) { - eval "\$opt_$2= ".($1 eq 'no' ? 0 : 1)."; 1;" or die $@; + must_eval("\$opt_$2= ".($1 eq 'no' ? 0 : 1)."; 1;"); } elsif (m/^usergroups$/) { $defaultgid= -1; } elsif (m/^nousergroups$/) { @@ -814,7 +649,7 @@ while () { $yes= $1 eq 'no' ? 0 : 1; $_= $2; @groupglobs=() if $_ eq '*'; - s/[-+._]/\\$1/g; + s/[-+._]/\\$&/g; s/\*/\.\*/g; s/\?/\./g; unshift @groupglobs, [ $_, $yes ];