Python - 使用SSH遠(yuǎn)程登錄

一错蝴、SSH簡(jiǎn)介

SSH(Secure Shell)屬于在傳輸層上運(yùn)行的用戶層協(xié)議斗锭,相對(duì)于Telnet來(lái)說(shuō)具有更高的安全性草则。SSH是專為遠(yuǎn)程登錄會(huì)話和其他網(wǎng)絡(luò)服務(wù)提供安全性的協(xié)議屁桑。利用 SSH 協(xié)議可以有效防止遠(yuǎn)程管理過(guò)程中的信息泄露問(wèn)題庙洼。SSH最初是UNIX系統(tǒng)上的一個(gè)程序陨界,后來(lái)又迅速擴(kuò)展到其他操作平臺(tái)巡揍。SSH在正確使用時(shí)可彌補(bǔ)網(wǎng)絡(luò)中的漏洞。SSH客戶端適用于多種平臺(tái)菌瘪。幾乎所有UNIX平臺(tái)—包括HP-UX腮敌、LinuxAIX俏扩、Solaris糜工、Digital UNIXIrix录淡,以及其他平臺(tái)啤斗,都可運(yùn)行SSH。

二赁咙、SSH遠(yuǎn)程連接

SSH遠(yuǎn)程連接有兩種方式钮莲,一種是通過(guò)用戶名和密碼直接登錄,另一種則是用過(guò)密鑰登錄彼水。

1崔拥、用戶名和密碼登錄

老王要在自己的主機(jī)登錄老張的電腦,他可以通過(guò)運(yùn)行以下代碼來(lái)實(shí)現(xiàn)

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 跳過(guò)了遠(yuǎn)程連接中選擇‘是’的環(huán)節(jié),
ssh.connect('IP', 22, '用戶名', '密碼')
stdin, stdout, stderr = ssh.exec_command('df') print stdout.read()

?
????????在這里要用到paramiko模塊凤覆,這是一個(gè)第三方模塊链瓦,要自自己導(dǎo)入(要想使用paramiko模塊,還要先導(dǎo)入pycrypto模塊才能用)。

查看并啟動(dòng)ssh服務(wù)
service ssh status

添加用戶:useradd -d /home/zet zet
passwd zet
賦予ssh權(quán)限
vi /etc/ssh/sshd_config
添加
AllowUsers:zet

2慈俯、密鑰登錄

???????老王要在自己的主機(jī)登錄老張的電腦渤刃,老王用命令ssh.keygen -t rsa生成公鑰和私鑰,他將自己的公鑰發(fā)給老張贴膘,使用ssh-copy-id -i ~/ssh/id_rsa.pub laozhang@IP命令

然后運(yùn)行以下代碼來(lái)實(shí)現(xiàn)

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 
ssh.connect('IP', 22, '用戶名', key)
stdin, stdout, stderr = ssh.exec_command('df') print stdout.read()

???????關(guān)于密鑰登錄卖子,每個(gè)人都有一個(gè)公鑰,一個(gè)私鑰刑峡,公鑰是給別人的洋闽,私鑰是自己留著,只有自己的私鑰能解開(kāi)自己公鑰加密的文件突梦。

???????老王有一個(gè)機(jī)密文件要發(fā)給老張诫舅,就要先下載老張的公鑰進(jìn)行加密,這樣老張就能用自己私鑰解開(kāi)這份機(jī)密文件宫患,獲得內(nèi)容刊懈。

???????如果老張要確認(rèn)是否是老王本人給他的機(jī)密文件,就去下載一個(gè)老王的公鑰娃闲,隨機(jī)寫(xiě)一些字符虚汛,用老王的公鑰加密,發(fā)給老王畜吊,老王解密之后發(fā)回給老張,如果老張收到的解密后的字母和自己發(fā)出去的一樣户矢,對(duì)方就是老王無(wú)疑了玲献。

三、使用SSH連接服務(wù)器

客戶端代碼:

#-*- coding:utf8 -*-
 
import threading
import paramiko
import subprocess
 
def ssh_command(ip, user, passwd, command, port = 22):
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())    #設(shè)置自動(dòng)添加和保存目標(biāo)ssh服務(wù)器的ssh密鑰
    client.connect(ip, port, username=user, password=passwd)  #連接
    ssh_session = client.get_transport().open_session() #打開(kāi)會(huì)話
    if ssh_session.active:
        ssh_session.send(command)   #發(fā)送command這個(gè)字符串梯浪,并不是執(zhí)行命令
        print ssh_session.recv(1024)    #返回命令執(zhí)行結(jié)果(1024個(gè)字符)
        while True:
            command = ssh_session.recv(1024)    #從ssh服務(wù)器獲取命令
            try:
                cmd_output = subprocess.check_output(command, shell=True)
                ssh_session.send(cmd_output)
            except Exception, e:
                ssh_session.send(str(e))
        client.close()
    return
 
ssh_command('127.0.0.1', 'zet', 'zet', 'clientconnected',8001)

服務(wù)端代碼:

#-*- coding:utf8 -*-
 
import socket
import paramiko
import threading
import sys
 
# 使用 Paramiko示例文件的密鑰
#host_key = paramiko.RSAKey(filename='test_rsa.key')
host_key = paramiko.RSAKey(filename='/root/.ssh/id_rsa')
 
class Server(paramiko.ServerInterface):
    def __init__(self):
        self.event = threading.Event()
    def check_channel_request(self, kind, chanid):
        if kind == 'session':
             return paramiko.OPEN_SUCCEEDED
        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
    def check_auth_password(self, username, password):
        if (username == 'qing') and (password == 'qing'):
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED
 
server = sys.argv[1]
ssh_port = int(sys.argv[2])
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    #TCP socket
    #這里value設(shè)置為1捌年,表示將SO_REUSEADDR標(biāo)記為TRUE,操作系統(tǒng)會(huì)在服務(wù)器socket被關(guān)閉或服務(wù)器進(jìn)程終止后馬上釋放該服務(wù)器的端口挂洛,否則操作系統(tǒng)會(huì)保留幾分鐘該端口礼预。
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 8001))   #綁定ip和端口
    sock.listen(100)    #最大連接數(shù)為100
    print '[+] Listening for connection ...'
    client, addr = sock.accept()
except Exception, e:
    print '[-] Listen failed: ' + str(e)
    sys.exit(1)
print '[+] Got a connection!'
 
try:
    bhSession = paramiko.Transport(client)
    bhSession.add_server_key(host_key)
    server = Server()
    try:
        bhSession.start_server(server=server)
    except paramiko.SSHException, x:
        print '[-] SSH negotiation failed'
    chan = bhSession.accept(20) #設(shè)置超時(shí)值為20
    print '[+] Authenticated!'
    print chan.recv(1024)
    chan.send("Welcome to bh_ssh")
    while True:
        try:
            command = raw_input("Enter command:").strip("\n")   #strip移除字符串頭尾指定的字符(默認(rèn)為空格),這里是換行
            if command != 'exit':
                chan.send(command)
                print chan.recv(1024) + '\n'
            else:
                chan.send('exit')
                print 'exiting'
                bhSession.close()
                raise Exception('exit')
        except KeyboardInterrupt:
            bhSession.close()
except Exception, e:
    print '[-] Caught exception: ' + str(e)
    try:
        bhSession.close()
    except:
        pass
    sys.exit(1)

四、接下來(lái)是了解一下進(jìn)程的創(chuàng)建過(guò)程虏劲,用最原始的方式實(shí)現(xiàn)了一個(gè)ssh shell命令的執(zhí)行托酸。

#coding=utf8
 
'''
用python實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的shell,了解進(jìn)程創(chuàng)建
類unix 環(huán)境下 fork和exec 兩個(gè)系統(tǒng)調(diào)用完成進(jìn)程的創(chuàng)建
'''
 
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  # 返回值是一個(gè)16位的二進(jìn)制數(shù)字柒巫,高8位為退出狀態(tài)碼励堡,低8位為程序結(jié)束系統(tǒng)信號(hào)的編號(hào)
    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 %s@%s -p %s " % (user, host, port)
    ret = myspawn(cmdline)
 
 
if __name__ == "__main__":
    host = ''
    user = ''
    password = ''
    ssh(host, user, password=password)


一個(gè)SSH項(xiàng)目,需要在客戶端集成一個(gè)交互式ssh功能堡掏,大概就是客戶端跟服務(wù)器申請(qǐng)個(gè)可用的機(jī)器应结,服務(wù)端返回個(gè)ip,端口,密碼鹅龄, 然后客戶端就可以直接登錄到機(jī)器上操做了揩慕。該程序基于paramiko模塊。

???????經(jīng)查找扮休,從paramiko的源碼包demos目錄下迎卤,可以看到交互式shell的實(shí)現(xiàn),就是那個(gè)demo.py肛炮。但是用起來(lái)有些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) # 終端大小改變時(shí)碍扔,修改pty終端大小
    stdin = os.fdopen(sys.stdin.fileno(), 'r', 0) # stdin buff置為空,否則粘貼多字節(jié)或者按方向鍵的時(shí)候顯示不正確
    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信號(hào)將休眠的select系統(tǒng)調(diào)用喚醒引發(fā)的系統(tǒng)中斷秕重,忽略中斷重新調(diào)用解決不同。
                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)
#關(guān)閉連接
channel.close()
ssh.close()
interactive.py代碼中主要修復(fù)了幾個(gè)問(wèn)題:

1、當(dāng)讀取鍵盤輸入時(shí)溶耘,方向鍵會(huì)有問(wèn)題二拐,因?yàn)榘匆淮畏较蜴I會(huì)產(chǎn)生3個(gè)字節(jié)數(shù)據(jù),我的理解是按鍵一次會(huì)被select捕捉一次標(biāo)準(zhǔn)輸入有變化凳兵,但是我每次只處理1個(gè)字節(jié)的數(shù)據(jù)百新,其他的數(shù)據(jù)會(huì)存放在輸入緩沖區(qū)中,等待下次按鍵的時(shí)候一起發(fā)過(guò)去。這就導(dǎo)致了本來(lái)3個(gè)字節(jié)才能完整定義一個(gè)方向鍵的行為逐样,但是我只發(fā)過(guò)去一個(gè)字節(jié)翁涤,所以終端并不知道我要干什么。所以沒(méi)有變化铅辞,當(dāng)下次觸發(fā)按鍵,才會(huì)把上一次的信息完整發(fā)過(guò)去萨醒,看起來(lái)就是按一下方向鍵有延遲斟珊。多字節(jié)的粘貼也是一個(gè)原理。解決辦法是將輸入緩沖區(qū)置為0富纸,這樣就沒(méi)有緩沖囤踩,有多少發(fā)過(guò)去多少,這樣就不會(huì)有那種顯示的延遲問(wèn)題了晓褪。

2高职、終端大小適應(yīng)。paramiko.channel會(huì)創(chuàng)建一個(gè)pty(偽終端)辞州,有個(gè)默認(rèn)的大姓俊(width=80, height=24),所以登錄過(guò)去會(huì)發(fā)現(xiàn)能顯示的區(qū)域很小,并且是固定的埃元。編輯vim的時(shí)候尤其痛苦涝涤。channel中有resize_pty方法,但是需要獲取到當(dāng)前終端的大小岛杀。經(jīng)查找阔拳,當(dāng)終端窗口發(fā)生變化時(shí),系統(tǒng)會(huì)給前臺(tái)進(jìn)程組發(fā)送SIGWINCH信號(hào)类嗤,也就是當(dāng)進(jìn)程收到該信號(hào)時(shí)糊肠,獲取一下當(dāng)前size,然后再同步到pty中遗锣,那pty中的進(jìn)程等于也感受到了窗口變化货裹,也會(huì)收到SIGWINCH信號(hào)。

3精偿、讀寫(xiě)‘慢’設(shè)備(包括pipe弧圆,終端設(shè)備,網(wǎng)絡(luò)連接等)笔咽。讀時(shí)搔预,數(shù)據(jù)不存在,需要等待叶组;寫(xiě)時(shí)拯田,緩沖區(qū)滿或其他原因,需要等待甩十。ssh通道屬于這一類的船庇。本來(lái)進(jìn)程因?yàn)榫W(wǎng)絡(luò)沒(méi)有通信,select調(diào)用為阻塞中的狀態(tài)枣氧,但是當(dāng)終端窗口大小變化溢十,接收到SIGWINCH信號(hào)被喚醒垮刹。此時(shí)select會(huì)出現(xiàn)異常达吞,觸發(fā)系統(tǒng)中斷(4, 'Interrupted system call'),但是這種情況只會(huì)出現(xiàn)一次荒典,當(dāng)重新調(diào)用select方法又會(huì)恢復(fù)正常酪劫。所以捕獲到select異常后重新進(jìn)行select可以解決該問(wèn)題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寺董,一起剝皮案震驚了整個(gè)濱河市覆糟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌遮咖,老刑警劉巖滩字,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡麦箍,警方通過(guò)查閱死者的電腦和手機(jī)漓藕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挟裂,“玉大人享钞,你說(shuō)我怎么就攤上這事【魅兀” “怎么了栗竖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)渠啤。 經(jīng)常有香客問(wèn)我狐肢,道長(zhǎng),這世上最難降的妖魔是什么埃篓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任处坪,我火速辦了婚禮,結(jié)果婚禮上架专,老公的妹妹穿的比我還像新娘同窘。我一直安慰自己,他們只是感情好部脚,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布想邦。 她就那樣靜靜地躺著,像睡著了一般委刘。 火紅的嫁衣襯著肌膚如雪丧没。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天锡移,我揣著相機(jī)與錄音呕童,去河邊找鬼。 笑死淆珊,一個(gè)胖子當(dāng)著我的面吹牛夺饲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播施符,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼往声,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了戳吝?” 一聲冷哼從身側(cè)響起浩销,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎听哭,沒(méi)想到半個(gè)月后慢洋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體塘雳,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年普筹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粉捻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斑芜,死狀恐怖肩刃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杏头,我是刑警寧澤盈包,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站醇王,受9級(jí)特大地震影響呢燥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寓娩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一叛氨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棘伴,春花似錦寞埠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至阱穗,卻和暖如春饭冬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背揪阶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工昌抠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲁僚。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓炊苫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蕴茴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子劝评,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345