如何在paramiko中首次调用命令时设置密码?
我有一个用户叫 test
。
我用 chage
命令设置了当这个用户登录时需要更改密码。
chage -E 2012-01-25 -M 30 -d 0 -W 10 -I 5 test
所以当我尝试运行命令 ls
时,
[root@localhost ~]# ssh test@localhost "ls"
WARNING: Your password has expired.
Password change required but no TTY available.
You have new mail in /var/spool/mail/root
然后我尝试用 ssh
连接,
[root@localhost ~]# ssh test@localhost
You are required to change your password immediately (root enforced)
Last login: Tue Dec 27 09:55:55 2011 from localhost
WARNING: Your password has expired.
You must change your password now and login again!
Changing password for user test.
Changing password for test.
(current) UNIX password:
接着我可以为这个用户设置密码。
如果我尝试用 paramiko
连接,
In [1]: import paramiko
In [2]: ssh_conn = paramiko.SSHClient()
In [3]: ssh_conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
In [4]: ssh_conn.load_system_host_keys()
In [5]: ssh_conn.connect('n2001', username='root_acc23', password='test')
In [6]: a = ssh_conn.exec_command('ls')
In [7]: print a[2].read()
WARNING: Your password has expired.
Password change required but no TTY available.
我在网上查了一下,找到了一些用 invoke_shell
设置新密码的解决方案,所以我写了一个函数。
def chage_password_change(ssh_conn, password, curr_pass):
'''
If got error on login then set with interactive mode.
'''
interact = ssh_conn.invoke_shell()
buff = ''
while not buff.endswith('UNIX password: '):
resp = interact.recv(9999)
buff += resp
interact.send(curr_pass + '\n')
buff = ''
while not buff.endswith('New password: '):
resp = interact.recv(9999)
buff += resp
interact.send(password + '\n')
buff = ''
while not buff.endswith('Retype new password: '):
resp = interact.recv(9999)
buff += resp
interact.send(password + '\n')
interact.shutdown(2)
if interact.exit_status_ready():
print "EXIT :", interact.recv_exit_status()
print "Last Password"
print "LST :", interact.recv(-1)
这个函数在某些情况下是有效的,比如当我们输入的密码包含数字、字母和特殊字符的组合时。
但是当我们输入一个短密码或者在更改密码时出现错误,比如这样:
[root@localhost ~]# ssh test@localhost
You are required to change your password immediately (root enforced)
Last login: Tue Dec 27 10:41:15 2011 from localhost
WARNING: Your password has expired.
You must change your password now and login again!
Changing password for user test.
Changing password for test.
(current) UNIX password:
New password:
Retype new password:
BAD PASSWORD: it is too short
在这个命令中,我们得到了错误 BAD PASSWORD: it is too short。所以我在我的函数中无法判断这个错误。我在执行 interact.recv(-1)
时收到了这个错误,但我认为这是标准输出。那有没有办法判断这就是错误呢?
我查看了 paramiko 的文档,发现 Channel
类有一些方法 recv_stderr_ready
和 recv_stderr
,但是这个错误没有出现在那些数据中。
提前谢谢你的帮助。
3 个回答
以下这些代码可能会有问题,可能会导致一些错误:
while not buff.endswith('Retype new password: '):
resp = interact.recv(9999)
buff += resp // this will append the output from the shell
修复代码:
这样使用会更好
while not buff.endswith('Retype new password: '):
resp = interact.recv(9999)
buff = resp
现在每次循环时,会解析当前更新后的输出文本来自命令行。
祝好,
Eldad
如果你在找一种过期密码更改的方法,这个也许可以用得上。
import time
from contextlib import closing
import paramiko
def wait_until_channel_endswith(channel, endswith, wait_in_seconds=15):
"""Continues execution if the specified string appears at the end of the channel
Raises: TimeoutError if string cannot be found on the channel
"""
timeout = time.time() + wait_in_seconds
read_buffer = b''
while not read_buffer.endswith(endswith):
if channel.recv_ready():
read_buffer += channel.recv(4096)
elif time.time() > timeout:
raise TimeoutError(f"Timeout while waiting for '{endswith}' on the channel")
else:
time.sleep(1)
def change_expired_password_over_ssh(host, username, current_password, new_password):
"""Changes expired password over SSH with paramiko"""
with closing(paramiko.SSHClient()) as ssh_connection:
ssh_connection.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_connection.connect(hostname=host, username=username, password=current_password)
ssh_channel = ssh_connection.invoke_shell()
wait_until_channel_endswith(ssh_channel, b'UNIX password: ')
ssh_channel.send(f'{current_password}\n')
wait_until_channel_endswith(ssh_channel, b'New password: ')
ssh_channel.send(f'{new_password}\n')
wait_until_channel_endswith(ssh_channel, b'Retype new password: ')
ssh_channel.send(f'{new_password}\n')
wait_until_channel_endswith(ssh_channel, b'all authentication tokens updated successfully.\r\n')
使用方法:
change_expired_password_over_ssh('192.168.1.1', 'username', 'expired-password', 'new-password')
简单来说,你可以让你的函数在调用外部命令之前,先检查一下密码的长度,如果你知道一个合适的长度限制的话。这样做性能也更好。不过,如果你不知道这个长度限制,那就没办法了。
从你的描述来看,我不太明白,但如果你从 interact.recv(-1) 收到的消息是“密码不正确”,那就说明出错了,你可以根据这个信息继续处理。这个消息应该是从标准错误输出(stderr)或者标准输出(stdout)中返回的,所以你要检查这两个地方。如果你知道如果新密码被接受时会返回什么文本,那你也可以检查这个;你先收到哪个消息就说明发生了什么,然后你的函数可以根据这个继续执行。