#!/usr/bin/perl # expect_launcher.pl # Meryll Larkin # February 1, 2019 # April 23, 2020 - corrected logic error with file deletion at end of script run # Corrected sudo vs su for pwd # August 8, 2020 - added ability to receive commands from a file, one command per line. # # November 2, 2021 - added support for moving and restoring .ssh/authorized_keys file # A perl expect script to launch commands or scripts on remote servers # with or without sudo # use strict; # for name of outfile, (for date) use POSIX qw(strftime); use Net::SSH::Expect; # for hidden password use Term::ReadKey; # for "move" for .ssh/authorized_keys use File::Copy; # flush buffer to output so all is not lost if # you need to cancel script before it finishes $| = 1; my $pid = $$; my $today = strftime "%Y%m%d",localtime; # my $timeout1 = '10'; my $timeout1 = '5'; # my $timeout2 = '5'; my $timeout2 = '3'; my @commands; my $delete_infile = 0; print "\n Welcome to Expect Launcher!\n"; print qq| Use this program to login to remote computers and launch remote scripts or commands, either as an unprivileged user or as root.|; print "\n"; print qq| You will be asked to provide login credentials on the command line, a file with a list of remote computers you wish to administer, which will also need to contain your sudo password, if required. If a file contains passwords, you will be asked if you want to delete the file automatically at the completion of the tasks. |; chomp(my $ssh_keyfile = $ENV{"HOME"}); # if ssh_keyfile ends in a slash, remove final slash $ssh_keyfile =~ s/\/$//; $ssh_keyfile .= '/.ssh/authorized_keys'; my $restore_authorized_keys = 0; my $ssh_keyfile_save = ${ssh_keyfile} . "_save_" . $today . '_' . $pid; # If an authorized_keys file exists in the .ssh folder ask user if he/she # wants to move it. if ( -e $ssh_keyfile ) { print "\n Move ssh authorized_keys? Expect requires use of passwords. (Y/n) "; chomp (my $response = ); if ($response !~ /n/i ) { $restore_authorized_keys = 1; move($ssh_keyfile, $ssh_keyfile_save); if ( -e $ssh_keyfile_save ) { print " $ssh_keyfile moved to $ssh_keyfile_save\n"; print " It will be restored at end of script\n\n"; } else { print " ERROR while trying to move $ssh_keyfile\n"; } } } my @junk; my $user; my $pwd; ($user, $pwd) = &get_login("ssh",$user,$pwd); my $sudo_boo = 0; my $root_boo = 0; my $root_pwd_boo = 0; my $sudo_pwd_boo = 0; my $sudo_root= ""; my $sudo_pwd; my $root_pwd; print " What are the commands that you wish to run on remote host?\n"; print " Type full first command, include the path if it is a script, then press :\n"; print " "; while (1) { chomp (my $cmd = ); last if ($cmd =~ /^\s*q\s*$/i); # is it a cli command or a file? if ( -e $cmd ) { # print "$cmd is a file. Gathering commands\n"; print "\n $cmd is a file. Your Choices Are:\n\n"; print " A) Assume each line is a command and the whole file is an array of commands.\n"; print " B) Run this file as a script.\n\n"; print " Your choice [A|B]? "; chomp (my $response = ); if (( $response !~ /^\s*A\s*$/i ) && ( $response !~ /^\s*B\s*$/i)) { print " Response not understood. Quitting.\n"; exit; } if ( $response =~ /^\s*A\s*$/i ) { open my $handle, '<', $cmd; chomp(@commands = <$handle>); close $handle; } elsif ( $response =~ /^\s*B\s*$/i ) { push (@commands,$cmd); print " Next command or \"Q\" for quit:\n"; print " "; } } else { push (@commands,$cmd); print " Next command or \"Q\" for quit:\n"; print " "; } } print " Does your task require root privileges? [y|N] "; chomp (my $response = ); if ($response =~ /y/i) { print " root privileges are required\n"; $root_boo = 1; print " Does your login have sudo privileges? [y|N] "; chomp (my $response = ); if ($response =~ /y/i) { $sudo_boo = 1; print " Is there a unique sudo pw on each host? [y|N] "; chomp (my $response = ); if ($response !~ /y/i) { my $user1; ($user1, $sudo_pwd) = &get_login("sudo",$user,$pwd); $sudo_pwd = $pwd if ($sudo_pwd !~ /\w/); $user = $user1; } else { $sudo_pwd_boo = 1; &display_file_example($sudo_pwd_boo); } } else { # root required, no sudo privileges print " Is there a unique root pw on each host? [y|N] "; chomp (my $response = ); if ($response !~ /y/i) { ($user, $root_pwd) = &get_login("root",$user,$pwd); } else { $root_pwd_boo = 1; &display_file_example($sudo_pwd_boo); } } } else { print " root privileges are not required\n"; } my $infile = &get_filename("input - host list", $sudo_pwd_boo); chomp $infile; my $outfile = "expect_${today}_${pid}" . '.log'; open (INPUT, $infile) || die "Unable to open $infile " . $! . "\n"; if (($sudo_pwd_boo) || ($root_pwd_boo)) { print " This file contains passwords. Automatically delete at end of run? [Y|n] "; chomp (my $delete_me = ); $delete_infile = 1 if ($delete_me !~ /n/i); } open (OUTPUT, ">>$outfile") || die "Unable to open $outfile " . $! . "\n"; my $sudo_command = ' sudo su - '; my $root_command = ' su - '; my $who = ' whoami '; while () { chomp (my $line = $_); # ignore lines that begin with comment character next if ($line =~ /^\s*#/); # ignore empty lines next if ($line !~ /\w/); my $hozt; if (($sudo_pwd_boo) || ($root_pwd_boo)) { ($hozt, $sudo_pwd, @junk) = split('\s+',$line); $root_pwd = $sudo_pwd; } else { ($hozt, @junk) = split('\s+',$line); } print "\n************************************"; print "\n************************************\n\n"; print " Working on $hozt\n"; print "\n-------------------------------\n\n"; print OUTPUT "\n\n*************\n$hozt\n-----------\n"; my $login_output; my $ssh = Net::SSH::Expect->new ( host => $hozt, password => $pwd, user => $user, raw_pty => 1, timeout => $timeout1 ); eval { $login_output = $ssh->login($timeout2); } or do { print OUTPUT "login failed\n\n"; print " login failed on $hozt\n\n"; next; }; $ssh->exec("stty raw -echo"); if ($sudo_boo) { $ssh->send(" $sudo_command "); chomp (my $response = $ssh->read_all()); print $response; $ssh->send("$sudo_pwd"); chomp ($response = $ssh->read_all()); print $response; } if (($root_boo) && ( ! $sudo_boo)) { $ssh->send(" $root_command "); chomp (my $response = $ssh->read_all()); print $response; $ssh->send("$root_pwd"); chomp ($response = $ssh->read_all()); print $response; } foreach my $command (@commands) { # ignore empty lines next if ($command !~ /\w/); # ignore comment lines next if ($command =~ /^\s*#/); $command =~ s/^\,//; $command =~ s/\,$//; # print "command = $command\n"; print OUTPUT "$command\n"; $ssh->send(" $command"); print "Result of command \'$command\'\n"; #M# chomp ($response = $ssh->read_all()); my $line; # returns the next line, removing it from the input stream: while ( defined ($line = $ssh->read_line()) ) { print $line . "\n"; print OUTPUT $line . "\n"; } #M# print $response; #M# print OUTPUT $response; } $ssh->close(); } close INPUT; if ($delete_infile) { unlink $infile; print " Input $infile file deleted\n"; } close OUTPUT; if ($restore_authorized_keys) { move($ssh_keyfile_save, $ssh_keyfile); if ( -e $ssh_keyfile ) { print " $ssh_keyfile file restored.\n\n"; } else { print " ERROR while trying to restore $ssh_keyfile\n"; } } print "\n Also see results in $outfile\n\n"; exit (0); # ********************************* sub get_filename { my ($filetype, $sudo_pwd_boo) = @_; if ($sudo_pwd_boo) { print " What is the (path and) filename for the $filetype file with sudo key value pairs? "; } else { print " What is the (path and) filename for the $filetype file? "; } my $filename = ; return $filename; } # ********************************* sub get_login { my ($type,$user,$pwd1) = @_; if ($user eq "NULL") { print " Username for $type: "; chomp ($user = ); } if ($type eq "sudo" ) { print " Hidden $type password: (or press enter for same as login pwd) "; } else { print " Hidden $type password: "; } ReadMode('noecho'); # don't echo chomp (my $pwd = ); ReadMode(0); # back to normal if ($pwd !~ /\w/) { $pwd = $pwd1; } print "\n"; return ($user,$pwd); } # ********************************* sub display_file_example { my $with_sudo = shift; if ($with_sudo) { $sudo_root= "sudo" } else { $sudo_root= "root" } print "\n * Then your file with hostnames (or ip addresses) should also\n"; print " contain your $sudo_root pw in space-delimited key-value pairs\n"; print " Like so:\n"; print qq|# host_name_or_ip ${sudo_root}_password localhost thisIsMyPWD 127.0.0.1 thisIsMy936 |; print "\n"; } # *********************************