#!/usr/bin/perl -w #============================================================================== # lat-shadow # ========= # 0.0.1 (2012-11-10) # (copyleft)2012 Jean-Philippe Pialasse, inspired by Altiplano bvba #============================================================================== package esmith; use strict; #use Cwd; use esmith::db; use esmith::util; use Getopt::Long; use Pod::Usage; use POSIX; use constant DATETIME => strftime("%Y-%m-%d_%H-%M-%S", localtime); 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 $passwlist="./passwords.new"; my $shadowfile="./shadow_tmp"; # copy of shadow file to read my $newshadow="/etc/shadow"; # shadow file in place my $original_cp="./shadow_before_" . DATETIME; # copy in case #============================================================================== # Main #============================================================================== # Analyze commandline options GetOptions ("help" => \$Hlp, "add" => \$Add, "force" => \$Frc, "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)) ) { &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) { getcwd; die("change current directory, can not work in /etc/") unless getcwd ne "/etc"; # make a copy of shadow file in current directory system("cp -f $newshadow $original_cp"); system("cp -f $newshadow $shadowfile"); #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) )# if username is with legal characters { if ( db_get_type(\%accounts, $username) eq "user" ) { my $PasswordSet=db_get_prop(\%accounts, $username,'PasswordSet'); my $password = $fields[1]; # checkif present in shadow and password is empty (!!) ( if not forced) print $username ." : " ;#." ". $password . " " .$PasswordSet . "\n"; open(SHADOWFILE,"$shadowfile") || die; open(SHADOWNEW,">$newshadow") || die; flock(SHADOWFILE,1)||die; flock(SHADOWNEW,2)||die; while() { if(m/^$username:!!:/ ) { # one of our users no password set #print $_ ; s/^$username:(!!):(.*):(.*):(.*):(.*):(.*):(.*):$/$username:$password:$2:$3:$4:$5:$6:$7:/i; #print $_ ; (print SHADOWNEW $_) or die "can not write $newshadow: $!"; $PasswordSet="no"; print "password set.\n"; } elsif (m/^$username:/ ) { # one of our users if there is already a pass set #print $_ ; if ($Frc) { # if we force s/^$username:(.*):(.*):(.*):(.*):(.*):(.*):(.*):$/$username:$password:$2:$3:$4:$5:$6:$7:/i; #print $_ ; (print SHADOWNEW $_) or die "can not write $newshadow: $!"; $PasswordSet="yes"; print "password set.\n"; } else { # if force not set print "$username has already a password, you need to force.\n"; (print SHADOWNEW $_) or die "can not write $newshadow: $!"; } } else # not a user we want to change { (print SHADOWNEW $_) or die "can not write $newshadow: $!"; } } close(SHADOWFILE)or die "can not close $shadowfile"; close(SHADOWNEW) or die "can not close $newshadow"; system("cp -f $newshadow $shadowfile"); # Set password #esmith::util::setUserPassword($username, $fields[3]); db_set_prop(\%accounts, $username, 'PasswordSet', $PasswordSet); # if success set db accounts to say password is set } else { print "'$username' is not present in db or is not set as user.\a\n"; } } else { print "'$username' contain illegal characters.\a\n"; } } else { print "Please provide at least an account username and encrypted password to transfert.\n\a";} } system("rm -f $shadowfile "); } #============================================================================== # 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; } #============================================================================== # 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 transfer crypted password from a box to another =head1 DESCRIPTION Transfer an user encrypted password fom one SME box shadow file to another box shadow file on SME servers (7.x/8.x). This tool is functionally equivalent some python script that allow this kind of manipulation. This could be used when the adminsitrator do not know the plain apssword of its users and want to migrate a SME box. It can be run from the command line or called from an other script. It allows you, for example, to tranfert all your users password after creating them with lat-users and dump files obtains from the older box. See F for the format of the input file. =head1 SYNOPSIS B -a -c "username | encryptedPassword" B -a [-f] -i /path/to/password.list =head1 OPTIONS The following options are supported: =over 4 =item B<-a>, B<--add> Add the encrypted passwords for 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<-f>, B<--force> Force update if password is not empty in the shadow file on the SME box =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. =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. password* - Encrypted Password for the user from the /etc/shadow file * mandatory field =head1 EXAMPLES B Update empty password field in /etc/shadow for user 'harry' from the command line, with password entered. B Uses the arguments specified in F to update user shadow encrypted password. Please refer to F for an example of an input file. =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 #==============================================================================