import paramiko
import sys,os,socket,getpass,logging
try:
import termios
import tty
has_termios = True
except ImportError:#windows没有termios,可以通过它来判断程序当前运行环境
has_termios = False
def interactive_shell(chan,logger):
initLog(logger)
if has_termios:
posix_shell(chan,logger)
else:
windows_shell(chan,logger)
def initLog(logger):
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('command-history.log')
fh.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)
def posix_shell(chan,logger):
import select
#获取当前tty属性,用于退出后恢复
oldtty = termios.tcgetattr(sys.stdin)
try:
#改变tty为原始模式,不认识回车/ctrl+c等特殊符号(不会触发特殊按键对应的功能),以便把用户输入原封不动全部发往远程服务器
tty.setraw(sys.stdin.fileno())
tty.setcbreak(sys.stdin.fileno())
chan.settimeout(0.0)
flag = False
temp_list = []#列表的形式,一个一个字符地收集一条命令字符串,最后拼成一条命令
while True:
#同时监听用户输入和远程服务器返回数据
#select会阻塞到其中有一个句柄可读,中间两个参数在这没用,最后一个参数表是最多阻塞1秒不填就表示一直阻塞
r,w,e = select.select([chan,sys.stdin],[],[])
if chan in r:
try:
x = chan.recv(1024).decode()
if len(x) == 0:#有可能远程终端断开了
print('\r\n***EOF***\r\n')
break
#如果上一次循环时用户输入了tab,则返回的是命令补全的字符,可以利用它记录命令日志
if flag:
if x.startswith('\r\n'):
pass
else:
temp_list.append(x)
flag = False
#把返回结果一次性输出到本地终端
sys.stdout.write(x)
sys.stdout.flush()
except socket.timeout:
pass
if sys.stdin in r:
x = sys.stdin.read(1)
if len(x) == 0:
break
if x == '\t':#tab符,表示使用命令补全,不能把tab符号加入temp_list
flag = True
else:#不是tab符,表示命令还没输入完全
temp_list.append(x)
if x == '\r':#回车符,表示执行命令
logger.debug(''.join(temp_list))
temp_list.clear()#清空等待下一条命令
#逐字符把用户输入发送给远程服务器
chan.send(x)
except Exception as ex:
print(ex)
finally:
#退出时重置当前tty的属性
termios.tcsetattr(sys.stdin,termios.TCSADRAIN,oldtty)
def windows_shell(chan,logger):
import threading
sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")
def writeall(sock):
while True:
data = sock.recv(256).decode()
if not data:
sys.stdout.write('\r\n*** EOF ***\r\n\r\n')
sys.stdout.flush()
break
sys.stdout.write(data)
sys.stdout.flush()
writer = threading.Thread(target=writeall, args=(chan,))
writer.start()
try:
while True:
d = sys.stdin.read(1)
if not d:
break
chan.send(d)
except EOFError:
# user hit ^Z or F6
pass
def run():
#获取当前系统登录用户名
default_username = getpass.getuser()
username = input('Username [%s]: ' % default_username)
if len(username) == 0:
username = default_username
hostname = input('Hostname: ')
if len(hostname) == 0:
print('***Hostname required***')
sys.exit(1)
tran = paramiko.Transport((hostname,22))
tran.start_client()
default_auth = 'p'
auth = input('Auth by (p)assword or (r)sa key[%s]: ' % default_auth)
if len(auth) == 0:
auth = default_auth
if auth == 'r':
default_path = os.path.join(os.environ['HOME'],'.ssh','id_rsa')
path = input('RSA key[%s]: ' % default_path)
if len(path) == 0:
path = default_path
try:
key = paramiko.RSAKey.from_private_key_file(path)
except paramiko.PasswordRequiredException:
password = getpass.getpass('RSA key password: ')#密钥文件需要密码,提示用户输入密码
key = paramiko.RSAKey.from_private_key_file(path,password)
tran.auth_publickey(username,key)
else:
#print(username,hostname)
pw = getpass.getpass(prompt='Password for %s@%s: ' % (username,hostname))
tran.auth_password(username,pw)
#打开通道,建立与远程服务器的持久连接
chan = tran.open_session()
chan.get_pty()
chan.invoke_shell()
#用于记录命令历史到日志文件
logger = logging.getLogger('history-log')
interactive_shell(chan,logger)
chan.close()
tran.close()
if __name__ == '__main__':
run()