Class: Net::SSH::Proxy::Command

Inherits:
Object
  • Object
show all
Defined in:
lib/net/ssh/proxy/command.rb

Overview

An implementation of a command proxy. To use it, instantiate it, then pass the instantiated object via the :proxy key to Net::SSH.start:

require 'net/ssh/proxy/command'

proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p')
Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
  ...
end

Direct Known Subclasses

Jump

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command_line_template) ⇒ Command

Create a new socket factory that tunnels via a command executed with the user's shell, which is composed from the given command template. In the command template, `%h' will be substituted by the host name to connect and `%p' by the port.


33
34
35
36
37
# File 'lib/net/ssh/proxy/command.rb', line 33

def initialize(command_line_template)
  @command_line_template = command_line_template
  @command_line = nil
  @timeout = 60
end

Instance Attribute Details

#command_lineObject (readonly)

The command line for the session


24
25
26
# File 'lib/net/ssh/proxy/command.rb', line 24

def command_line
  @command_line
end

#command_line_templateObject (readonly)

The command line template


21
22
23
# File 'lib/net/ssh/proxy/command.rb', line 21

def command_line_template
  @command_line_template
end

#timeoutObject

Timeout in seconds in open, defaults to 60


27
28
29
# File 'lib/net/ssh/proxy/command.rb', line 27

def timeout
  @timeout
end

Instance Method Details

#close_on_error(io) ⇒ Object


116
117
118
119
# File 'lib/net/ssh/proxy/command.rb', line 116

def close_on_error(io)
  Process.kill('TERM', io.pid)
  Thread.new { io.close }
end

#open(host, port, connection_options = nil) ⇒ Object

Return a new socket connected to the given host and port via the proxy that was requested when the socket factory was instantiated.


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/net/ssh/proxy/command.rb', line 41

def open(host, port, connection_options = nil)
  command_line = @command_line_template.gsub(/%(.)/) {
    case $1
    when 'h'
      host
    when 'p'
      port.to_s
    when 'r'
      remote_user = connection_options && connection_options[:remote_user]
      if remote_user
        remote_user
      else
        raise ArgumentError, "remote user name not available"
      end
    when '%'
      '%'
    else
      raise ArgumentError, "unknown key: #{$1}"
    end
  }
  begin
    io = IO.popen(command_line, "r+")
    begin
      if result = IO.select([io], nil, [io], @timeout)
        if result.last.any? || io.eof?
          raise "command failed"
        end
      else
        raise "command timed out"
      end
    rescue StandardError
      close_on_error(io)
      raise
    end
  rescue StandardError => e
    raise ConnectError, "#{e}: #{command_line}"
  end
  @command_line = command_line
  if Gem.win_platform?
    # read_nonblock and write_nonblock are not available on Windows
    # pipe. Use sysread and syswrite as a replacement works.
    def io.send(data, flag)
      syswrite(data)
    end

    def io.recv(size)
      sysread(size)
    end
  else
    def io.send(data, flag)
      begin
        result = write_nonblock(data)
      rescue IO::WaitWritable, Errno::EINTR
        IO.select(nil, [self])
        retry
      end
      result
    end

    def io.recv(size)
      begin
        result = read_nonblock(size)
      rescue IO::WaitReadable, Errno::EINTR
        timeout_in_seconds = 20
        if IO.select([self], nil, [self], timeout_in_seconds) == nil
          raise "Unexpected spurious read wakeup"
        end
        retry
      end
      result
    end
  end
  io
end