I'm writing a Python script that changes the username and password of a Linux account user - it's part of a larger internal web-gui system that queues up password change requests from apache2 (which can't run as root), and then changes the passwords itself. The python script itself obviously must run as root in order to change passwords.
The password change function is pretty straightforward:
def chpasswd(user, passwd):
if os.getuid() != 0:
syslog.syslog("Error: chpasswd.py must be run as root")
return
proc = Popen(
['/usr/sbin/chpasswd'],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
print "Changing: " + user + ':' + passwd
out, err = proc.communicate(user + ':' + passwd)
proc.wait()
print out
if proc.returncode != 0:
print "Error: Return code", proc.returncode, ", stderr: ", out, err
if out:
syslog.syslog("stdout: " + out)
if err:
syslog.syslog("stderr: " + err)
The print statements are just there for temporary debugging. This runs fine and doesn't report any errors - there's nothing on out or err; but for some reason the actual UNIX password simply isn't changed.
The script which invokes this function is listening on a locally bound TCP socket. When it receives a change password request (in the form of user:password - later to be encrypted but for now plaintext) it adds it to a queue and then invokes the chpasswd function.
So, typical usage would be:
# telnet localhost 7001
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
jsmith:mynewpassword
When the server is running in a bash window (not as a daemon) it prints out:
# python chpasswd.py
Starting Password Server
New connection from: 127.0.0.1
Changing: jsmith:mynewpassword
The last statement, you can see, is the print statement in my chpasswd function.
But after doing the above, when I actually try to login as a user using the new password, I get:
$ su jsmith
Password:
su: Authentication failure
Is there some obvious thing I'm doing wrong here? My suspicion was that somehow the connection with Popen is not actually closing, or perhaps the single line user:password text is not being transmitted. So I tried doing something like:
out, err = proc.communicate(user + ':' + passwd + '\x04')
Notice the extra \x04 character at the end, indicating End Of Transmission. Adding this in still didn't get it to work however - the password remained unchanged.
I'm running this on Debian Wheezy, in case it makes any difference.
Update:
Investigating further, I can see that my chpasswd function actually is changing the password - if I cat the /etc/shadow file before and after connecting to my password server, I see there is a different hash.
It's just that when I try to authenticate using the plaintext password, it doesn't work. Therefore, my suspicion is that somehow, the communication with Popen is either adding additional characters, or losing characters somehow. Of course, since /etc/shadow is a hash, I can't figure out exactly what's going on here.
The problem in this particular instance was that telnet adds "\r\n" after you press return on entering text. Since your server was not stripping the data of whitespace this was preserved when changing the password.
It is possible to get telnet to not send the carriage return and newline characters by ending a line with the end-of-transmission character (EOT). You can do this by pressing Ctrl-D.
eg
$ telnet localhost 7001
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
jsmith:mynewpassword^DChanging: jsmith:mynewpassword
Alternatively you can pipe the line into telnet
echo -n jsmith:mynewpassword | telnet localhost 7001
Obviously, you'll only want to do this for testing or the new password will end up in your shell history. (The -n argument suppresses the printing of newline characters by echo)
Or you might want to do away with telnet altogether and use netcat instead.
echo -n jsmith:mynewpassword | netcat localhost 7001
Related
I need to parse the output of a command on a remote machine I have to connect via SSH.
This remote machine runs Ubuntu and I can only access the SSH via a "console wrapper" (sorry don't know the exact term for it) called BOSCLI in which I can only run a set of specific commands.
On connect I get a prompt for sudo password, after entered I'm at the prompt and I do not need to enter it again.
At first I started using exec_command which didn't work, for obvious reasons. I have switched now to invoke_shell() and then using send() but only the password prompt is sent, and not the following command.
Of course I've read a lot of other questions here and other websites with no success...
def conectar(url,user,passw, puerto, sudoPass):
cliente = paramiko.SSHClient()
cliente.set_missing_host_key_policy(paramiko.AutoAddPolicy())
cliente.connect(url,port=puerto, username=user, password=passw)
if cliente.get_transport() is None: raise Exception(paramiko.SSHException)
time.sleep(2)
canal = cliente.invoke_shell()
stdin = canal.makefile('wb')
stdout = canal.makefile('rb')
stderr = canal.makefile_stderr('r')
while not canal.recv_ready():
time.sleep(2)
aux = canal.send(sudoPass+'\n') #sudo pass
out = canal.recv(1024)
print(aux)
time.sleep(1)
aux = canal.send('''dhcp pool status\n''')
print(aux)
out += canal.recv(9999)
#ssh_stdin, ssh_stdout, ssh_stderr = cliente.exec_command('dhcp pool status',get_pty=True)
#ssh_stdout.channel.recv_exit_status()
cliente.close()
print(stdout.read())
print(stderr.read())
print(out.decode('ascii'))
The output should be a long text with all the DHCP statistics on the different pools for the next method to parse, however I'm receiving empty outputs.
There's a thing also that is confusing me the most right now which is that actually 'out' has content (which is the welcome MOTD, etc on the shell), but stdout is empty.
print(aux) returns 9 first
print(aux) returns 17 afterwards.
print(stdout.read()) returns b''
print(stderr.read()) returns b''
out content is the following:
Welcome to Ubuntu 12.04.5 LTS (GNU/Linux 3.13.0-66-generic x86_64)
* Documentation: https://help.ubuntu.com/
System information as of Tue Jul 2 11:34:22 CEST 2019
System load: 0.42 Users logged in: 0
Usage of /: 32.9% of 26.51GB IP address for eth0:
Memory usage: 22% IP address for eth1:
Swap usage: 4% IP address for eth2:
Processes: 194 IP address for docker0:
Graph this data and manage this system at:
https://landscape.canonical.com/
Last login: Tue Jul
[sudo] password for bos:
(pho-xem1) (Nuevo) bcli 0 [] normal>
Which is the command prompt after passing the sudo pass.
You probably send the command too early, before the server (or actually the boscli shell) expects it.
You should wait for the prompt, before you send the command.
Or as a quick and dirty hack, just wait for a short interval.
As for the stdout: stdout is just a wrapper around Channel.recv. As you are already consuming the output in Channel.recv, you won't get anything more in stdout. Either read stdout or use Channel.recv, but not both.
I'm modifying a script in python to run in securecrt 8.5.2 in order to backup the running-config of some cisco ASR9K equipment I have in charge, but the script seems to end abruptly after the second sucessful ssh2 hop (2nd tab) and does not send the commands I scripted (the exit in this specifical example), here's the code I have, as I'd said it's a modified version of the one's in vandyke page for opening ssh2.
One important thing is that I have to tab each session of each individual routers, because it doesn't permit doing an ssh direct from the active cli, so I had to improvise and implement this "connect in TAB", I'm suspecting that the secureCRT doesn't know if it is in the new tab I've opened so, it doesn't know where to send the commands.
I was playing with the line 30, but it doesn't seem to have any effect. I was changing the expected text, but it doesn't seem to recognize the correct tab or doesn't read the correct one.
Personal Background: A complete beginner in the python language.
# $language = "python"
# $interface = "1.0"
# Connect to an SSH server using the SSH2 protocol. Specify the
# username and password and hostname on the command line as well as
# some SSH2 protocol specific options.
host = "X.X.X.a"
host2 = "X.X.X.b"
def main():
crt.Screen.Synchronous = True
# Prompt for a username and password instead of embedding it in a script...
#
usr = crt.Dialog.Prompt("Enter the user name for" + host, "Username", "", True)
passwd = crt.Dialog.Prompt("Enter TACACS+ for" + host, "Login", "", True)
# Build a command-line string to pass to the Connect method.
cmd = "/SSH2 /L %s /PASSWORD %s /C AES-128-CTR /M SHA1 %s" % (usr, passwd, host)
crt.Session.Connect(cmd)
crt.Screen.WaitForString("X.X.X.a#")
crt.Screen.Send("copy running-config tftp:\r")
crt.Screen.WaitForString("Host name or IP address (control-c to abort): []?")
crt.Screen.Send("tftpserver.com\r")
crt.Screen.WaitForString("Destination file name (control-c to abort): [running-config]?")
crt.Screen.Send("X.X.X.a_running_config\r")
crt.Screen.WaitForString("X.X.X.a")
cmd2 = "/SSH2 /L %s /PASSWORD %s /C AES-128 /M SHA1 %s" % (usr, passwd, host2)
crt.Session.ConnectInTab(cmd2)
crt.Screen.WaitForString("X.X.X.b#")
crt.Screen.Send("exit\r")
main()
crt.Session.ConnectInTab(cmd2)
It connects to the equipment in a new tab, but what I expect is that the script will keep doing the same it did for the host1 (X.X.X.a) and send the same boring stuff to the host2 (X.X.X.b) via ssh2 tab, and continue the itterative process until I do this for all the equipments I need.
Thanks for reading me.
Well it's not even funny easy was to solve this very sub-optimal code or script but it was not much of a problem, the only thing is that I had to dissconect to the previous session when I inyected all the commands, so in order to put the cursor on the new tab, the previous session must be dissconected first.
The solution?
crt.Session.Disconnect()
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.
I am using this code for executing command on remote server.
import subprocess
import sys
COMMAND="ls"
ssh = subprocess.Popen(["ssh", "%s" % HOST, COMMAND],
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result = ssh.stdout.readlines()
if result == []:
error = ssh.stderr.readlines()
print >>sys.stderr, "ERROR: %s" % error
else:
print result
When I try to execute this script, I get prompt for password. Is there any way I could avoid it, for example, can I enter password in script somehow? Also, password should be encrypted somehow so that people who have access to the script cannot see it.
Why make it so complicated? Here's what I suggest:
1) Create a ssh config section in your ~/.ssh/config file:
Host myserver
HostName 50.50.50.12 (fill in with your server's ip)
Port xxxx (optional)
User me (your username for server)
2) If you have generated your ssh keypair do it now (with ssh-keygen). Then upload with:
$ ssh-copy-id myserver
3) Now you can use subprocess with ssh. For example, to capture output, I call:
result = subprocess.check_output(['ssh', 'myserver', 'cat', 'somefile'])
Simple, robust, and the only time a password is needed is when you copy the public key to the server.
BTW, you code will probably work just fine as well using these steps.
One way is to create a public key, put it on the server, and do ssh -i /path/to/pub/key user#host or use paramiko like this:
import paramiko
import getpass
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
p = getpass.getpass()
ssh.connect('hostname', username='user', password=p)
stdin, stdout, stderr = ssh.exec_command('ls')
print stdout.readlines()
ssh.close()
You should use pexpect or paramiko to connect to remote machine,then spawn a child ,and then run subprocess to achieve what you want.
Here's what I did when encountering this issue before:
Set up your ssh keys for access to the server.
Set up an alias for the server you're accessing. Below I'll call it remote_server.
Put the following two lines at the end of ~/.bash_profile.
eval $(ssh-agent -s)
ssh-add
Now every time you start your shell, you will be prompted for a passphrase. By entering it, you will authenticate your ssh keys and put them 'in hand' at the start of your bash session. For the remainder of your session you will be able to run commands like
ssh remote_server ls
without being prompted for a passphrase. Here ls will run on the remote server and return the results to you. Likewise your python script should run without password prompt interruption if you execute it from the shell.
You'll also be able to ssh to the server just by typing ssh remote_server without having to enter your username or password every time.
The upside to doing it this way is that you should be doing this anyway to avoid password annoyances and remembering funky server names :) Also you don't have to worry about having passwords saved anywhere in your script. The only potential downside is that if you want to share the python script with others, they'll have to do this configuring as well (which they should anyway).
You don't really need something like pexpect to handle this. SSH keys already provide a very good and secure solution to this sort of issue.
The simplest way to get the results you want would probably be to generate an ssh key and place it in the .ssh folder of your device. I believe github has a pretty good guide to doing that, if you look into it. Once you set up the keys correctly on both systems, you won't actually have to add a single line to your code. When you don't specify a password it will automatically use the key to authenticate you.
While subprocess.Popen might work for wrapping ssh access, this is not the preferred way to do so.
I recommend using paramiko.
import paramiko
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(server, username=user,password=password)
...
ssh_client.close()
And If you want to simulate a terminal, as if a user was typing:
chan=ssh_client.invoke_shell()
def exec_cmd(cmd):
"""Gets ssh command(s), execute them, and returns the output"""
prompt='bash $' # the command line prompt in the ssh terminal
buff=''
chan.send(str(cmd)+'\n')
while not chan.recv_ready():
time.sleep(1)
while not buff.endswith(prompt):
buff+=ssh_client.chan.recv(1024)
return buff[:len(prompt)]
Example usage: exec_cmd('pwd')
If you don't know the prompt in advance, you can set it with:
chan.send('PS1="python-ssh:"\n')
You could use following.
import subprocess
import sys
COMMAND="ls"
ssh = subprocess.Popen("powershell putty.exe user#HOST -pw "password", stdout=PIPE, stdin=PIPE, stderr=STDOUT)
result = ssh.stdout.readlines()
if result == []:
error = ssh.stderr.readlines()
print >>sys.stderr, "ERROR: %s" % error
else:
print result
I'm a relatively new programmer in Python, and I have created this XMLRPC Server function as follows:
def shell(self, command, username):
if username in loggedIn:
return os.system(command)
else:
string = time.asctime() , " not logged in"
string = "".join(string)
return string
For the client side, I have written
command = raw_input ("$ ")
if command == "exit":
exit()
else:
server.shell(command, username)
However, when I run the command in the client program, the output would be in the server window and not the client window, something like this:
#client side
$ ls
#server side
#some results
localhost - - [14/Feb/2013 14:26:25] "POST /RPC2 HTTP/1.0" 200 -
And the cd command is also broken (i.e. I couldn't change to other directories even when the command is issued). Is there any way of doing so, and if so, how?
os.system doesn't return output to the command line, only the return code. If you want to capture stdout/stderr, you have to use subprocess.Popen. You can then also provide a working directory to execute your command in (using the cwd argument). As far as I know, cd might work in the shell, but won't change the working directory for the python instance. So the next call will not run in the directory you changed to. Using Popen with cwd should work.