Loop though a list of hostnames to see if using ssh on them is successful - expect

I have a task which has been giving me headaches quite a while. Basically, I'm given a list of hostnames alongside their usernames and passwords (each hostname has a username and a password). I am to write a script so that I can automatically ssh from the list of login details provided. I am using an expect script to do it. This is what I've come up with so far.
#!/usr/bin/expect
# Values to retrieve elements from the list based on the index
set x 0
set y 0
set HSTNMLIST [list 192.168.6.99 192.168.0.32 192.168.3.77]
set USRNMLIST [list user1 user2 user3]
set PSSWRDLIST [list asfsa bvgtrnh bdfbdfg]
foreach n $HSTNMLIST {
spawn ssh $USRNMLIST[$x]#$n
expect "password"
send "$PSSWRDLIST[$y]\r"
if { expect "continue connecting" } {
send "No\r"
incr $x
} else {
send "No\r"
expect "Permission denied"
send "No\r"
incr $y
}
}
interact
Right now I'm stuck at figuring out how retrieve an element based on the indexes in the loop function. Thank you for being patient with me.

You could do:
for {set idx 0} {$idx < [llength $HSTNMLIST]} {incr i} {
set host [lindex $HSTNMLIST $idx]
set user [lindex $USRNMLIST $idx]
set pswd [lindex $PSSWRDLIST $idx]
spawn ssh $user#$host
...
send "$pswd\r"
Or are you wanting to try all combinations of username/password for each host?

Related

How to verify output from bash script controlling ghci (Haskell)

I'm attempting to create a test script for evaluation. The script needs to open Haskell GHCI and send various commands to ghci and check if the outputs are correct. It needs to continue to the end of all commands and give a final score, that is, don't just stop running if the output is incorrect. Currently, I have this.
#!/bin/env expect
spawn ghci
expect ".*> "
send ":l main.hs\n"
expect "*Main>"
send "transform [(5,1),(6,1),(8,15),(9,1)]\n"
expect "*Main>"
It opens ghci and loads the proper haskell file (main.hs). It then runs the transform function with that list parameter. How can I get this to verify the output is equal to what I want and give points accordingly. EX: Pseudo
#!/bin/env expect
spawn ghci
expect ".*> "
send ":l main.hs\n"
expect "*Main>"
send "transform [(5,1),(6,1),(8,15),(9,1)]\n"
if OUTPUT = [(5,5),(6,5),(8,5),(9,5)]
then POINTS+= 5
send "translate [(5,1),(6,1),(8,15),(9,1)]\n"
if OUTPUT = [(5,10),(6,10),(8,10),(9,10)]
then POINTS+= 5
expect "*Main>"
send ":quit"
OUTPUT SCORE
All the commands are done in Haskell, but ran from a shell script. Can anyone help?
Here's one (untested) approach:
#!/bin/env expect
spawn ghci
expect "*> "
send ":l main.hs\n"
expect "*Main>"
set POINTS 0
send "transform [(5,1),(6,1),(8,15),(9,1)]\n"
expect {
-ex {[(5,5),(6,5),(8,5),(9,5)]} {
incr POINTS 5
exp_continue
}
*Main>
}
send "translate [(5,1),(6,1),(8,15),(9,1)]\n"
expect {
-ex {[(5,10),(6,10),(8,10),(9,10)]} {
incr POINTS 5
exp_continue
}
*Main>
}
send ":quit"
puts $POINTS
For detailed documentation, see https://www.tcl.tk/man/expect5.31/expect.1.html and https://www.tcl-lang.org/man/tcl/TclCmd/contents.htm .

Connect CISCO Anyconnect VPN via bash

As title says, trying to connect vpn via bash. The following script seemed closest to the answer I'm looking for:
#!/bin/bash
/opt/cisco/anyconnect/bin/vpn -s << EOF
connect https://your.cisco.vpn.hostname/vpn_name
here_goes_your_username
here_goes_your_passwordy
EOF
When I run this the vpn starts but then exits without an error and without connecting. This seems to be caused by the -s. If I remove this parameter the VPN will start but none of the commands (ie connect vpn, username, password) will be entered. From what I read the -s option will allow the username/password to be passed.
Help!
I had to download the expect packages (yum install expect). Here is the code I used to automate vpn connection
#!/usr/bin/expect
eval spawn /opt/cisco/anyconnect/bin/vpn connect vpn.domain.com
expect "Username: " { send "username\r" }
expect "Password: " { send "password\r" }
set timeout 60
expect "VPN>"
Real easy! :D
Although expect can be cleaner, it is not strictly necessary. Assuming /opt/cisco/anyconnect/bin/vpnagentd is running as it automatically should be:
To connect:
printf "USERNAME\nPASSWORD\ny" | /opt/cisco/anyconnect/bin/vpn -s connect HOST
Replace USERNAME, PASSWORD, and HOST. The \ny at the end is to accept the login banner - this is specific to my host, and so you may not need it.
I understand that there are obvious security concerns with this method; it's for illustration purposes only.
To get state:
/opt/cisco/anyconnect/bin/vpn state
To disconnect:
/opt/cisco/anyconnect/bin/vpn disconnect
This was tested with AnyConnect v3.1.05160.
If you are using macOS, I recommend to save your vpn password in Keychain, then request it from your Anyconnect script.
For example, say I want to connect to foo.bar.com with account foo and password bar.
Save foo and bar pair in Keychain (login not iCloud) with name fookey
Run the following bash script to connect
/opt/cisco/anyconnect/bin/vpn connect foo.bar.com -s << EOM
0 # foo.bar.com doesn't require two factor authorization
foo # vpn account
$(sudo security find-generic-password -ws fookey) # vpn password
EOM
Using this approach, you don't need to type in your vpn password every time, and you won't write your password to files without encryption.
If you are not familiar with bash script, read below for explanation:
/opt/cisco/anyconnect/bin/vpn connect -s enters non-interactivel mode.
<< EOM ... EOM is called here-docs, which uses a string to replace a file. It is very useful to script interactive CLI, by writing each respond as a new line.
security is a nice tool to access your Keychain from the command line.
c# solution ... in this case profile is the group name.
//file = #"C:\Program Files (x86)\Cisco\Cisco AnyConnect Secure Mobility Client\vpncli.exe"
var file = vpnInfo.ExecutablePath;
var host = vpnInfo.Host;
var profile = vpnInfo.ProfileName;
var user = vpnInfo.User;
var pass = vpnInfo.Password;
var confirm = "y";
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = file,
Arguments = string.Format("-s"),
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
};
proc.OutputDataReceived += (s, a) => stdOut.AppendLine(a.Data);
proc.ErrorDataReceived += (s, a) => stdOut.AppendLine(a.Data);
//make sure it is not running, otherwise connection will fail
var procFilter = new HashSet<string>() { "vpnui", "vpncli" };
var existingProcs = Process.GetProcesses().Where(p => procFilter.Contains(p.ProcessName));
if (existingProcs.Any())
{
foreach (var p in existingProcs)
{
p.Kill();
}
}
proc.Start();
proc.BeginOutputReadLine();
//simulate profile file
var simProfile = string.Format("{1}{0}{2}{0}{3}{0}{4}{0}{5}{0}"
, Environment.NewLine
, string.Format("connect {0}", host)
, profile
, user
, pass
, confirm
);
proc.StandardInput.Write(simProfile);
proc.StandardInput.Flush();
//todo: these should be configurable values
var waitTime = 500; //in ms
var maxWait = 10;
var count = 0;
var output = stdOut.ToString();
while (!output.Contains("state: Connected"))
{
if (count > maxWait)
throw new Exception("Unable to connect to VPN.");
Thread.Sleep(waitTime);
output = stdOut.ToString();
count++;
}
stdOut.Append("VPN connection established! ...");
Building on Brayden Hancock's answer, I built a solution that reads the password from the macOS Keychain.
As a first step, I added a new password item with the account field set to mycompany-vpn via the Keychain Access app. The first part of the script reads that item back from the keychain and extracts the password using the ruby snippet, the expect script section does the rest.
#!/usr/bin/env bash
get_pw () {
security 2>&1 >/dev/null find-generic-password -ga mycompany-vpn \
|ruby -e 'print $1 if STDIN.gets =~ /^password: "(.*)"$/'
}
USER=username
ADDR=vpn.company.com
PASSWORD=$(get_pw)
/usr/bin/expect -f - <<EOD
set timeout 10
spawn /opt/cisco/anyconnect/bin/vpn connect $ADDR
expect "\r\nUsername:*" {send -- "$USER\r"}
expect "Password: " {send -- "$PASSWORD\r"}
expect "Connected"
EOD

Running commands in background from TCL script and formatting output

I have a tcl script which runs multiple shell commands serially.
Something like this:
abc.tcl
command 1
command 2
command 3
...
command n
This script prints the outputs of these commands into a text file in the following format:
### ### ### ### ### ###
Command name
### ### ### ### ### ###
Command Output
### ### ### ### ### ##
I was trying to get the script to run faster but making the shell commands run in parallel instead of serially. By pushing them to the background (command a &). But I'm at a loss how to retain the formatting of my output text file as was the case before.
When I push the commands in to background I'm forced to append their outputs into a temporary file, but these files just have the output of the commands in a dump together. It's difficult to differentiate between the different outputs.
Is there someway I can redirect the output of each command running in the background to an individual temp file (maybe the name of the temp file can have the process id of the background running process). And once all commands have run, I can cat the outputs together in to the proper format? Any ideas/suggestions on how I can accomplish this.
If the commands don't have state that depends on each other, you can parallelize them. There are many ways to do this, but one of the easier is to use the thread package's thread pooling (which requires a threaded Tcl, the norm on many platform nowadays):
package require Thread
set pool [tpool::create -maxworkers 4]
# The list of *scripts* to evaluate
set tasks {
{command 1}
{command 2}
...
{command n}
}
# Post the work items (scripts to run)
foreach task $tasks {
lappend jobs [tpool::post $pool $task]
}
# Wait for all the jobs to finish
for {set running $jobs} {[llength $running]} {} {
tpool::wait $pool $running running
}
# Get the results; you might want a different way to print the results...
foreach task $tasks job $jobs {
set jobResult [tpool::get $pool $job]
puts "TASK: $task"
puts "RESULT: $jobResult"
}
The main tweakable is the size of the thread pool, which defaults to a limit of 4. (Set it via the -maxworkers option to tpool::create which I've listed explicitly above.) The best value to choose depends on how many CPU cores you've got and how much CPU load each task generates on average; you'll need to measure and tune…
You can also use the -initcmd option to pre-load each worker thread in the pool with a script of your choice. That's a good place to put your package require calls. The workers are all completely independent of each other and of the master thread; they do not share state. You'd get the same model if you ran each piece of code in a separate process (but then you'd end up writing more code to do the coordinating).
[EDIT]: Here's a version that will work with Tcl 8.4 and which uses subprocesses instead.
namespace eval background {}
proc background::task {script callback} {
set f [open |[list [info nameofexecutable]] "r+"]
fconfigure $f -buffering line
puts $f [list set script $script]
puts $f {fconfigure stdout -buffering line}
puts $f {puts [list [catch $script msg] $msg]; exit}
fileevent $f readable [list background::handle $f $script $callback]
}
proc background::handle {f script callback} {
foreach {code msg} [read $f] break
catch {close $f}
uplevel "#0" $callback [list $script $code $msg]
}
proc accumulate {script code msg} {
puts "#### COMMANDS\n$script"
puts "#### CODE\n$code"
puts "#### RESULT\n$msg"
# Some simple code to collect the results
if {[llength [lappend ::accumulator $msg]] == 3} {
set ::done yes
}
}
foreach task {
{after 1000;subst hi1}
{after 2000;subst hi2}
{after 3000;subst hi3}
} {
background::task $task accumulate
}
puts "WAITING FOR TASKS..."
vwait done
Notes: the tasks are Tcl commands that produce a result, but they must not print the result out; the fabric code (in background::task) handles that. These are subprocesses; they share nothing with one another, so anything you want them to do or be configured with must be sent as part of the task. A more sophisticated version could keep a hot pool of subprocesses around and in general work very much like a thread pool (subject to the subtle differences due to being in a subprocess and not a thread) but that was more code than I wanted to write here.
Result codes (i.e., exception codes) are 0 for “ok”, 1 for “error”, and other values in less common cases. They're exactly the values documented on the Tcl 8.6 catch manual page; it's up to you to interpret them correctly. (I suppose I should also add code to make the ::errorInfo and ::errorCode variable contents be reported back in the case of an error, but that makes the code rather more complex…)

How to display a listbox or text when a program is running

This is my coding. I am facing a problem where it didn't display the ".display_message" while it is still running. I receive the message only after the proc torun {} completed its task. Btw, after 2000 is actually my program running. It is a very long code where I feel it is not applicable so I removed it to simplified it. Please guide me in this. Thanks.
proc torun {} {
set total [.display_pc2 size]
.display_message insert end "test"
for {set x 0} {$x < $total} {incr x} {
set machine [.display_pc2 get $x]
.display_message insert end "Copy to $machine now. Please wait..."
.display_message see end
after 2000
.display_message insert end "Copy to $machine done"
.display_message see end
after 2000
}
}
You should completely change the way you write this code and use idle callbacks.
The idea is that you create a list of "tasks" to do (probably that would be a list of targets computers), save it somewhere and then process it one item at a time, rescheduling the execution using idle callback.
A sketch follows:
proc show_transfer_start {target} {
.display_progress add ... $target
}
proc show_transfer_result {res} {
.display_progress add ... $res
}
proc schedule_transfer {target rest} {
after idle [after 0 [list do_step $target $rest]]
}
proc do_step {target rest} {
set res [copy --to $target]
show_transfer_result $res
if {[llength $rest] > 0} {
set next [lindex $rest 0]
show_transfer_start $next
schedule_tranfer $next [lrange $rest 1 end]
}
}
set targets [list box1 box2 box3]
set first [lindex $targets 0]
show_transfer_start $first
schedule_transfer $first [lrange $targets 1 end]
I've never done tk (if it's gui what you're doing), but maybe you need some equivalent to flush when using puts to force-display a message.
Since I can't guess what's done inside the .display_message proc.
Edit:
Just got an idea: You can use the after command to fake-multithread your app.
after 0 [list .display_message insert end "Copy to $machine now. Please wait..."; .display_message see end]
Which will run independently from your current proc as event handler. Maybe that fixes your flush problem. (Requires the eventloop or update command)

How do i provide password for su command while using net/telnet in ruby

I am using Net/TELNET to connect to remote host trying to run su command in order to get root privilage. This is how I am doing it
require 'net/telnet'
localhost = Net::Telnet::new("Host" => "192.147.217.27",
"Timeout" => 50,
"Prompt" => /[$%#>] \z/n)
localhost.login("dvsdkrp", "dvsdkvrp") { |c| print c }
localhost.cmd("cd /home/dvsdkrp/workdir/smruti") { |c| print c }
localhost.cmd("su") { |c| print c }
localhost.puts("passwd"){ |c| print c }
I am able to login and able to do smruti directory but when I use su command it takes me to the password prompt but then I get this error, even after I changed the Timeout parameter to 150
Password: C:/Ruby/lib/ruby/1.8/net/telnet.rb:552:in `waitfor': timed out
while waiting for more data (Timeout::Error)
from C:/Ruby/lib/ruby/1.8/net/telnet.rb:679:in `cmd'
from tel.rb:7
What should I do?
The prompt you specify when you create the Telnet object is the one its going to look for before it runs its next command. If you are timing out then I think its because its not seeing the prompt it expects. If you use the Match option when you send the 'su' command, you can specify a prompt specifically for this command.
su_prompt = "Password: "
localhost.cmd("String" => "su", "Match" => /#{su_prompt}/) { |c| print c }
localhost.cmd("passwd")
This new prompt is only used for the current command. The originally specified prompt is still in affect for the rest of the session.

Resources