#!/usr/bin/perl -w my $rcsid = '$Id: setgroup,v 1.3 2006/05/23 20:30:08 vogelke Exp $'; =head1 NAME setgroup - modify /etc/group =head1 SYNOPSIS setgroup [-adhmv] [-f groupfile] user group1 [group2 ...] =head1 OPTIONS =over 4 =item B<-a> Add user to these groups. =item B<-d> Remove user from these groups. =item B<-f> file Use "file" instead of /etc/group. =item B<-h> Print a brief help message and exit. =item B<-m> Print the manual page and exit. =item B<-v> Prints the version and exits. =back =head1 DESCRIPTION B will read the group file, parse it into an array of hashes, alter memberships for a given user, and write the results in /etc/group format. The members of each group are written in lexically sorted order; if you want to pretty up your group file, just add a user to a group which already holds that user. If neither -a nor -d is specified, the group membership is set exactly as given on the command line. Here's the array of hashes for a 3-line group file, the first line of which is a comment: @AoH = ( { 'str' => '# Group file' }, { 'gid' => '0', 'mem' => ['root', 'vogelke'], 'name' => 'wheel', 'pw' => '*' }, { 'gid' => '1', 'mem' => ['daemon'], 'name' => 'daemon', 'pw' => '*' } ); =head1 AUTHOR Karl Vogel Sumaria Systems, Inc. =cut use Getopt::Long; use Pod::Usage; use File::Basename; use strict; my $cnt; # number of colons in each input line my $gfh; # filehandle. my $gid; # group-id number my $list; # rejoined members of group my $name; # group name my $pw; # group password my $rec; # anonymous hash holding one group my @AoH; # array of hashes holding groupfile my @mem; # anonymous array holding members of a group my $user; # user specified on command line. my @groups; # groups specified on command line. # # Command line options. # my $myname = basename($0); $myname =~ s/\.\w*$//; # strip any extension my %options; $options{'f'} = '/etc/group'; $options{'a'} = 0; $options{'d'} = 0; my @getopt_args = ( 'a', # add to these groups 'd', # remove from these groups 'f=s', # groupfile to use 'h|?', # print usage 'm', # print manpage 'v', # print version ); Getopt::Long::config("noignorecase", "bundling"); usage() unless GetOptions(\%options, @getopt_args); manpage() if $options{'m'}; version() if $options{'v'}; usage() if $options{'h'} || !@ARGV; usage("Only use one of -a or -d\n") if $options{'a'} && $options{'d'}; $user = shift(@ARGV); usage("No groups specified\n") unless @ARGV; foreach (@ARGV) { push(@groups, $_); } # # Create the array of hashes. # open($gfh, "< $options{'f'}") or die "$options{'f'}: can't read: $!\n"; my %ghash; # map groupname to record number in @AoH. my %uhash; # map username to user's groups. my $k = 0; while (<$gfh>) { chomp; $rec = {}; # Group files can hold comments and blank lines. # A group does NOT hold a '#' mark. Format: # group: Name of the group. # passwd: Group's encrypted password, if any. # gid: The group's decimal ID. # member: Group members. $cnt = tr/:/:/; if ($cnt == 3 && !m/#/) { ($name, $pw, $gid, $list) = split(/:/); @mem = split(/,/, $list); $rec->{'name'} = $name; $rec->{'pw'} = $pw; $rec->{'gid'} = $gid; $rec->{'mem'} = [@mem]; $ghash{$name} = $k; # keep line number for lookup. foreach (@mem) { $uhash{$_} .= "$name "; } } else { $rec->{'str'} = $_; } push(@AoH, $rec); $k++; } close($gfh); # # Make changes: add, delete, or set user groups. # if ($options{'a'}) { foreach (@groups) { $k = $ghash{$_} || die "$_: no such group\n"; $rec = $AoH[$k]; $a = $rec->{'mem'}; # make sure user isn't already in group. push(@$a, $user) unless grep(/^${user}$/, @$a); } } elsif ($options{'d'}) { foreach (@groups) { $k = $ghash{$_} || die "$_: no such group\n"; $rec = $AoH[$k]; $a = $rec->{'mem'}; @$a = grep(!/^${user}$/, @$a); } } else { # $uhash{$user} holds list of groups containing $user. $_ = $uhash{$user}; s/ $//; my @work = split; # remove from all groups... foreach (@work) { $k = $ghash{$_}; $rec = $AoH[$k]; $a = $rec->{'mem'}; @$a = grep(!/^${user}$/, @$a); } # ...then add the ones on the command line. foreach (@groups) { $k = $ghash{$_} || die "$_: no such group\n"; $rec = $AoH[$k]; $a = $rec->{'mem'}; push(@$a, $user); } } # # Print new groupfile to stdout. # foreach $rec (@AoH) { if (defined($rec->{'name'})) { # The members are in an anonymous array. $a = $rec->{'mem'}; $list = join(",", sort (@$a)); $_ = join(":", $rec->{'name'}, $rec->{'pw'}, $rec->{'gid'}, $list); } else { $_ = $rec->{'str'}; } print "$_\n"; } exit(0); #--------------------------------------------------------------------- # Print a usage message or manpage from the comment header and exit. sub usage { my ($emsg) = @_; require Pod::Usage; import Pod::Usage qw(pod2usage); warn "$emsg\n" if defined $emsg; pod2usage(-verbose => 1); } sub manpage { require Pod::Usage; import Pod::Usage qw(pod2usage); pod2usage(-exitstatus => 0, -verbose => 2); } #--------------------------------------------------------------------- # Print the current version and exit. sub version { $_ = $rcsid; s/,v / /; @_ = split; print "$myname v$_[2] $_[3] $_[4]\n"; exit(0); } __END__