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