#!/usr/bin/perl -w #============================================================================== # lat-groups # ========== # 0.9.0 (2004-09-08) # (c)2003-2004 Altiplano bvba #============================================================================== package esmith; use strict; use esmith::db; use esmith::util; use Pod::Usage; use Getopt::Long; my %conf; tie %conf, 'esmith::config'; my %accounts; tie %accounts, 'esmith::config', '/home/e-smith/db/accounts'; my ($Hlp, $Cml, $Frc, $Inp, $Noa); my $Add =0; my $Del =0; my $Rem =0; #============================================================================== # Main #============================================================================== # Analyze commandline options GetOptions ("help" => \$Hlp, "add" => \$Add, "delete" => \$Del, "force" => \$Frc, "no-admin", => \$Noa, "remove-user" => \$Rem, "command-line=s" => \$Cml, "input-file=s" => \$Inp); if ( $Hlp ) { &PrintPod(9); exit; } # We need one argument or the other, but not both if ((($Cml && $Inp && $Rem) || (! $Cml && ! $Inp && ! $Rem)) || ($Add + $Del + $Rem != 1)) { &PrintPod(1); exit; } my @records; if ($Inp) { open(LIST,"< $Inp") || die "Can't find $Inp.\n"; @records = grep(!/(^\s*#)|(^\s*$)/,); close(LIST); } elsif ($Cml) { @records=($Cml); } else { &PrintPod(1); exit; } # Add groups if ($Add) { # Process each group foreach my $record (@records) { my @fields=split(/\|/,$record); for (my $cnt=0; $cnt <= $#fields; ++$cnt) { for ($fields[$cnt]) { s/^\s+//; s/\s+$//; }} my $groupname = $fields[0]; # We need a valid group name if ( ! &TestName($fields[0])) { exit; } # Create the group, if needed if ( ! db_get(\%accounts, $groupname)) { # Find a usable GID my $gid; if ($fields[2]) { $gid = $fields[2]; } else { $gid = &FindUid;} if (&TestUid($gid)) { my %group = ("Description", $fields[1], "Uid",$gid, "Gid",$gid, "Members",""); print "Creating group '$fields[0]' (GID:$gid).\n"; db_set(\%accounts, $groupname, 'group', \%group); system("/sbin/e-smith/signal-event", "group-create", $groupname) == 0 or die ("An error occurred while creating group $groupname.\n\a"); } } # Add the members to the group, if they are valid accounts if ( db_get_type(\%accounts, $groupname) eq "group" ) { for (my $cnt=3; $cnt < @fields; ++$cnt ) { # Check if this user exists on the server if (((db_get(\%accounts, $fields[$cnt])) && (db_get_type(\%accounts, $fields[$cnt])) eq "user") || ($fields[$cnt] eq 'admin')) { # Is she already a member of this group? my $members = db_get_prop(\%accounts, $groupname, 'Members'); if (! $members) { $members="" } if (! ($members =~ m/\b$fields[$cnt]\b/)) { # Append the new member to the list and update the group if (length($members) > 0) { $members=$members.",";} $members = $members."$fields[$cnt]"; print "Adding user '$fields[$cnt]' to group '$fields[0]'.\n"; db_set_prop(\%accounts, $groupname, 'Members', $members); system("/sbin/e-smith/signal-event", "group-modify", $groupname) == 0 or die ("An error occurred while updating group $groupname.\n\a"); } } else { print "User '$fields[$cnt]' doesn't exist on this server.\n\a"; } } } else { print "Error: '$groupname' is not a valid group name on this system.\a\n"; } # Remove 'admin' and 'www' from /etc/group if we don't want them. if ($Noa) { system("/usr/bin/gpasswd -d www $groupname > /dev/null"); system("/usr/bin/gpasswd -d admin $groupname > /dev/null"); } } } # Delete groups if ($Del) { &ExpandWildCard; # Check for wildcards and expand if necessary # Process each group foreach my $record (@records) { my @fields=split(/\|/,$record); for (my $cnt=0; $cnt <= $#fields; ++$cnt) { for ($fields[$cnt]) { s/^\s+//; s/\s+$//; }} my $groupname = $fields[0]; # Silly, but if 'www' and 'admin' aren't member of this group an error message will be shown :-/ system("/usr/bin/gpasswd -a www $groupname > /dev/null"); system("/usr/bin/gpasswd -a admin $groupname > /dev/null"); if ((db_get_type(\%accounts, $groupname)) && (db_get_type(\%accounts, $groupname)) eq 'group') { my $yn = 'yes'; if (! $Frc) { print "Delete group '$groupname' [yes/NO/all]? "; $yn = ; if ($yn =~ /^a/i) { $Frc = -1; $yn="yes"; } } if ($yn =~ /^y/i) { print "Deleting group '$groupname'.\n"; db_delete(\%accounts, $groupname); system("/sbin/e-smith/signal-event", "group-delete", $groupname); } } else { print "Group '$groupname' doesn't exist on this server.\n\a"; } } } # Remove users from group if ($Rem) { &ExpandWildCard; # Check for wildcards and expand if necessary # Process each group foreach my $record (@records) { my @fields=split(/\|/,$record); for (my $cnt=0; $cnt <= $#fields; ++$cnt) { for ($fields[$cnt]) { s/^\s+//; s/\s+$//; }} my $groupname = $fields[0]; if ( db_get(\%accounts, $groupname)) { # Remove the members from the group for (my $cnt=1; $cnt < @fields; ++$cnt ) { my $members = db_get_prop(\%accounts, $groupname, 'Members'); if (! $members) { $members="" } my $yn = 'yes'; if (! $Frc) { print "Remove user '$fields[$cnt]' from group '$groupname' [yes/NO/all]? "; $yn = ; if ($yn =~ /^a/i) { $Frc = -1; $yn="yes"; } } if ($yn =~ /^y/i) { print "Removing member '$fields[$cnt]' from group '$fields[0]'.\n"; $members =~ s/$fields[$cnt]//; $members =~ s/,,/,/ ; $members =~ s/(^,)|(,$)//; db_set_prop(\%accounts, $groupname, 'Members', $members); system("/sbin/e-smith/signal-event", "group-modify", $groupname) == 0 or die ("An error occurred while updating group $groupname.\n\a"); } } } else { print "The group '$groupname' does not exist on this server.\n\a"; } } } #============================================================================== # Subroutines #============================================================================== # Find an unused Uid/Gid sub FindUid { open(ACC,"/home/e-smith/db/accounts") || die "Can't find /home/e-smith/db/accounts"; my @recs = grep(/Uid\|\d*/,); close(ACC); open(PWD,"/etc/passwd") || die "Can't find /etc/passwd"; my @pwds = grep(/\:\d*\:/,); close(PWD); open(GRP,"/etc/group") || die "Can't find /etc/group"; my @grps = grep(/\:\d*\:/,); close(GRP); my $newuid=db_get(\%conf, 'MinUid'); do { ++$newuid;} until ((! grep(/Uid\|$newuid\D/,@recs)) && (! grep(/\:$newuid\:/,@pwds)) && (! grep(/\:$newuid\:/,@grps))); return $newuid; } #============================================================================== # Test the gid for availability and legality sub TestUid { open(ACC,"/home/e-smith/db/accounts") || die "Can't find /home/e-smith/db/accounts"; my @recs = grep(/Gid\|\d*/,); close(ACC); open(PWD,"/etc/passwd") || die "Can't find /etc/passwd"; my @pwds = grep(/\:\d*\:/,); close(PWD); open(GRP,"/etc/group") || die "Can't find /etc/group"; my @grps = grep(/\:\d*\:/,); close(GRP); if (! ($_[0] =~ /^\d*$/)) { print "Error: A group ID should contain only numbers.\a\n"; exit; } elsif ( $_[0] < db_get(\%conf, 'MinUid')) { print "Error: The group ID should be greater or equal to ".&FindUid.".\a\n"; exit; } elsif ((grep(/Gid\|$_[0]\D/,@recs)) or (grep(/\:$_[0]\:/,@pwds)) or (grep(/\:$_[0]\:/,@grps))) { print "Error: The gid '$_[0]' is already in use\a\n"; exit; } else { return 1 }; } #============================================================================== # Test name for illegal characters sub TestName { if ( $_[0] =~ /^[a-z][a-z\-\_\d]*$/ ) { return -1; } else { print "The group name '$_[0]' contains illegal characters.\n"; print "The name should contain only lower-case letters, numbers, hyphens and\n"; print "underscores, and should start with a lower-case letter.\n\a"; return 0; } } #============================================================================== # Test for wildcards in the groupname. If any wildecards are found, the array # @records is expanded with the groupnames that meet the conditions. sub ExpandWildCard { my $ctrec = 0; foreach my $record (@records) { my @fld=split(/\|/,$record); for (my $cnt=0; $cnt <= $#fld; ++$cnt) { for ($fld[$cnt]) { s/^\s+//; s/\s+$//; }} if ($fld[0] =~ /\*|\?/) { # Does it contain the wildcards? $fld[0] =~ s/\*/\.\*/g; # Replace * with .* to allow for grep. $fld[0] =~ s/\?/\./g; # Replace ? with . to allow for grep. open USRS, "; close(USRS); my $cu = 0; foreach my $tst (@match) { $tst =~ /\=/; $tst = $`; for (my $cnt=1; $cnt <= $#fld; ++$cnt) { $tst = $tst." | ".$fld[$cnt]; }; if ($cu == 0 ) { $records[$ctrec] = $tst; $cu =1; } else { push(@records, $tst); } } } ++$ctrec; } } #============================================================================== # Print the pod text as a help screen sub PrintPod { my ($verbose, $message) = @_; pod2usage(-verbose => $verbose, -message => $message, -exitval => 64); } #============================================================================== =pod =head1 NAME B - The lazy administrator's tool to create groups and to add members to it. =head1 DESCRIPTION Creates or deletes groups on Mitel's or SME Servers (5.x/6.x/7.x). This tool is functionally equivalent to the 'groups' option in the server-manager, but can be run from the command line or called from an other script. It allows you, for example, to add a large number of accounts to a group, or delete groups on a remote machine via an ssh console. See F for the format of the input file. =head1 SYNOPSIS B -a [-n] -c "group | descr. | gid | user1| user2" B -a [-n] -i /path/to/groups.list B -r [-f] -c "group | user1 | user2" B -d [-f] -c "group" B -d [-f] -i /path/to/groups.list =head1 OPTIONS The following options are supported: =over 4 =item B<-a>, B<--add> Add a group to the server, or add users to an existing group. =item B<-c "Arguments">, B<--command-line="Aruments"> Take arguments from the command line. See the 'Arguments' section below for the various arguments that are accepted. =item B<-d>, B<--delete> Delete a group from the server. Wildcards (* and ?) are accepted. =item B<-f>, B<--force> Don't prompt before deleting. =item B<-h>, B<--help> Extended help for this tool =item B<-i FILE>, B<--input-file=FILE> Use the information from F to create or delete the group(s). See F for an example of an input file. =item B<-n>, B<--no-admin> Do not add 'admin' and 'www' as users to this group. Remove them if they are already member of this group. Default behaviour on SME is to add both users to any group, thus limiting the number of groups on the server to 28. Note, however, that SME is very persistent with this 'feature'. Each time you add or remove a user via the server-manager, both 'admin' and 'www' are added again. =item B<-r>, B<--remove-users> Remove users from this group. Wildcards (* and ?) are accepted for the groupname. =back =head2 Arguments: group* : Must contain only lower-case letters, numbers, hyphens, periods and underscores, and should start with a lower-case letter. Wildcards (* and ?) can only be used to delete groups. descr. : Free-text description of the group gid : Group ID. If omitted, a suitable gid will be generated. member(s) : Username of a member of the group. Must be an existing account on the server. * mandatory field =head1 EXAMPLES B Creates group 'hogwarts' with users 'harry' and 'hermione'. B Creates the groups defined in /root/griffindor.list. Refer to /usr/doc/lazy-admin-tools/example.groups for an example of an input file. B Creates the groups defined in /root/griffindor.list. The users 'admin' and 'www' are *not* added to the group. B Deletes all groups whose name start with 'class'. All groups are deleted without prompting (-f). B Removes users 'admin' and 'www' from all groups on the system. Don't prompt before removing. B Creates group 'quidditch' with group ID 7005. =head1 SEE ALSO lat-users(8), lat-pseudonyms(8), lat-ibays(8), lat-quota(8), lat-domains(8), lat-hosts(8), lat-procmail(8), lat-pptp(8), lat-dump(8) =head1 VERSION Version 0.9.0 (2004-09-08). The latest version is hosted at B =head1 COPYRIGHT (c)2003-2004, Altiplano bvba (B). Released under the terms of the GNU license. =head1 BUGS Please report bugs to =cut #==============================================================================