#!/usr/bin/perl # # Copyright (C) 1995-9, 2003 Ian Jackson # Copyright (C) 1999, 2003 # Chancellor Masters and Scholars of the University of Cambridge # # Improved by Ben Harris in 1999 and 2003 for Unix # Support's own nefarious purposes. # # 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. # # It 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. # # $Id$ sub usage { &unlock; &p_out; print(< [--info] groupmanage [ ...] groupmanage --create [ ...] actions: --clear --add ... --remove ... --manager-clear --manager-add ... --manager-remove ... --title --owner [root only] groupmanage is Copyright. It is free software, released under the GNU GPL v2 or later. There is NO WARRANTY. See the GPL for details. END exit(1); } @ARGV || &usage('too few arguments'); if ($>) { exec 'userv','root','groupmanage',@ARGV; &quit("unable to execute userv to gain root privilege: $!"); } chdir("/etc") || die "groupmanage: chdir /etc: $!\n"; $groupname= shift(@ARGV); $groupname =~ y/\n//d; $groupname =~ m/^\w[-0-9A-Za-z]*$/ || &quit("first argument is invalid - must be group name"); @ARGV || push(@ARGV,'--info'); $callinguser= exists $ENV{'USERV_UID'} ? $ENV{'USERV_UID'} : $<; %opt= ('user-create','0', 'user-create-minunameu','5', 'user-create-min','10000', 'user-create-max','19999', 'user-create-nameintitle','0', 'user-create-maxperu','5', 'group-file','group', 'gtmp-file','gtmp', 'grouplist-file','grouplist', 'name-regexp','', 'admin-group','', 'finish-command',''); %ovalid= ('user-create','boolean', 'user-create-minunameu','number', 'user-create-min','number', 'user-create-max','number', 'user-create-nameintitle','boolean', 'user-create-maxperu','number', 'group-file','string', 'gtmp-file','string', 'grouplist-file','string', 'name-regexp','string', 'admin-group','string', 'finish-command','string'); sub ov_boolean { $cov= $_ eq 'yes' ? 1 : $_ eq 'no' ? 0 : &quit("groupmanage.conf:$.: bad boolean value"); } sub ov_number { m/^[0-9]{1,10}$/ || &quit("groupmanage.conf:$.: bad numerical value"); } sub ov_string { } open(GMC,"groupmanage.conf") || &quit("read groupmanage.conf: $!"); while () { next if m/^\#/ || !m/\S/; s/\s*\n$//; s/^\s*([-0-9a-z]+)\s*// || &quit("groupmanage.conf:$.: bad option format"); $co= $1; defined($opt{$co}) || &quit("groupmanage.conf:$.: unknown option $co"); $cov= $_; $ovf= 'ov_'.$ovalid{$co}; &$ovf; $opt{$co}= $cov; } close(GMC); if ($ARGV[0] eq '--info') { @ARGV == 1 || &usage('no arguments allowed after --info'); &p_out; &load; &checkexists; &display; &p_out; exit(0); } sub naming { $callinguser || return; &p_out; if ($opt{'user-create-minunameu'}) { print(STDERR <- You must quote at least $opt{'user-create-minunameu'} chars of your username $createby (or all of it if it is shorter). END } if ($opt{'name-regexp'}) { print(STDERR <= $opt{'user-create-minunameu'} && substr($createby,0,length($upart)) eq $upart) || &naming; } else { $groupname =~ m/${opt{'name-regexp'}}/ || &naming; } $create= 1; shift(@ARGV); } &lock; &load; if ($create) { $bythisowner < $opt{'user-create-maxperu'} || &quit("you already have $bythisowner group(s)"); $groupfileix==-1 || &quit("group already exists, cannot create it"); $grouplistix==-1 || &quit("group is already in grouplist, cannot create it"); for ($gid= $opt{'user-create-min'}; $gid < $opt{'user-create-max'} && defined(getgrgid($gid)); $gid++) { } $gid <= $opt{'user-create-max'} || &quit("out of gids to use, contact admin"); $password=''; @members=($createby); $description= "${createby}'s -- user-defined, no title"; $owner= $createby; @managers=(); @members= ($createby); $groupfileix=$#groupfile+1; $grouplistix=$#grouplist+1; &p("created group $groupname"); } else { &checkexists; &p("modifying group $groupname"); } &weare($owner) || grep(&weare($_),@managers) || !$callinguser || &quit("you may not manage $groupname"); $action= 'none'; while (@ARGV) { $_= shift(@ARGV); if (m/^--(add|remove)$/) { $action= $1; $clist= 'members'; $what= 'member'; } elsif (m/^--owner$/) { !$callinguser || &quit("only root may change owner"); @ARGV || &usage("no username owner after --owner"); $owner= shift(@ARGV); &p("owner set to $owner"); } elsif (m/^--manager-(add|remove)$/) { $action= $1; $clist= 'managers'; $what= 'manager'; } elsif (m/^--clear$/) { &p('cleared list of members'); @members=(); $action='none'; $memc++; } elsif (m/^--manager-clear$/) { &p('cleared list of managers'); @managers=(); $action='none'; } elsif (m/^--title$/) { &weare($owner) || !$callinguser || &quit("only group's owner ($owner) may change title"); @ARGV || &usage("no title after --title"); $_= shift(@ARGV); y/\020-\176//cd; y/:\\//d; if ($opt{'user-create-nameintitle'} && $gid >= $opt{'user-create-min'} && $gid <= $opt{'user-create-max'}) { $_= "${owner}'s -- $_"; } $description= $_; &p("title set to $description"); } elsif (m/^-/) { &usage("unknown option $_"); } elsif (m/^\w[-0-9A-Za-z]*$/) { y/\n//d; $chgu=$_; getpwnam($chgu) || &quit("username $chgu does not exist"); eval "\@l = \@$clist; 1" || &quit("internal error: $@"); $already= grep($_ eq $chgu, @l); if ($action eq 'add') { if ($already) { &p("$chgu already $what"); } else { &p("added $what $chgu"); push(@l,$chgu); $memc+= ($clist eq 'members'); } } elsif ($action eq 'remove') { if ($already) { &p("removed $what $chgu"); @l= grep($_ ne $chgu, @l); $memc+= ($clist eq 'members'); } else { &p("$chgu is already not $what"); } } else { &usage("username found but no action to take for them"); } eval "\@$clist = \@l; 1" || &quit("internal error: $@"); } else { &usage("bad username or option $_"); } } &p("nb: a change to group membership only takes effect at the user's next login") if $memc; $groupfile[$groupfileix]= "$groupname:$password:$gid:".join(',',@members)."\n"; $grouplist[$grouplistix]= "$groupname:$description:$owner:".join(',',@managers).":$homedir\n"; &save($opt{'group-file'},@groupfile); &save($opt{'grouplist-file'},@grouplist); if ($opt{'finish-command'}) { !system($opt{'finish-command'}) || &quit("finish-command: $?"); } unlink($opt{'gtmp-file'}) || &quit("unlock group (remove gtmp): $!"); &p_out; exit(0); sub load { open(GF,"< $opt{'group-file'}") || &quit("read group: $!"); @groupfile=; close(GF); $groupfileix=-1; for ($i=0; $i<=$#groupfile; $i++) { $_= $groupfile[$i]; s/\n$//; next if m/^\#/; m/^(\w[-0-9A-Za-z]*):([^:]*):(\d+):([-0-9A-Za-z,]*)$/ || &quit("bad entry in group: $_"); $gname2gid{$1}=$3; next unless $1 eq $groupname; $groupfileix<0 || &quit("duplicate entries in group"); $groupfileix= $i; $password= $2; $gid= $3; @members= split(/,/,$4); } open(GL,"< $opt{'grouplist-file'}") || &quit("read grouplist: $!"); @grouplist=; close(GL); $grouplistix=-1; for ($i=0; $i<=$#grouplist; $i++) { $_= $grouplist[$i]; s/\n$//; next if m/^\#/; m/^(\w[-0-9A-Za-z]*):([^:]*):(\w[-0-9A-Za-z]*):([-0-9A-Za-z,]*):([^:]*)$/ || &quit("bad entry in grouplist: $_"); $bythisowner++ if ($create && $3 eq $createby && $gname2gid{$1} >= $opt{'user-create-min'} && $gname2gid{$1} <= $opt{'user-create-max'}); next unless $1 eq $groupname; $grouplistix<0 || &quit("duplicate entries in grouplist"); $grouplistix= $i; $description= $2; $owner= $3; $homedir= $5; @managers= split(/,/,$4); } } sub checkexists { $grouplistix>=0 || &quit("no entry in grouplist for $groupname"); $groupfileix>=0 || &quit("no entry in group for $groupname"); } sub weare { return 0 if $_[0] eq ''; @pw= getpwnam($_[0]); return @pw && $pw[2] == $callinguser ? 1 : 0; } sub save { $filename= shift(@_); unlink("$filename~"); open(DUMP,"> $filename.new") || &quit("create new $filename: $!"); print(DUMP @_) || &quit("write new $filename: $!"); close(DUMP) || &quit("close new $filename: $!"); link("$filename","$filename~") || &quit("create backup $filename: $!"); rename("$filename.new","$filename") || &quit("install new $filename: $!"); } sub quit { &unlock; &p_out; die "groupmanage: @_\n"; } sub lock { link($opt{'group-file'},$opt{'gtmp-file'}) || &quit("create gtmp: $!"); $locked++; } sub unlock { return unless $locked; $locked--; unlink($opt{'gtmp-file'}) || warn("unlock group file (remove gtmp): $!\n"); } sub display { print(<