Path: blob/master/lib/net/ssh/command_stream.rb
19847 views
# -*- coding: binary -*-12class Net::SSH::CommandStream34attr_accessor :channel, :thread, :error, :ssh, :session, :logger5attr_accessor :lsock, :rsock, :monitor67module PeerInfo8include ::Rex::IO::Stream9attr_accessor :peerinfo10attr_accessor :localinfo11end1213def shell_requested(channel, success)14unless success15error = Net::SSH::ChannelRequestFailed.new('Shell/exec channel request failed')16handle_error(error: error)17end1819self.channel = channel2021channel[:data] = ''22channel[:extended_data] = ''2324channel.on_eof do25cleanup26end2728channel.on_close do29cleanup30end3132channel.on_data do |ch, data|33self.rsock.write(data)34channel[:data] << data35end3637channel.on_extended_data do |ch, ctype, data|38self.rsock.write(data)39channel[:extended_data] << data40end41end4243def initialize(ssh, cmd = nil, pty: false, cleanup: false, session: nil, logger: nil)44self.session = session45self.logger = logger46self.lsock, self.rsock = Rex::Socket.tcp_socket_pair()47self.lsock.extend(Rex::IO::Stream)48self.lsock.extend(PeerInfo)49self.rsock.extend(Rex::IO::Stream)5051self.ssh = ssh52self.thread = Thread.new(ssh, cmd, pty, cleanup) do |rssh, rcmd, rpty, rcleanup|53info = rssh.transport.socket.getpeername_as_array54if Rex::Socket.is_ipv6?(info[1])55self.lsock.peerinfo = "[#{info[1]}]:#{info[2]}"56else57self.lsock.peerinfo = "#{info[1]}:#{info[2]}"58end5960info = rssh.transport.socket.getsockname61if Rex::Socket.is_ipv6?(info[1])62self.lsock.localinfo = "[#{info[1]}]:#{info[2]}"63else64self.lsock.localinfo = "#{info[1]}:#{info[2]}"65end6667channel = rssh.open_channel do |rch|68# A PTY will write us to {u,w}tmp and lastlog69rch.request_pty if rpty7071if rcmd.nil?72rch.send_channel_request('shell', &method(:shell_requested))73else74rch.exec(rcmd, &method(:shell_requested))75end76end7778channel.on_open_failed do |ch, code, desc|79error = Net::SSH::ChannelOpenFailed.new(code, 'Session channel open failed')80handle_error(error: error)81end8283self.monitor = Thread.new do84begin85Kernel.loop do86next if not self.rsock.has_read_data?(1.0)8788buff = self.rsock.read(16384)89break if not buff9091verify_channel92self.channel.send_data(buff) if buff93end94rescue ::StandardError => e95handle_error(error: e)96end97end9899begin100Kernel.loop { rssh.process(0.5) { true } }101rescue ::StandardError => e102handle_error(error: e)103end104105# Shut down the SSH session if requested106if !rcmd.nil? && rcleanup107rssh.close108end109end110rescue ::StandardError => e111# XXX: This won't be set UNTIL there's a failure from a thread112handle_error(error: e)113ensure114self.monitor.kill if self.monitor115end116117#118# Prevent a race condition119#120def verify_channel121while ! self.channel122raise EOFError if ! self.thread.alive?123::IO.select(nil, nil, nil, 0.10)124end125end126127def handle_error(error: nil)128self.error = error if error129130if self.logger131self.logger.print_error("SSH Command Stream encountered an error: #{self.error} (Server Version: #{self.ssh.transport.server_version.version})")132end133134cleanup135end136137def cleanup138self.session.alive = false if self.session139self.monitor.kill140self.lsock.close rescue nil141self.rsock.close rescue nil142self.ssh.close rescue nil143self.thread.kill144end145146end147148149