1package Unix::GroupFile;
2
3# $Id: GroupFile.pm,v 1.6 2000/05/02 15:59:34 ssnodgra Exp $
4
5use strict;
6use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
7use Unix::ConfigFile;
8
9require Exporter;
10
11@ISA = qw(Unix::ConfigFile Exporter);
12# Items to export into callers namespace by default. Note: do not export
13# names by default without a very good reason. Use EXPORT_OK instead.
14# Do not simply export all your public functions/methods/constants.
15@EXPORT = qw(
16
17);
18$VERSION = '0.06';
19
20# Package variables
21my $MAXLINELEN = 511;
22
23# Implementation Notes
24#
25# This module adds 3 new fields to the basic ConfigFile object.  The fields
26# are 'gid', 'gpass', and 'group'.  All three of these fields are hashes.
27# The gid field maps names to GIDs.  The gpass field maps names to passwords.
28# The group fields maps GIDs to another hash of group members.  There are
29# no real values in the group subhash, just a '1' as a placeholder.  This is
30# a hash instead of a list because it makes duplicate elimination and user
31# deletion much easier to deal with.
32
33# Preloaded methods go here.
34
35# Read in the data structures from the supplied file
36sub read {
37    my ($this, $fh) = @_;
38
39    while (<$fh>) {
40	chop;
41	my ($name, $password, $gid, $users) = split /:/;
42	my @users = split /,/, $users;
43	if (defined $this->{group}{$gid}) {
44	    foreach (@users) {
45		$this->{group}{$gid}{$_} = 1;
46	    }
47	}
48	else {
49	    $this->group($name, $password, $gid, @users);
50	}
51    }
52    return 1;
53}
54
55
56# Add, modify, or get a group
57sub group {
58    my $this = shift;
59    my $name = shift;
60
61    # If no more parameters, we return group info
62    unless (@_) {
63	my $gid = $this->gid($name);
64	return undef unless defined $gid;
65	return ($this->passwd($name), $gid, $this->members($name));
66    }
67
68    # Create or modify a group
69    return undef if @_ < 2;
70    my $password = shift;
71    my $gid = shift;
72
73    # Have to be careful with this test - 0 is a legitimate return value
74    return undef unless defined $this->gid($name, $gid);
75    $this->passwd($name, $password);
76    $this->members($name, @_);
77    return ($gid, $password, $this->members($name));
78}
79
80
81# Delete a group
82sub delete {
83    my ($this, $name) = @_;
84
85    my $gid = $this->gid($name);
86    return 0 unless defined $gid;
87    delete $this->{gpass}{$name};
88    delete $this->{group}{$gid};
89    delete $this->{gid}{$name};
90    return 1;
91}
92
93
94# Add users to an existing group
95sub add_user {
96    my $this = shift;
97    my $name = shift;
98    my @groups = ($name eq "*") ? $this->groups : ($name);
99
100    foreach (@groups) {
101	my $gid = $this->gid($_);
102	return 0 unless defined $gid;
103	foreach my $user (@_) {
104	    $this->{group}{$gid}{$user} = 1;
105	}
106    }
107    return 1;
108}
109
110
111# Remove users from an existing group
112sub remove_user {
113    my $this = shift;
114    my $name = shift;
115    my @groups = ($name eq "*") ? $this->groups : ($name);
116
117    foreach (@groups) {
118	my $gid = $this->gid($_);
119	return 0 unless defined $gid;
120	foreach my $user (@_) {
121	    delete $this->{group}{$gid}{$user};
122	}
123    }
124    return 1;
125}
126
127
128# Rename a user
129sub rename_user {
130    my ($this, $oldname, $newname) = @_;
131
132    my $count = 0;
133    foreach ($this->groups) {
134	my $gid = $this->gid($_);
135	if (exists $this->{group}{$gid}{$oldname}) {
136	    delete $this->{group}{$gid}{$oldname};
137	    $this->{group}{$gid}{$newname} = 1;
138	    $count++;
139	}
140    }
141    return $count;
142}
143
144
145# Return the list of groups
146# Accepts a sorting order parameter: gid or name (default gid)
147sub groups {
148    my $this = shift;
149    my $order = @_ ? shift : "gid";
150
151    return keys %{$this->{gid}} unless wantarray;
152    if ($order eq "name") {
153	return sort keys %{$this->{gid}};
154    }
155    else {
156	return sort { $this->gid($a) <=> $this->gid($b) } keys %{$this->{gid}};
157    }
158}
159
160
161# Returns the maximum GID in use in the file
162sub maxgid {
163    my $this = shift;
164    my @gids = sort { $a <=> $b } keys %{$this->{group}};
165    return pop @gids;
166}
167
168
169# Output the file to disk
170sub write {
171    my ($this, $fh) = @_;
172
173    foreach my $name ($this->groups) {
174	my @users = $this->members($name);
175	my $head = join(":", $name, $this->passwd($name), $this->gid($name), "");
176	my $ind = join(":", "$name%n", $this->passwd($name), $this->gid($name), "");
177	print $fh $this->joinwrap($MAXLINELEN, $head, $ind, ",", "", @users),
178		"\n" or return 0;
179    }
180    return 1;
181}
182
183
184# Accessors (these all accept a group name and an optional value)
185sub passwd {
186    my $this = shift;
187    my $name = shift;
188    @_ ? $this->{gpass}{$name} = shift : $this->{gpass}{$name};
189}
190
191
192# Note that it is illegal to change a group's GID to one used by another group
193# This method also has to take into account side effects produced by doing
194# this, such as the fact that the member hash is keyed against the GID.
195sub gid {
196    my $this = shift;
197    my $name = shift;
198
199    return $this->{gid}{$name} unless @_;
200    my $newgid = shift;
201    my $oldgid = $this->{gid}{$name};
202    # Return OK if you try to set the same GID a group already has
203    return $oldgid if defined $oldgid && $newgid == $oldgid;
204    return undef if grep { $newgid == $_ } values %{$this->{gid}};
205    if (defined $oldgid) {
206	$this->{group}{$newgid} = $this->{group}{$oldgid};
207	delete $this->{group}{$oldgid};
208    }
209    $this->{gid}{$name} = $newgid;
210}
211
212
213# Return or set the list of users in a group
214sub members {
215    my $this = shift;
216    my $name = shift;
217
218    my $gid = $this->gid($name);
219    return undef unless defined $gid;
220    if (@_) {
221	$this->{group}{$gid} = { };
222	$this->add_user($name, @_);
223    }
224    return keys %{$this->{group}{$gid}} unless wantarray;
225    return sort keys %{$this->{group}{$gid}};
226}
227
228# Autoload methods go after =cut, and are processed by the autosplit program.
229
2301;
231__END__
232# Below is the stub of documentation for your module. You better edit it!
233
234=head1 NAME
235
236Unix::GroupFile - Perl interface to /etc/group format files
237
238=head1 SYNOPSIS
239
240  use Unix::GroupFile;
241
242  $grp = new Unix::GroupFile "/etc/group";
243  $grp->group("bozos", "*", $grp->maxgid + 1, @members);
244  $grp->remove_user("coolgrp", "bgates", "badguy");
245  $grp->add_user("coolgrp", "joecool", "goodguy");
246  $grp->remove_user("*", "deadguy");
247  $grp->passwd("bozos", $grp->encpass("newpass"));
248  $grp->commit();
249  undef $grp;
250
251=head1 DESCRIPTION
252
253The Unix::GroupFile module provides an abstract interface to /etc/group format
254files.  It automatically handles file locking, getting colons and commas in
255the right places, and all the other niggling details.
256
257This module also handles the annoying problem (at least on some systems) of
258trying to create a group line longer than 512 characters.  Typically this is
259done by creating multiple lines of groups with the same GID.  When a new
260GroupFile object is created, all members of groups with the same GID are
261merged into a single group with a name corresponding to the first name found
262in the file for that GID.  When the file is committed, long groups are written
263out as multiple lines of no more than 512 characters, with numbers appended to
264the group name for the extra lines.
265
266=head1 METHODS
267
268=head2 add_user( GROUP, @USERS )
269
270This method will add the list of users to an existing group.  Users that are
271already members of the group are silently ignored.  The special group name *
272will add the users to every group.  Returns 1 on success or 0 on failure.
273
274=head2 commit( [BACKUPEXT] )
275
276See the Unix::ConfigFile documentation for a description of this method.
277
278=head2 delete( GROUP )
279
280This method will delete the named group.  It has no effect if the supplied
281group does not exist.
282
283=head2 encpass( PASSWORD )
284
285See the Unix::ConfigFile documentation for a description of this method.
286
287=head2 gid( GROUP [,GID] )
288
289Read or modify a group's GID.  Returns the GID in either case.  Note that it
290is illegal to change a group's GID to a GID that is already in use by another
291group.  In this case, the method returns undef.
292
293=head2 group( GROUP [,PASSWD, GID, @USERS] )
294
295This method can add, modify, or return information about a group.  Supplied
296with a single group parameter, it will return a list consisting of (PASSWORD,
297GID, @MEMBERS), or undef if no such group exists.  If you supply at least
298three parameters, the named group will be created or modified if it already
299exists.  The list is also returned to you in this case.  Note that it is
300illegal to specify a GID that is already in use by another group.  In this
301case, the method returns undef.
302
303=head2 groups( [SORTBY] )
304
305This method returns a list of all existing groups.  By default the list will
306be sorted in order of the GIDs of the groups.  You may also supply "name" as a
307parameter to the method to get the list sorted by group name.  In scalar
308context, this method returns the total number of groups.
309
310=head2 maxgid( )
311
312This method returns the maximum GID in use by all groups.
313
314=head2 members( GROUP [,@USERS] )
315
316Read or modify the list of members associated with a group.  If you specify
317any users when you call the method, all existing members of the group are
318removed and your list becomes the new set of members.  In scalar context,
319this method returns the total number of members in the group.
320
321=head2 new( FILENAME [,OPTIONS] )
322
323See the Unix::ConfigFile documentation for a description of this method.
324
325=head2 passwd( GROUP [,PASSWD] )
326
327Read or modify a group's password.  Returns the encrypted password in either
328case.  If you have a plaintext password, use the encpass method to encrypt it
329before passing it to this method.
330
331=head2 remove_user( GROUP, @USERS )
332
333This method will remove the list of users from an existing group.  Users that
334are not members of the group are silently ignored.  The special group name *
335will remove the users from every group.  Returns 1 on success or 0 on failure.
336
337=head2 rename_user( OLDNAME, NEWNAME )
338
339This method will change one username to another in every group.  Returns the
340number of groups affected.
341
342=head1 AUTHOR
343
344Steve Snodgrass, ssnodgra@fore.com
345
346=head1 SEE ALSO
347
348Unix::AliasFile, Unix::AutomountFile, Unix::ConfigFile, Unix::PasswdFile
349
350=cut
351