Python select() not waiting for terminal input after forkpty() - python

I am trying to write a python script that will automatically log in to a remote host via ssh and update a users password. Since ssh demands that it take its input from a terminal, I am using os.forkpty(), running ssh in the child process and using the parent process to send command input through the pseudo terminal. Here is what I have so far:
import os, sys, time, getpass, select, termios
# Time in seconds between commands sent to tty
SLEEP_TIME = 1
NETWORK_TIMEOUT = 15
#------------------------------Get Passwords------------------------------------
# get username
login = getpass.getuser()
# get current password
current_pass = getpass.getpass("Enter current password: ")
# get new password, retry if same as current password
new_pass = current_pass
first_try = True
while new_pass == current_pass:
if first_try:
first_try = False
else:
# New password equal to old password
print("New password must differ from current password.")
# Get new password
new_pass = getpass.getpass("Enter new password: ")
new_pass_confirm = getpass.getpass("Confirm new password: ")
while new_pass != new_pass_confirm:
# Passwords do not match
print("Passwords do not match")
new_pass = getpass.getpass("Enter new password: ")
new_pass_confirm = getpass.getpass("Confirm new password: ")
#------------------------------End Get Passwords--------------------------------
ssh = "/usr/bin/ssh" # ssh bin location
args = ["ssh", login + "#localhost", "-o StrictHostKeyChecking=no"]
#fork
pid, master = os.forkpty()
if pid == 0:
# Turn off echo so master does not need to read back its own input
attrs = termios.tcgetattr(sys.stdin.fileno())
attrs[3] = attrs[3] & ~termios.ECHO
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attrs)
os.execv(ssh, args)
else:
select.select([master], [], [])
os.write(master, current_pass + "\n")
select.select([master], [], [])
os.write(master, "passwd\n")
select.select([master], [], [])
os.write(master, current_pass + "\n")
select.select([master], [], [])
os.write(master, new_pass + "\n")
select.select([master], [], [])
os.write(master, new_pass + "\n")
select.select([master], [], [])
os.write(master, "id\n")
select.select([master], [], [])
sys.stdout.write(os.read(master, 2048))
os.wait()
The script prompts the user for his/her current and new passwords, then forks and sends appropriate responses to ssh login prompt and then passwd prompts.
The problem I am having is that the select syscalls are not behaving as I would expect. They don't appear to be blocking at all. I'm thinking that I am misunderstanding something about the way select works with the master end of a pty.
If I replace them all with time.sleep(1), the script works fine, but I don't want to have to rely on that solution because I can't always guarantee the network will respond in a short time, and I don't want to make it something rediculous that will take forever (I intend to use this to programatically log into several servers to update passwords)
Is there a way to reliably poll the master side of a pty to wait for the slave's output?
Note: I realize there are better solutions to this problem with things like sshpass and chpasswd, but I am in an environment where this cannot be run as root and very few utilities are available. Thankfully python is.

select doesn't read any data; it simply blocks until data is available to be read.
Since you don't read any data after the first select, there will still be data left in the buffer for you to read, so any subsequent select will not block.
You need to read the data in the buffer before calling select again. Doing this without blocking means that you will likely have to set the file to non-blocking mode (I don't know how to do that in Python).
A better way of providing the password over SSH would be to use the --stdin flag if your passwd supports it, and to run the command directly over SSH instead of through the created shell.
handle = subprocess.Popen(["ssh", login + "#localhost", "-o StrictHostKeyChecking=no", "passwd --stdin"])
handle.communicate("\n".join([oldpass, newpass, newpass, ""]))

Have a look in man ssh at the -f option. It may be what you need when launching ssh. It will block ssh until the password is typed and then fork by itself. You could probably use this feature to achieve what you want (but you may have to slightly change your current code because it will perform by itself what you currently try to embed in your script).
This option is generally used at the command line for starting a remote graphical program: once the password is typed, you can safely close the terminal and keep interacting with the remote process with its graphical interface. But I think using this feature here would lead to a much cleaner way than playing with low-level blocking features and similar things.

Related

SSH paramiko if condition on stdout.channel.recv() not working

I'm new to python and trying to make automations out of a bunch of unix cmds with paramiko
here is the function I'm trying to use
def sendcommand(self, command):
password ="somestring"
if self.client is not None:# Check if connection is made previously
stdin, stdout, stderr = self.client.exec_command(command, get_pty=True)
while not stdout.channel.exit_status_ready():# Print stdout data
if stdout.channel.recv_ready():
channel_data = stdout.channel.recv(9999)# Retrieve the first 9999 bytes
while stdout.channel.recv_ready():# Retrieve the next 9999 bytes
channel_data += stdout.channel.recv(9999)
if stdout.channel.closed is False:# Check if stdout is still open
stdin.write(password + '\n')
stdin.flush()
print(str(_data, "utf8"))# Print as string with utf8 encoding
else:
print("Could not open connection.")
this function works when I use some sudo command and the shell is waiting for the password, but when I need for exemple to create a user on centos and add a password (the sheel prompts for a new password) this won't wotk as it will feed the sudo password due to
if stdout.channel.closed is False:
I'm trying to find a way to add an if condition on the stdout stream so that when it asks for the sudo password it feeds the sudo password to stdin and when something else it writes other things, something like:
if channel.stdout.read().decode("utf-8").endswith("[sudo] password for user: "):
stdin.write(password2 + '\n')
stdin.flush()
Thanks for your help

ssh via python subprocess.Popen: terminate if password requested

I'm using a python script to manage ssh fingerprint problems after a workstation(s) is reimaged.
I attempt to connect with ssh, and if I get a any warnings I deal with them.
However, if there are no errors, then I am asked for a password to connect. At this point I want to terminate the process. However, the script hangs on the password request.
Here's the method:
def ssh_fingerprint_changed(node):
"""
Checks if a node's ssh fingerprint has changed or an old key is found, which can occur when a node is reimaged.
It does this by attempting to connect via ssh and inspecting stdout for an error message.
:param node: the ip or hostname of the node
:return: True if the node's fingerprint doesn't match the client's records. Else False.
"""
changed = False
cmd = ["ssh", "-q", ADMIN_USER + "#" + node, "exit"]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
print("Checking for fingerprint changes")
for line in proc.stdout: # loop on lines
print("in for loop") # NEVER REACHES HERE IF NO ERRORS, WAITING FOR PASSWORD
if b"Offending key" in line:
print("Offending key found.")
proc.stdin.write(b"no\n") # don't connect
changed = True
elif b"REMOTE HOST IDENTIFICATION HAS CHANGED!" in line:
print("REMOTE HOST IDENTIFICATION HAS CHANGED!")
changed = True
print(changed) # NEVER REACHES HERE IF NO ERRORS, WAITING FOR PASSWORD
if not changed: # then everything's good, but it will be waiting for a password to connect
print("Good to go, terminating ssh test.")
rc = proc.terminate()
else:
rc = proc.wait()
return changed
If I run this from the terminal ./my_python_script.py, I have the problems. Oddly, if I run in PyCharm, it doesn't hang on the password request and terminates shh, continuing with the script as expected.
The easy answer is simply to tell ssh that you don't want to support password authentication at all; you'll still get the messages you want if the host key is changed, but you won't ever have the process hanging waiting for a password to be entered.
cmd = ['ssh',
'-o', 'PasswordAuthentication no', ### <-- THIS LINE HERE
'-o', 'StrictHostKeyChecking yes', ### also, never modify known_hosts
'-q',
'%s#%s' % (ADMIN_USER, + node),
'exit']
If you did not want to process other prompts, I would suggest setting stdin=subprocess.DEVNULL (in Python 3) or passing the -n argument to ssh to prevent stdin from being passed to the process at all.

Python Telnet script

Thanks to Python Library i was able to use their example to telnet to Cisco switches, I am using this for learning purposes, specifically learning python.
However, although all the code seem generally easy to read, I am a bit confused as to the following:
1- why use the if statement below
2- why use the "\n" after the username and password write method
3- why am i not getting the output on my bash terminal when the changes are infact committed and successful
HOST = "172.16.1.76"
user = raw_input("Enter your Telnet username : ")
password = getpass.getpass()
tn = telnetlib.Telnet(HOST)
tn.read_until("Username: ")
tn.write(user + '\n') <----- 2
if password: <----- 1
tn.read_until("Password: ")
tn.write(password + "\n") <------2
tn.write("show run \n")
time.sleep(5)
output = tn.read_all() <----- 3
print output
print "=" * 30
print "Configuration Complete."
I am not sure as to why using the if statement above, typically once you input in the Username, you get the password prompt right afterward. why cant we just type :
tn.read_until("Username: ")
tn.write(user + '\n')
tn.read_until("Password: ")
tn.write(password + "\n")
As for the second point, why use the '\n' after the passwords and username in the write method if we going to hit enter after we add them anyway?
1: the line
password = getpass.getpass()
asks you for you password, if you leave it empty, password will contain the empty string which, in an if statement, is the same as False
the script doesn't know ahead of time if you have a password on your server or not, it simulates knowing by asking you first and if you don't input anything, it assumes it doesn't (otherwise it would get stuck on tn.read_until("Password: ") forever.
2: the '\n' simulates you hitting the return key. when you enter your password, for example 'password<RETURN>' the variable password will not contain a trailing newline (\n), this is why it is manually appended
3: this one i dont know, possibly 5 seconds isn't enough time to wait
After execute
tn = telnetlib.Telnet(HOST)
you have created a telnet channel from your machine to HOST. But you still need to communicate with HOST to push/send your commands and receive the outputs.
To push your commands to HOST, you need to execute tn.write("your_commands_or_input \n"), \n means newline/return, which tells your current commands need to be executed now. After the execution, HOST return the result, which will be caught by your telnet object "tn" and saved in its "local cache", you can search any keywords you expected in this cache by using tn.read_until method, if the expected keyword has been found, read_until will stop(always stop on the 1st found), and you can do anything you need(It's your turn now), else the read_until will keep waiting the output from HOST(Haven't you turn yet). Finally if you want to check all output have been cached, you can execute tn.read_all().
Remember some of the HOST using different login output, i.e Username vs username or Password vs password, you better to use regular expression to match them.
There is a python library on github, specifically for telneting to cisco devices.
pip install git+https://github.com/sergeyzelyukin/cisco-telnet.git
import ciscotelnet
with ciscotelnet.CiscoTelnet(host, verbose = False) as cisco:
if cisco.login(final_mode=CiscoTelnet.MODE_ENABLE, user="john", user_pass="12345678", enable_pass="cisco"):
# if cisco.login(final_mode=CiscoTelnet.MODE_ENABLE, line_pass="abcdef", enable_pass="cisco"):
print cisco.cmd("sh int status | inc Fa0/1")
print cisco.conf(["interface fast0/1", "descr blank", "load-interval 300"])
print cisco.wr()

Sending a password over SSH or SCP with subprocess.Popen

I'm trying to run an scp (secure copy) command using subprocess.Popen. The login requires that I send a password:
from subprocess import Popen, PIPE
proc = Popen(['scp', "user#10.0.1.12:/foo/bar/somefile.txt", "."], stdin = PIPE)
proc.stdin.write(b'mypassword')
proc.stdin.flush()
This immediately returns an error:
user#10.0.1.12's password:
Permission denied, please try again.
I'm certain the password is correct. I easily verify it by manually invoking scp on the shell. So why doesn't this work?
Note, there are many similar questions to this, asking about subprocess.Popen and sending a password for automated SSH or FTP login:
How can I set a users password in linux from a python script?
Use subprocess to send a password
The answer(s) to these questions don't work and/or don't apply because I am using Python 3.
Here's a function to ssh with a password using pexpect:
import pexpect
import tempfile
def ssh(host, cmd, user, password, timeout=30, bg_run=False):
"""SSH'es to a host using the supplied credentials and executes a command.
Throws an exception if the command doesn't return 0.
bgrun: run command in the background"""
fname = tempfile.mktemp()
fout = open(fname, 'w')
options = '-q -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oPubkeyAuthentication=no'
if bg_run:
options += ' -f'
ssh_cmd = 'ssh %s#%s %s "%s"' % (user, host, options, cmd)
child = pexpect.spawn(ssh_cmd, timeout=timeout) #spawnu for Python 3
child.expect(['[pP]assword: '])
child.sendline(password)
child.logfile = fout
child.expect(pexpect.EOF)
child.close()
fout.close()
fin = open(fname, 'r')
stdout = fin.read()
fin.close()
if 0 != child.exitstatus:
raise Exception(stdout)
return stdout
Something similar should be possible using scp.
The OpenSSH scp utility invokes the ssh program to make the SSH connection to the remote host, and the ssh process handles authentication. The ssh utility doesn't accept a password on the command line or on its standard input. I believe this is a deliberate decision on the part of the OpenSSH developers, because they feel that people should be using more secure mechanisms like key-based authentication. Any solution for invoking ssh is going to follow one of these approaches:
Use an SSH key for authentication, instead of a password.
Use sshpass, expect, or a similar tool to automate responding to the password prompt.
Use (abuse) the SSH_ASKPASS feature to get ssh to get the password by invoking another command, described here or here, or in some of the answers here.
Get the SSH server administrator to enable host-based authentication and use that. Note that host-based authentication is only suitable for certain network environments. See additional notes here and here.
Write your own ssh client using perl, python, java, or your favorite language. There are ssh client libraries available for most modern programming languages, and you'd have full control over how the client gets the password.
Download the ssh source code and build a modified version of ssh that works the way you want.
Use a different ssh client. There are other ssh clients available, both free and commercial. One of them might suit your needs better than the OpenSSH client.
In this particular case, given that you're already invoking scp from a python script, it seems that one of these would be the most reasonable approach:
Use pexpect, the python expect module, to invoke scp and feed the password to it.
Use paramiko, the python ssh implementation, to do this ssh task instead of invoking an outside program.
The second answer you linked suggests you use Pexpect (which is usually the right way to go about interacting with command line programs that expect input).
Pexpect has a library for exactly this: pxssh
http://pexpect.readthedocs.org/en/stable/api/pxssh.html
import pxssh
import getpass
try:
s = pxssh.pxssh()
hostname = raw_input('hostname: ')
username = raw_input('username: ')
password = getpass.getpass('password: ')
s.login(hostname, username, password)
s.sendline('uptime') # run a command
s.prompt() # match the prompt
print(s.before) # print everything before the prompt.
s.logout()
except pxssh.ExceptionPxssh as e:
print("pxssh failed on login.")
print(e)
I guess some applications interact with the user using stdin and some applications interact using terminal. In this case when we write the password using PIPE we are writing to stdin. But SCP application reads the password from terminal. As subprocess cannot interact with user using terminal but can only interact using stdin we cannot use the subprocess module and we must use pexpect for copying the file using scp.
Feel free for corrections.
Here is my scp function based on pexpect. It can handle wildcards (i.e. multiple file transfer), in addition to the password.
To handle multiple file transfer (i.e. wildcards), we need to issue a command via a shell. Refer to pexpect FAQ.
import pexpect
def scp(src,user2,host2,tgt,pwd,opts='',timeout=30):
''' Performs the scp command. Transfers file(s) from local host to remote host '''
cmd = f'''/bin/bash -c "scp {opts} {src} {user2}#{host2}:{tgt}"'''
print("Executing the following cmd:",cmd,sep='\n')
tmpFl = '/tmp/scp.log'
fp = open(tmpFl,'wb')
childP = pexpect.spawn(cmd,timeout=timeout)
try:
childP.sendline(cmd)
childP.expect([f"{user2}#{host2}'s password:"])
childP.sendline(pwd)
childP.logfile = fp
childP.expect(pexpect.EOF)
childP.close()
fp.close()
fp = open(tmpFl,'r')
stdout = fp.read()
fp.close()
if childP.exitstatus != 0:
raise Exception(stdout)
except KeyboardInterrupt:
childP.close()
fp.close()
return
print(stdout)
It can be used this way:
params = {
'src': '/home/src/*.txt',
'user2': 'userName',
'host2': '192.168.1.300',
'tgt': '/home/userName/',
'pwd': myPwd(),
'opts': '',
}
scp(**params)
This is a rewrite I did from the code posted by #Kobayashi and #sjbx but for the purposes of doing scp requests, so credit to those two.
def scp(host, user, password, from_dir, to_dir, timeout=300, recursive=False):
fname = tempfile.mktemp()
fout = open(fname, 'w')
scp_cmd = 'scp'
if recursive:
scp_cmd += ' -r'
scp_cmd += f' {user}#{host}:{from_dir} {to_dir}'
child = pexpect.spawnu(scp_cmd, timeout=timeout)
child.expect(['[pP]assword: '])
child.sendline(str(password))
child.logfile = fout
child.expect(pexpect.EOF)
child.close()
fout.close()
fin = open(fname, 'r')
stdout = fin.read()
fin.close()
if 0 != child.exitstatus:
raise Exception(stdout)
return stdout

How to open and search a file in a telnet session with Python

I'm using the following code to log into a server and go to a particular directory (where the logfile I want to search for a string resides). I have accomplished this with the Paramiko module (ssh), fairly straightforward. But the telnetlib module does not have many functions that I see to accomplish this. Does anyone know how I would open the file and search through it for a particular string (The server in question only allows Telnet logins - no ssh) ... Thanks:
import sys
import telnetlib
HOST = "10.28.46.14"
user = raw_input("Enter your username: ")
password = ""
tn = telnetlib.Telnet(HOST)
tn.read_until("login: ")
tn.write(user + "\n")
if password == "":
tn.read_until("Password: ")
tn.write(password + "\n")
#print "Login successful!"
else:
print "Your password is incorrect."
tn.write("cd /var/opt/mylog/log\n")
tn.write("ls\n")
tn.read_until("\n")
#tn.write("exit\n")
my_ls = tn.read_until("my.log")
print my_ls
Did you check with the owner of the machine about ssh vs telnet? There aren't many operating systems that ship with telnet out of the box anymore, because telnet is subject to replay attacks.
What if you tell tn to do a grep? You might append an "echo $?" after the grep, to get an exit code - 0 means there was one or more matches, anything else means there weren't any.

Categories

Resources