Chinaunix首页 | 论坛 | 博客
  • 博客访问: 52931
  • 博文数量: 11
  • 博客积分: 185
  • 博客等级: 入伍新兵
  • 技术积分: 135
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-09 11:07
文章分类

全部博文(11)

文章存档

2020年(1)

2013年(10)

我的朋友

分类: Python/Ruby

2013-01-09 13:51:48

能不能使用简单的ssh访问方法?
只有SSH2 lib? 它依赖PyCrypto,而这个PyCrypto在windows安装时,竟然需要编译环境!
于是我找来找去, 找来找去...
终于有了发现, putty有plink 和pscp, 就是为控制台方式提供命令. 不依赖其他库.
几经挫折后, 我终于封装了SSHUtil.实现了基本的功能.
1 实现python调用,
通过subprocess来完成
2 实现unix下使用,
编译了一个unix版本的.
3 实现密码输入,
通过-pw参数实现
4 实现rsa证书下载,
通过echo实现.
5 实现Expect对象,
通过注入Expect,和字符匹配,
总之, 可以基本完成ssh的功能,并可以上传下载文件.
代码随后贴出.
 

 
 
subprocess 模块, 采用国际友人的方法, 可以实时得到执行的结果, 方便制作Expect应答对象
原文在这里:
其主要目的是, 利用一个独立的循环来不断获取由stdout输出. 并通过stdin发送命令.
已知的问题是, 这样建立的ssh通道, 不能由两个线程来操作, 会出现字符乱, 并操作异常的问题, 主要是由于
多个线程都检测stdout, 造成输出交错, 线程不好判断自己的命令是否完成. 因此, 如果复杂的ssh控制, 最好
开发一个独立服务, 来统一发送命令和接受返回, 像连接托管服务, 后面要重构这部分代码, 改成这种模式.
 
贴一下这个模块, 想了解最新的, 可以通过上面的连接访问.
import os
import subprocess
import errno
import re

PIPE = subprocess.PIPE

if subprocess.mswindows:
    from win32file import ReadFile, WriteFile
    from win32pipe import PeekNamedPipe
    import msvcrt
else:
    import select
    import fcntl

class Popen(subprocess.Popen):
    def recv(self, maxsize=None):
        return self._recv('stdout', maxsize)

    def recv_err(self, maxsize=None):
        return self._recv('stderr', maxsize)

    def send_recv(self, input='', maxsize=None):
        return self.send(input), self.recv(maxsize), self.recv_err(maxsize)

    def get_conn_maxsize(self, which, maxsize):
        if maxsize is None:
            maxsize = 1024
        elif maxsize < 1:
            maxsize = 1
        return getattr(self, which), maxsize

    def _close(self, which):
        getattr(self, which).close()
        setattr(self, which, None)

    if subprocess.mswindows:
        def send(self, input):
            if not self.stdin:
                return None

            try:
                x = msvcrt.get_osfhandle(self.stdin.fileno())
                (errCode, written) = WriteFile(x, input)
            except ValueError:
                return self._close('stdin')
            except (subprocess.pywintypes.error, Exception), why:
                if why[0] in (109, errno.ESHUTDOWN):
                    return self._close('stdin')
                raise

            return written

        def _recv(self, which, maxsize):
            conn, maxsize = self.get_conn_maxsize(which, maxsize)
            if conn is None:
                return None

            try:
                x = msvcrt.get_osfhandle(conn.fileno())
                (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
                if maxsize < nAvail:
                    nAvail = maxsize
                if nAvail > 0:
                    (errCode, read) = ReadFile(x, nAvail, None)
            except ValueError:
                return self._close(which)
            except (subprocess.pywintypes.error, Exception), why:
                if why[0] in (109, errno.ESHUTDOWN):
                    return self._close(which)
                raise

            if self.universal_newlines:
                read = self._translate_newlines(read)
            return read

    else:
        def send(self, input):
            if not self.stdin:
                return None

            if not select.select([], [self.stdin], [], 0)[1]:
                return 0

            try:
                written = os.write(self.stdin.fileno(), input)
            except OSError, why:
                if why[0] == errno.EPIPE: #broken pipe
                    return self._close('stdin')
                raise

            return written

        def _recv(self, which, maxsize):
            conn, maxsize = self.get_conn_maxsize(which, maxsize)
            if conn is None:
                return None

            flags = fcntl.fcntl(conn, fcntl.F_GETFL)
            if not conn.closed:
                fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)

            try:
                if not select.select([conn], [], [], 0)[0]:
                    return ''

                r = conn.read(maxsize)
                if not r:
                    return self._close(which)

                if self.universal_newlines:
                    r = self._translate_newlines(r)
                return r
            finally:
                if not conn.closed:
                    fcntl.fcntl(conn, fcntl.F_SETFL, flags)

    @staticmethod
    def recv_some(p, t=.1, e=1, tr=5, stderr=0):
        if tr < 1:
            tr = 1
        x = time.time()+t
        y = []
        r = ''
        pr = p.recv
        if stderr:
            pr = p.recv_err
        while time.time() < x or r:
            r = pr()
            if r is None:
                if e:
                    raise Exception("Other end disconnected!")
                else:
                    break
            elif r:
                #return r
                y.append(r)
            else:
                #time.sleep(0.2)
                time.sleep(max((x-time.time())/tr, 0))
        return ''.join(y)

    @staticmethod
    def send_all(p, data):
        while len(data):
            sent = p.send(data)
            if sent is None:
                raise Exception("Other end disconnected!")
            data = buffer(data, sent)

    @staticmethod
    def recv_some_restring(p, prog, t=.1, e=1, tr=5, stderr=0):
        if tr < 1:
            tr = 1
        x = time.time()+t
        y = []
        r = ''
        pr = p.recv
        if stderr:
            pr = p.recv_err
        while time.time() < x or r:
            r = pr()
            if r is None:
                if e:
                    raise Exception(e)
                else:
                    break
            elif r:
                y.append(r)
                # print y
                result = prog.search(''.join(y))
                if result:
                    print '<>'
                    break
            else:
                time.sleep(max((x-time.time())/tr, 0))
        return ''.join(y)

ssh连接部分, 主要是利用subprocess, 调用plink和pscp,
1 plink路径:
        plink和pscp文件放在ext目录中,
        也可以修改getSSHShell和getSCP来修改
2 初始化ssh连接:
        通过initConn来做, 传入参数是{ 用户名@ip:口令, ... }
        isAdd是用于key下载的, 如果isAdd=True, 则先做一次口令下载动作.再连接ssh, 也就是要做两次ssh, 时间要久一点.
        返回: list , [ {'conn':conn, 'connStr':nodestr,'passwd':passwd,'kill':False} ], 是一个列, 里面放着已经连接好的conn
                conn是一个dict, "conn"是popen的对象, connStr是 用户名@ip,
3 向远程主机发送一个命令:
        通过doCommand, 传入参数: node, commandStr, expect
                                        node是一个conn的dict, initConn返回给你的list中的一个
                                        commandStr 就是要发送的命令了
                                        expect, 是应答对象, 在执行过程中, 要截获实时返回的数据, 或者判断终止条件, 或者yes/no的应答
        返回: 是执行过程中的stdout, 只有执行完成后, 才返回, 如果是一个很长时间的命令, 则不会返回, 要看实时消息, 要做expect
        expect demo( 简而言之, 就是一个class, 要定义一个方法: deal(param_string), 并需要有字符串返回值.):
        
# Expect Demo Object for ssh

class ExpectObj():
    status = 0
    password = ''
    def __init__(self, passwd):
        self.password = passwd
    def deal(self,src):
        #print(src)
        if src.find('yes')>2 and self.status == 0:
            self.status = 1
            return 'yes'
        elif src.find(' password:')>2 and self.status == 1:
            self.status = 2
            return self.password
        else:
            return None
        
 4 关闭conn
        通过closeConn来关闭ssh连接, 如果不关闭, 就只有等到python程序退出时, 才关闭了.
 
 5 kill, 在执行命令时, 常常会挂起, 导致不能结束, 可以kill掉这个conn正在执行命令
        原理是再建立一个conn, 查找之前的conn中正在执行的命令, 并kill之
        调用killProcess, 传入参数是node, 一个conn的dict
        btw, 这个功能有两种实现方法, 目前还在测试中, 不够稳定, 哪位道友有兴趣完善
 6 传输文件 上传用putFile, 下载用getFile
         原理是通过conn的连接串, 调用pscp, 发送和下载文件.
 
大致介绍到这里, 如果有问题, 欢迎反馈, 如果有意见, 欢迎批评, 肯定有错误, 欢迎指正.
 
 完整的代码如下:
 
import sys

import time
import datetime
import random

reload( sys )
sys.setdefaultencoding('utf-8')

class DLSSHUtil():

    tail = '\n\r'
    kill = 'DLKILL'
    def getSSHShell(self):
        from common import GetFixPath
        fix_path = GetFixPath()
        fix_path = os.path.abspath(fix_path)
        if sys.platform == 'win32':
            ssh_shell = fix_path+ '\\ext\\plink.exe '
        else:
            ssh_shell = fix_path+ '/ext/plink '
        return ssh_shell

    def getSCP(self):
        from common import GetFixPath
        fix_path = GetFixPath()
        fix_path = os.path.abspath(fix_path)
        if sys.platform == 'win32':
            ssh_shell = fix_path+'\\'+'ext\\pscp.exe '
        else:
            ssh_shell = fix_path+'/'+'ext/pscp '
        return ssh_shell

    def initConn(self,nodelist,isAdd=False):

        def EnableInterrupt(node):
            ostr = "stty isig intr '{0}' -echoctl; trap '/bin/true' SIGINT".format(self.kill)
            self.doCommand(node, ostr)
        def GetPtyNo(node):
            o = "w |awk '{print $2,$8}'|grep -w w|awk '{print $1}'"
            out = self.doCommand(node, o)
            return out

        def AddNewNode(loginStr,passwd):
            #print('add new node')
            out = ''
            ssh_shell = " echo y | "
            ssh_shell += self.getSSHShell()
            ssh_shell += ' -pw ' + passwd + ' '+loginStr
            ssh_shell += ' exit '
            print('get ssh key:' + loginStr)
            conn = Popen(ssh_shell, stdin=PIPE, stdout=PIPE, shell=True)
            while True:
                if conn.poll() == False:
                    break
                # out = Popen.recv_some(conn)
                # if out.find('error') > -1:
                    # conn.kill()
                    # conn = None
                    # break
            #out += 'ret code:'+str(conn.returncode)
            return out
        def login(loginStr,passwd):
            tail = self.tail
            shell = self.getSSHShell()
            shell += ' -pw ' + passwd + ' '+loginStr
            conn = Popen(shell, stdin=PIPE, stdout=PIPE, stderr=PIPE,shell=True)
            print('ssh connect:' + loginStr)
            while True:
                res = Popen.recv_some(conn)
                if res.find('password:') > 0:
                    Popen.send_all(conn, passwd + tail)
                elif res.find(']') > 0:
                    break
                # elif res.find('error')>-1 or res.find('ERROR') > -1:
                    # conn.kill()
                    # conn = None
                    # break
                #elif res.find('y/n') > 0:
                #    Popen.send_all(conn, tail)
            return conn

        conns = []
        tmpnodelist = []
        if isinstance(nodelist, dict):
            tmpnodelist = map(lambda x:[x,nodelist.get(x)], nodelist.keys())
        else:
            tmpnodelist = nodelist
        for (nodestr,passwd) in tmpnodelist:
            #try:
            if isAdd :
                out = AddNewNode(nodestr,passwd)
                #print(AddNewNode,out)
            conn = login(nodestr, passwd)
            conn_d = {'conn':conn, 'connStr':nodestr,'passwd':passwd,'kill':False}
            if conn is not None:
                conns.append(conn_d)

                #支持终止进程
                EnableInterrupt(conn_d)

                #第二种终止的方法
                ptsno = GetPtyNo(conn_d)
                conn_d['ptsno'] = ptsno
            # except Exception, ex:
                # print(ex, sys.exc_info()[:2])
                # continue

        return conns
    def closeConn(self,nodelist):
        tail = self.tail
        for node in nodelist:
            conn = node.get('conn')
            Popen.send_all(conn, 'exit' + tail)
            conn.wait()

    def killProcess(self, node):
        tmp_nodeList = self.initConn({node.get('connStr'):node.get('passwd')})

        ptsno = node.get('ptsno')
        o = " ps aux|grep -v 'grep\|sshd\|bash' |grep '"
        o += ptsno
        o += "'|awk '{print $2}'|sudo xargs kill"
        print(o)
        str = ""
        for node in tmp_nodeList:
            out = self.doCommand(node, o)
            print(out)
            str += out
        self.closeConn(tmp_nodeList)
        return str

    def killProcess2(self, node):
        out = self.doCommand(node, self.kill)
        out = self.doCommand(node, 'finish_doCommand')
        return out
    def putFile(self, node, filepath,destpath):
        #pscp -pw 235711 plink root@172.16.40.149:/opt/foo
        scp_shell = self.getSCP() + " -pw "
        scp_shell += node.get('passwd') + ' '
        scp_shell += filepath +' '
        scp_shell += node.get('connStr')+':'
        scp_shell += destpath
        putConn = Popen(scp_shell, stdin=PIPE, stdout=PIPE, shell=True)
        while True:
            if putConn.poll() == False:
                break

        out = 'ret code:'+str(putConn.returncode)
        return out

    def getFile(self, conn, srcpath, localpath):
        scp_shell = self.getSCP() + " -pw "
        scp_shell += conn.get('passwd') + ' '
        scp_shell += conn.get('connStr')+':'
        scp_shell += srcpath +' '
        scp_shell += localpath
        getConn = Popen(scp_shell, stdin=PIPE, stdout=PIPE, shell=True)
        while True:
            if getConn.poll() == False:
                break
        out = 'ret code:'+str(getConn.returncode)
        return out



    def doCommandAndWaitfor(self, node, cmd, response, t=.1, e=1, tr=5, stderr=0):
        tail = self.tail
        conn = node.get('conn')
        Popen.send_all(conn,cmd+tail)
        waitfor = '[.\r\n]*'+cmd+'[.\r\n]*'+response
        prog = re.compile(waitfor)
        print Popen.recv_some_restring(conn, prog, t, e, tr, stderr)

    def doCommandStr(self, node, commandStr,expect=None):
        tail = self.tail
        passwd = node.get('passwd')
        loginStr = node.get('connStr')
        shell = self.getSSHShell()
        shell += ' -pw ' + passwd + ' '+loginStr+ ' '+commandStr
        print(shell)
        conn = Popen(shell, stdin=PIPE, stdout=PIPE, shell=True)

        out = str(conn.poll())
#        res = Popen.recv_some(conn,e=0)
#        while True:
#            res = Popen.recv_some(conn,e=0)
#            out += res
#            if conn.returncode > -1:
#                break
#            elif len(res) > 0:
#                print(res)
#                isOpen = True
#            elif len(res) < 1 and isOpen:
#                break
        return out

    def doCommand(self, node, commandStr, expect=None):
        tail = '\n\r'
        endFlag = 'finish_doCommand'
        subCommand = ' ; echo ' +endFlag
        conn = node.get('conn')
        iskill = node.get('kill')
        if iskill != None and iskill:
            return "task is killed, command not send"

        totalCommand = commandStr + subCommand + tail
        Popen.send_all(conn, totalCommand)
        out = ''
        flagCount = 0
        while True:
            str = Popen.recv_some(conn, e=0)
            #print(str)
            if str.find(commandStr) > -1:
                str = str[len(totalCommand):]
                str = str.replace(commandStr,'')

            if str.find(subCommand) > -1:
                startPos = str.find(subCommand)+len(subCommand)
                str = str[startPos:]

            if expect is not None:
                res = expect.deal(str)
                if res is not None:
                    Popen.send_all(conn, res + tail)

            if str.find(endFlag) > -1:
                flagCount += 1
                endPos = str.find(endFlag)
                str = str[:endPos]
                out += str.replace('\r','').replace('\n','')
                if flagCount > 0:
                    break
            elif conn.returncode != None:
                #print(" return code :"+str(conn.returncode))
                out += str.replace('\r','').replace('\n','')
                break

            out += str
        return out



    def checkLinkOk(self, nodelist):
        nodeConns = self.initConn(nodelist, True)
        for node in nodeConns:
            out = self.doCommand(node,"ls -l")
            #print(out)
        self.closeConn(nodeConns)


    def sendPlink(self,nodelist):

        nodeConns = self.initConn(nodelist)
        for node in nodeConns:
            self.putFile(node, self.getSSHShell(),'/usr/bin/')
            self.putFile(node, self.getSCP(),'/usr/bin/')
            self.doCommand(node,"chmod 755 /usr/bin/plink")
            self.doCommand(node,"chmod 755 /usr/bin/pscp")
        self.closeConn(nodeConns)

    def getRemoteFile(self, nodelist, src, dest):
        nodeConns = self.initConn(nodelist)
        for node in nodeConns:
            self.getFile(node, src, dest+node.get("connStr"))

        self.closeConn(nodeConns)
阅读(3711) | 评论(0) | 转发(0) |
0

上一篇:lua实现PID算法

下一篇:Android NFC 开发

给主人留下些什么吧!~~