chiark / gitweb /
Some documentation.
[userv-utils.git] / 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         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 }