1. 程式人生 > >python獲取互動式ssh shell

python獲取互動式ssh shell

更新,最近在學unix環境程式設計,瞭解一下程序的建立過程,用最原始的方式實現了一個ssh命令的執行。

#coding=utf8

'''
用python實現了一個簡單的shell,瞭解程序建立
類unix 環境下 fork和exec 兩個系統呼叫完成程序的建立
'''

import sys, os


def myspawn(cmdline):
    argv = cmdline.split()
    if len(argv) == 0:
        return 
    program_file = argv[0]
    pid = os.fork()
    if pid < 0:
        sys.stderr.write("fork error")
    elif pid == 0:
        # child
        os.execvp(program_file, argv)
        sys.stderr.write("cannot exec: "+ cmdline)
        sys.exit(127)
    # parent
    pid, status = os.waitpid(pid, 0)
    ret = status >> 8  # 返回值是一個16位的二進位制數字,高8位為退出狀態碼,低8位為程式結束系統訊號的編號
    signal_num = status & 0x0F
    sys.stdout.write("ret: %s, signal: %s\n" % (ret, signal_num))
    return ret


def ssh(host, user, port=22, password=None):
    if password:
        sys.stdout.write("password is: '%s' , plz paste it into ssh\n" % (password))
    cmdline = "ssh %
[email protected]
%s -p %s " % (user, host, port) ret = myspawn(cmdline) if __name__ == "__main__": host = '' user = '' password = '' ssh(host, user, password=password)

#########################################################################

最近在做一個專案,需要在客戶端整合一個互動式ssh功能,大概就是客戶端跟伺服器申請個可用的機器,服務端返回個ip,埠,密碼, 然後客戶端就可以直接登入到機器上操做了。該程式基於paramiko模組。

經查詢,從paramiko的原始碼包demos目錄下,可以看到互動式shell的實現,就是那個demo.py。但是用起來有些bug,於是我給修改了一下interactive.py(我把windows的程式碼刪掉了,剩下的只能在linux下用)。程式碼如下:

#coding=utf-8
import socket
import sys
import os
import termios
import tty
import fcntl
import signal
import struct
import select

now_channel = None

def interactive_shell(chan):
    posix_shell(chan)


def ioctl_GWINSZ(fd):
    try:
        cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'aaaa'))
    except:
        return
    return cr


def getTerminalSize():
    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
    return int(cr[1]), int(cr[0])
    

def resize_pty(signum=0, frame=0):
    width, height = getTerminalSize()
    if now_channel is not None:
        now_channel.resize_pty(width=width, height=height)



def posix_shell(chan):
    global now_channel
    now_channel = chan
    resize_pty()
    signal.signal(signal.SIGWINCH, resize_pty) # 終端大小改變時,修改pty終端大小
    stdin = os.fdopen(sys.stdin.fileno(), 'r', 0) # stdin buff置為空,否則貼上多位元組或者按方向鍵的時候顯示不正確
    fd = stdin.fileno()
    oldtty = termios.tcgetattr(fd)
    newtty = termios.tcgetattr(fd)
    newtty[3] = newtty[3] | termios.ICANON
    try:
        termios.tcsetattr(fd, termios.TCSANOW, newtty)
        tty.setraw(fd)
        tty.setcbreak(fd)
        chan.settimeout(0.0)
        while True:
            try:
                r, w, e = select.select([chan, stdin], [], [])
            except:
                # 解決SIGWINCH訊號將休眠的select系統呼叫喚醒引發的系統中斷,忽略中斷重新呼叫解決。
                continue
            if chan in r:
                try:
                    x = chan.recv(1024)
                    if len(x) == 0:
                        print 'rn*** EOFrn',
                        break
                    sys.stdout.write(x)
                    sys.stdout.flush()
                except socket.timeout:
                    pass
            if stdin in r:
                x = stdin.read(1)
                if len(x) == 0:
                    break
                chan.send(x)
    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)

使用示例:

#coding=utf8
import paramiko
import interactive


#記錄日誌
paramiko.util.log_to_file('/tmp/aaa')
#建立ssh連線
ssh=paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('192.168.1.11',port=22,username='hahaha',password='********',compress=True)

#建立互動式shell連線
channel=ssh.invoke_shell()
#建立互動式管道
interactive.interactive_shell(channel)
#關閉連線
channel.close()
ssh.close()


interactive.py程式碼中主要修復了幾個問題:

1、當讀取鍵盤輸入時,方向鍵會有問題,因為按一次方向鍵會產生3個位元組資料,我的理解是按鍵一次會被select捕捉一次標準輸入有變化,但是我每次只處理1個位元組的資料,其他的資料會存放在輸入緩衝區中,等待下次按鍵的時候一起發過去。這就導致了本來3個位元組才能完整定義一個方向鍵的行為,但是我只發過去一個位元組,所以終端並不知道我要幹什麼。所以沒有變化,當下次觸發按鍵,才會把上一次的資訊完整發過去,看起來就是按一下方向鍵有延遲。多位元組的貼上也是一個原理。解決辦法是將輸入緩衝區置為0,這樣就沒有緩衝,有多少發過去多少,這樣就不會有那種顯示的延遲問題了。

2、終端大小適應。paramiko.channel會建立一個pty(偽終端),有個預設的大小(width=80, height=24),所以登入過去會發現能顯示的區域很小,並且是固定的。編輯vim的時候尤其痛苦。channel中有resize_pty方法,但是需要獲取到當前終端的大小。經查詢,當終端視窗發生變化時,系統會給前臺程序組傳送SIGWINCH訊號,也就是當程序收到該訊號時,獲取一下當前size,然後再同步到pty中,那pty中的程序等於也感受到了視窗變化,也會收到SIGWINCH訊號。

3、讀寫‘慢’裝置(包括pipe,終端裝置,網路連線等)。讀時,資料不存在,需要等待;寫時,緩衝區滿或其他原因,需要等待。ssh通道屬於這一類的。本來程序因為網路沒有通訊,select呼叫為阻塞中的狀態,但是當終端視窗大小變化,接收到SIGWINCH訊號被喚醒。此時select會出現異常,觸發系統中斷(4, 'Interrupted system call'),但是這種情況只會出現一次,當重新呼叫select方法又會恢復正常。所以捕獲到select異常後重新進行select可以解決該問題。