chiark / gitweb /
Preserve permissions on /etc/shadow etc.
[chiark-utils.git] / sync-accounts / sync-accounts
index a1b149340ab0b7a49619aec251d3ffcb26a00ce5..1f48714c991a4b610bd8d26a08e1e304ce594e0d 100755 (executable)
@@ -1,8 +1,10 @@
 #!/usr/bin/perl
+# $Id: sync-accounts,v 1.10 1999-01-03 02:16:49 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
@@ -137,7 +161,7 @@ use POSIX;
 $configfile= '/etc/sync-accounts';
 $def_createuser= 'sync-accounts-createuser';
 $ch_homebase= '/home';
-$opt_usergroups= 1;
+$defaultgid= -1; # -1 => usergroups; -2 => nousergroups
 @groupglobs= [ '.*', 0 ];
 regroupglobs();
 
@@ -159,6 +183,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 +314,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 +343,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 +359,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,23 +389,26 @@ 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],
+       @newpwent= ($lu,'x',$useuid,$usegid,$rempasswd{$ru}->[4],
                    "$ch_homebase/$lu",$rempasswd{$ru}->[6]);
        
        defined($c= open CU,"-|") or die $!;
@@ -457,6 +508,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)) {
@@ -473,13 +547,9 @@ sub finish () {
        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 $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 $!;
        }
     }
@@ -518,13 +588,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;