I'd like to feed inputs to a command line interface for Cisco AnyConnect vpncli.exe (v2.3) to automate its (re)connection. It does not take username nor password as command line arguments, but reads those interactively from the user via the command line interface.
Problem is that piping input to vpncli.exe doesn't seem to work for the password. It works for everything except the password. So doing things like this doesn't work:
vpncli.exe < input.txt
type input.txt | vpncli.exe
The application just gets stuck at where it asks for the password.
Below is an example of normal (working) execution where the user enters the parameters:
Notice how the password characters get transformed to *.
Using tools like AutoIt or AutoHotKey to send the inputs to a command prompt window works, but is clumsy and fragile (does not work if the tool can't get to the command prompt window for some reason).
Is there any way to send inputs to such interactive CLI application using PowerShell?
(Or using any other scripting language or some other means?)
you need to create an usual text file like
connect myvpnhost
myloginname
mypassword
save it as myfile.dat (for example) and then call
"%ProgramFiles%\Cisco\Cisco AnyConnect Secure Mobility Client\vpncli.exe" -s < myfile.dat
There are at least two ways to read input in a Windows console application.
ReadConsole: reads input either from keyboard or redirection (documentation).
ReadConsoleInput: reads only raw keystrokes (documentation).
The vpncli.exe application uses ReadConsoleInput in order to read the password, that's way redirecting the password does not work. You can, though, use WriteConsoleInput. I have a small Python script that does exactly that:
import subprocess
import win32console
ANYCONNECT_BIN = 'c:\\Program Files\\Cisco\\Cisco AnyConnect Secure Mobility Client\\vpncli.exe'
def write_console_input(text):
stdin = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)
ir = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
ir.KeyDown = True
for ch in text:
ir.Char = unicode(ch)
stdin.WriteConsoleInput([ir])
def main():
proc = subprocess.Popen([ANYCONNECT_BIN,'connect','VPN'],stdin=subprocess.PIPE)
proc.stdin.write('%s\n%s\n' % ('GROUP', 'USERNAME'))
write_console_input('%s\n' % 'PASSWORD')
ret = proc.wait()
print ret
if __name__ == '__main__':
main()
Related
I'm trying to write a set of scripts, a main payload script that runs some commands over ssh and a small expect wrapper that runs it. (For sake of argument, please accept that I can't put my ssh keys on my target machines.)
The expect script runs fine, filling in the password when required. However, it also passes the prompt (user#machine's password:) through to stdout. I want to remove the matched prompts and only the matched prompts from the output. I still want the results of the remote scripts running, so log_user 0 is too broad.
Is there a way to do this?
How does one send an echo y pipe to plink when starting them from [Diagnostics.Process]::Start
$meProcessID = ([Diagnostics.Process]::Start("echo y | plink.exe", "$($hostName) -l $($uPwd.GetNetworkCredential().Username) -pw $($uPwd.GetNetworkCredential().Password) \`"echo '$($uPwd.GetNetworkCredential().Password)' | sudo -S '/home/someuser/somescript.sh'\`"")).Id
Though there is this it doesn't really answer my question since it's asking about calling bash from powershell and mine is really about passing a value to a Window command from standard output to bypass a Host-Read type of prompt.
Ansgar Wiechers' helpful answer contains an effective solution and sensible security warnings.
Using a System.Diagnostics.Process instance with .RedirectStandardInput = $true, and use of .StandardInput to provide standard input after the process has started, gives you more flexibility, yet in your case the only modification that was needed was to pass your command line as an argument (2nd parameter), via option -c, to program cmd.exe (1st parameter).
[Diagnostics.Process]::Start()'s first parameter is only the executable name / path, not a full command line.
It is the 2nd parameter that accepts a string containing the arguments to pass to the executable.
Since you're using shell features, namely connecting multiple commands with a pipeline, you must use cmd.exe as the executable, and pass your pipeline as an argument to cmd.exe's /c option.
You could use powershell.exe too, but in this simple case it is sufficient - and faster - to use cmd.exe.
Here's a simplified example:
$meProcessID = ([Diagnostics.Process]::Start(
# Program to launch
'cmd',
# Arguments to pass
'/c echo 42 | powershell -nop -c "''stdin input: '' + $Input" & pause'
).Id
The above demonstrates that stdin input 42 is seen by the powershell process as such ($Input); it opens a new console window that shows the following:
stdin input: 42
Press any key to continue . . .
Redirect STDIN of the receiving process. Something like this:
$username = $uPwd.GetNetworkCredential().Username
$password = $uPwd.GetNetworkCredential().Password
$p = New-Object Diagnostics.Process
$p.StartInfo.FileName = 'plink.exe'
$p.StartInfo.Arguments = $hostName, '-l', $username, '-pw', $password,
"`"echo '${password}' | sudo -S '/home/someuser/somescript.sh'\`""
$p.StartInfo.RedirectStandardInput = $true
$p.StartInfo.UseShellExecute = $false
$p.Start()
$p.StandardInput.WriteLine('y')
$p.Id # get the PID
With that said, I assume that the echo y is for accepting the host key. Doing that effectively disables an important SSH security mechanism to counter Man-in-the-Middle attacks, so I strongly recommend against doing this. It's better to verify the SSH host key of the remote host and import it into the registry before doing automated connections. You could prepare a .reg file with the hash and import that wherever needed. You may also want to use public key authentication instead of password authentication.
You have been warned.
My requirement is like this:
I need to log in to a remote device (say Router/switch) and execute following commands.
telnet xx.xx.xx.xx
//give password here
sys
interface g x/x/x
shut
desc free-port
exit
There are Hundreds of devices for which I cannot waste time doing above damn thing 100 times. I need to write a automated script which does it. so My questions are as follows:
I use Windows system, so What is the best scripting language to be used : Ruby / shell script / perl ? (I was formerly ROR Developer, so i know Ruby, Linux terminal. Now I am working in networking domain. )
What I thought was : Put all Devices into an array and using for loop, call devices one by one and execute above said commands.
I don't have knowledge of scripting, so please guide me further. I don't know where to start from.
Step 1: decide the file structure of your program.
For example, this is the simplest structure
if_admin/
|--config.yml
|--run.rb
Step 2: write a config file or a bunch of config files that contain the different parts of the commands you need to run on the targets.
For example, you can use a yaml file like this:
xx.xx.xx.xx:
password: s3cret
router-shelf: x
slot: x
port: x
yy.yy.yy.yy:
...
Step 3: implement what you want to do
require 'yaml'
require 'net/telnet'
config = YAML.load_file('./config.yml')
config.each do |host, conf|
telnet = Net::Telnet.new('Host' => host)
telnet.login(conf['password'])
telnet.puts <<-CMD
sys
interface g #{conf['router-shelf']}/#{conf['slot']}/#{conf['port']}
shut
desc free-port
CMD
telnet.close
end
If you can use expect script , you are in luck.
#!/usr/bin/expect
set timeout 60
set cmds [list "ssh host1 ..." "ssh host2 ..." "ssh host3 ..."]
foreach cmd $cmds {
spawn -noecho bash -c $cmd
expect {
-re "password" {
exp_send "$env(PASS_WORD)\"
exp_continue
}
eof { wait } ; # at this time the last spawn'ed process has exited
}
}
Here is the rough idea of above script :-
set cmds [list.... will be used as list to store set of commands.
foreach will iterate though those commands
spawn will spawn process for each of the command. you can write multiple command with single telnet in bash, just break down commands using \ (backslash) so it is easily readable and extendable.
expect block will pass password whenever it encounter certain regex.
eof will wait till all commands in spawn process are finish.
set timeout -1 will keep loop running. i think default time for expect script is 10secs.
You can create one more foreach loop for host-list.
I think this will be enough to get you started for your automation process.
As to the question of "What is the best scripting language to be used", I would say go with one that does what you need and one that you're comfortable with using.
If you want to go with Perl, one module that you could use is Net::Telnet. Of course, you'll need Perl itself. I'd recommend using Strawberry Perl, which should already have Net::Telnet installed.
Another possible route is to use putty, which is a SSH and telnet client. You could combine that with TTY Plus, which provides an interface that uses tabs for different putty sessions. And it lets you issue commands to multiple putty sessions. This is one possibility that wouldn't involve a lot of code writing.
I have written one shell script which ask for some username and password from standart input.
Once username and password is typed there is a output depending upon the parameters passed in the script.
Say my script name is XYZ.ksh.
Now my problem is that users of these script want to use want to use this script in conjugation with other shell commands like grep, less, more, wc etc.
Normally yes they can use
XYZ.ksh | grep abc
But in my case since XYZ is prompting for username and password we are not able to use "|" in front of that. It blocks forever.
I just wanted to know how can I implement the functinality.
What I tried
I tried taking input of "more commands " from user where user types things like "| grep abc"
but when i used this input in my script it did not work.
Use <<< like this:
XYZ.ksh <<< "your inputs" | grep abc
In your script you can test to see if stdout is connected to a terminal with:
if [[ -t 1 ]]
That way you can supress the prompt if the output is not going to the console.
Alternatively, with your "more commands" solution, run the command connected to a named pipe.
There are multiple solutions commonly used for this kind of problem but none of them is perfect :
Read password from standard input. It makes it really hard to use the script in pipes. This method is used by commands that deal with changing passwords : passwd, smbpasswd
Provide username and password in the command line parameters. This solution is good for using the script in pipes, but command line can be viewed by anyone, using ps -ef for exemple. This is used by mysql, htpasswd, sqlplus, ...
Store username and password unencrypted in a file in user's home directory. This solution is good for using the script in pipes, but the script must check if the file is visible or modifiable by other users. This is used by mysql
Store private key in local file and public key in distant file, as used by SSH. You must have a good encryption knowledge to do this correctly (or rely on SSH), but it's excellent for use in pipes, even creating pipes accross different machines !
Don't deal with passwords, and assume that if a user is logged in in the system, he has the right to run the program. You may give execute privilege only to one group to filter who can use the program. This is used by sqlplus from Oracle, VirtualBox, games on some Linux distributions, ...
My preferred solution would be the last, as the system is certainly better than any program I could write with regard to security.
If the password is used to login to some other service, then I would probably go for the private file containing the password.
One less-than-optimal possibility is to display the prompt to stderr instead of stdout.
echo -n "Username:" >/dev/stderr
A better solution would be to check stdin of the shell. If it's a terminal, then open it for writing and redirect to that file. Unfortunately, I'm not sure how to do that in bash or ksh; perhaps something like
echo -n "Username:" >/dev/tty
You can use (I assume you are reading username and password in your script with read)
(
read -p "user:" USER
read -p "pass:" PASS
) < /dev/tty > /dev/tty
and you'll be able to run
$ cmd | XYZ.ksh
However, I agree with other answers: just don't ask for user and password and give the correct permissions to the script to allow access.
I am running a command to push files to Google App Engine, and it might ask me for my email and password:
$ appcfg.py update .
Application: my-cdn; version: 3. # printed out
Server: appengine.google.com. # printed out
Scanning files on local disk. # printed out
Initiating update. # printed out
Email: email#gmail.com # now it asks me...
Password:
I am running that in Ruby right now using this: %x[appcfg.py update .]. How can I fill out the email and password? I have seen something like this with capistrano:
%x[appcfg.py update .] do |channel, stream, data|
channel.send_data "#{yaml['production']['email']}\n" if data =~ /^Email:/
end
...but haven't figured out how to set that up without it.
What's the best way to fill out things the command line asks for programmatically?
Another issue is that if I run the command through ruby, I can see output as the command runs, but it never shows me the "Email: " line, it stops here:
Application: my-cdn; version: 3.
Server: appengine.google.com.
Scanning files on local disk.
Initiating update.
# ... can't see "Email: "
Thanks for the tips.
Generally, you can use Open3.popen3(command) do |input, output, error| ... end to invoke a command, write to its input stream and read from its output and error streams (you need to require "open3" first).
However that usually does not work with programs that ask for a password (because they access the terminal directly instead of simply reading from stdin). In that case, you need to use PTY (require "pty") instead of open3. PTY.getpty will spawn a shell and return an array containing the shell's output stream, input stream and pid. You can use those to invoke commands and read their input (be aware that the output you read will also include the shell prompt and the command invocation).