Python 与paramiko的x11转发

Python 与paramiko的x11转发,python,x11,paramiko,x11-forwarding,Python,X11,Paramiko,X11 Forwarding,我正在尝试使用paramiko运行一个命令,该命令应该能够打开一个X窗口。我使用的脚本如下所示: import paramiko ssh_client = paramiko.SSHClient() ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh_client.connect('192.168.122.55', username='us

我正在尝试使用
paramiko
运行一个命令,该命令应该能够打开一个X窗口。我使用的脚本如下所示:

import paramiko                                    

ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect('192.168.122.55', username='user', password='password')
transport = ssh_client.get_transport()
session = transport.open_session()

session.request_x11()
stdin = session.makefile('wb')
stdout = session.makefile('rb')
stderr = session.makefile_stderr('rb')
session.exec_command('env; xterm')
transport.accept()

print 'Exit status:', session.recv_exit_status()
print 'stdout:\n{}'.format(stdout.read())
print 'stderr:\n{}'.format(stderr.read())
session.close()
不幸的是,当我运行上面的脚本时,我得到以下输出:

Exit status: 1
stdout:
SHELL=/bin/bash
XDG_SESSION_COOKIE=8025e1ba5e6c47be0d2f3ad6504a25ee-1347286654.617967-1932974971
SSH_CLIENT=192.168.122.1 58654 22
USER=user
MAIL=/var/mail/user
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
PWD=/home/user
LANG=en_US.UTF-8
SHLVL=1
HOME=/home/user
LOGNAME=user
SSH_CONNECTION=192.168.122.1 58654 192.168.122.55 22
DISPLAY=localhost:10.0
_=/usr/bin/env

stderr:  
xterm: Xt error: Can't open display: localhost:10.0
如果在终端中运行以下命令:

ssh -X user@192.168.122.55 'env; xterm'
然后我得到了相同的环境变量(尽管有些端口已更改),因此我认为我的环境是正确的。但是,我仍然缺少使
paramiko
与x11转发一起工作的东西

我试过的几件事是:

  • 使用
    request_x11
    中的
    handler
    参数:除了打印值之外,我没有得到比使用默认处理程序更进一步的结果
  • 使用
    request_x11
    中的
    auth_cookie
    参数:尝试根据
    xauth列表
    输出对正在使用的cookie值进行硬编码。这样做的目的是避免根据
    paramiko
    本身中的文档字符串可能发生的问题:
如果省略auth_cookie,则将生成一个新的安全随机128位值 生成、使用和返回。您需要使用此值来 验证传入的x11请求并将其替换为实际的本地请求 x11 cookie(需要一些x11协议的知识)

我还可以做些什么来让它工作或解决问题

注: 这是以前在中提出的问题:

  • :唯一的响应指向我已尝试使用但无效的
    请求\u x11
    文档
  • :接受的响应建议使用
    处理程序
    参数,但它是错误的
  • :一年多没有答复
      • x11
        请求可能使用了您可能无法正确处理的
        MIT-MAGIC-COOKIE-1
      • 直接使用ssh,我发现需要确认x11请求(cookiechallenge?)
      • .Xauthority
        文件也可能是一个问题
      • 您可以尝试
        strace
        ssh进程并查看正常流程
      • 在脚本中,您可以将
        xterm
        替换为
        strace xterm
        ,并与上述内容进行比较
      一些链接:

      祝你好运

      编辑: 在Gary的答案上构建,具有多个x11连接

      #!/usr/bin/env python
      
      import os
      import select
      import sys
      import getpass
      import paramiko
      import socket
      import logging
      import Xlib.support.connect as xlib_connect
      LOGGER = logging.getLogger(__name__)
      
      # connection settings
      host = '192.168.122.55'
      user = 'user'
      password = getpass.getpass()
      
      ssh_client = paramiko.SSHClient()
      ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
      ssh_client.connect(host, username=user, password=password)
      del password
      
      # maintain map
      # { fd: (channel, remote channel), ... }
      channels = {}
      
      poller = select.poll()
      def x11_handler(channel, (src_addr, src_port)):
          '''handler for incoming x11 connections
          for each x11 incoming connection,
          - get a connection to the local display
          - maintain bidirectional map of remote x11 channel to local x11 channel
          - add the descriptors to the poller
          - queue the channel (use transport.accept())'''
          x11_chanfd = channel.fileno()
          local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
          local_x11_socket_fileno = local_x11_socket.fileno()
          channels[x11_chanfd] = channel, local_x11_socket
          channels[local_x11_socket_fileno] = local_x11_socket, channel
          poller.register(x11_chanfd, select.POLLIN)
          poller.register(local_x11_socket, select.POLLIN)
          LOGGER.debug('x11 channel on: %s %s', src_addr, src_port)
          transport._queue_incoming_channel(channel)
      
      def flush_out(session):
          while session.recv_ready():
              sys.stdout.write(session.recv(4096))
          while session.recv_stderr_ready():
              sys.stderr.write(session.recv_stderr(4096))
      
      # get local disply
      local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
      # start x11 session
      transport = ssh_client.get_transport()
      session = transport.open_session()
      session.request_x11(handler=x11_handler)
      session.exec_command('xterm')
      session_fileno = session.fileno()
      poller.register(session_fileno, select.POLLIN)
      # accept first remote x11 connection
      transport.accept()
      
      # event loop
      while not session.exit_status_ready():
          poll = poller.poll()
          # accept subsequent x11 connections if any
          if len(transport.server_accepts) > 0:
              transport.accept()
          if not poll: # this should not happen, as we don't have a timeout.
              break
          for fd, event in poll:
              if fd == session_fileno:
                  flush_out(session)
              # data either on local/remote x11 socket
              if fd in channels.keys():
                  channel, counterpart = channels[fd]
                  try:
                      # forward data between local/remote x11 socket.
                      data = channel.recv(4096)
                      counterpart.sendall(data)
                  except socket.error:
                      channel.close()
                      counterpart.close()
                      del channels[fd]
      
      print 'Exit status:', session.recv_exit_status()
      flush_out(session)
      session.close()
      

      在阅读paramiko代码时,我意识到paramiko只实现了一种建立x11通道的方法。它没有将通道连接到本地x11显示器。那就留给你了

      下面是我刚刚编写的一个小实现:

      #!/usr/bin/env python
      
      import os
      import select
      import sys
      
      import paramiko
      import Xlib.support.connect as xlib_connect
      
      
      local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
      local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
      
      
      ssh_client = paramiko.SSHClient()
      ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
      ssh_client.connect('server', username='username', password='password')
      transport = ssh_client.get_transport()
      session = transport.open_session()
      session.request_x11(single_connection=True)
      session.exec_command('xterm')
      x11_chan = transport.accept()
      
      session_fileno = session.fileno()
      x11_chan_fileno = x11_chan.fileno()
      local_x11_socket_fileno = local_x11_socket.fileno()
      
      poller = select.poll()
      poller.register(session_fileno, select.POLLIN)
      poller.register(x11_chan_fileno, select.POLLIN)
      poller.register(local_x11_socket, select.POLLIN)
      while not session.exit_status_ready():
          poll = poller.poll()
          if not poll: # this should not happen, as we don't have a timeout.
              break
          for fd, event in poll:
              if fd == session_fileno:
                  while session.recv_ready():
                      sys.stdout.write(session.recv(4096))
                  while session.recv_stderr_ready():
                      sys.stderr.write(session.recv_stderr(4096))
              if fd == x11_chan_fileno:
                  local_x11_socket.sendall(x11_chan.recv(4096))
              if fd == local_x11_socket_fileno:
                  x11_chan.send(local_x11_socket.recv(4096))
      
      print 'Exit status:', session.recv_exit_status()
      while session.recv_ready():
          sys.stdout.write(session.recv(4096))
      while session.recv_stderr_ready():
          sys.stdout.write(session.recv_stderr(4096))
      session.close()
      
      一些注意事项:

      • 我正在使用python Xlib中的一些辅助函数。这是Xlib的纯python实现。有关安装的详细信息,请参见此问题:

      • 我如何实现这个的一些细节让我相信它只适用于1 x11连接(因此
        session.request_x11(single_connection=True)
        ),我想继续这样做,让它处理多个连接,但这将不得不再等一天

      • 此代码基本上使用以下命令以异步方式将以下通道/套接字连接在一起:

        • session.stdout
          ->
          sys.stdout
        • session.stderr
          ->
          sys.stderr
        • x11频道
          ->
          local\u x11\u插座
        • local\u x11\u插座
          ->
          x11频道
      • paramiko
        模块向
        logging
        模块输出大量有用的调试信息。您可以通过配置日志记录模块来查看:

        import logging
        logging.basicConfig(level=logging.DEBUG)
        

      鉴于您要求的是最低版本,我认为这会使其尽可能易于使用。这是一个基于这两种代码的版本,但这将x11会话命令与常规代码分开,从而使主程序简单,会话代码可重用:

      import paramiko
      import os
      import select
      import sys
      import Xlib.support.connect as xlib_connect
      
      def run(transport, session, command):
          def x11_handler(channel, (src_addr, src_port)):
              x11_fileno = channel.fileno()
              local_x11_channel = xlib_connect.get_socket(*local_x11_display[:3])
              local_x11_fileno = local_x11_channel.fileno()
      
              # Register both x11 and local_x11 channels
              channels[x11_fileno] = channel, local_x11_channel
              channels[local_x11_fileno] = local_x11_channel, channel
      
              poller.register(x11_fileno, select.POLLIN)
              poller.register(local_x11_fileno, select.POLLIN)
      
              transport._queue_incoming_channel(channel)
      
          def flush_out(channel):
              while channel.recv_ready():
                  sys.stdout.write(channel.recv(4096))
              while channel.recv_stderr_ready():
                  sys.stderr.write(channel.recv_stderr(4096))
      
          local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
      
          channels = {}
          poller = select.poll()
          session_fileno = session.fileno()
          poller.register(session_fileno)
      
          session.request_x11(handler=x11_handler)
          session.exec_command(command)
          transport.accept()
      
          # event loop
          while not session.exit_status_ready():
              poll = poller.poll()
              if not poll: # this should not happen, as we don't have a timeout.
                  break
              for fd, event in poll:
                  if fd == session_fileno:
                      flush_out(session)
                  # data either on local/remote x11 channels/sockets
                  if fd in channels.keys():
                      sender, receiver = channels[fd]
                      try:
                          receiver.sendall(sender.recv(4096))
                      except:
                          sender.close()
                          receiver.close()
                          channels.remove(fd)
      
          flush_out(session)
          return session.recv_exit_status()
      
      if __name__ == '__main__':
          ssh_client = paramiko.SSHClient()
          ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
          ssh_client.connect('192.168.122.55', username='user', password='password')
          transport = ssh_client.get_transport()
          session = transport.open_session()
          run(transport, session, 'xterm')
      
      我知道你可以自己做。但是,只要复制函数
      run
      ,任何人都可以毫不费力地使用它


      正确的答案是。这个例子是为了让新手更容易。对于那些在Mac OS X Leopard中工作的人来说,没有select.poll()。下面是使用kqueue而不是poll的修改版本。如有任何改进/更正,将不胜感激

      #!/usr/bin/env python
      
      import os
      import select
      import sys
      import paramiko
      import socket
      import Xlib.support.connect as xlib_connect
      
      # get local display
      local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
      
      ssh_client = paramiko.SSHClient()
      ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
      ssh_client.connect('hostname', port=22, username='username', password='password')
      
      channels = {}
      kq = select.kqueue()
      
      def x11Handler(x11_chan, (src_addr, src_port)):
          x11_chan_fileno = x11_chan.fileno()
          local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
          local_x11_socket_fileno = local_x11_socket.fileno()
          channels[x11_chan_fileno] = x11_chan, local_x11_socket
          channels[local_x11_socket_fileno] = local_x11_socket, x11_chan
      
          ev = [select.kevent(x11_chan_fileno, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD), select.kevent(local_x11_socket_fileno, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD)]
          kevents = kq.control(ev, 0, None)
          transport._queue_incoming_channel(x11_chan)
      
      def flushOut(session):
          while session.recv_ready():
              sys.stdout.write(session.recv(4096))
          while session.recv_stderr_ready():
              sys.stderr.write(session.recv_stderr(4096))
      
      # start x11 session
      transport = ssh_client.get_transport()
      session = transport.open_session()
      session.request_x11(handler=x11Handler)
      session.exec_command('xterm')
      
      # accept first remote x11 connection
      x11_chan = transport.accept()
      session_fileno = session.fileno()
      
      session_ev = [select.kevent(session_fileno, 
          filter=select.KQ_FILTER_READ,
          flags=select.KQ_EV_ADD)] 
      
      kevents_session = kq.control(session_ev, 0, None)
      
      # event loop
      while not session.exit_status_ready():
          r_events = kq.control(None, 4)
          # accept subsequent x11 connections if any
      
          if len(transport.server_accepts) > 0:
              transport.accept()
          if not r_events: # this should not happen, as we don't have a timeout.
              break
          for event in r_events:
              print event
              if event.ident & session_fileno:
                  flushOut(session)
              # data either on local/remote x11 socket
              if event.ident in channels.keys():
                  x11_chan, counterpart = channels[event.ident]
                  try:
                      # forward data between local/remote x11 socket.
                      data = x11_chan.recv(4096)
                      counterpart.sendall(data)
                  except socket.error:
                      x11_chan.close()
                      counterpart.close()
                      del channels[event.ident]
      
      flushOut(session)
      kq.close()
      session.close()
      

      感谢Gary van der Merwe和dnozay的代码。下面的代码严重依赖它,用于在Windows上运行X程序。显著的区别是使用select.select而不是poll,因为poll在Windows中不可用。欢迎任何改进或更正

      import select
      import sys
      import paramiko
      import Xlib.support.connect as xlib_connect
      import os
      import socket
      import subprocess
      
      
      
      # run xming
      XmingProc = subprocess.Popen("C:/Program Files (x86)/Xming/Xming.exe :0 -clipboard -multiwindow")
      ssh_client = paramiko.SSHClient()
      ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
      ssh_client.connect(SSHServerIP, SSHServerPort, username=user, password=pwd)
      transport = ssh_client.get_transport()
      channelOppositeEdges = {}
      
      local_x11_display = xlib_connect.get_display(os.environ['DISPLAY'])
      inputSockets = []
      
      def x11_handler(channel, (src_addr, src_port)):
          local_x11_socket = xlib_connect.get_socket(*local_x11_display[:3])
          inputSockets.append(local_x11_socket)
          inputSockets.append(channel)
          channelOppositeEdges[local_x11_socket.fileno()] = channel
          channelOppositeEdges[channel.fileno()] = local_x11_socket
          transport._queue_incoming_channel(channel)
      
      session = transport.open_session()
      inputSockets.append(session)
      session.request_x11(handler = x11_handler)
      session.exec_command('xterm')
      transport.accept()
      
      while not session.exit_status_ready():
          readable, writable, exceptional = select.select(inputSockets,[],[])
          if len(transport.server_accepts) > 0:
              transport.accept()
          for sock in readable:
              if sock is session:
                  while session.recv_ready():
                      sys.stdout.write(session.recv(4096))
                  while session.recv_stderr_ready():
                      sys.stderr.write(session.recv_stderr(4096))   
              else: 
                  try:
                      data = sock.recv(4096)
                      counterPartSocket  = channelOppositeEdges[sock.fileno()]
                      counterPartSocket.sendall(data)
                  except socket.error:
                      inputSockets.remove(sock)
                      inputSockets.remove(counterPartSocket)
                      del channelOppositeEdges[sock.fileno()]
                      del channelOppositeEdges[counterPartSocket.fileno()]
                      sock.close()
                      counterPartSocket.close()
      
      print 'Exit status:', session.recv_exit_status()
      while session.recv_ready():
          sys.stdout.write(session.recv(4096))
      while session.recv_stderr_ready():
          sys.stdout.write(session.recv_stderr(4096))
      session.close()
      XmingProc.terminate()
      XmingProc.wait()
      

      在paramico会话处于活动状态时:(1)在远程计算机上以root用户身份运行
      netstat-lpn
      。它是否侦听端口6010,地址127.0.0.1?(2)
      ls-l~user/.Xauthority
      是怎么说的?你能提供一个对你有用的最小示例的来源吗?这是对我有用的答案。我仍然会把这个例子翻译成真实的代码,但我认为这会很有帮助。谢谢你的回复。我一直在看你的例子;但是,尽管很有希望,但对于我尝试过的其他命令,它似乎不起作用。例如,如果我尝试
      gnome终端
      我会得到
      自动启动错误:X11初始化失败。
      如果我尝试
      firefox
      ,我会得到
      错误:无法打开显示:localhost:10.0
      。其他两个答案声称基于您的答案,因此您获得奖金是公平的。再次感谢,谢谢你们的代码。我有两条评论,第一条是python xlib
      get_socket
      函数在版本0.26中使用4个参数,因此调用应该是
      xlib_connect.get_socket(*local_x11_display[:4])
      。在X11握手过程中,我还遇到了一个
      无效的MIT-MAGIC-COOKIE-1键错误
      ,因此我必须使用
      xhost+
      打开访问权限,这通常在<