ipif: fix for make breakage
[userv-utils] / groupmanage / groupmanage
1 #!/usr/bin/perl
2 #
3 # Copyright (C) 1995-9, 2003 Ian Jackson <ijackson@chiark.greenend.org.uk>
4 # Copyright (C) 1999, 2003
5 # Chancellor Masters and Scholars of the University of Cambridge
6 #
7 # Improved by Ben Harris <bjh21@cam.ac.uk> in 1999 and 2003 for Unix
8 # Support's own nefarious purposes.
9 #
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.
14 #
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.
19 #
20 # $Id$
21
22 sub usage {
23 &unlock;
24 &p_out;
25 print(<<END) || die "groupmanage: write usage: $!\n";
26 groupmanage: $_[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]
40 groupmanage is Copyright. It is free software, released under the GNU
41 GPL v2 or later. There is NO WARRANTY. See the GPL for details.
42 END
43 exit(1);
44 }
45
46 @ARGV || &usage('too few arguments');
47
48 if ($>) {
49 exec 'userv','root','groupmanage',@ARGV;
50 &quit("unable to execute userv to gain root privilege: $!");
51 }
52
53 chdir("/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
63 $callinguser= exists $ENV{'USERV_UID'} ? $ENV{'USERV_UID'} : $<;
64
65 %opt= ('user-create','0',
66 'user-create-minunameu','5',
67 'user-create-min','10000',
68 'user-create-max','19999',
69 'user-create-nameintitle','0',
70 'user-create-maxperu','5',
71 'group-file','group',
72 'gtmp-file','gtmp',
73 'grouplist-file','grouplist',
74 'name-regexp','',
75 'name-maxlen','8',
76 'admin-group','',
77 'finish-command','');
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',
83 'user-create-maxperu','number',
84 'group-file','string',
85 'gtmp-file','string',
86 'grouplist-file','string',
87 'name-regexp','string',
88 'name-maxlen','number',
89 'admin-group','string',
90 'finish-command','string');
91
92 sub ov_boolean {
93 $cov= $_ eq 'yes' ? 1 :
94 $_ eq 'no' ? 0 :
95 &quit("groupmanage.conf:$.: bad boolean value");
96 }
97
98 sub ov_number {
99 m/^[0-9]{1,10}$/ || &quit("groupmanage.conf:$.: bad numerical value");
100 }
101
102 sub ov_string {
103
104 }
105
106 open(GMC,"groupmanage.conf") || &quit("read groupmanage.conf: $!");
107 while (<GMC>) {
108 next if m/^\#/ || !m/\S/;
109 s/\s*\n$//;
110 s/^\s*([-0-9a-z]+)\s*// || &quit("groupmanage.conf:$.: bad option format");
111 $co= $1;
112 defined($opt{$co}) || &quit("groupmanage.conf:$.: unknown option $co");
113 $cov= $_;
114 $ovf= 'ov_'.$ovalid{$co};
115 &$ovf;
116 $opt{$co}= $cov;
117 }
118 close(GMC);
119
120 if ($ARGV[0] eq '--info') {
121 @ARGV == 1 || &usage('no arguments allowed after --info');
122 &p_out;
123 &load;
124 &checkexists;
125 &display;
126 &p_out;
127 exit(0);
128 }
129
130 sub naming {
131 $callinguser || return;
132 &p_out;
133 if ($opt{'user-create-minunameu'}) {
134 print(STDERR <<END) || &quit("write err re name: $!");
135 groupmanage: groups you create must be named after you ...
136 <usernamepart>-<identifier>
137 You must quote at least $opt{'user-create-minunameu'} chars of your username $createby
138 (or all of it if it is shorter).
139 END
140 }
141 if ($opt{'name-regexp'}) {
142 print(STDERR <<END) || &quit("write err re name: $!");
143 groupmanage: groups you create must match a pattern...
144 The pattern is the Perl regular expression /$opt{'name-regexp'}/.
145 END
146 }
147 exit(1);
148 }
149
150 if ($ARGV[0] eq '--create') {
151 $opt{'user-create'} || !$callinguser ||
152 &quit("group creation by users disabled by administrator");
153 length($groupname) <= $opt{'name-maxlen'} ||
154 &quit("group names must be $opt{'name-maxlen'} chars or fewer");
155 $!=0; (@pw= getpwuid($callinguser))
156 || &quit("cannot get your passwd entry: $!");
157 $createby= $pw[0];
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 }
169 $create= 1;
170 shift(@ARGV);
171 }
172
173 &lock;
174 &load;
175
176 if ($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 ||
197 &quit("you may not manage $groupname");
198
199 $action= 'none';
200 while (@ARGV) {
201 $_= shift(@ARGV);
202 if (m/^--(add|remove)$/) {
203 $action= $1; $clist= 'members'; $what= 'member';
204 } elsif (m/^--owner$/) {
205 !$callinguser || &quit("only root may change owner");
206 @ARGV || &usage("no username owner after --owner");
207 $owner= shift(@ARGV);
208 &p("owner set to $owner");
209 } elsif (m/^--manager-(add|remove)$/) {
210 $action= $1; $clist= 'managers'; $what= 'manager';
211 } elsif (m/^--clear$/) {
212 &p('cleared list of members');
213 @members=(); $action='none'; $memc++;
214 } elsif (m/^--manager-clear$/) {
215 &p('cleared list of managers');
216 @managers=(); $action='none';
217 } elsif (m/^--title$/) {
218 &weare($owner) || !$callinguser ||
219 &quit("only group's owner ($owner) may change title");
220 @ARGV || &usage("no title after --title");
221 $_= shift(@ARGV); y/\020-\176//cd; y/:\\//d;
222 if ($opt{'user-create-nameintitle'} &&
223 $gid >= $opt{'user-create-min'} && $gid <= $opt{'user-create-max'}) {
224 $_= "${owner}'s -- $_";
225 }
226 $description= $_;
227 &p("title set to $description");
228 } elsif (m/^-/) {
229 &usage("unknown option $_");
230 } elsif (m/^\w[-0-9A-Za-z]*$/) {
231 y/\n//d;
232 $chgu=$_;
233 defined(getpwnam($chgu)) || &quit("username $chgu does not exist");
234 eval "\@l = \@$clist; 1" || &quit("internal error: $@");
235 $already= grep($_ eq $chgu, @l);
236 if ($action eq 'add') {
237 if ($already) {
238 &p("$chgu already $what");
239 } else {
240 &p("added $what $chgu");
241 push(@l,$chgu);
242 $memc+= ($clist eq 'members');
243 }
244 } elsif ($action eq 'remove') {
245 if ($already) {
246 &p("removed $what $chgu");
247 @l= grep($_ ne $chgu, @l);
248 $memc+= ($clist eq 'members');
249 } else {
250 &p("$chgu is already not $what");
251 }
252 } else {
253 &usage("username found but no action to take for them");
254 }
255 eval "\@$clist = \@l; 1" || &quit("internal error: $@");
256 } else {
257 &usage("bad username or option $_");
258 }
259 }
260 &p("nb: a change to group membership only takes effect at the user's next login")
261 if $memc;
262 $groupfile[$groupfileix]=
263 "$groupname:$password:$gid:".join(',',@members)."\n";
264 $grouplist[$grouplistix]=
265 "$groupname:$description:$owner:".join(',',@managers).":$homedir\n";
266 &save($opt{'group-file'},@groupfile);
267 &save($opt{'grouplist-file'},@grouplist);
268 if ($opt{'finish-command'}) {
269 !system($opt{'finish-command'}) || &quit("finish-command: $?");
270 }
271 unlink($opt{'gtmp-file'}) || &quit("unlock group (remove gtmp): $!");
272 &p_out;
273 exit(0);
274
275 sub load {
276 open(GF,"< $opt{'group-file'}") || &quit("read group: $!");
277 @groupfile=<GF>; close(GF);
278 $groupfileix=-1;
279 for ($i=0; $i<=$#groupfile; $i++) {
280 $_= $groupfile[$i]; s/\n$//;
281 next if m/^\#/;
282 m/^(\w[-0-9A-Za-z]*):([^:]*):(\d+):([-0-9A-Za-z,]*)$/ ||
283 &quit("bad entry in group: $_");
284 $gname2gid{$1}=$3;
285 next unless $1 eq $groupname;
286 $groupfileix<0 || &quit("duplicate entries in group");
287 $groupfileix= $i;
288 $password= $2;
289 $gid= $3;
290 @members= split(/,/,$4);
291 }
292 open(GL,"< $opt{'grouplist-file'}") || &quit("read grouplist: $!");
293 @grouplist=<GL>; close(GL);
294 $grouplistix=-1;
295 for ($i=0; $i<=$#grouplist; $i++) {
296 $_= $grouplist[$i]; s/\n$//;
297 next if m/^\#/;
298 m/^(\w[-0-9A-Za-z]*):([^:]*):(\w[-0-9A-Za-z]*):([-0-9A-Za-z,]*):([^:]*)$/ ||
299 &quit("bad entry in grouplist: $_");
300 $bythisowner++ if ($create && $3 eq $createby &&
301 $gname2gid{$1} >= $opt{'user-create-min'} &&
302 $gname2gid{$1} <= $opt{'user-create-max'});
303 next unless $1 eq $groupname;
304 $grouplistix<0 || &quit("duplicate entries in grouplist");
305 $grouplistix= $i;
306 $description= $2;
307 $owner= $3;
308 $homedir= $5;
309 @managers= split(/,/,$4);
310 }
311 }
312
313 sub checkexists {
314 $grouplistix>=0 || &quit("no entry in grouplist for $groupname");
315 $groupfileix>=0 || &quit("no entry in group for $groupname");
316 }
317
318 sub weare {
319 return 0 if $_[0] eq '';
320 @pw= getpwnam($_[0]);
321 return @pw && $pw[2] == $callinguser ? 1 : 0;
322 }
323
324 sub save {
325 $filename= shift(@_);
326 unlink("$filename~");
327 open(DUMP,"> $filename.new") || &quit("create new $filename: $!");
328 print(DUMP @_) || &quit("write new $filename: $!");
329 close(DUMP) || &quit("close new $filename: $!");
330 link("$filename","$filename~") || &quit("create backup $filename: $!");
331 rename("$filename.new","$filename") || &quit("install new $filename: $!");
332 }
333
334 sub quit {
335 &unlock;
336 &p_out;
337 die "groupmanage: @_\n";
338 }
339
340 sub lock {
341 link($opt{'group-file'},$opt{'gtmp-file'}) || &quit("create gtmp: $!");
342 $locked++;
343 }
344
345 sub unlock {
346 return unless $locked;
347 $locked--;
348 unlink($opt{'gtmp-file'}) || warn("unlock group file (remove gtmp): $!\n");
349 }
350
351 sub display {
352 print(<<END) || &quit("write to stdout: $!\n");
353 group $groupname
354 gid $gid
355 description $description
356 owner $owner
357 managers @managers
358 members @members
359 homedir $homedir
360 END
361 }
362
363 sub p_out {
364 print(STDOUT "$stdout_string") || &quit("write to stdout: $!\n");
365 $stdout_string= '';
366 }
367
368 sub p {
369 $stdout_string.= $_[0]."\n";
370 }