diff options
author | (no author) <(no author)@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2000-04-14 14:35:50 +0000 |
---|---|---|
committer | (no author) <(no author)@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2000-04-14 14:35:50 +0000 |
commit | ed131ebad88c2948d7b40f93ff6ce39f1eb8a2f7 (patch) | |
tree | 7bbf62c5e0d0bb8ff971637cf432c6f16824f8f6 /lib/net | |
parent | be1fea072cd0d22788ef8a931c0c6b64a2503b5d (diff) |
This commit was manufactured by cvs2svn to create tag 'v1_4_4'.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/tags/v1_4_4@668 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/net')
-rw-r--r-- | lib/net/ftp.rb | 642 | ||||
-rw-r--r-- | lib/net/http.rb | 312 | ||||
-rw-r--r-- | lib/net/pop.rb | 315 | ||||
-rw-r--r-- | lib/net/protocol.rb | 724 | ||||
-rw-r--r-- | lib/net/session.rb | 565 | ||||
-rw-r--r-- | lib/net/smtp.rb | 178 | ||||
-rw-r--r-- | lib/net/telnet.rb | 749 |
7 files changed, 3222 insertions, 263 deletions
diff --git a/lib/net/ftp.rb b/lib/net/ftp.rb new file mode 100644 index 0000000000..cbb2832a0d --- /dev/null +++ b/lib/net/ftp.rb @@ -0,0 +1,642 @@ +=begin + += net/ftp.rb + +written by Shugo Maeda <shugo@ruby-lang.org> + +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. + +=end + +require "socket" +require "monitor" + +module Net + + class FTPError < StandardError; end + class FTPReplyError < FTPError; end + class FTPTempError < FTPError; end + class FTPPermError < FTPError; end + class FTPProtoError < FTPError; end + + class FTP + include MonitorMixin + + FTP_PORT = 21 + CRLF = "\r\n" + + attr_accessor :passive, :return_code, :debug_mode + attr_reader :welcome, :lastresp + + def FTP.open(host, user = nil, passwd = nil, acct = nil) + new(host, user, passwd, acct) + end + + def initialize(host = nil, user = nil, passwd = nil, acct = nil) + super + @passive = false + @return_code = "\n" + @debug_mode = false + if host + connect(host) + if user + login(user, passwd, acct) + end + end + end + + def open_socket(host, port) + if defined? SOCKSsocket and ENV["SOCKS_SERVER"] + @passive = true + return SOCKSsocket.open(host, port) + else + return TCPsocket.open(host, port) + end + end + private :open_socket + + def connect(host, port = FTP_PORT) + if @debug_mode + print "connect: ", host, ", ", port, "\n" + end + synchronize do + @sock = open_socket(host, port) + voidresp + end + end + + def sanitize(s) + if s =~ /^PASS /i + return s[0, 5] + "*" * (s.length - 5) + else + return s + end + end + private :sanitize + + def putline(line) + if @debug_mode + print "put: ", sanitize(line), "\n" + end + line = line + CRLF + @sock.write(line) + end + private :putline + + def getline + line = @sock.readline # if get EOF, raise EOFError + if line[-2, 2] == CRLF + line = line[0 .. -3] + elsif line[-1] == ?\r or + line[-1] == ?\n + line = line[0 .. -2] + end + if @debug_mode + print "get: ", sanitize(line), "\n" + end + return line + end + private :getline + + def getmultiline + line = getline + buff = line + if line[3] == ?- + code = line[0, 3] + begin + line = getline + buff << "\n" << line + end until line[0, 3] == code and line[3] != ?- + end + return buff << "\n" + end + private :getmultiline + + def getresp + resp = getmultiline + @lastresp = resp[0, 3] + c = resp[0] + case c + when ?1, ?2, ?3 + return resp + when ?4 + raise FTPTempError, resp + when ?5 + raise FTPPermError, resp + else + raise FTPProtoError, resp + end + end + private :getresp + + def voidresp + resp = getresp + if resp[0] != ?2 + raise FTPReplyError, resp + end + end + private :voidresp + + def sendcmd(cmd) + synchronize do + putline(cmd) + return getresp + end + end + + def voidcmd(cmd) + synchronize do + putline(cmd) + voidresp + end + end + + def sendport(host, port) + af = (@sock.peeraddr)[0] + if af == "AF_INET" + hbytes = host.split(".") + pbytes = [port / 256, port % 256] + bytes = hbytes + pbytes + cmd = "PORT " + bytes.join(",") + elsif af == "AF_INET6" + cmd = "EPRT |2|" + host + "|" + sprintf("%d", port) + "|" + else + raise FTPProtoError, host + end + voidcmd(cmd) + end + private :sendport + + def makeport + sock = TCPserver.open(@sock.addr[3], 0) + port = sock.addr[1] + host = TCPsocket.getaddress(@sock.addr[2]) + resp = sendport(host, port) + return sock + end + private :makeport + + def makepasv + if @sock.peeraddr[0] == "AF_INET" + host, port = parse227(sendcmd("PASV")) + else + host, port = parse229(sendcmd("EPSV")) + # host, port = parse228(sendcmd("LPSV")) + end + return host, port + end + private :makepasv + + def transfercmd(cmd) + if @passive + host, port = makepasv + conn = open_socket(host, port) + resp = sendcmd(cmd) + if resp[0] != ?1 + raise FTPReplyError, resp + end + else + sock = makeport + resp = sendcmd(cmd) + if resp[0] != ?1 + raise FTPReplyError, resp + end + conn = sock.accept + end + return conn + end + private :transfercmd + + def getaddress + thishost = Socket.gethostname + if not thishost.index(".") + thishost = Socket.gethostbyname(thishost)[0] + end + if ENV.has_key?("LOGNAME") + realuser = ENV["LOGNAME"] + elsif ENV.has_key?("USER") + realuser = ENV["USER"] + else + realuser = "anonymous" + end + return realuser + "@" + thishost + end + private :getaddress + + def login(user = "anonymous", passwd = nil, acct = nil) + if user == "anonymous" and passwd == nil + passwd = getaddress + end + + resp = "" + synchronize do + resp = sendcmd('USER ' + user) + if resp[0] == ?3 + resp = sendcmd('PASS ' + passwd) + end + if resp[0] == ?3 + resp = sendcmd('ACCT ' + acct) + end + end + if resp[0] != ?2 + raise FTPReplyError, resp + end + @welcome = resp + end + + def retrbinary(cmd, blocksize, callback = Proc.new) + synchronize do + voidcmd("TYPE I") + conn = transfercmd(cmd) + loop do + data = conn.read(blocksize) + break if data == nil + callback.call(data) + end + conn.close + voidresp + end + end + + def retrlines(cmd, callback = nil) + if iterator? + callback = Proc.new + elsif not callback.is_a?(Proc) + callback = Proc.new {|line| print line, "\n"} + end + synchronize do + voidcmd("TYPE A") + conn = transfercmd(cmd) + loop do + line = conn.gets + break if line == nil + if line[-2, 2] == CRLF + line = line[0 .. -3] + elsif line[-1] == ?\n + line = line[0 .. -2] + end + callback.call(line) + end + conn.close + voidresp + end + end + + def storbinary(cmd, file, blocksize, callback = nil) + if iterator? + callback = Proc.new + end + use_callback = callback.is_a?(Proc) + synchronize do + voidcmd("TYPE I") + conn = transfercmd(cmd) + loop do + buf = file.read(blocksize) + break if buf == nil + conn.write(buf) + callback.call(buf) if use_callback + end + conn.close + voidresp + end + end + + def storlines(cmd, file, callback = nil) + if iterator? + callback = Proc.new + end + use_callback = callback.is_a?(Proc) + synchronize do + voidcmd("TYPE A") + conn = transfercmd(cmd) + loop do + buf = file.gets + break if buf == nil + if buf[-2, 2] != CRLF + buf = buf.chomp + CRLF + end + conn.write(buf) + callback.call(buf) if use_callback + end + conn.close + voidresp + end + end + + def getbinaryfile(remotefile, localfile, blocksize, callback = nil) + if iterator? + callback = Proc.new + end + use_callback = callback.is_a?(Proc) + f = open(localfile, "w") + begin + f.binmode + retrbinary("RETR " + remotefile, blocksize) do |data| + f.write(data) + callback.call(data) if use_callback + end + ensure + f.close + end + end + + def gettextfile(remotefile, localfile, callback = nil) + if iterator? + callback = Proc.new + end + use_callback = callback.is_a?(Proc) + f = open(localfile, "w") + begin + retrlines("RETR " + remotefile) do |line| + line = line + @return_code + f.write(line) + callback.call(line) if use_callback + end + ensure + f.close + end + end + + def putbinaryfile(localfile, remotefile, blocksize, callback = nil) + if iterator? + callback = Proc.new + end + use_callback = callback.is_a?(Proc) + f = open(localfile) + begin + f.binmode + storbinary("STOR " + remotefile, f, blocksize) do |data| + callback.call(data) if use_callback + end + ensure + f.close + end + end + + def puttextfile(localfile, remotefile, callback = nil) + if iterator? + callback = Proc.new + end + use_callback = callback.is_a?(Proc) + f = open(localfile) + begin + storlines("STOR " + remotefile, f) do |line| + callback.call(line) if use_callback + end + ensure + f.close + end + end + + def acct(account) + cmd = "ACCT " + account + voidcmd(cmd) + end + + def nlst(dir = nil) + cmd = "NLST" + if dir + cmd = cmd + " " + dir + end + files = [] + retrlines(cmd) do |line| + files.push(line) + end + return files + end + + def list(*args, &block) + cmd = "LIST" + args.each do |arg| + cmd = cmd + " " + arg + end + if block + retrlines(cmd, &block) + else + lines = [] + retrlines(cmd) do |line| + lines << line + end + return lines + end + end + alias ls list + alias dir list + + def rename(fromname, toname) + resp = sendcmd("RNFR " + fromname) + if resp[0] != ?3 + raise FTPReplyError, resp + end + voidcmd("RNTO " + toname) + end + + def delete(filename) + resp = sendcmd("DELE " + filename) + if resp[0, 3] == "250" + return + elsif resp[0] == ?5 + raise FTPPermError, resp + else + raise FTPReplyError, resp + end + end + + def chdir(dirname) + if dirname == ".." + begin + voidcmd("CDUP") + return + rescue FTPPermError + if $![0, 3] != "500" + raise FTPPermError, $! + end + end + end + cmd = "CWD " + dirname + voidcmd(cmd) + end + + def size(filename) + voidcmd("TYPE I") + resp = sendcmd("SIZE " + filename) + if resp[0, 3] != "213" + raise FTPReplyError, resp + end + return resp[3..-1].strip.to_i + end + + MDTM_REGEXP = /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ + + def mtime(filename, local = false) + str = mdtm(filename) + ary = str.scan(MDTM_REGEXP)[0].collect {|i| i.to_i} + return local ? Time.local(*ary) : Time.gm(*ary) + end + + def mkdir(dirname) + resp = sendcmd("MKD " + dirname) + return parse257(resp) + end + + def rmdir(dirname) + voidcmd("RMD " + dirname) + end + + def pwd + resp = sendcmd("PWD") + return parse257(resp) + end + alias getdir pwd + + def system + resp = sendcmd("SYST") + if resp[0, 3] != "215" + raise FTPReplyError, resp + end + return resp[4 .. -1] + end + + def abort + line = "ABOR" + CRLF + print "put: ABOR\n" if @debug_mode + @sock.send(line, Socket::MSG_OOB) + resp = getmultiline + unless ["426", "226", "225"].include?(resp[0, 3]) + raise FTPProtoError, resp + end + return resp + end + + def status + line = "STAT" + CRLF + print "put: STAT\n" if @debug_mode + @sock.send(line, Socket::MSG_OOB) + return getresp + end + + def mdtm(filename) + resp = sendcmd("MDTM " + filename) + if resp[0, 3] == "213" + return resp[3 .. -1].strip + end + end + + def help(arg = nil) + cmd = "HELP" + if arg + cmd = cmd + " " + arg + end + sendcmd(cmd) + end + + def quit + voidcmd("QUIT") + end + + def close + @sock.close if @sock and not @sock.closed? + end + + def closed? + @sock == nil or @sock.closed? + end + + def parse227(resp) + if resp[0, 3] != "227" + raise FTPReplyError, resp + end + left = resp.index("(") + right = resp.index(")") + if left == nil or right == nil + raise FTPProtoError, resp + end + numbers = resp[left + 1 .. right - 1].split(",") + if numbers.length != 6 + raise FTPProtoError, resp + end + host = numbers[0, 4].join(".") + port = (numbers[4].to_i << 8) + numbers[5].to_i + return host, port + end + private :parse227 + + def parse228(resp) + if resp[0, 3] != "228" + raise FTPReplyError, resp + end + left = resp.index("(") + right = resp.index(")") + if left == nil or right == nil + raise FTPProtoError, resp + end + numbers = resp[left + 1 .. right - 1].split(",") + if numbers[0] == "4" + if numbers.length != 9 || numbers[1] != "4" || numbers[2 + 4] != "2" + raise FTPProtoError, resp + end + host = numbers[2, 4].join(".") + port = (numbers[7].to_i << 8) + numbers[8].to_i + elsif numbers[0] == "6" + if numbers.length != 21 || numbers[1] != "16" || numbers[2 + 16] != "2" + raise FTPProtoError, resp + end + v6 = ["", "", "", "", "", "", "", ""] + for i in 0 .. 7 + v6[i] = sprintf("%02x%02x", numbers[(i * 2) + 2].to_i, + numbers[(i * 2) + 3].to_i) + end + host = v6[0, 8].join(":") + port = (numbers[19].to_i << 8) + numbers[20].to_i + end + return host, port + end + private :parse228 + + def parse229(resp) + if resp[0, 3] != "229" + raise FTPReplyError, resp + end + left = resp.index("(") + right = resp.index(")") + if left == nil or right == nil + raise FTPProtoError, resp + end + numbers = resp[left + 1 .. right - 1].split(resp[left + 1, 1]) + if numbers.length != 4 + raise FTPProtoError, resp + end + port = numbers[3].to_i + host = (@sock.peeraddr())[3] + return host, port + end + private :parse228 + + def parse257(resp) + if resp[0, 3] != "257" + raise FTPReplyError, resp + end + if resp[3, 2] != ' "' + return "" + end + dirname = "" + i = 5 + n = resp.length + while i < n + c = resp[i, 1] + i = i + 1 + if c == '"' + if i > n or resp[i, 1] != '"' + break + end + i = i + 1 + end + dirname = dirname + c + end + return dirname + end + private :parse257 + end + +end diff --git a/lib/net/http.rb b/lib/net/http.rb new file mode 100644 index 0000000000..4847f88bf7 --- /dev/null +++ b/lib/net/http.rb @@ -0,0 +1,312 @@ +=begin + += net/http.rb + +maintained by Minero Aoki <aamine@dp.u-netsurf.ne.jp> +This file is derived from http-access.rb + +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. + +=end + +require 'net/session' + + +module Net + + +class HTTPError < ProtocolError; end +class HTTPBadResponse < HTTPError; end + + +=begin + += HTTP class + +== Class Methods + +: new( address, port = 80 ) + create new HTTP object. + +: port + returns HTTP default port, 80 + +: command_type + returns Command class, HTTPCommand + + +== Methods + +: get( path, header = nil, ret = '' ) + get data from "path" on connecting host. + "header" is a Hash like { 'Accept' => '*/*', ... }. + The data will be written to "ret" using "<<" method. + This method returns response header (Hash) and "ret". + +: head( path, header = nil ) + get only header from "path" on connecting host. + "header" is a Hash like { 'Accept' => '*/*', ... }. + This method returns header as a Hash like + + { 'content-length' => 'Content-Length: 2554', + 'content-type' => 'Content-Type: text/html', + ... } + +=end + + class HTTP < Protocol + + protocol_param :port, '80' + protocol_param :command_type, '::Net::HTTPCommand' + + + def get( path, u_header = nil, ret = '' ) + header = connecting { + @command.get ret, edit_path(path), u_header + } + return header, ret + end + + def head( path, u_header = nil ) + connecting { + @command.head edit_path(path), u_header + } + end + + + private + + + def connecting + if @socket.closed? then + @socket.reopen + end + header = yield + @socket.close unless keep_alive? header + + header + end + + def keep_alive?( header ) + if str = header[ 'connection' ] then + if /\Aconnection:\s*keep-alive/i === str then + return true + end + else + if @http_version == '1.1' then + return true + end + end + + false + end + + + def do_finish + unless @command.error_occured or @socket.closed? then + head '/', { 'Connection' => 'Close' } + end + end + + + def edit_path( path ) + path + end + + class << self + def Proxy( p_addr, p_port ) + klass = super + klass.module_eval %- + def edit_path( path ) + 'http://' + address + + (@port == #{self.port} ? '' : ':' + @port.to_s) + path + end + - + klass + end + end + + end + + HTTPSession = HTTP + + + class HTTPCommand < Command + + HTTPVersion = '1.1' + + def initialize( sock ) + @http_version = HTTPVersion + + @in_header = {} + @in_header[ 'Host' ] = sock.addr + @in_header[ 'Connection' ] = 'Keep-Alive' + @in_header[ 'Accept' ] = '*/*' + + super sock + end + + + attr :http_version + + def get( ret, path, u_header = nil ) + header = get_response( + sprintf( 'GET %s HTTP/%s', path, HTTPVersion ), u_header ) + + if chunked? header then + clen = read_chunked_body( ret ) + header.delete 'transfer-encoding' + header[ 'content-length' ] = "Content-Length: #{clen}" + else + if clen = content_length( header ) then + @socket.read clen, ret + else + @socket.read_all ret + end + end + + header + end + + + def head( path, u_header = nil ) + get_response sprintf( 'HEAD %s HTTP/%s', path, HTTPVersion ), u_header + end + + + # def put + + # def delete + + # def trace + + # def options + + + private + + + def do_quit + unless @socket.closed? then + @socket.close + end + end + + def get_response( line, u_header ) + @socket.writeline line + write_header u_header + rep = get_reply + header = read_header + reply_must rep, SuccessCode + + header + end + + def get_reply + str = @socket.readline + unless /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i === str then + raise HTTPBadResponse, "wrong status line format: #{str}" + end + @http_version = $1 + status = $2 + discrip = $3 + + klass = case status[0] + when ?1 then + case status[2] + when ?0 then ContinueCode + when ?1 then SuccessCode + else UnknownCode + end + when ?2 then SuccessCode + when ?3 then RetryCode + when ?4 then ServerBusyCode + when ?5 then FatalErrorCode + else UnknownCode + end + klass.new( status, discrip ) + end + + + def content_length( header ) + unless str = header[ 'content-length' ] then + return nil + end + unless /\Acontent-length:\s*(\d+)/i === str then + raise HTTPBadResponse, "content-length format error" + end + $1.to_i + end + + def chunked?( header ) + if str = header[ 'transfer-encoding' ] then + if /\Atransfer-encoding:\s*chunked/i === str then + return true + end + end + + false + end + + + def read_header + header = {} + while true do + line = @socket.readline + break if line.empty? + /\A[^:]+/ === line + nm = $& + nm.strip! + nm.downcase! + header[ nm ] = line + end + + header + end + + def write_header( user ) + if user then + header = @in_header.dup.update user + else + header = @in_header + end + header.each do |n,v| + @socket.writeline n + ': ' + v + end + @socket.writeline '' + + if tmp = header['Connection'] then + /close/i === tmp + else + false + end + end + + def read_chunked_body( ret ) + line = nil + len = nil + total = 0 + + while true do + line = @socket.readline + unless /[0-9a-hA-H]+/ === line then + raise HTTPBadResponse, "chunk size not given" + end + len = $&.hex + break if len == 0 + @socket.read( len, ret ); total += len + @socket.read 2 # \r\n + end + while true do + line = @socket.readline + break if line.empty? + end + + total + end + + end + + +end # module Net diff --git a/lib/net/pop.rb b/lib/net/pop.rb index bd4551571d..0ec2481557 100644 --- a/lib/net/pop.rb +++ b/lib/net/pop.rb @@ -1,8 +1,14 @@ -# -# pop.rb version 1.0.1 -# -# author: Minero Aoki <aamine@dp.u-netsurf.ne.jp> -# +=begin + += net/pop.rb + +written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> + +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. + +=end + require 'net/session' require 'md5' @@ -10,101 +16,250 @@ require 'md5' module Net - class POP3Session < Session - attr :mails +=begin - def each() @mails.each{|m| yield m} end +== Net::POP3 +=== Super Class - private +Net::Protocol + +=== Class Methods + +: new( address = 'localhost', port = 110 ) + This method create a new POP3 object. + This will not open connection yet. + + +=== Methods + +: start( account, password ) + This method start POP3. + +: each{|popmail| ...} + This method is equals to "pop3.mails.each" +: mails + This method returns an array of ((URL:#POPMail)). + This array is renewed when login. - def proto_initialize - @proto_type = POP3Command - @port = 110 - @mails = [].freeze +=end + + class POP3 < Protocol + + protocol_param :port, '110' + protocol_param :command_type, '::Net::POP3Command' + + protocol_param :mail_type, '::Net::POPMail' + + def initialize( addr = nil, port = nil ) + super + @mails = [].freeze end + + attr :mails + + def each + @mails.each {|m| yield m } + end + + + private + def do_start( acnt, pwd ) - @proto.auth( acnt, pwd ) + @command.auth( acnt, pwd ) + t = self.type.mail_type @mails = [] - @proto.list.each_with_index do |size,idx| + @command.list.each_with_index do |size,idx| if size then - @mails.push POPMail.new( idx, size, @proto ) + @mails.push t.new( idx, size, @command ) end end @mails.freeze end + end - def do_finish - @proto.quit - end + POP = POP3 + POPSession = POP3 + POP3Session = POP3 +=begin - class POPMail +== Net::POPMail - def initialize( idx, siz, pro ) - @num = idx - @size = siz - @proto = pro +A class of mail which exists on POP server. - @deleted = false - end +=== Super Class - attr :size +Object - def all( dest = '' ) - @proto.retr( @num, dest ) - end - alias pop all - alias mail all - def top( lines, dest = '' ) - @proto.top( @num, lines, dest ) - end +=== Method - def header( dest = '' ) - top( 0, dest ) - end +: all +: pop +: mail + This method fetches a mail and return it. - def delete - @proto.dele( @num ) - @deleted = true - end - alias delete! delete +: header + This method fetches only mail header. - def deleted? - @deleted - end +: top( lines ) + This method fetches mail header and 'lines' lines body. + +: delete +: delete! + This method deletes mail. + +: size + size of mail(bytes) + +: deleted? + true if mail was deleted + +=end + + class POPMail + + def initialize( n, s, cmd ) + @num = n + @size = s + @command = cmd + @deleted = false end - end + attr :size - class APOPSession < POP3Session + def all( dest = '' ) + @command.retr( @num, dest ) + end + alias pop all + alias mail all - def proto_initialize - super - @proto_type = APOPCommand + def top( lines, dest = '' ) + @command.top( @num, lines, dest ) + end + + def header( dest = '' ) + top( 0, dest ) + end + + def delete + @command.dele( @num ) + @deleted = true + end + alias delete! delete + + def deleted? + @deleted end + def uidl + @command.uidl @num + end + + end + + +=begin + +== Net::APOP + +This class has no new methods. Only way of authetication is changed. + +=== Super Class + +Net::POP3 + +=end + + class APOP < POP3 + + protocol_param :command_type, 'Net::APOPCommand' + end + APOPSession = APOP + + +=begin + +== Net::POP3Command + +POP3 command class. + +=== Super Class + +Net::Command + +=== Class Methods + +: new( socket ) + This method creates new POP3Command object. 'socket' must be ProtocolSocket. + + +=== Methods + +: auth( account, password ) + This method do POP authorization (no RPOP) + In case of failed authorization, raises Protocol::ProtocolError exception. + +: list + a list of mails which existing on server. + The list is an array like "array[ number ] = size". + + ex: + + The list from server is + + 1 2452 + 2 3355 + 4 9842 + : + + then, an array is - POPSession = POP3Session - POP3 = POP3Session + [ nil, 2452, 3355, nil, 9842, ... ] +: quit + This method ends POP using 'QUIT' commmand. + +: rset + This method reset all changes done in current session, + by sending 'RSET' command. + +: top( num, lines = 0 ) + This method gets all mail header and 'lines' lines body + by sending 'TOP' command. 'num' is mail number. + + WARNING: the TOP command is 'Optional' in RFC1939 (POP3) + +: retr( num : Integer ) + This method gets a mail by 'RETR' command. 'num' is mail number. + +: dele( num : Integer ) + This method deletes a mail on server by 'DELE'. + +=end class POP3Command < Command + def initialize( sock ) + super + check_reply SuccessCode + end + + def auth( acnt, pass ) - @socket.writeline( 'USER ' + acnt ) + @socket.writeline 'USER ' + acnt check_reply_auth @socket.writeline( 'PASS ' + pass ) @@ -115,8 +270,7 @@ module Net def list - @socket.writeline( 'LIST' ) - check_reply( SuccessCode ) + getok 'LIST' arr = [] @socket.read_pendlist do |line| @@ -129,40 +283,40 @@ module Net def rset - @socket.writeline( 'RSET' ) - check_reply( SuccessCode ) + getok 'RSET' end def top( num, lines = 0, dest = '' ) - @socket.writeline( sprintf( 'TOP %d %d', num, lines ) ) - check_reply( SuccessCode ) - - return @socket.read_pendstr( dest ) + getok sprintf( 'TOP %d %d', num, lines ) + @socket.read_pendstr( dest ) end def retr( num, dest = '', &block ) - @socket.writeline( sprintf( 'RETR %d', num ) ) - check_reply( SuccessCode ) - - return @socket.read_pendstr( dest, &block ) + getok sprintf( 'RETR %d', num ) + @socket.read_pendstr( dest, &block ) end def dele( num ) - @socket.writeline( sprintf( 'DELE %s', num ) ) - check_reply( SuccessCode ) + getok sprintf( 'DELE %d', num ) end + def uidl( num ) + rep = getok( sprintf 'UIDL %d', num ) + uid = rep.msg.split(' ')[1] + + uid + end + private def do_quit - @socket.writeline( 'QUIT' ) - check_reply( SuccessCode ) + getok 'QUIT' end @@ -190,6 +344,22 @@ module Net end +=begin + +== APOPCommand + +=== Super Class + +POP3Command + +=== Methods + +: auth( account, password ) + This method do authorization by sending 'APOP' command. + If server is not APOP server, this raises Net::ProtoAuthError exception. + On other errors, raises Net::ProtocolError. + +=end class APOPCommand < POP3Command @@ -222,9 +392,4 @@ module Net end - - unless Session::Version == '1.0.1' then - $stderr.puts "WARNING: wrong version of session.rb & pop.rb" - end - end diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb new file mode 100644 index 0000000000..4d80153f2c --- /dev/null +++ b/lib/net/protocol.rb @@ -0,0 +1,724 @@ +=begin + += net/protocol.rb + +written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> + +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. + +=end + + +require 'socket' + + +module Net + + Version = '1.1.8' + +=begin + +== Net::Protocol + +the abstruct class for Internet protocol + +=== Super Class + +Object + +=== Class Methods + +: new( address = 'localhost', port = nil ) + This method Creates a new protocol object. + +: start( address = 'localhost', port = nil, *args ) +: start( address = 'localhost', port = nil, *args ){|proto| .... } + This method creates a new Protocol object and start session. + If you call this method with block, Protocol object give itself + to block and finish session when block returns. + +: Proxy( address, port ) + This method creates a proxy class of its protocol. + Arguments are address/port of proxy host. + + +=== Methods + +: address + the address of connecting server (FQDN). + +: port + connecting port number + +: start( *args ) + This method start protocol. If you call this method when the protocol + is already started, this only returns false without doing anything. + + '*args' are specified in subclasses. + +: finish + This method ends protocol. If you call this method before protocol starts, + it only return false without doing anything. + +: active? + true if session have been started + +=end + + class Protocol + + Version = ::Net::Version + + class << self + + def start( address = 'localhost', port = nil, *args ) + instance = new( address, port ) + + if iterator? then + instance.start( *args ) { yield instance } + else + instance.start *args + instance + end + end + + def Proxy( p_addr, p_port ) + klass = Class.new( self ) + klass.module_eval %- + + def initialize( addr, port ) + @proxyaddr = '#{p_addr}' + @proxyport = '#{p_port}' + super @proxyaddr, @proxyport + @address = addr + @port = port + end + + def connect( addr, port ) + super @proxyaddr, @proxyport + end + private :connect + + attr_reader :proxyaddr, :proxyport + - + def klass.proxy? + true + end + + klass + end + + def proxy? + false + end + + + private + + def protocol_param( name, val ) + module_eval %- + def self.#{name.id2name} + #{val} + end + - + end + + end + + + # + # sub-class requirements + # + # protocol_param command_type + # protocol_param port + # + # private method do_start (optional) + # private method do_finish (optional) + # + + protocol_param :port, 'nil' + protocol_param :command_type, 'nil' + protocol_param :socket_type, '::Net::Socket' + + + def initialize( addr = nil, port = nil ) + @address = addr || 'localhost' + @port = port || self.type.port + + @active = false + @pipe = nil + + @command = nil + @socket = nil + end + + + attr_reader :address, :port, + :command, :socket + + + def start( *args ) + return false if active? + @active = true + + begin + connect @address, @port + do_start *args + yield if iterator? + ensure + finish if iterator? + end + end + + def finish + ret = active? + + do_finish if @command + disconnect + @active = false + + ret + end + + def active? + @active + end + + def set_pipe( arg ) # un-documented + @pipe = arg + end + + + private + + + def do_start + end + + def do_finish + @command.quit + end + + + def connect( addr, port ) + @socket = self.type.socket_type.open( addr, port, @pipe ) + @command = self.type.command_type.new( @socket ) + end + + def disconnect + @command = nil + if @socket and not @socket.closed? then + @socket.close + end + @socket = nil + end + + end + + Session = Protocol + + + + class Command + + def initialize( sock ) + @socket = sock + @error_occured = false + @last_reply = nil + end + + attr_reader :socket, :error_occured, :last_reply + attr_writer :socket + + def quit + if @socket and not @socket.closed? then + do_quit + @error_occured = false + end + end + + + private + + def do_quit + end + + # abstract get_reply() + + def check_reply( *oks ) + @last_reply = get_reply + reply_must( @last_reply, *oks ) + end + + def reply_must( rep, *oks ) + oks.each do |i| + if i === rep then + return rep + end + end + + @error_occured = true + rep.error! @socket.sending + end + + def getok( line, ok = SuccessCode ) + @socket.writeline line + check_reply ok + end + + end + + + class ProtocolError < StandardError ; end + class ProtoSyntaxError < ProtocolError ; end + class ProtoFatalError < ProtocolError ; end + class ProtoUnknownError < ProtocolError ; end + class ProtoServerError < ProtocolError ; end + class ProtoAuthError < ProtocolError ; end + class ProtoCommandError < ProtocolError ; end + class ProtoRetryError < ProtocolError ; end + + class ReplyCode + + class << self + + def error_type( err ) + @err = err + end + + def error!( mes ) + raise @err, mes + end + + end + + def initialize( cod, mes ) + @code = cod + @msg = mes + @data = nil + end + + attr_reader :code, :msg + + def []( key ) + if @data then + @data[key] + else + nil + end + end + + def []=( key, val ) + unless h = @data then + @data = h = {} + end + h[key] = val + end + + + def error!( sending ) + mes = <<MES + +status %s +writing string is: +%s + +error message from server is: +%s +MES + type.error! sprintf( mes, @code, Net.quote(sending), Net.quote(@msg) ) + end + + end + + class SuccessCode < ReplyCode + error_type ProtoUnknownError + end + + class ContinueCode < SuccessCode + error_type ProtoUnknownError + end + + class ErrorCode < ReplyCode + error_type ProtocolError + end + + class SyntaxErrorCode < ErrorCode + error_type ProtoSyntaxError + end + + class FatalErrorCode < ErrorCode + error_type ProtoFatalError + end + + class ServerBusyCode < ErrorCode + error_type ProtoServerError + end + + class RetryCode < ReplyCode + error_type ProtoRetryError + end + + class UnknownCode < ReplyCode + error_type ProtoUnknownError + end + + + + class WriteAdapter + + def initialize( sock, mid ) + @sock = sock + @mid = mid + end + + def write( str ) + @sock.__send__ @mid, str + end + alias << write + + end + + class ReadAdapter + + def initialize( block ) + @block = block + end + + def <<( str ) + @block.call str + end + + end + + + class Socket + + def initialize( addr, port, pipe = nil ) + @addr = addr + @port = port + @pipe = pipe + + @closed = true + @ipaddr = '' + @sending = '' + @buffer = '' + + @socket = TCPsocket.new( addr, port ) + @closed = false + @ipaddr = @socket.addr[3] + end + + attr :pipe, true + + class << self + alias open new + end + + def reopen + unless closed? then + @socket.close + @buffer = '' + end + @socket = TCPsocket.new( @addr, @port ) + end + + attr :socket, true + + def close + @socket.close + @closed = true + end + + def closed? + @closed + end + + def address + @addr.dup + end + alias addr address + + attr_reader :port + + def ip_address + @ipaddr.dup + end + alias ipaddr ip_address + + attr_reader :sending + + + CRLF = "\r\n" + D_CRLF = ".\r\n" + TERMEXP = /\n|\r\n|\r/o + + + def read( len, ret = '' ) + @pipe << "reading #{len} bytes...\n" if pre = @pipe ; @pipe = nil + + rsize = 0 + while rsize + @buffer.size < len do + rsize += writeinto( ret, @buffer.size ) + fill_rbuf + end + writeinto( ret, len - rsize ) + + @pipe << "read #{len} bytes\n" if @pipe = pre + ret + end + + + def read_all( ret = '' ) + @pipe << "reading all...\n" if pre = @pipe; @pipe = nil + + rsize = 0 + begin + while true do + rsize += writeinto( ret, @buffer.size ) + fill_rbuf + end + rescue EOFError + ; + end + + @pipe << "read #{rsize} bytes\n" if @pipe = pre + ret + end + + + def readuntil( target ) + until idx = @buffer.index( target ) do + fill_rbuf + end + + ret = '' + writeinto( ret, idx + target.size ) + ret + end + + + def readline + ret = readuntil( CRLF ) + ret.chop! + ret + end + + + def read_pendstr( dest = '' ) + @pipe << "reading text...\n" if pre = @pipe ; @pipe = nil + + rsize = 0 + + while (str = readuntil( CRLF )) != D_CRLF do + rsize += str.size + str.gsub!( /\A\./o, '' ) + dest << str + end + + @pipe << "read #{rsize} bytes\n" if @pipe = pre + dest + end + + + def read_pendlist + @pipe << "reading list...\n" if pre = @pipe ; @pipe = nil + + arr = [] + str = nil + call = iterator? + + while (str = readuntil( CRLF )) != D_CRLF do + str.chop! + arr.push str + yield str if iterator? + end + + @pipe << "read #{arr.size} lines\n" if @pipe = pre + arr + end + + + private + + + READ_BLOCK = 1024 * 8 + + def fill_rbuf + @buffer << @socket.sysread( READ_BLOCK ) + end + + def writeinto( ret, len ) + bsi = @buffer.size + ret << @buffer[ 0, len ] + @buffer = @buffer[ len, bsi - len ] + + @pipe << %{read "#{Net.quote ret}"\n} if @pipe + len + end + + + public + + + def write( str ) + do_write_beg + do_write_do str + do_write_fin + end + + + def writeline( str ) + do_write_beg + do_write_do str + do_write_do CRLF + do_write_fin + end + + + def write_bin( src, block = nil ) + do_write_beg + if block then + block.call WriteAdapter.new( self, :do_write_do ) + else + src.each do |bin| + do_write_do bin + end + end + do_write_fin + end + + + def write_pendstr( src ) + @pipe << "writing text from #{src.type}\n" if pre = @pipe ; @pipe = nil + + do_write_beg + if iterator? then + yield WriteAdapter.new( self, :write_pendstr_inner ) + else + write_pendstr_inner src + end + each_crlf_line2( :i_w_pend ) + do_write_do D_CRLF + wsize = do_write_fin + + @pipe << "wrote #{wsize} bytes text" if @pipe = pre + wsize + end + + + private + + + def write_inner( src ) + each_crlf_line( src, :do_write_do ) + end + + + def write_pendstr_inner( src ) + each_crlf_line src, :i_w_pend + end + + def i_w_pend( line ) + do_write_do '.' if line[0] == ?. + do_write_do line + end + + + def each_crlf_line( src, mid ) + beg = 0 + buf = pos = s = bin = nil + + adding( src ) do + beg = 0 + buf = @wbuf + while pos = buf.index( TERMEXP, beg ) do + s = $&.size + break if pos + s == buf.size - 1 and buf[-1] == ?\r + + send mid, buf[ beg, pos - beg ] << CRLF + beg = pos + s + end + @wbuf = buf[ beg, buf.size - beg ] if beg != 0 + end + end + + def adding( src ) + i = nil + + case src + when String + 0.step( src.size, 512 ) do |i| + @wbuf << src[ i, 512 ] + yield + end + + when File + while i = src.read( 512 ) do + @wbuf << i + yield + end + + else + src.each do |bin| + @wbuf << bin + yield if @wbuf.size > 512 + end + end + end + + def each_crlf_line2( mid ) + buf = @wbuf + beg = pos = nil + + buf << "\n" unless /\n|\r/o === buf[-1,1] + + beg = 0 + while pos = buf.index( TERMEXP, beg ) do + send mid, buf[ beg, pos - beg ] << CRLF + beg = pos + $&.size + end + end + + + def do_write_beg + @writtensize = 0 + @sending = '' + @wbuf = '' + end + + def do_write_do( arg ) + if @pipe or @sending.size < 128 then + @sending << Net.quote( arg ) + else + @sending << '...' unless @sending[-1] == ?. + end + + s = @socket.write( arg ) + @writtensize += s + s + end + + def do_write_fin + if @pipe then + @pipe << 'write "' + @pipe << @sending + @pipe << "\"\n" + end + + @socket.flush + @writtensize + end + + end + + + def Net.quote( str ) + str = str.gsub( "\n", '\\n' ) + str.gsub!( "\r", '\\r' ) + str.gsub!( "\t", '\\t' ) + str + end + +end # module Net diff --git a/lib/net/session.rb b/lib/net/session.rb index b0977e7e14..7cf423ce3a 100644 --- a/lib/net/session.rb +++ b/lib/net/session.rb @@ -1,82 +1,197 @@ -# -# session.rb version 1.0.1 -# -# author: Minero Aoki <aamine@dp.u-netsurf.ne.jp> -# +=begin -require 'socket' += net/session.rb +written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> -class String +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. - def doquote - str = self.gsub( "\n", '\\n' ) - str.gsub!( "\r", '\\r' ) - str.gsub!( "\t", '\\t' ) - return str - end +=end -end +require 'socket' module Net - DEBUG = $DEBUG - # DEBUG = false + Version = '1.1.4' +=begin - class Session +== Net::Protocol - Version = '1.0.1' +the abstruct class for Internet protocol - def initialize( addr = 'localhost', port = nil ) - proto_initialize - @address = addr - @port = port if port - @active = false - end +=== Super Class + +Object + +=== Constants + +: Version + The version of Session class. It is a string like "1.1.4". + + +=== Class Methods + +: new( address = 'localhost', port = nil ) + This method Creates a new Session object. + +: start( address = 'localhost', port = nil, *args ) +: start( address = 'localhost', port = nil, *args ){|session| .... } + This method creates a new Session object and start session. + If you call this method with block, Session object give itself + to block and finish session when block returns. + +: Proxy( address, port ) + This method creates a proxy class of its protocol. + Arguments are address/port of proxy host. + + +=== Methods + +: address + the address of connecting server (FQDN). + +: port + connecting port number + +: start( *args ) + This method start protocol. If you call this method when the protocol + is already started, this only returns false without doing anything. + + '*args' are specified in subclasses. + +: finish + This method ends protocol. If you call this method before protocol starts, + it only return false without doing anything. + +: active? + true if session have been started + +=end + + class Protocol class << self + def start( address = 'localhost', port = nil, *args ) - inst = new( address, port ) - ret = inst.start( *args ) + instance = new( address, port ) if iterator? then - ret = yield( inst ) - inst.finish + instance.start( *args ) { yield instance } + else + instance.start *args + instance end - return ret end + + def Proxy( p_addr, p_port ) + klass = Class.new( self ) + klass.module_eval %- + + def initialize( addr, port ) + @proxyaddr = '#{p_addr}' + @proxyport = '#{p_port}' + super @proxyaddr, @proxyport + @address = addr + @port = port + end + + def connect( addr, port ) + super @proxyaddr, @proxyport + end + private :connect + + attr :proxyaddr + attr :proxyport + - + def klass.proxy? + true + end + + klass + end + + def proxy? + false + end + + + private + + def protocol_param( name, val ) + module_eval %- + def self.#{name.id2name} + #{val} + end + - + end + + end + + + # + # sub-class requirements + # + # protocol_param command_type + # protocol_param port + # + # private method do_start (optional) + # private method do_finish (optional) + # + + protocol_param :port, 'nil' + protocol_param :command_type, 'nil' + protocol_param :socket_type, '::Net::ProtocolSocket' + + + def initialize( addr = nil, port = nil ) + @address = addr || 'localhost' + @port = port || self.type.port + + @active = false + @pipe = nil + + @command = nil + @socket = nil end attr :address attr :port + attr :command attr :socket - attr :proto_type - attr :proto, true def start( *args ) return false if active? + @active = true - if ProtocolSocket === args[0] then - @socket = args.shift - else - @socket = ProtocolSocket.open( @address, @port ) + begin + connect @address, @port + do_start *args + yield if iterator? + ensure + finish if iterator? end - @proto = @proto_type.new( @socket ) - do_start( *args ) - - @active = true end def finish - if @proto then + if @command then do_finish - @proto = nil + disconnect + end + + if @socket and not @socket.closed? then + @socket.close + @socket = nil + end + + if active? then + @active = false return true else @@ -84,20 +199,72 @@ module Net end end - def active?() @active end + def active? + @active + end + + def set_pipe( arg ) # un-documented + @pipe = arg + end + + + private + + + def do_start + end + + def do_finish + end + + + def connect( addr, port ) + @socket = self.type.socket_type.open( addr, port, @pipe ) + @command = self.type.command_type.new( @socket ) + end + + def disconnect + @command.quit + @command = nil + @socket = nil + end end + Session = Protocol + + +=begin + +== Net::Command + +=== Super Class + +Object + +=== Class Methods + +: new( socket ) + This method create new Command object. 'socket' must be ProtocolSocket. + This method is abstract class. + + +=== Methods +: quit + This method dispatch command which ends the protocol. + +=end class Command def initialize( sock ) @socket = sock - check_reply( SuccessCode ) + @error_occured = false end attr :socket, true + attr :error_occured def quit if @socket and not @socket.closed? then @@ -107,21 +274,31 @@ module Net @socket.close unless @socket.closed? @socket = nil end + @error_occured = false end end private def check_reply( *oks ) - rep = get_reply + reply_must( get_reply, *oks ) + end + + def reply_must( rep, *oks ) oks.each do |i| if i === rep then return rep end end + @error_occured = true rep.error! @socket.sending end + + def getok( line, ok = SuccessCode ) + @socket.writeline line + check_reply ok + end end @@ -133,6 +310,7 @@ module Net class ProtoServerError < ProtocolError ; end class ProtoAuthError < ProtocolError ; end class ProtoCommandError < ProtocolError ; end + class ProtoRetryError < ProtocolError ; end class ReplyCode @@ -145,64 +323,157 @@ module Net attr :msg def error!( sending ) - err, tag = Errors[ self.type ] - mes = sprintf( <<MES, tag, @code, sending.doquote, @msg.doquote ) + mes = <<MES -%s: status %s +status %s writing string is: %s error message from server is: %s MES - raise err, mes + raise self.type::Error, + sprintf( mes, @code, Net.quote(sending), Net.quote(@msg) ) end end - class SuccessCode < ReplyCode ; end - class ContinueCode < SuccessCode ; end - class ErrorCode < ReplyCode ; end - class SyntaxErrorCode < ErrorCode ; end - class FatalErrorCode < ErrorCode ; end - class ServerBusyCode < ErrorCode ; end - class UnknownCode < ReplyCode ; end + class SuccessCode < ReplyCode + Error = ProtoUnknownError + end - class ReplyCode - Errors = { - SuccessCode => [ ProtoUnknownError, 'unknown error' ], - ContinueCode => [ ProtoUnknownError, 'unknown error' ], - ErrorCode => [ ProtocolError, 'protocol error' ], - SyntaxErrorCode => [ ProtoSyntaxError, 'syntax error' ], - FatalErrorCode => [ ProtoFatalError, 'fatal error' ], - ServerBusyCode => [ ProtoServerError, 'probably server busy' ], - UnknownCode => [ ProtoUnknownError, 'unknown error' ] - } + class ContinueCode < SuccessCode + Error = ProtoUnknownError + end + + class ErrorCode < ReplyCode + Error = ProtocolError + end + + class SyntaxErrorCode < ErrorCode + Error = ProtoSyntaxError + end + + class FatalErrorCode < ErrorCode + Error = ProtoFatalError + end + + class ServerBusyCode < ErrorCode + Error = ProtoServerError end + class RetryCode < ReplyCode + Error = ProtoRetryError + end + + class UnknownCode < ReplyCode + Error = ProtoUnknownError + end + + +=begin + +== Net::ProtocolSocket + +=== Super Class + +Object + +=== Class Methods + +: new( address = 'localhost', port = nil ) + This create new ProtocolSocket object, and connect to server. + + +=== Methods + +: close + This method closes socket. + +: address, addr + a FQDN address of server + +: ip_address, ipaddr + an IP address of server + +: port + connecting port number. + +: closed? + true if ProtocolSokcet have been closed already + + +: read( length ) + This method read 'length' bytes and return the string. + +: readuntil( target ) + This method read until find 'target'. Returns read string. + +: readline + read until "\r\n" and returns it without "\r\n". + +: read_pendstr + This method read until "\r\n.\r\n". + At the same time, delete period at line head and final line ("\r\n.\r\n"). + +: read_pendlist +: read_pendlist{|line| .... } + This method read until "\r\n.\r\n". This method resembles to 'read_pendstr', + but 'read_pendlist' don't check period at line head, and returns array which + each element is one line. + + When this method was called with block, evaluate it for each reading a line. + +: write( src ) + This method send 'src'. ProtocolSocket read strings from 'src' by 'each' + iterator. This method returns written bytes. + +: writebin( src ) + This method send 'src'. ProtocolSokcet read string from 'src' by 'each' + iterator. This method returns written bytes. + +: writeline( str ) + This method writes 'str'. There has not to be bare "\r" or "\n" in 'str'. + +: write_pendstr( src ) + This method writes 'src' as a mail. + ProtocolSocket reads strings from 'src' by 'each' iterator. + This returns written bytes. + +=end class ProtocolSocket - def initialize( addr, port ) - @address = addr - @port = port + def initialize( addr, port, pipe = nil ) + @addr = addr + @port = port + @pipe = pipe + @closed = true @ipaddr = '' - @closed = false @sending = '' @buffer = '' @socket = TCPsocket.new( addr, port ) + @closed = false @ipaddr = @socket.addr[3] - - @dout = Net::DEBUG end + attr :pipe, true + class << self alias open new end + def reopen + unless closed? then + @socket.close + @buffer = '' + end + @socket = TCPsocket.new( @addr, @port ) + end + attr :socket, true @@ -211,11 +482,21 @@ MES @closed = true end - def closed?() @closed end + def closed? + @closed + end + + def address + @addr.dup + end + alias addr address - def addr() @address.dup end - def port() @port end - def ipaddr() @ipaddr.dup end + attr :port + + def ip_address + @ipaddr.dup + end + alias ipaddr ip_address attr :sending @@ -226,16 +507,35 @@ MES def read( len, ret = '' ) - rsize = 0 + @pipe << "reading #{len} bytes...\n" if pre = @pipe ; @pipe = nil + rsize = 0 while rsize + @buffer.size < len do - rsize += @buffer.size - ret << fetch_rbuf( @buffer.size ) + rsize += writeinto( ret, @buffer.size ) fill_rbuf end - ret << fetch_rbuf( len - rsize ) + writeinto( ret, len - rsize ) - return ret + @pipe << "read #{len} bytes\n" if @pipe = pre + ret + end + + + def read_all( ret = '' ) + @pipe << "reading all...\n" if pre = @pipe; @pipe = nil + + rsize = 0 + begin + while true do + rsize += writeinto( ret, @buffer.size ) + fill_rbuf + end + rescue EOFError + ; + end + + @pipe << "read #{rsize} bytes\n" if @pipe = pre + ret end @@ -244,19 +544,21 @@ MES fill_rbuf end - return fetch_rbuf( idx + target.size ) + ret = '' + writeinto( ret, idx + target.size ) + ret end def readline ret = readuntil( CRLF ) ret.chop! - return ret + ret end def read_pendstr( dest = '' ) - $stderr.puts "reading pendstr" if pre = @dout ; @dout = false + @pipe << "reading text...\n" if pre = @pipe ; @pipe = nil rsize = 0 @@ -266,12 +568,14 @@ MES dest << str end - $stderr.puts "read pendstr #{rsize} bytes" if @dout = pre - return dest + @pipe << "read #{rsize} bytes\n" if @pipe = pre + dest end def read_pendlist + @pipe << "reading list...\n" if pre = @pipe ; @pipe = nil + arr = [] str = nil call = iterator? @@ -282,7 +586,8 @@ MES yield str if iterator? end - return arr + @pipe << "read #{arr.size} lines\n" if @pipe = pre + arr end @@ -295,22 +600,16 @@ MES @buffer << @socket.sysread( READ_BLOCK ) end - def fetch_rbuf( len ) + def writeinto( ret, len ) bsi = @buffer.size - ret = @buffer[ 0, len ] + ret << @buffer[ 0, len ] @buffer = @buffer[ len, bsi - len ] - if @dout then - $stderr.print 'read "' - debugout ret - $stderr.print "\"\n" - end - return ret + @pipe << %{read "#{Net.quote ret}"\n} if @pipe + len end - ### write - public @@ -319,7 +618,7 @@ MES each_crlf_line( src ) do |line| do_write_do line end - return do_write_fin + do_write_fin end @@ -328,7 +627,7 @@ MES src.each do |bin| do_write_do bin end - return do_write_fin + do_write_fin end @@ -336,13 +635,12 @@ MES do_write_beg do_write_do str do_write_do CRLF - return do_write_fin + do_write_fin end def write_pendstr( src ) - $stderr.puts "writing pendstr from #{src.type}" if pre = @dout - @dout = false + @pipe << "writing text from #{src.type}" if pre = @pipe ; @pipe = nil do_write_beg each_crlf_line( src ) do |line| @@ -352,8 +650,8 @@ MES do_write_do D_CRLF wsize = do_write_fin - $stderr.puts "wrote pendstr #{wsize} bytes" if @dout = pre - return wsize + @pipe << "wrote #{wsize} bytes text" if @pipe = pre + wsize end @@ -363,18 +661,18 @@ MES def each_crlf_line( src ) buf = '' beg = 0 - pos = nil + pos = s = bin = nil - src.each do |b| - buf << b + src.each do |bin| + buf << bin beg = 0 - while (pos = buf.index(TERMEXP, beg)) and (pos < buf.size - 2) do - pos += $&.size - tmp = buf[ beg, pos - beg ] - tmp.chop! - yield tmp << CRLF - beg = pos + while pos = buf.index( TERMEXP, beg ) do + s = $&.size + break if pos + s == buf.size - 1 and buf[-1] == ?\r + + yield buf[ beg, pos - beg ] << CRLF + beg = pos + s end buf = buf[ beg, buf.size - beg ] if beg != 0 end @@ -382,52 +680,49 @@ MES buf << "\n" unless /\n|\r/o === buf[-1,1] beg = 0 - while pos = buf.index(TERMEXP, beg) do - pos += $&.size - tmp = buf[ beg, pos - beg ] - tmp.chop! - yield tmp << CRLF - beg = pos + while pos = buf.index( TERMEXP, beg ) do + yield buf[ beg, pos - beg ] << CRLF + beg = pos + $&.size end end def do_write_beg - $stderr.print 'write "' if @dout - @writtensize = 0 @sending = '' end def do_write_do( arg ) - debugout arg if @dout - - if @sending.size < 128 then - @sending << arg + if @pipe or @sending.size < 128 then + @sending << Net.quote( arg ) else @sending << '...' unless @sending[-1] == ?. end + s = @socket.write( arg ) @writtensize += s - return s + s end def do_write_fin - $stderr.puts if @dout + if @pipe then + @pipe << 'write "' + @pipe << @sending + @pipe << "\"\n" + end @socket.flush - return @writtensize + @writtensize end + end - def debugout( ret ) - while ret and tmp = ret[ 0, 50 ] do - ret = ret[ 50, ret.size - 50 ] - tmp = tmp.inspect - $stderr.print tmp[ 1, tmp.size - 2 ] - end - end + def Net.quote( str ) + str = str.gsub( "\n", '\\n' ) + str.gsub!( "\r", '\\r' ) + str.gsub!( "\t", '\\t' ) + str end -end +end # module Net diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index 9f534c20c0..7a04aa2aa2 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -1,89 +1,166 @@ -# -# smtp.rb version 1.0.1 -# -# author Minero Aoki <aamine@dp.u-netsurf.ne.jp> -# +=begin + += net/smtp.rb + +written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> + +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. + +=end + require 'net/session' module Net - class SMTPSession < Session - def proto_initialize - @proto_type = SMTPCommand - @port = 25 - end +=begin + +== Net::SMTP + +=== Super Class + +Net::Protocol + +=== Class Methods + +: new( address = 'localhost', port = 25 ) + This method create new SMTP object. + + +=== Methods + +: start( helo_domain = ENV['HOSTNAME'] ) + This method opens TCP connection and start SMTP. + If protocol had been started, do nothing and return false. + +: sendmail( mailsrc, from_domain, to_addrs ) + This method sends 'mailsrc' as mail. SMTPSession read strings + from 'mailsrc' by calling 'each' iterator, and convert them + into "\r\n" terminated string when write. + + Exceptions which SMTP raises are: + * Net::ProtoSyntaxError: syntax error (errno.500) + * Net::ProtoFatalError: fatal error (errno.550) + * Net::ProtoUnknownError: unknown error + * Net::ProtoServerBusy: temporary error (errno.420/450) + +: finish + This method ends SMTP. + If protocol had not started, do nothind and return false. + +=end + + class SMTP < Protocol + + protocol_param :port, '25' + protocol_param :command_type, '::Net::SMTPCommand' + def sendmail( mailsrc, fromaddr, toaddrs ) - @proto.mailfrom( fromaddr ) - @proto.rcpt( toaddrs ) - @proto.data - @proto.sendmail( mailsrc ) + @command.mailfrom fromaddr + @command.rcpt toaddrs + @command.data + @command.sendmail mailsrc end private - def do_start( helodom = nil ) + def do_start( helodom = ENV['HOSTNAME'] ) unless helodom then - helodom = ENV[ 'HOSTNAME' ] + raise ArgumentError, "cannot get hostname" end - @proto.helo( helodom ) - end - - def do_finish - @proto.quit + @command.helo helodom end end - SMTP = SMTPSession + SMTPSession = SMTP + + +=begin + +== Net::SMTPCommand + +=== Super Class + +Net::Command + +=== Class Methods + +: new( socket ) + This method creates new SMTPCommand object, and open SMTP. + + +=== Methods + +: helo( helo_domain ) + This method send "HELO" command and start SMTP. + helo_domain is localhost's FQDN. +: mailfrom( from_addr ) + This method sends "MAIL FROM" command. + from_addr is your mail address(????@????). +: rcpt( to_addrs ) + This method sends "RCPT TO" command. + to_addrs is array of mail address(???@???) of destination. + +: data( mailsrc ) + This method send 'mailsrc' as mail. SMTP reads strings from 'mailsrc' + by calling 'each' iterator. + +: quit + This method sends "QUIT" command and ends SMTP session. + +=end class SMTPCommand < Command + def initialize( sock ) + super + check_reply SuccessCode + end + + def helo( fromdom ) - @socket.writeline( 'HELO ' << fromdom ) - check_reply( SuccessCode ) + getok sprintf( 'HELO %s', fromdom ) end def mailfrom( fromaddr ) - @socket.writeline( 'MAIL FROM:<' + fromaddr + '>' ) - check_reply( SuccessCode ) + getok sprintf( 'MAIL FROM:<%s>', fromaddr ) end def rcpt( toaddrs ) toaddrs.each do |i| - @socket.writeline( 'RCPT TO:<' + i + '>' ) - check_reply( SuccessCode ) + getok sprintf( 'RCPT TO:<%s>', i ) end end def data - @socket.writeline( 'DATA' ) - check_reply( ContinueCode ) + getok 'DATA', ContinueCode end - def sendmail( mailsrc ) - @socket.write_pendstr( mailsrc ) - check_reply( SuccessCode ) + def writemail( mailsrc ) + @socket.write_pendstr mailsrc + check_reply SuccessCode end + alias sendmail writemail private def do_quit - @socket.writeline( 'QUIT' ) - check_reply( SuccessCode ) + getok 'QUIT' end @@ -91,19 +168,19 @@ module Net arr = read_reply stat = arr[0][0,3] - cls = UnknownCode - case stat[0] - when ?2 then cls = SuccessCode - when ?3 then cls = ContinueCode - when ?4 then cls = ServerBusyCode - when ?5 then - case stat[1] - when ?0 then cls = SyntaxErrorCode - when ?5 then cls = FatalErrorCode - end - end - - return cls.new( stat, arr.join('') ) + klass = UnknownCode + klass = case stat[0] + when ?2 then SuccessCode + when ?3 then ContinueCode + when ?4 then ServerBusyCode + when ?5 then + case stat[1] + when ?0 then SyntaxErrorCode + when ?5 then FatalErrorCode + end + end + + klass.new( stat, arr.join('') ) end @@ -120,9 +197,4 @@ module Net end - - unless Session::Version == '1.0.1' then - $stderr.puts "WARNING: wrong version of session.rb & smtp.rb" - end - end diff --git a/lib/net/telnet.rb b/lib/net/telnet.rb new file mode 100644 index 0000000000..4acaaadd3a --- /dev/null +++ b/lib/net/telnet.rb @@ -0,0 +1,749 @@ +=begin +$Date$ + +== SIMPLE TELNET CLIENT LIBRARY + +net/telnet.rb + +Version 1.30 + +Wakou Aoyama <wakou@fsinet.or.jp> + + +=== MAKE NEW TELNET OBJECT + + host = Net::Telnet::new({ + "Binmode" => false, # default: false + "Host" => "localhost", # default: "localhost" + "Output_log" => "output_log", # default: nil (no output) + "Dump_log" => "dump_log", # default: nil (no output) + "Port" => 23, # default: 23 + "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 Telnet or TCPsocket object + }) + +Telnet object has socket class methods. + +if set "Telnetmode" option to false. not telnet command interpretation. +"Waittime" is time to confirm "Prompt". There is a possibility that +the same character as "Prompt" is included in the data, and, when +the network or the host is very heavy, the value is enlarged. + +=== STATUS OUTPUT + + host = Net::Telnet::new({"Host" => "localhost"}){|c| print c } + +connection status output. + +example + + Trying localhost... + Connected to localhost. + + +=== WAIT FOR MATCH + + line = host.waitfor(/match/) + line = host.waitfor({"Match" => /match/, + "String" => "string", + "Timeout" => secs}) + # if ignore timeout then set "Timeout" to false. + +if set "String" option, then Match == Regexp.new(quote("string")) + + +==== REALTIME OUTPUT + + host.waitfor(/match/){|c| print c } + host.waitfor({"Match" => /match/, + "String" => "string", + "Timeout" => secs}){|c| print c} + +of cource, set sync=true or flush is necessary. + + +=== SEND STRING AND WAIT PROMPT + + line = host.cmd("string") + line = host.cmd({"String" => "string", + "Prompt" => /[$%#>] \z/n, + "Timeout" => 10}) + + +==== REALTIME OUTPUT + + host.cmd("string"){|c| print c } + host.cmd({"String" => "string", + "Prompt" => /[$%#>] \z/n, + "Timeout" => 10}){|c| print c } + +of cource, set sync=true or flush is necessary. + + +=== SEND STRING + + host.print("string") + # == host.write("string\n") + + +=== TURN TELNET COMMAND INTERPRETATION + + host.telnetmode # turn on/off + host.telnetmode(true) # on + host.telnetmode(false) # off + + +=== TOGGLE NEWLINE TRANSLATION + + host.binmode # turn true/false + host.binmode(true) # no translate newline + host.binmode(false) # translate newline + + +=== LOGIN + + host.login("username", "password") + host.login({"Name" => "username", + "Password" => "password", + "Prompt" => /[$%#>] \z/n, + "Timeout" => 10}) + +if no password prompt. + + host.login("username") + host.login({"Name" => "username", + "Prompt" => /[$%#>] \z/n, + "Timeout" => 10}) + + +==== REALTIME OUTPUT + + host.login("username", "password"){|c| print c } + host.login({"Name" => "username", + "Password" => "password", + "Prompt" => /[$%#>] \z/n, + "Timeout" => 10}){|c| print c } + +of cource, set sync=true or flush is necessary. + + +== EXAMPLE + +=== LOGIN AND SEND COMMAND + + 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 + + +=== CHECKS A POP SERVER TO SEE IF YOU HAVE MAIL + + 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} + + +== HISTORY + +=== Version 1.30 + +2000/04/03 18:27:02 + +- telnet.rb --> net/telnet.rb + +=== Version 1.20 + +2000/01/24 17:02:57 + +- respond to "IAC WILL x" with "IAC DONT x" +- respond to "IAC WONT x" with "IAC DONT x" +- better dumplog format + thanks to WATANABE Hirofumi <Hirofumi.Watanabe@jp.sony.com> + +=== Version 1.10 + +2000/01/18 17:47:31 + +- bug fix: write method +- respond to "IAC WILL BINARY" with "IAC DO BINARY" + +=== Version 1.00 + +1999/10/04 22:51:26 + +- bug fix: waitfor(preprocess) method + thanks to Shin-ichiro Hara <sinara@blade.nagaokaut.ac.jp> +- add simple support for AO, DM, IP, NOP, SB, SE +- COUTION! TimeOut --> TimeoutError + +=== Version 0.50 + +1999/09/21 21:24:07 + +- add write method + +=== Version 0.40 + +1999/09/17 17:41:41 + +- bug fix: preprocess method + +=== Version 0.30 + +1999/09/14 23:09:05 + +- change prompt check order. + not IO::select([@sock], nil, nil, waittime) and prompt === line + --> prompt === line and not IO::select([@sock], nil, nil, waittime) + +=== Version 0.24 + +1999/09/13 22:28:33 + +- Telnet#login +if ommit password, then not require password prompt. + +=== Version 0.232 + +1999/08/10 05:20:21 + +- STATUS OUTPUT sample code typo. thanks to Tadayoshi Funaba <tadf@kt.rim.or.jp> + host = Telnet.new({"Hosh" => "localhost"){|c| print c } + --> host = Telnet.new({"Host" => "localhost"){|c| print c } + +=== Version 0.231 + +1999/07/16 13:39:42 + +- TRUE --> true, FALSE --> false + +=== Version 0.23 + +1999/07/15 22:32:09 + +- waitfor: if end of file reached, then return nil. + +=== Version 0.22 + +1999/06/29 09:08:51 + +- new, waitfor, cmd: {"Timeout" => false} # ignore timeout + +=== Version 0.21 + +1999/06/28 18:18:55 + +- waitfor: not rescue (EOFError) + +=== Version 0.20 + +1999/06/04 06:24:58 + +- waitfor: support for divided telnet command + +=== Version 0.181 + +1999/05/22 + +- bug fix: print method + +=== Version 0.18 + +1999/05/14 + +- respond to "IAC WON'T SGA" with "IAC DON'T SGA" +- DON'T SGA : end of line --> CR + LF +- bug fix: preprocess method + +=== Version 0.17 + +1999/04/30 + +- bug fix: $! + "\n" --> $!.to_s + "\n" + +=== Version 0.163 + +1999/04/11 + +- STDOUT.write(message) --> yield(message) if iterator? + +=== Version 0.162 + +1999/03/17 + +- add "Proxy" option +- required timeout.rb + +=== Version 0.161 + +1999/02/03 + +- select --> IO::select + +=== Version 0.16 + +1998/10/09 + +- preprocess method change for the better +- add binmode method. +- change default Binmode. TRUE --> FALSE + +=== Version 0.15 + +1998/10/04 + +- add telnetmode method. + +=== Version 0.141 + +1998/09/22 + +- change default prompt. /[$%#>] $/ --> /[$%#>] \Z/ + +=== Version 0.14 + +1998/09/01 + +- IAC WILL SGA send EOL --> CR+NULL +- IAC WILL SGA IAC DO BIN send EOL --> CR +- NONE send EOL --> LF +- add Dump_log option. + +=== Version 0.13 + +1998/08/25 + +- add print method. + +=== Version 0.122 + +1998/08/05 + +- support for HP-UX 10.20 thanks to WATANABE Tetsuya <tetsu@jpn.hp.com> +- socket.<< --> socket.write + +=== Version 0.121 + +1998/07/15 + +- string.+= --> string.concat + +=== Version 0.12 + +1998/06/01 + +- add timeout, waittime. + +=== Version 0.11 + +1998/04/21 + +- add realtime output. + +=== Version 0.10 + +1998/04/13 + +- first release. + +=end + +require "socket" +require "delegate" +require "timeout" + +module Net + class Telnet < SimpleDelegator + + IAC = 255.chr # "\377" # "\xff" # interpret as command: + DONT = 254.chr # "\376" # "\xfe" # you are not to use option + DO = 253.chr # "\375" # "\xfd" # please, you use option + WONT = 252.chr # "\374" # "\xfc" # I won't use option + WILL = 251.chr # "\373" # "\xfb" # I will use option + SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation + GA = 249.chr # "\371" # "\xf9" # you may reverse the line + EL = 248.chr # "\370" # "\xf8" # erase the current line + EC = 247.chr # "\367" # "\xf7" # erase the current character + AYT = 246.chr # "\366" # "\xf6" # are you there + AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish + IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently + BREAK = 243.chr # "\363" # "\xf3" # break + DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning + NOP = 241.chr # "\361" # "\xf1" # nop + SE = 240.chr # "\360" # "\xf0" # end sub negotiation + EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode) + ABORT = 238.chr # "\356" # "\xee" # Abort process + SUSP = 237.chr # "\355" # "\xed" # Suspend process + EOF = 236.chr # "\354" # "\xec" # End of file + SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls + + OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission + OPT_ECHO = 1.chr # "\001" # "\x01" # Echo + OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection + OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead + OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation + OPT_STATUS = 5.chr # "\005" # "\x05" # Status + OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark + OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo + OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width + OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size + OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition + OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops + OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition + OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition + OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops + OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition + OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition + OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII + OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout + OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro + OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal + OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP + OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output + OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location + OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type + OPT_EOR = 25.chr # "\031" # "\x19" # End of Record + OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification + OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking + OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number + OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime + OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD + OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size + OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed + OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control + OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode + OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location + OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option + OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option + OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option + OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option + OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List + + NULL = "\000" + CR = "\015" + LF = "\012" + EOL = CR + LF + v = $-v + $-v = false + VERSION = "1.30" + RELEASE_DATE = "$Date$" + $-v = v + + def initialize(options) + @options = options + @options["Binmode"] = false unless @options.has_key?("Binmode") + @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["Telnetmode"] = true unless @options.has_key?("Telnetmode") + @options["Timeout"] = 10 unless @options.has_key?("Timeout") + @options["Waittime"] = 0 unless @options.has_key?("Waittime") + + @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) + 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.concat ' ' * (32 - hexvals.length) + hexvals = format "%s %s %s %s " * 4, *hexvals.unpack('a2' * 16) + 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?(Telnet) + @sock = @options["Proxy"].sock + elsif @options["Proxy"].kind_of?(TCPsocket) + @sock = @options["Proxy"] + else + raise "Error; Proxy is Telnet or TCPSocket object." + end + else + message = "Trying " + @options["Host"] + "...\n" + yield(message) if iterator? + @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(@options["Timeout"]){ + @sock = TCPsocket.open(@options["Host"], @options["Port"]) + } + end + rescue TimeoutError + raise TimeoutError, "timed-out; opening of the host" + rescue + @log.write($!.to_s + "\n") if @options.has_key?("Output_log") + @dumplog.log_dump('#', $!.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 iterator? + @log.write(message) if @options.has_key?("Output_log") + @dumplog.log_dump('#', message) if @options.has_key?("Dump_log") + end + + super(@sock) + end # initialize + + attr :sock + + def telnetmode(mode = 'turn') + if 'turn' == mode + @options["Telnetmode"] = @options["Telnetmode"] ? false : true + else + @options["Telnetmode"] = mode ? true : false + end + end + + def binmode(mode = 'turn') + if 'turn' == mode + @options["Binmode"] = @options["Binmode"] ? false : true + else + @options["Binmode"] = mode ? true : false + end + end + + def preprocess(string) + str = string.dup + + # combine CR+NULL into CR + str.gsub!(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"] + + # combine EOL into "\n" + str.gsub!(/#{EOL}/no, "\n") unless @options["Binmode"] + + str.gsub!(/#{IAC}( + [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]| + [#{DO}#{DONT}#{WILL}#{WONT}] + [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]| + #{SB}[^#{IAC}]*#{IAC}#{SE} + )/xno){ + 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 + } + + str + end # preprocess + + def waitfor(options) + time_out = @options["Timeout"] + waittime = @options["Waittime"] + + 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") + 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 TimeoutError, "timed-out; wait for the next data" + end + begin + c = @sock.sysread(1024 * 1024) + @dumplog.log_dump('<', c) if @options.has_key?("Dump_log") + if @options["Telnetmode"] + if Integer(c.rindex(/#{IAC}#{SE}/no)) < + Integer(c.rindex(/#{IAC}#{SB}/no)) + buf = preprocess(rest + 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) + buf = preprocess(rest + c[0 ... pt]) + rest = c[pt .. -1] + else + buf = preprocess(c) + rest = '' + end + end + @log.print(buf) if @options.has_key?("Output_log") + line.concat(buf) + yield buf if iterator? + rescue EOFError # End of file reached + if line == '' + line = nil + yield nil if iterator? + end + break + end + end + line + end + + 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 + + def print(string) + str = string.dup + "\n" + + str.gsub!(/#{IAC}/no, IAC + IAC) if @options["Telnetmode"] + + unless @options["Binmode"] + if @telnet_option["BINARY"] and @telnet_option["SGA"] + # IAC WILL SGA IAC DO BIN send EOL --> CR + str.gsub!(/\n/n, CR) + elsif @telnet_option["SGA"] + # IAC WILL SGA send EOL --> CR+NULL + str.gsub!(/\n/n, CR + NULL) + else + # NONE send EOL --> CR+LF + str.gsub!(/\n/n, EOL) + end + end + + self.write(str) + end + + def cmd(options) + match = @options["Prompt"] + time_out = @options["Timeout"] + + 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") + else + string = options + end + + self.print(string) + if iterator? + waitfor({"Prompt" => match, "Timeout" => time_out}){|c| yield c } + else + waitfor({"Prompt" => match, "Timeout" => time_out}) + end + end + + def login(options, password = nil) + if options.kind_of?(Hash) + username = options["Name"] + password = options["Password"] + else + username = options + end + + if iterator? + line = waitfor(/login[: ]*\z/n){|c| yield c } + if password + line.concat( cmd({"String" => username, + "Match" => /Password[: ]*\z/n}){|c| yield c } ) + line.concat( cmd(password){|c| yield c } ) + else + line.concat( cmd(username){|c| yield c } ) + end + else + line = waitfor(/login[: ]*\z/n) + if password + line.concat( cmd({"String" => username, + "Match" => /Password[: ]*\z/n}) ) + line.concat( cmd(password) ) + else + line.concat( cmd(username) ) + end + end + line + end + + end +end |