#!/usr/bin/perl -w #============================================================================== # lat-users # ========= # 0.9.0 (2004-09-08) # (c)2003-2004 Altiplano bvba #============================================================================== package esmith; use strict; use esmith::db; use esmith::util; use Getopt::Long; use Pod::Usage; my %conf; tie %conf, 'esmith::config'; my %accounts; tie %accounts, 'esmith::config', '/home/e-smith/db/accounts'; my ($Hlp, $Cml, $Frc, $Inp, $Pwf); my $Add =0; my $Del =0; my $Kaa =0; my $passwlist="./passwords.new"; #============================================================================== # Main #============================================================================== # Analyze commandline options GetOptions ("help" => \$Hlp, "add" => \$Add, "delete" => \$Del, "force" => \$Frc, "nickname" => \$Kaa, "passwords" => \$Pwf, "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) || (! $Cml && ! $Inp)) || ($Add + $Del != 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 accounts if ($Add) { # Process each user foreach my $record (@records) { my @fields=split(/\|/,$record); for (my $cnt=0; $cnt <= $#fields; ++$cnt) { for ($fields[$cnt]) { s/^\s+//; s/\s+$//; }} my $username = $fields[0]; if ( @fields >= 1) { if ( &TestName($username)) { my $uid; if ($fields[11]) { $uid = $fields[11]; } else { $uid = &FindUid;} if (&TestUid($uid)) { my %user = ("FirstName", $fields[1], "LastName", $fields[2], "PasswordSet", "no", "Uid",$uid, "Dept", $fields[4], "Company", $fields[5], "Shell", "/usr/bin/rssh", "VPNClientAccess","no", "Street", $fields[6], "City", $fields[7], "Phone", $fields[8], "EmailForward", $fields[9], "ForwardAddress", $fields[10]); if ( ! $user{'FirstName'} ) { $user{"FirstName"} = $username; } if ( ! $user{'LastName' } ) { $user{"LastName"} = $username; } if ( ! $user{'EmailForward'} ) { $user{"EmailForward"} = "local"; } if ( ! $user{'EmailForward'} =~/^(local)$|^(forward)$|^(both)$/) { $user{"EmailForward"} = "local"; } # Create or update account if ( ! db_get(\%accounts, $username)) { print "Creating user account for $username (Uid:$uid).\n"; db_set(\%accounts, $username, 'user', \%user); system("/sbin/e-smith/signal-event", "user-create", $username) == 0 or die ("An error occurred while creating account '$username'.\n"); } else { print "Updating user account $username.\n"; foreach my $value (keys %user) { if ( ! $user{$value} ) { $user{$value} = "" } if ($user{$value} eq "" ) { $user{$value} = db_get_prop(\%accounts, $username, $value) } $user{"PasswordSet"} = db_get_prop(\%accounts, $username, "PasswordSet"); $user{"Uid"} = db_get_prop(\%accounts, $username, "Uid"); } db_set(\%accounts, $username, 'user', \%user); system("/sbin/e-smith/signal-event", "user-modify", $username) == 0 or die ("An error occurred while modifying account '$username'.\n"); } # Set password if ((! $fields[3]) && ($Pwf)) { $fields[3] = &RandomPassword($username);} if ($fields[3]) { esmith::util::setUserPassword($username, $fields[3]); db_set_prop(\%accounts, $username, 'PasswordSet', 'yes'); } # Should we assign the user to a group? if ( @fields > 11) { for (my $cntgrps=12; $cntgrps < @fields; $cntgrps++) { system("/usr/sbin/lat-groups -a -c='$fields[$cntgrps]|$fields[$cntgrps]||$username'"); } } if ($Kaa) { print "creating SME default pseudonyms\a\n"; $user{"FirstName"} = lc($user{"FirstName"}) ; $user{"LastName"} = lc($user{"LastName"}); system("/usr/sbin/lat-pseudonyms -a -c='$username|".$user{"FirstName"}.".".$user{"LastName"}."|".$user{"FirstName"}."_".$user{"LastName"}."'"); } } } else { print "User '$username' is not a correct username.\a\n"; } } else { print "Please provide at least an account name and the first and last name of the user.\n\a";} } } # Delete accounts if ($Del) { &ExpandWildCard; # Check for wildcards and expand if necessary foreach my $record (@records) { my @fields=split(/\|/,$record); for (my $cnt=0; $cnt <= $#fields; ++$cnt) { for ($fields[$cnt]) { s/^\s+//; s/\s+$//; }} my $username = $fields[0]; if ((db_get(\%accounts, $username)) && (db_get_type(\%accounts, $username) eq "user")) { my $yn = 'yes'; my $userdir = `cat /etc/passwd|grep "^$username:"|cut -d":" -f6|tr -d '\n'`; #print "user folder: '$userdir'\n"; if ( $userdir eq "/home/e-smith/files/users/$username" ) { if (! $Frc) { print "Do you want to delete user '$username'?\n"; print "All files belonging to this user account will be deleted! [yes/NO/all] "; $yn = ; if ($yn =~ /^a/i) { $Frc = -1; $yn="yes"; } } if ($yn =~ /^y/i) { print "Deleting user account '$username'.\n"; db_delete(\%accounts, $username); system("/sbin/e-smith/signal-event", "user-delete", $username); } } else { print "'$username' is not a regular SME user with its home in /home/e-smith/files/users/, can not remove it\n\a";} } else { print "Can't find user '$username'.\n\a";} } } #============================================================================== # Subroutines #============================================================================== # Find a 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 uid/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(/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); if (! ($_[0] =~ /^\d*$/)) { print "Error: A user ID should contain only numbers.\a\n"; return 0; } elsif ( $_[0] < db_get(\%conf, 'MinUid')) { print "Error: The user ID should be greater or equal to ".&FindUid."\a\n"; return 0; } elsif ((grep(/Uid\|$_[0]\D/,@recs)) or (grep(/\:$_[0]\:/,@pwds)) or (grep(/\:$_[0]\:/,@grps))) { print "Error: The uid/gid '$_[0]' is already in use\a\n"; return 0; } else { return 1 }; } #============================================================================== # Test name for illegal characters and length sub TestName { if ( ! $_[0] =~ /^[a-z][a-z\-\d]*$/ ) { print "The name '$_[0]' contains illegal characters.\n"; print "User names should contain only lower-case letters, "; print "numbers, hyphens or periods\n"; print "and should start with a lower-case letter.\n\a"; return 0; } if ( length($_[0]) > 31 ) { print "The name '$_[0]' is too long. The maximum is 31 characters.\n"; return 0; } return -1; } #============================================================================== # Create a random password sub RandomPassword { my $password; my $_rand; my $password_length = 8; my @chars = split(" ", "a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 , . ; - & ! ?"); srand; $_rand = int(rand 62); for (my $i=0; $i <= $password_length-1 ;$i++) { $password .= $chars[$_rand]; $_rand = int(rand 69); } open(PWD, ">> $passwlist") or die "Can't create or open $passwlist.\n"; print PWD "Username: $_[0]\nPassword: $password\n\n"; close(PWD); return $password; } #============================================================================== # Test for wildcards in the username. If any wildecards are found, the array # @records is expanded with the user names 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 add user accounts =head1 DESCRIPTION Creates or deletes user accounts on Mitel's SME servers (5.x/6.x). This tool is functionally equivalent to the 'User accounts' 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 create a large number of accounts in a batch process, or delete accounts on a remote machine via an ssh console. See F for the format of the input file. =head1 SYNOPSIS B -a [-p] -c "user | first | last | password | department | company | street | city | tel | forward | email | uid | group1 [| group..]" B -a [-p] -i /path/to/users.list B -d [-f] -c "user" B -d [-f] -i /path/to/users.list =head1 OPTIONS The following options are supported: =over 4 =item B<-a>, B<--add> Add a user account to the server. =item B<-c "Arguments">, B<--command-line="Arguments"> 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 user account 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 FILE to create or delete the user accounts. See F for an example of an input file. =item B<-p>, B<--passwords> Generate random passwords for the users and write them to F<./passwords.new>. If a password was supplied, this option will be ignored. =item B<-n>, B<--nickname> Generate standard pseudonyms for the user: firstname_lastname and firstname.lastname. =back =head2 Arguments: user* - 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 users. first* - First name last* - Last name password - Password for the user (in clear-text!) department - Department company - Company street - Street name and number city - Zip & City tel - Telephone number forward - E-mail delivery: 'local', 'forward' or 'both' email - Forwarding e-mail adres uid - User ID. If omitted, a suitable uid will be generated. group(s) - Group name(s) to which the user should be added. If the group doesn't exist, it will be created. * mandatory field =head1 EXAMPLES B Creates user 'harry' from the command line, with password 'Quidditch'. B Uses the arguments specified in F to create user accounts. Please refer to F for an example of an input file. B Deletes all user accounts that start with 'user'. All users and their files will be deleted without prompting (-f). B Creates user accounts as defined in F and generates a random password for each user. The names and passwords are written to F<./passwords.new>. B Creates user 'ron' with user ID 6005. All other fields (company, departments, etc.) are left empty. B Creates user 'ron' and assigns him to groups quiddich and dada. If any of these groups doesn't exist, it will be created. B Creates user 'ron' and assigns him to groups quiddich and dada. If any of these groups doesn't exist, it will be created. Also create pseudonyms ron_weasley and ron.weasley associated with user ron if available, else produces an alert. =head1 SEE ALSO lat-group(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 #==============================================================================