#!/usr/bin/perl
-# $Id: sync-accounts,v 1.6 1999-01-03 01:11:08 ian Exp $
-# usage: sync-accounts [-n] [-C<config-file>] [<host> ...]
-# 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
+# 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 <ian@davenant.greenend.org.uk>
+# 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 3, or (at
+# your option) any later version.
#
-# lockpasswd <suffix/filename>
-# lockgroup <suffix/filename>
-# 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.
+# 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 <filename>
-# Append log messages to <filename> instead of stdout.
-# Errors still go to stderr.
+# You should already have a copy of the GNU General Public License.
+# If not, consult the Free Software Foundation's website at
+# www.fsf.org, or the GNU Project website at www.gnu.org.
#
-# 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 <min>
-# uidmax <max>
-# homebase <pathname>
-# 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 <min>-<max> are ignored and will
-# never be used). The default home directory location is
-# <pathname>/<username>.
-#
-# 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 <commandname>
-# 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 <glob-pattern>
-# nogroup <glob-pattern>
-# 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 <shorthostname>
-# 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 <command>
-# getgroup <command>
-# 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 <command>
-# 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 <username> [remote=<remoteusername>]
-# Specifies that account data should be copied for local user
-# <username> from the remote account <remoteusername> (assumed to
-# be the same as <username> if not specified). The account
-# password, comment field, and shell will be copied
-# unconditionally. If sameuid is specified the uid will be
-# checked.
-#
-# users <ruidmin>-<ruidmax>
-# Specifies that all remote users whose uid is in the given range
-# are to be copied to corresponding local user accounts.
-#
-# nouser <username>
-# Specifies that data for <username> 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.25 2007-09-21 21:21:15 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;
}
}
+die "hosts must not be specified with -q\n" if @ARGV && $display;
+
for $h (@ARGV) { $wanthost{$h}= 1; }
open CF,"< $configfile" or die "$configfile: $!";
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 (<O>) {
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 ($) {
$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 // $@";
+ must_eval($ggfunc);
}
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";
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;
}
}
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;");
}
}
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 () {
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) {
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 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 ref $e && $e->[$PW_USER] eq $lu;
hosthead("from $ch_name");
print ($lu eq $ru ? " $lu" : " $lu($ru)") or die $!;
print "<DUPLICATE>" if $displaydone{$lu}++;
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) {
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";
}
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";
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;
} else {
next;
}
- $e->[3]= $cusers;
+ $e->[$GR_USERS]= $cusers;
$modifiedgroup= 1;
}
}
}
sub finish () {
+ my ($record);
+
for $h (keys %wanthost) {
die "host $h not in config file\n" if $wanthost{$h};
}
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) {
+ 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]";
}
- print "\n\n" or die $! if $hostheaddone;
+ 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)) {
- eval "\$modified= \$modified$file; \$data_ref= \\\@own$file;".
- " \$fetched= \$own_fetched$file; 1;" or die $@;
+ $realfile= $file{$file,$PW_format};
+ must_eval("\$modified= \$modified$file; \$data_ref= \\\@own$file;".
+ " \$fetched= \$own_fetched$file; 1;");
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;
while (<CF>) {
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;
} 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+)$,) {
- 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+)$/) {
+ must_eval("\$ch_lock_$1= \$3; \$ch_lockstyle_$1= \$2; 1;");
+ } elsif (m/^lock(passwd|group)\s+(none)$/) {
+ must_eval("\$ch_lockstyle_$1= \$2; 1;");
+ } elsif (m,^(homebase|defaultshell)\s+(/\S+)$,) {
+ 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$/) {
} 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 $!;
}
- } elsif (m/^(no|)(sameuid|usergroups)$/) {
- eval "\$opt_$2= ".($1 eq 'no' ? 0 : 1)."; 1;" or die $@;
+ } elsif (m/^(no|)(sameuid)$/) {
+ must_eval("\$opt_$2= ".($1 eq 'no' ? 0 : 1)."; 1;");
+ } 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;
@groupglobs=() if $_ eq '*';
- s/[-+._]/\\$1/g;
+ s/[-+._]/\\$&/g;
s/\*/\.\*/g;
s/\?/\./g;
unshift @groupglobs, [ $_, $yes ];