changes from Cambridge University (Ben Harris) unedited; will edit shortly
[userv-utils] / groupmanage / groupmanage
CommitLineData
d38b93ab 1#!/usr/bin/perl
2#
fe112c08 3# Copyright (C)1995-9 Ian Jackson <ijackson@chiark.greenend.org.uk>
85dbc5e7 4# Copyright (C) 1999, 2003
5# Chancellor Masters and Scholars of the University of Cambridge
6#
7# Hacked by Ben Harris <bjh21@cam.ac.uk> in 1999 and 2003 for Unix
8# Support's own nefarious purposes.
fe112c08 9#
d38b93ab 10# This is free software; you can redistribute it and/or modify it
11# under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2, or (at your option)
13# any later version.
fe112c08 14#
d38b93ab 15# It is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
fe112c08 19#
20# $Id$
d38b93ab 21
22sub usage {
23 &unlock;
24 &p_out;
25 print(<<END) || die "groupmanage: write usage: $!\n";
26groupmanage: $_[0]
27 usage:
28 groupmanage <groupname> [--info]
29 groupmanage <groupname> <action> [<action> ...]
30 groupmanage <groupname> --create [<action> <action> ...]
31 actions:
32 --clear
33 --add <username> <username> ...
34 --remove <username> <username> ...
35 --manager-clear
36 --manager-add <username> <username> ...
37 --manager-remove <username> <username> ...
38 --title <string>
39 --owner <username> [root only]
fe112c08 40groupmanage is Copyright. It is free software, released under the GNU
41GPL v2 or later. There is NO WARRANTY. See the GPL for details.
d38b93ab 42END
43 exit(1);
44}
45
46@ARGV || &usage('too few arguments');
47
48if ($>) {
49 exec 'userv','root','groupmanage',@ARGV;
50 &quit("unable to execute userv to gain root privilege: $!");
51}
52
53chdir("/etc") || die "groupmanage: chdir /etc: $!\n";
54
55$groupname= shift(@ARGV);
56$groupname =~ y/\n//d;
57
58$groupname =~ m/^\w[-0-9A-Za-z]*$/ ||
59 &quit("first argument is invalid - must be group name");
60
61@ARGV || push(@ARGV,'--info');
62
d38b93ab 63$callinguser= exists $ENV{'USERV_UID'} ? $ENV{'USERV_UID'} : $<;
85dbc5e7 64$callingname = exists $ENV{'USERV_USER'} ? $ENV{'USERV_USER'} : getpwuid($<);
d38b93ab 65
66%opt= ('user-create','0',
67 'user-create-minunameu','5',
68 'user-create-min','10000',
69 'user-create-max','19999',
70 'user-create-nameintitle','0',
85dbc5e7 71 'user-create-maxperu','5',
72 'group-file','group',
73 'gtmp-file','gtmp',
74 'grouplist-file','grouplist',
75 'name-regexp','',
76 'admin-group','',
77 'finish-command','');
d38b93ab 78%ovalid= ('user-create','boolean',
79 'user-create-minunameu','number',
80 'user-create-min','number',
81 'user-create-max','number',
82 'user-create-nameintitle','boolean',
85dbc5e7 83 'user-create-maxperu','number',
84 'group-file','string',
85 'gtmp-file','string',
86 'grouplist-file','string',
87 'name-regexp','string',
88 'admin-group','string',
89 'finish-command','string');
d38b93ab 90
91sub ov_boolean {
92 $cov= $_ eq 'yes' ? 1 :
93 $_ eq 'no' ? 0 :
94 &quit("groupmanage.conf:$.: bad boolean value");
95}
96
97sub ov_number {
98 m/^[0-9]{1,10}$/ || &quit("groupmanage.conf:$.: bad numerical value");
99}
100
85dbc5e7 101sub ov_string {
102
103}
104
d38b93ab 105open(GMC,"groupmanage.conf") || &quit("read groupmanage.conf: $!");
106while (<GMC>) {
107 next if m/^\#/ || !m/\S/;
108 s/\s*\n$//;
109 s/^\s*([-0-9a-z]+)\s*// || &quit("groupmanage.conf:$.: bad option format");
110 $co= $1;
111 defined($opt{$co}) || &quit("groupmanage.conf:$.: unknown option $co");
112 $cov= $_;
113 $ovf= 'ov_'.$ovalid{$co};
114 &$ovf;
115 $opt{$co}= $cov;
116}
117close(GMC);
118
85dbc5e7 119if ($ARGV[0] eq '--info') {
120 @ARGV == 1 || &usage('no arguments allowed after --info');
121 &p_out;
122 &load;
123 &checkexists;
124 &display;
125 &p_out;
126 exit(0);
127}
128
d38b93ab 129sub naming {
130 $callinguser || return;
131 &p_out;
85dbc5e7 132 if ($opt{'user-create-minunameu'}) {
133 print(STDERR <<END) || &quit("write err re name: $!");
d38b93ab 134groupmanage: groups you create must be named after you ...
135 <usernamepart>-<identifier>
136 You must quote at least $opt{'user-create-minunameu'} chars of your username $createby
137 (or all of it if it is shorter).
138END
85dbc5e7 139 }
140 if ($opt{'name-regexp'}) {
141 print(STDERR <<END) || &quit("write err re name: $!");
142groupmanage: groups you create must match a pattern...
143 The pattern is the Perl regular expression /$opt{'name-regexp'}/.
144END
145 }
d38b93ab 146 exit(1);
147}
148
149if ($ARGV[0] eq '--create') {
150 $opt{'user-create'} || !$callinguser ||
85dbc5e7 151 ($opt{'admin-group'} &&
152 (getgrnam($opt{'admin-group'}))[3] =~ /(^| )$callingname( |$)/) ||
d38b93ab 153 &quit("group creation by users disabled by administrator");
154 length($groupname) <= 8 || &quit("group names must be 8 chars or fewer");
d38b93ab 155 $!=0; (@pw= getpwuid($callinguser))
156 || &quit("cannot get your passwd entry: $!");
157 $createby= $pw[0];
85dbc5e7 158 if ($opt{'user-create-minunameu'}) {
159 $groupname =~ m/^([-0-9A-Za-z]+)-([0-9a-z]+)$/ || &naming;
160 $upart= $1;
161 $idpart= $2;
162 $upart eq $createby ||
163 (length($upart) >= $opt{'user-create-minunameu'} &&
164 substr($createby,0,length($upart)) eq $upart)
165 || &naming;
166 } else {
167 $groupname =~ m/${opt{'name-regexp'}}/ || &naming;
168 }
d38b93ab 169 $create= 1;
170 shift(@ARGV);
171}
172
173&lock;
174&load;
175
176if ($create) {
177 $bythisowner < $opt{'user-create-maxperu'} ||
178 &quit("you already have $bythisowner group(s)");
179 $groupfileix==-1 || &quit("group already exists, cannot create it");
180 $grouplistix==-1 || &quit("group is already in grouplist, cannot create it");
181 for ($gid= $opt{'user-create-min'};
182 $gid < $opt{'user-create-max'} && defined(getgrgid($gid));
183 $gid++) { }
184 $gid <= $opt{'user-create-max'} || &quit("out of gids to use, contact admin");
185 $password=''; @members=($createby);
186 $description= "${createby}'s -- user-defined, no title";
187 $owner= $createby; @managers=(); @members= ($createby);
188 $groupfileix=$#groupfile+1;
189 $grouplistix=$#grouplist+1;
190 &p("created group $groupname");
191} else {
192 &checkexists;
193 &p("modifying group $groupname");
194}
195
196&weare($owner) || grep(&weare($_),@managers) || !$callinguser ||
85dbc5e7 197 ($opt{'admin-group'} &&
198 (getgrnam($opt{'admin-group'}))[3] =~ /(^| )$callingname( |$)/) ||
d38b93ab 199 &quit("you may not manage $groupname");
200
201$action= 'none';
202while (@ARGV) {
203 $_= shift(@ARGV);
204 if (m/^--(add|remove)$/) {
205 $action= $1; $clist= 'members'; $what= 'member';
206 } elsif (m/^--owner$/) {
207 !$callinguser || &quit("only root may change owner");
208 @ARGV || &usage("no username owner after --owner");
209 $owner= shift(@ARGV);
210 &p("owner set to $owner");
211 } elsif (m/^--manager-(add|remove)$/) {
212 $action= $1; $clist= 'managers'; $what= 'manager';
213 } elsif (m/^--clear$/) {
214 &p('cleared list of members');
215 @members=(); $action='none'; $memc++;
216 } elsif (m/^--manager-clear$/) {
217 &p('cleared list of managers');
218 @managers=(); $action='none';
219 } elsif (m/^--title$/) {
220 &weare($owner) || !$callinguser ||
221 &quit("only group's owner ($owner) may change title");
222 @ARGV || &usage("no title after --title");
223 $_= shift(@ARGV); y/\020-\176//cd; y/:\\//d;
224 if ($opt{'user-create-nameintitle'} &&
225 $gid >= $opt{'user-create-min'} && $gid <= $opt{'user-create-max'}) {
226 $_= "${owner}'s -- $_";
227 }
228 $description= $_;
229 &p("title set to $description");
230 } elsif (m/^-/) {
231 &usage("unknown option $_");
232 } elsif (m/^\w[-0-9A-Za-z]*$/) {
233 y/\n//d;
234 $chgu=$_;
ac36fe36 235 getpwnam($chgu) || &quit("username $chgu does not exist");
d38b93ab 236 eval "\@l = \@$clist; 1" || &quit("internal error: $@");
237 $already= grep($_ eq $chgu, @l);
238 if ($action eq 'add') {
239 if ($already) {
240 &p("$chgu already $what");
241 } else {
242 &p("added $what $chgu");
243 push(@l,$chgu);
244 $memc+= ($clist eq 'members');
245 }
246 } elsif ($action eq 'remove') {
247 if ($already) {
248 &p("removed $what $chgu");
249 @l= grep($_ ne $chgu, @l);
250 $memc+= ($clist eq 'members');
251 } else {
252 &p("$chgu is already not $what");
253 }
254 } else {
255 &usage("username found but no action to take for them");
256 }
257 eval "\@$clist = \@l; 1" || &quit("internal error: $@");
258 } else {
259 &usage("bad username or option $_");
260 }
261}
262&p("nb: a change to group membership only takes effect at the user's next login")
263 if $memc;
264$groupfile[$groupfileix]=
265 "$groupname:$password:$gid:".join(',',@members)."\n";
266$grouplist[$grouplistix]=
267 "$groupname:$description:$owner:".join(',',@managers).":$homedir\n";
85dbc5e7 268&save($opt{'group-file'},@groupfile);
269&save($opt{'grouplist-file'},@grouplist);
270if ($opt{'finish-command'}) {
271 !system($opt{'finish-command'}) || &quit("finish-command: $!");
272}
273unlink($opt{'gtmp-file'}) || &quit("unlock group (remove gtmp): $!");
d38b93ab 274&p_out;
275exit(0);
276
277sub load {
85dbc5e7 278 open(GF,"< $opt{'group-file'}") || &quit("read group: $!");
d38b93ab 279 @groupfile=<GF>; close(GF);
280 $groupfileix=-1;
281 for ($i=0; $i<=$#groupfile; $i++) {
282 $_= $groupfile[$i]; s/\n$//;
283 next if m/^\#/;
284 m/^(\w[-0-9A-Za-z]*):([^:]*):(\d+):([-0-9A-Za-z,]*)$/ ||
285 &quit("bad entry in group: $_");
286 $gname2gid{$1}=$3;
287 next unless $1 eq $groupname;
288 $groupfileix<0 || &quit("duplicate entries in group");
289 $groupfileix= $i;
290 $password= $2;
291 $gid= $3;
292 @members= split(/,/,$4);
293 }
85dbc5e7 294 open(GL,"< $opt{'grouplist-file'}") || &quit("read grouplist: $!");
d38b93ab 295 @grouplist=<GL>; close(GL);
296 $grouplistix=-1;
297 for ($i=0; $i<=$#grouplist; $i++) {
298 $_= $grouplist[$i]; s/\n$//;
299 next if m/^\#/;
300 m/^(\w[-0-9A-Za-z]*):([^:]*):(\w[-0-9A-Za-z]*):([-0-9A-Za-z,]*):([^:]*)$/ ||
301 &quit("bad entry in grouplist: $_");
302 $bythisowner++ if ($create && $3 eq $createby &&
303 $gname2gid{$1} >= $opt{'user-create-min'} &&
304 $gname2gid{$1} <= $opt{'user-create-max'});
305 next unless $1 eq $groupname;
306 $grouplistix<0 || &quit("duplicate entries in grouplist");
307 $grouplistix= $i;
308 $description= $2;
309 $owner= $3;
310 $homedir= $5;
311 @managers= split(/,/,$4);
312 }
313}
314
315sub checkexists {
316 $grouplistix>=0 || &quit("no entry in grouplist for $groupname");
317 $groupfileix>=0 || &quit("no entry in group for $groupname");
318}
319
320sub weare {
321 return 0 if $_[0] eq '';
322 @pw= getpwnam($_[0]);
323 return @pw && $pw[2] == $callinguser ? 1 : 0;
324}
325
326sub save {
327 $filename= shift(@_);
328 unlink("$filename~");
329 open(DUMP,"> $filename.new") || &quit("create new $filename: $!");
330 print(DUMP @_) || &quit("write new $filename: $!");
331 close(DUMP) || &quit("close new $filename: $!");
332 link("$filename","$filename~") || &quit("create backup $filename: $!");
333 rename("$filename.new","$filename") || &quit("install new $filename: $!");
334}
335
336sub quit {
337 &unlock;
338 &p_out;
339 die "groupmanage: @_\n";
340}
341
342sub lock {
85dbc5e7 343 # NFS-safe Locking per Linux open(2)
344 my($hostname) = `hostname`;
345 chomp($hostname);
346 my($hitching_post) = "$opt{'gtmp-file'}.$hostname.$$";
347 open(LOCK, ">$hitching_post") || die "$hitching_post: $!";
348 close(LOCK);
349 link($hitching_post, $opt{'gtmp-file'});
350 if ((stat($hitching_post))[3] != 2) {
351 close(OUT);
352 unlink($hitching_post);
353 &quit("group file locked -- giving up...");
354 }
355 unlink($hitching_post);
356# link($opt{'group-file'},$opt{'gtmp-file'}) || &quit("create gtmp: $!");
d38b93ab 357 $locked++;
358}
359
360sub unlock {
361 return unless $locked;
362 $locked--;
85dbc5e7 363 unlink($opt{'gtmp-file'}) || warn("unlock group file (remove gtmp): $!\n");
d38b93ab 364}
365
366sub display {
367 print(<<END) || &quit("write to stdout: $!\n");
368group $groupname
369gid $gid
370description $description
371owner $owner
372managers @managers
373members @members
374homedir $homedir
375END
376}
377
378sub p_out {
379 print(STDOUT "$stdout_string") || &quit("write to stdout: $!\n");
380 $stdout_string= '';
381}
382
383sub p {
384 $stdout_string.= $_[0]."\n";
385}