2011-10-29 4 views
3

현재 접근 방식을 이해할 때까지는 무차별 대용 암호를 통해 ssh를 배우는 중입니다. 어떤 시행 착오 끝에 나는 "pty-req"와 "shell"요청을 성공적으로 보낼 수 있었고, login preamble을 얻을 수 있었고, 명령을 보내고 stdout을받을 수 있었다. SSH 서비스 stderr 및 상태 메시지를 수신하려고합니다. 다른 SSH 구현 (paramiko, Net :: SSH)을 통해 읽는 것은 그다지 가이드가 아니 었습니다.클라이언트로 twisted.conch를 사용하여 ssh로 확장 된 데이터를 수신합니다.

#!/usr/bin/env python 


from twisted.conch.ssh import transport 
from twisted.conch.ssh import userauth 
from twisted.conch.ssh import connection 
from twisted.conch.ssh import common 
from twisted.conch.ssh.common import NS 
from twisted.conch.ssh import keys 
from twisted.conch.ssh import channel 
from twisted.conch.ssh import session 
from twisted.internet import defer 

from twisted.internet import defer, protocol, reactor 
from twisted.python import log 
import struct, sys, getpass, os 
log.startLogging(sys.stdout) 


USER = 'dward' 
HOST = '192.168.0.19' # pristine.local 
PASSWD = "password" 
PRIVATE_KEY = "~/id_rsa" 

class SimpleTransport(transport.SSHClientTransport): 
    def verifyHostKey(self, hostKey, fingerprint): 
     print 'host key fingerprint: %s' % fingerprint 
     return defer.succeed(1) 

    def connectionSecure(self): 
     self.requestService(
      SimpleUserAuth(USER, 
       SimpleConnection())) 

class SimpleUserAuth(userauth.SSHUserAuthClient): 
    def getPassword(self): 
     return defer.succeed(PASSWD) 

    def getGenericAnswers(self, name, instruction, questions): 
     print name 
     print instruction 
     answers = [] 
     for prompt, echo in questions: 
      if echo: 
       answer = raw_input(prompt) 
      else: 
       answer = getpass.getpass(prompt) 
      answers.append(answer) 
     return defer.succeed(answers) 

    def getPublicKey(self): 
     path = os.path.expanduser(PRIVATE_KEY) 
     # this works with rsa too 
     # just change the name here and in getPrivateKey 
     if not os.path.exists(path) or self.lastPublicKey: 
      # the file doesn't exist, or we've tried a public key 
      return 
     return keys.Key.fromFile(filename=path+'.pub').blob() 

    def getPrivateKey(self): 
     path = os.path.expanduser(PRIVATE_KEY) 
     return defer.succeed(keys.Key.fromFile(path).keyObject) 



class SimpleConnection(connection.SSHConnection): 
    def serviceStarted(self): 
     self.openChannel(SmartChannel(2**16, 2**15, self))   




class SmartChannel(channel.SSHChannel): 
    name = "session" 


    def getResponse(self, timeout = 10): 
     self.onData = defer.Deferred() 
     self.timeout = reactor.callLater(timeout, self.onData.errback, Exception("Timeout")) 
     return self.onData 

    def openFailed(self, reason): 
     print "Failed", reason 

    @defer.inlineCallbacks  
    def channelOpen(self, ignoredData): 
     self.data = '' 
     self.oldData = '' 
     self.onData = None 
     self.timeout = None 
     term = os.environ.get('TERM', 'xterm') 
     #winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678') 
     winSize = (25,80,0,0) #struct.unpack('4H', winsz) 
     ptyReqData = session.packRequest_pty_req(term, winSize, '') 

     try: 
      result = yield self.conn.sendRequest(self, 'pty-req', ptyReqData, wantReply = 1) 
     except Exception as e: 
      print "Failed with ", e 

     try: 
      result = yield self.conn.sendRequest(self, "shell", '', wantReply = 1) 
     except Exception as e: 
      print "Failed shell with ", e 


     #fetch preample  
     data = yield self.getResponse() 
     """ 
     Welcome to Ubuntu 11.04 (GNU/Linux 2.6.38-8-server x86_64) 

      * Documentation: http://www.ubuntu.com/server/doc 

      System information as of Sat Oct 29 13:09:50 MDT 2011 

      System load: 0.0    Processes:   111 
      Usage of /: 48.0% of 6.62GB Users logged in:  1 
      Memory usage: 39%    IP address for eth1: 192.168.0.19 
      Swap usage: 3% 

      Graph this data and manage this system at https://landscape.canonical.com/ 
      New release 'oneiric' available. 
      Run 'do-release-upgrade' to upgrade to it. 

      Last login: Sat Oct 29 01:23:16 2011 from 192.168.0.17 
     """ 
     print data 
     while data != "" and data.strip().endswith("~$") == False: 
      try: 
       data = yield self.getResponse() 
       print repr(data) 
       """ 
       \x1B]0;[email protected]: ~\[email protected]:~$ 
       """ 
      except Exception as e: 
       print e 
       break 

     self.write("false\n") 
     #fetch response 
     try: 
      data = yield self.getResponse() 
     except Exception as e: 
      print "Failed to catch response?", e 
     else: 
      print data 
      """ 
       false 
       \x1B]0;[email protected]: ~\[email protected]:~$ 
      """ 

     self.write("true\n") 
     #fetch response 
     try: 
      data = yield self.getResponse() 
     except Exception as e: 
      print "Failed to catch response?", e 
     else: 
      print data 
      """ 
      true 
      \x1B]0;[email protected]: ~\[email protected]:~$ 
      """ 

     self.write("echo Hello World\n\x00") 
     try: 
      data = yield self.getResponse() 
     except Exception as e: 
      print "Failed to catch response?", e 
     else:    
      print data 
      """ 
      echo Hello World 
      Hello World 
      \x1B]0;[email protected]: ~\[email protected]:~$ 
      """ 

     #Close up shop 
     self.loseConnection() 
     dbgp = 1 


    def request_exit_status(self, data): 
     status = struct.unpack('>L', data)[0] 
     print 'status was: %s' % status  

    def dataReceived(self, data): 
     self.data += data 
     if self.onData is not None: 
      if self.timeout and self.timeout.active(): 
       self.timeout.cancel() 
      if self.onData.called == False:     
       self.onData.callback(data) 

    def extReceived(self, dataType, data): 
     dbgp = 1 
     print "Extended Data recieved! dataType = %s , data = %s " % (dataType, data,) 
     self.extendData = data 

    def closed(self): 
     print 'got data : %s' % self.data.replace("\\r\\n","\r\n") 
     self.loseConnection() 
     reactor.stop() 



protocol.ClientCreator(reactor, SimpleTransport).connectTCP(HOST, 22) 
reactor.run() 

http://tools.ietf.org/html/rfc4250#section-4.9.3이 또한 내가 명시 적으로 잘못된 명령에 추가하는 시도 :

는 RFC의 SSH에 대한 중 하나를보고 말했다, 나는 나열된 요청 아마 하나가 내가 찾던 될 수 있다고 생각 원격 쉘 :

self.write("ls -alF badPathHere\n\x00") 
    try: 
     data = yield self.getResponse() 
    except Exception as e: 
     print "Failed to catch response?", e 
    else:    
     print data 
     """ 
     ls -alF badPathHere 
     ls: cannot access badPathHere: No such file or directory 
     \x1B]0;[email protected]: ~\[email protected]:~$ 
     """ 

그리고 열려진 같다 OpenSSH의 채널에 대한 소스 코드를 파고 열려진

답변

0

혼입되고 세션 로직은 2227 function -> session_input_channel_req 줄에서 session.c으로 처리됩니다. pty-req가 주어지면 "shell"요청은 ​​do_exec_pty로 연결되어 결국 session_set_fds (s, ptyfd, fdout, -1, 1, 1). 네 번째 인수는 보통 stderr을 처리 할 책임이있는 파일 디스크립터가되지만 아무 것도 제공되지 않으므로 stderr에 대한 확장 된 데이터가 없습니다.

궁극적으로, stderr FD를 제공하기 위해 openssh를 수정 한 경우에도 문제는 셸에 있습니다. 이 시점에서 완전한 추측 작업을하지만 xterm이나 putty 같은 터미널을 통해 ssh 서비스에 로그인하는 것과 비슷하게, "2> someFile"과 같은 것을 통해 명시 적으로 방향이 변경되지 않으면 stderr와 stdout이 함께 전송됩니다. SSH 서비스 공급자.

+1

PTY를 요청하는 경우 stderr와 stdout이 같은 위치로 갈 것입니다. pseudo-TTY 자체를 "화면"처럼 생각할 수 있습니다. 분명히 stdout과 stderr를 보내려합니다. 둘 다 사용자가 볼 수있는 곳입니다. 'pty-req' 요청보다는'exec' 요청으로 어떤 일이 일어나는지 보셨습니까? – Glyph

+0

PTY stderr를 캡처하지 않고 exec가 do_exec_pty 및 do_exec_no_pty로 분할하는 지점이 있습니다. 내 테스트에서 그것은 많은 감각을 실행에 대한 returnval을 제공하지 않는 유일한 쉘입니다. – David

관련 문제