chiark / gitweb /
Check error return from open of new files.
[chiark-utils.git] / sync-accounts / sync-accounts
index a1b149340ab0b7a49619aec251d3ffcb26a00ce5..172b78c6a095fa94599f3a81e57096923e1c431a 100755 (executable)
@@ -1,8 +1,10 @@
 #!/usr/bin/perl
+# $Id: sync-accounts,v 1.13 1999-01-03 03:35:24 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
 #
 # The config file consists of directives, one per line.  Leading and
@@ -14,7 +16,7 @@
 #  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 `/'.
+#      not start with `/'.  If set to /dev/null no locking is done.
 #
 #  logfile <filename>
 #      Append log messages to <filename> instead of stdout.
 #
 #  usergroups
 #  nousergroups
+#  defaultgid <gid>
 #      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'.
+#      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 <commandname>
 #      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:
+#        SYNCACCOUNT_CREATE_USER
+#        SYNCACCOUNT_CREATE_UID
+#        SYNCACCOUNT_CREATE_GID
+#        SYNCACCOUNT_CREATE_COMMENT
+#        SYNCACCOUNT_CREATE_HOME
+#        SYNCACCOUNT_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 <glob-pattern>
 #  nogroup <glob-pattern>
 #      Specifies that the membership of the local groups specified
 #      glob-pattern for a particular group takes effect.  The default
 #      is `nogroups *'.
 #
+#  defaultshell <pathname>
+#      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:
 #
@@ -137,7 +166,8 @@ 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();
 
@@ -159,6 +189,10 @@ 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";
     }
@@ -286,7 +320,7 @@ sub fetchgroup () {
 sub syncusergroup ($$) {
     my ($lu,$luid) = @_;
 
-    return 1 if !$opt_usergroups;
+    return 1 if $defaultgid != -1;
 #print STDERR "syncusergroup($lu,$luid)\n";
     $ugfound=0;
     
@@ -315,7 +349,15 @@ sub syncusergroup ($$) {
     $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) = @_;
 
@@ -323,9 +365,21 @@ sub syncuser ($$) {
     next unless $ch_doinghost;
     return if $doneuser{$lu}++;
     return if !length $ru;
-    $diagstr= "user $lu from $ch_name!$ru";
 
     fetchown();
+
+    if ($display) {
+       for $e (@ownpasswd) {
+           next unless $e->[0] eq $lu;
+           hosthead("from $ch_name");
+           print ($lu eq $ru ? " $lu" : " $lu($ru)") or die $!;
+           print "<DUPLICATE>" if $displaydone{$lu}++;
+       }
+       return;
+    }
+    
+    $diagstr= "user $lu from $ch_name!$ru";
+
 #print STDERR "syncuser($lu,$ru) doing\n";
     fetchpasswd();
 
@@ -341,24 +395,27 @@ sub syncuser ($$) {
 
        if ($opt_sameuid) {
            $useuid= $rempasswd{$ru}->[2];
+           $usegid= $rempasswd{$ru}->[3];
        } 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) {
+           for $e ($defaultgid==-1 ? (@ownpasswd, @owngroup) : (@ownpasswd)) {
                $tuid= $e->[2]; 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= ($lu,'x',$useuid,$usegid,$rempasswd{$ru}->[4],
+                   "$ch_homebase/$lu",$ch_defaultshell);
        
        defined($c= open CU,"-|") or die $!;
        if (!$c) {
@@ -418,7 +475,11 @@ sub syncuser ($$) {
        copyfield('passwd',$lu,1, $rempasswd{$ru}->[1]);
     }
     copyfield('passwd',$lu,4, $rempasswd{$ru}->[4]); # comment
-    copyfield('passwd',$lu,6, $rempasswd{$ru}->[6]); # shell
+
+    $newsh= $rempasswd{$ru}->[6];
+    $oksh= $checkedshell{$newsh};
+    if (!length $oksh) { $checkedshell{$newsh}= $oksh= (-x $newsh) ? 1 : 0; }
+    copyfield('passwd',$lu,6, $newsh) if $oksh;
 
     if (!$nogroups) {
        for $e (@owngroup) {
@@ -457,6 +518,29 @@ sub finish () {
     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 $!;
+           }
+       }
+       print "\n\n" or die $! if $hostheaddone;
+       exit 0;
+    }
     
     umask 077;
     for $file (qw(passwd shadow group)) {
@@ -466,20 +550,16 @@ sub finish () {
        die $file unless $fetched;
        banner();
        $newfile= $no_act ? "$file.new" : "/etc/$file.new";
-       open NF,"> $newfile";
+       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 $?;
        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 $!;
-           }
+           (@stats= stat "/etc/$file") or die "$file: $!";
+           chown $stats[4],$stats[5], $newfile or die $!;
+           chmod $stats[2] & 07777, $newfile or die $!;
            rename $newfile, "/etc/$file" or die $!;
        }
     }
@@ -507,7 +587,7 @@ while (<CF>) {
        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,^(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 +598,19 @@ while (<CF>) {
     } 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;
+       } 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;