class Net::Telnet
Net::Telnet
Предоставляет функциональность клиента telnet.
Этот класс также имеет, через делегирование, все методы объекта сокета (по умолчанию, TCPSocket, но может быть установлен параметром Proxy в new()). Это предоставляет методы, такие как close() для завершения сессии и sysread() для чтения данных непосредственно от хоста, вместо механизма waitfor(). Обратите внимание, что если вы используете sysread() непосредственно в режиме telnet, вам, вероятно, следует передать вывод через preprocess() для извлечения последовательностей команд telnet.
Обзор
Протокол telnet позволяет клиенту удаленно войти в учетную запись пользователя на сервере и выполнить команды через оболочку. Эквивалент выполняется путем создания класса Net::Telnet с параметром Host, установленным на ваш хост, вызова login() с вашим пользователем и паролем, выдачи одного или нескольких вызовов cmd() и затем вызова close() для завершения сессии. Методы waitfor(), print(), puts() и write(), на основе которых реализован cmd(), необходимы только если вы выполняете что-то более сложное.
Объект Net::Telnet также может использоваться для подключения к не-telnet-сервисам, таким как SMTP или HTTP. В этом случае, обычно требуется указать параметр Port для указания порта для подключения и установить параметр Telnetmode в значение false, чтобы предотвратить попытки клиента интерпретировать последовательности команд telnet. Обычно, login() не будет работать с другими протоколами, и вам нужно будет самостоятельно обрабатывать аутентификацию.
Для некоторых протоколов можно указать параметр Prompt один раз при создании объекта Telnet и использовать вызовы cmd(); для других, вам нужно будет указывать последовательность ответа как параметр Match для каждого вызова cmd() или вызывать puts() и waitfor() напрямую; для ещё других, вам придётся использовать sysread() вместо waitfor() и самостоятельно анализировать ответы сервера.
Стоит отметить, что при создании нового объекта Net::Telnet, вы можете предоставить прокси-канал IO через параметр Proxy. Это может использоваться для подключения объекта Telnet к другим объектам Telnet, к уже открытым сокетам или к любому объекту чтения-записи IO. Это может быть полезно, например, для настройки тестовой среды для тестирования.
Примеры
Вход в систему и отправка команды, выводя все выходные данные на стандартный вывод
localhost = Net::Telnet::new("Host" => "localhost",
"Timeout" => 10,
"Prompt" => /[$%#>] \z/n)
localhost.login("username", "password") { |c| print c }
localhost.cmd("command") { |c| print c }
localhost.close
Проверка сервера POP на наличие почты
pop = Net::Telnet::new("Host" => "your_destination_host_here",
"Port" => 110,
"Telnetmode" => false,
"Prompt" => /^\+OK/n)
pop.cmd("user " + "your_username_here") { |c| print c }
pop.cmd("pass " + "your_password_here") { |c| print c }
pop.cmd("list") { |c| print c }
Ссылки
Существует большое количество RFC, относящихся к протоколу Telnet. RFC 854-861 определяют базовый протокол. Полный список соответствующих RFC см. на www.omnifarious.org/~hopper/technical/telnet-rfc.html
Атрибуты
Публичные методы класса
# File lib/net/telnet.rb, line 273
def initialize(options) # :yield: mesg
@options = options
@options["Host"] = "localhost" unless @options.has_key?("Host")
@options["Port"] = 23 unless @options.has_key?("Port")
@options["Prompt"] = /[$%#>] \z/n unless @options.has_key?("Prompt")
@options["Timeout"] = 10 unless @options.has_key?("Timeout")
@options["Waittime"] = 0 unless @options.has_key?("Waittime")
unless @options.has_key?("Binmode")
@options["Binmode"] = false
else
unless (true == @options["Binmode"] or false == @options["Binmode"])
raise ArgumentError, "Binmode option must be true or false"
end
end
unless @options.has_key?("Telnetmode")
@options["Telnetmode"] = true
else
unless (true == @options["Telnetmode"] or false == @options["Telnetmode"])
raise ArgumentError, "Telnetmode option must be true or false"
end
end
@telnet_option = { "SGA" => false, "BINARY" => false }
if @options.has_key?("Output_log")
@log = File.open(@options["Output_log"], 'a+')
@log.sync = true
@log.binmode
end
if @options.has_key?("Dump_log")
@dumplog = File.open(@options["Dump_log"], 'a+')
@dumplog.sync = true
@dumplog.binmode
def @dumplog.log_dump(dir, x) # :nodoc:
len = x.length
addr = 0
offset = 0
while 0 < len
if len < 16
line = x[offset, len]
else
line = x[offset, 16]
end
hexvals = line.unpack('H*')[0]
hexvals += ' ' * (32 - hexvals.length)
hexvals = format("%s %s %s %s " * 4, *hexvals.unpack('a2' * 16))
line = line.gsub(/[\000-\037\177-\377]/n, '.')
printf "%s 0x%5.5x: %s%s\n", dir, addr, hexvals, line
addr += 16
offset += 16
len -= 16
end
print "\n"
end
end
if @options.has_key?("Proxy")
if @options["Proxy"].kind_of?(Net::Telnet)
@sock = @options["Proxy"].sock
elsif @options["Proxy"].kind_of?(IO)
@sock = @options["Proxy"]
else
raise "Error: Proxy must be an instance of Net::Telnet or IO."
end
else
message = "Trying " + @options["Host"] + "...\n"
yield(message) if block_given?
@log.write(message) if @options.has_key?("Output_log")
@dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
begin
if @options["Timeout"] == false
@sock = TCPSocket.open(@options["Host"], @options["Port"])
else
Timeout.timeout(@options["Timeout"], Net::OpenTimeout) do
@sock = TCPSocket.open(@options["Host"], @options["Port"])
end
end
rescue Net::OpenTimeout
raise Net::OpenTimeout, "timed out while opening a connection to the host"
rescue
@log.write($ERROR_INFO.to_s + "\n") if @options.has_key?("Output_log")
@dumplog.log_dump('#', $ERROR_INFO.to_s + "\n") if @options.has_key?("Dump_log")
raise
end
@sock.sync = true
@sock.binmode
message = "Connected to " + @options["Host"] + ".\n"
yield(message) if block_given?
@log.write(message) if @options.has_key?("Output_log")
@dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
end
end Создаёт новый объект Net::Telnet.
Пытается подключиться к хосту (если не предоставлен параметр Proxy: см. ниже). Если указан блок, он передаёт сообщения об статусе попытки подключения к серверу в формате:
Trying localhost... Connected to localhost.
options — это хэш параметров. Следующий пример перечисляет все параметры и их значения по умолчанию.
host = Net::Telnet::new(
"Host" => "localhost", # default: "localhost"
"Port" => 23, # default: 23
"Binmode" => false, # default: false
"Output_log" => "output_log", # default: nil (no output)
"Dump_log" => "dump_log", # default: nil (no output)
"Prompt" => /[$%#>] \z/n, # default: /[$%#>] \z/n
"Telnetmode" => true, # default: true
"Timeout" => 10, # default: 10
# if ignore timeout then set "Timeout" to false.
"Waittime" => 0, # default: 0
"Proxy" => proxy # default: nil
# proxy is Net::Telnet or IO object
)
Параметры имеют следующие значения:
- Host
-
имя хоста или IP-адрес хоста для подключения в виде строки. По умолчанию “localhost”.
- Port
-
порт для подключения. По умолчанию 23.
- Binmode
-
если false (по умолчанию), выполняется замена символов новой строки. Исходящий LF преобразуется в CRLF, а входящий CRLF в LF. Если true, эта замена не выполняется. Это значение также можно установить с помощью метода binmode(). Выходная замена применяется только к методам puts() и print(), а не к методу write(). Точный характер замены новой строки также зависит от параметров telnet SGA и BIN.
- Output_log
-
имя файла, в который будут записываться сообщения о статусе соединения и весь принятый трафик. В случае правильной сессии telnet, это будет включать в себя ввод клиента, как эхо от хоста; в противном случае, он включает только ответы сервера. Вывод добавляется в этот файл без изменений.
- Dump_log
-
как Output_log, за исключением того, что вывод записывается в формате hexdump (16 байт на строке как пары шестнадцатеричных значений, за которыми следует их печатный эквивалент), с сообщениями о статусе соединения, предваряемыми '#', отправленным трафиком, предваряемым '>', и принятым трафиком, предваряемым '<'. По умолчанию журнал dump не ведётся.
- Prompt
-
регулярное выражение, соответствующее последовательности приглашения командной строки хоста. Это необходимо классу Telnet для определения момента завершения вывода команды от хоста и готовности хоста принять новую команду. По умолчанию это регулярное выражение /[$%#>] z/n.
- Telnetmode
-
булево значение, по умолчанию true. В режиме telnet трафик, полученный от хоста, анализируется на наличие специальных последовательностей команд, и эти последовательности экранируются в исходящем трафике, отправленном с помощью puts() или print() (но не write()). Если вы используете объект Net::Telnet для подключения к не-telnet-сервису (такому как SMTP или POP), это значение должно быть установлено в “false”, чтобы предотвратить нежелательное искажение данных. Это значение также можно установить методом telnetmode().
- Timeout
-
число секунд, ожидаемых до таймаута как первоначальной попытки подключения к хосту (в этом конструкторе), что приводит к исключению Net::OpenTimeout, так и всех попыток чтения данных от хоста, что приводит к исключению Net::ReadTimeout (в waitfor(), cmd() и login()). Значение по умолчанию — 10 секунд. Вы можете отключить таймаут, установив это значение в false. В этом случае попытка подключения в конечном итоге закончится таймаутом на вызове сокета connect(2) с ошибкой Errno::ETIMEDOUT (но обычно только через несколько минут), но другие попытки чтения данных от хоста будут зависать неопределённое время, если данные не поступают.
- Waittime
-
время ожидания после обнаружения чего-то похожего на приглашение (т. е., полученных данных, соответствующих регулярному выражению параметра Prompt) для проверки, появляются ли дополнительные данные. Если дополнительные данные появятся в это время, Net::Telnet предположит, что то, что было видно, на самом деле не было приглашением. Это делается для того, чтобы избежать ложных срабатываний, но также может привести к пропускам реальных приглашений (например, если фоновый процесс записывает в терминал вскоре после отображения приглашения).
- Proxy
-
объект прокси, используемый вместо открытия прямого соединения с хостом. Должен быть либо другим объектом Net::Telnet, либо объектом IO. Если это другой объект Net::Telnet, этот экземпляр будет использовать его сокет для связи. Если объект IO, он используется напрямую для связи. Любой другой тип объекта приведёт к ошибке.
Методы публичного экземпляра
# File lib/net/telnet.rb, line 405
def binmode(mode = nil)
case mode
when nil
@options["Binmode"]
when true, false
@options["Binmode"] = mode
else
raise ArgumentError, "argument must be true or false"
end
end Включить (mode == false) или выключить (mode == true) преобразование символов новой строки, или вернуть текущее значение (mode не указано).
# File lib/net/telnet.rb, line 417
def binmode=(mode)
if (true == mode or false == mode)
@options["Binmode"] = mode
else
raise ArgumentError, "argument must be true or false"
end
end Включить преобразование символов новой строки (false) или выключить (true).
# File lib/net/telnet.rb, line 757 def close @sock.close end
Закрывает соединение
# File lib/net/telnet.rb, line 678
def cmd(options) # :yield: recvdata
match = @options["Prompt"]
time_out = @options["Timeout"]
fail_eof = @options["FailEOF"]
if options.kind_of?(Hash)
string = options["String"]
match = options["Match"] if options.has_key?("Match")
time_out = options["Timeout"] if options.has_key?("Timeout")
fail_eof = options["FailEOF"] if options.has_key?("FailEOF")
else
string = options
end
self.puts(string)
if block_given?
waitfor({"Prompt" => match, "Timeout" => time_out, "FailEOF" => fail_eof}){|c| yield c }
else
waitfor({"Prompt" => match, "Timeout" => time_out, "FailEOF" => fail_eof})
end
end Отправить команду хосту.
Точнее, отправляет строку хосту и считывает все полученные данные до тех пор, пока не увидит приглашение или другую соответствующую последовательность.
Если блок предоставлен, полученные данные будут переданы ему по мере считывания. Предоставлен ли блок или нет, полученные данные будут возвращены как строка. Обратите внимание, что полученные данные включают приглашение и, в большинстве случаев, ответ хоста на нашу команду.
options может быть строкой, определяющей строку или команду для отправки хосту; или это хеш опций. Если хеш, можно указать следующие опции:
- Строка
-
команда или другая строка для отправки хосту.
- Match
-
регулярное выражение, последовательность для поиска в полученных данных перед возвращением. Если не указано, будет использовано значение опции Prompt, заданное при создании этого экземпляра, или, в противном случае, стандартное приглашение /[$%#>] z/n.
- Timeout
-
время в секундах ожидания данных от хоста перед подъёмом ошибки Timeout. Если не указано, будет использовано значение опции Timeout, заданное при создании экземпляра, или, в противном случае, значение по умолчанию 10 секунд.
Команда или строка будут иметь последовательность новой строки, добавленную к ним.
# File lib/net/telnet.rb, line 722
def login(options, password = nil) # :yield: recvdata
login_prompt = /[Ll]ogin[: ]*\z/n
password_prompt = /[Pp]ass(?:word|phrase)[: ]*\z/n
if options.kind_of?(Hash)
username = options["Name"]
password = options["Password"]
login_prompt = options["LoginPrompt"] if options["LoginPrompt"]
password_prompt = options["PasswordPrompt"] if options["PasswordPrompt"]
else
username = options
end
if block_given?
line = waitfor(login_prompt){|c| yield c }
if password
line += cmd({"String" => username,
"Match" => password_prompt}){|c| yield c }
line += cmd(password){|c| yield c }
else
line += cmd(username){|c| yield c }
end
else
line = waitfor(login_prompt)
if password
line += cmd({"String" => username,
"Match" => password_prompt})
line += cmd(password)
else
line += cmd(username)
end
end
line
end Вход в систему на хост с заданным именем пользователя и паролем.
Имя пользователя и пароль могут быть предоставлены либо в виде двух строковых аргументов в указанном порядке, либо в виде хеша с ключами “Name” и “Password”.
Этот метод ищет строки “login” и “Password” от хоста, чтобы определить, когда отправлять имя пользователя и пароль. Если последовательность входа в систему не следует этому шаблону (например, вы подключаетесь к службе, отличной от telnet), вам нужно будет обработать вход самостоятельно.
Пароль можно опустить, либо предоставив только один строковый аргумент, который будет использоваться в качестве имени пользователя, либо предоставив хеш без ключа “Password”. В этом случае метод не будет искать приглашение “Password:” ; если оно будет отправлено, с ним придётся разобраться в последующих вызовах.
Метод возвращает все данные, полученные во время процесса входа в систему от хоста, включая отображенное имя пользователя, но не пароль (который хост не должен отображать). Если блок передан, эти полученные данные также передаются в блок по мере их получения.
# File lib/net/telnet.rb, line 431
def preprocess(string)
# combine CR+NULL into CR
string = string.gsub(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"]
# combine EOL into "\n"
string = string.gsub(/#{EOL}/no, "\n") unless @options["Binmode"]
# remove NULL
string = string.gsub(/#{NULL}/no, '') unless @options["Binmode"]
string.gsub(/#{IAC}(
[#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
[#{DO}#{DONT}#{WILL}#{WONT}]
[#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|
#{SB}[^#{IAC}]*#{IAC}#{SE}
)/xno) do
if IAC == $1 # handle escaped IAC characters
IAC
elsif AYT == $1 # respond to "IAC AYT" (are you there)
self.write("nobody here but us pigeons" + EOL)
''
elsif DO[0] == $1[0] # respond to "IAC DO x"
if OPT_BINARY[0] == $1[1]
@telnet_option["BINARY"] = true
self.write(IAC + WILL + OPT_BINARY)
else
self.write(IAC + WONT + $1[1..1])
end
''
elsif DONT[0] == $1[0] # respond to "IAC DON'T x" with "IAC WON'T x"
self.write(IAC + WONT + $1[1..1])
''
elsif WILL[0] == $1[0] # respond to "IAC WILL x"
if OPT_BINARY[0] == $1[1]
self.write(IAC + DO + OPT_BINARY)
elsif OPT_ECHO[0] == $1[1]
self.write(IAC + DO + OPT_ECHO)
elsif OPT_SGA[0] == $1[1]
@telnet_option["SGA"] = true
self.write(IAC + DO + OPT_SGA)
else
self.write(IAC + DONT + $1[1..1])
end
''
elsif WONT[0] == $1[0] # respond to "IAC WON'T x"
if OPT_ECHO[0] == $1[1]
self.write(IAC + DONT + OPT_ECHO)
elsif OPT_SGA[0] == $1[1]
@telnet_option["SGA"] = false
self.write(IAC + DONT + OPT_SGA)
else
self.write(IAC + DONT + $1[1..1])
end
''
else
''
end
end
end Предварительная обработка полученных данных с хоста.
Выполняет преобразование символов новой строки и обнаруживает последовательности команд telnet. Вызывается автоматически методом waitfor(). Вы должны использовать этот метод самостоятельно только в том случае, если вы считали ввод непосредственно с помощью sysread() или аналогичного метода, и даже тогда только в режиме telnet.
# File lib/net/telnet.rb, line 625
def print(string)
string = string.gsub(/#{IAC}/no, IAC + IAC) if @options["Telnetmode"]
if @options["Binmode"]
self.write(string)
else
if @telnet_option["BINARY"] and @telnet_option["SGA"]
# IAC WILL SGA IAC DO BIN send EOL --> CR
self.write(string.gsub(/\n/n, CR))
elsif @telnet_option["SGA"]
# IAC WILL SGA send EOL --> CR+NULL
self.write(string.gsub(/\n/n, CR + NULL))
else
# NONE send EOL --> CR+LF
self.write(string.gsub(/\n/n, EOL))
end
end
end Отправляет строку хосту.
Это не автоматически добавляет последовательность новой строки к строке. Встроенные символы новой строки могут быть преобразованы, и последовательности команд telnet могут быть экранированы в зависимости от значений telnetmode, binmode и telnet, установленных хостом.
# File lib/net/telnet.rb, line 647 def puts(string) self.print(string + "\n") end
Отправляет строку хосту.
То же, что и print(), но добавляет последовательность новой строки к строке.
# File lib/net/telnet.rb, line 381
def telnetmode(mode = nil)
case mode
when nil
@options["Telnetmode"]
when true, false
@options["Telnetmode"] = mode
else
raise ArgumentError, "argument must be true or false, or missing"
end
end Включить (mode == true) или выключить (mode == false) интерпретацию команд telnet или вернуть текущее значение (mode не предоставлено). Он должен быть включен для реальных сессий telnet, выключен, если используется Net::Telnet для подключения к службе, не использующей telnet, например, SMTP.
# File lib/net/telnet.rb, line 395
def telnetmode=(mode)
if (true == mode or false == mode)
@options["Telnetmode"] = mode
else
raise ArgumentError, "argument must be true or false"
end
end Включить (true) или выключить (false) интерпретацию команд telnet. Он должен быть включен для реальных сессий telnet, выключен, если используется Net::Telnet для подключения к службе, не использующей telnet, например, SMTP.
# File lib/net/telnet.rb, line 528
def waitfor(options) # :yield: recvdata
time_out = @options["Timeout"]
waittime = @options["Waittime"]
fail_eof = @options["FailEOF"]
if options.kind_of?(Hash)
prompt = if options.has_key?("Match")
options["Match"]
elsif options.has_key?("Prompt")
options["Prompt"]
elsif options.has_key?("String")
Regexp.new( Regexp.quote(options["String"]) )
end
time_out = options["Timeout"] if options.has_key?("Timeout")
waittime = options["Waittime"] if options.has_key?("Waittime")
fail_eof = options["FailEOF"] if options.has_key?("FailEOF")
else
prompt = options
end
if time_out == false
time_out = nil
end
line = ''
buf = ''
rest = ''
until(prompt === line and not IO::select([@sock], nil, nil, waittime))
unless IO::select([@sock], nil, nil, time_out)
raise Net::ReadTimeout, "timed out while waiting for more data"
end
begin
c = @sock.readpartial(1024 * 1024)
@dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
if @options["Telnetmode"]
c = rest + c
if Integer(c.rindex(/#{IAC}#{SE}/no) || 0) <
Integer(c.rindex(/#{IAC}#{SB}/no) || 0)
buf = preprocess(c[0 ... c.rindex(/#{IAC}#{SB}/no)])
rest = c[c.rindex(/#{IAC}#{SB}/no) .. -1]
elsif pt = c.rindex(/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) ||
c.rindex(/\r\z/no)
buf = preprocess(c[0 ... pt])
rest = c[pt .. -1]
else
buf = preprocess(c)
rest = ''
end
else
# Not Telnetmode.
#
# We cannot use preprocess() on this data, because that
# method makes some Telnetmode-specific assumptions.
buf = rest + c
rest = ''
unless @options["Binmode"]
if pt = buf.rindex(/\r\z/no)
buf = buf[0 ... pt]
rest = buf[pt .. -1]
end
buf.gsub!(/#{EOL}/no, "\n")
end
end
@log.print(buf) if @options.has_key?("Output_log")
line += buf
yield buf if block_given?
rescue EOFError # End of file reached
raise if fail_eof
if line == ''
line = nil
yield nil if block_given?
end
break
end
end
line
end Считывание данных с хоста до тех пор, пока не будет найдена определенная последовательность.
Если блок передан, полученные данные будут передаваться по мере считывания (не обязательно все сразу), или nil, если EOF произойдёт до получения каких-либо данных. Предоставлен ли блок или нет, все считанные данные будут возвращены в одной строке, или снова nil, если EOF произойдёт до получения каких-либо данных. Обратите внимание, что полученные данные включают последовательность, которую мы искали.
options может быть либо регулярным выражением, либо хешем опций. Если регулярное выражение, это определяет ожидаемые данные. Если хеш, можно указать следующие опции:
- Match
-
регулярное выражение, определяющее ожидаемые данные.
- Prompt
-
как для Match; используется только если Match не указан.
- Строка
-
как для Match, за исключением строки, которая будет преобразована в регулярное выражение. Используется только если Match и Prompt не указаны.
- Timeout
-
количество секунд ожидания данных от хоста перед подъёмом ошибки Timeout::Error. Если установлено в false, таймаут не произойдёт. Если не указано, будет использовано значение опции Timeout, заданное при создании экземпляра, или, в противном случае, значение по умолчанию 10 секунд.
- Waittime
-
количество секунд ожидания после сопоставления с входными данными, чтобы посмотреть, придут ли дополнительные данные. Если дополнительные данные придут в течение этого времени, мы будем считать, что сопоставления не произошло, и продолжим попытки сопоставления. Если не указано, будет использовано значение опции Waittime, заданное при создании экземпляра, или, в противном случае, значение по умолчанию 0 секунд, что означает, что не нужно ждать дополнительных входных данных.
- FailEOF
-
если true, при закрытии подключения удалённым концом будет поднята ошибка EOFError. В противном случае, по умолчанию действует старое поведение, что функция вернёт любые уже полученные данные, или nil, если ничего не было получено.
# File lib/net/telnet.rb, line 610
def write(string)
length = string.length
while 0 < length
IO::select(nil, [@sock])
@dumplog.log_dump('>', string[-length..-1]) if @options.has_key?("Dump_log")
length -= @sock.syswrite(string[-length..-1])
end
end Записать string на хост.
Не выполняет никаких преобразований на string. Будет регистрировать string в дамп-лог, если опция Dump_log установлена.
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.