aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/buffering.rb179
-rw-r--r--lib/net/https.rb146
-rw-r--r--lib/net/protocols.rb51
-rw-r--r--lib/net/telnets.rb224
-rw-r--r--lib/openssl.rb97
5 files changed, 697 insertions, 0 deletions
diff --git a/lib/buffering.rb b/lib/buffering.rb
new file mode 100644
index 0000000..5832025
--- /dev/null
+++ b/lib/buffering.rb
@@ -0,0 +1,179 @@
+=begin
+
+= buffering.rb -- Buffering mix-in module.
+
+ Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
+
+ $IPR: buffering.rb,v 1.13 2001/09/13 16:42:49 gotoyuzo Exp $
+
+=end
+
+module Buffering
+ include Enumerable
+ attr_accessor :sync
+ BLOCK_SIZE = 1024
+
+ #
+ # for reading.
+ #
+ private
+
+ def fill_rbuff
+ @rbuffer = "" unless defined? @rbuffer
+ begin
+ if self.respond_to?(:to_io)
+ IO.select([self.to_io], nil, nil)
+ end
+ @rbuffer << self.sysread(BLOCK_SIZE)
+ rescue EOFError
+ @eof = true
+ end
+ end
+
+ def consume_rbuff(size=nil)
+ if @rbuffer.size == 0
+ @eof = nil
+ nil
+ else
+ size = @rbuffer.size unless size
+ ret = @rbuffer[0, size]
+ @rbuffer[0, size] = ""
+ ret
+ end
+ end
+
+ public
+
+ def read(size=nil)
+ fill_rbuff unless defined? @rbuffer
+ until @eof
+ break if size && size <= @rbuffer.size
+ fill_rbuff
+ end
+ consume_rbuff(size)
+ end
+
+ def gets(eol=$/)
+ fill_rbuff unless defined? @rbuffer
+ idx = @rbuffer.index(eol)
+ until @eof
+ break if idx
+ fill_rbuff
+ idx = @rbuffer.index(eol)
+ end
+ if eol.is_a?(Regexp)
+ size = idx ? idx+$&.size : nil
+ else
+ size = idx ? idx+eol.size : nil
+ end
+ consume_rbuff(size)
+ end
+
+ def each(eol=$/)
+ while line = self.gets(eol?)
+ yield line
+ end
+ end
+ alias each_line each
+
+ def readlines(eol=$/)
+ ary = []
+ while line = self.gets(eol)
+ ary << line
+ end
+ ary
+ end
+
+ def readline(eol=$/)
+ raise EOFErorr if eof?
+ gets(eol)
+ end
+
+ def getc
+ c = read(1)
+ c ? c.to_i : nil
+ end
+
+ def each_byte
+ while c = getc
+ yield(c)
+ end
+ end
+
+ def readchar
+ raise EOFErorr if eof?
+ getc
+ end
+
+ def ungetc(c)
+ @buffer[0,0] = c.chr
+ end
+
+ def eof?
+ @eof && @rbuffer.size == 0
+ end
+ alias eof eof?
+
+ #
+ # for writing.
+ #
+ private
+
+ def do_write(s)
+ @wbuffer = "" unless defined? @wbuffer
+ @wbuffer << s
+ if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/)
+ remain = idx ? idx + $/.size : @wbuffer.length
+ nwritten = 0
+ while remain > 0
+ nwrote = syswrite(@wbuffer[nwritten,remain])
+ remain -= nwrote
+ nwritten += nwrote
+ end
+ @wbuffer = ""
+ end
+ end
+
+ public
+
+ def write(s)
+ do_write(s)
+ s.length
+ end
+
+ def << (s)
+ do_write(s)
+ self
+ end
+
+ def puts(*args)
+ s = ""
+ args.each{ |arg| s << arg.to_s + $/ }
+ do_write(s)
+ nil
+ end
+
+ def print(*args)
+ s = ""
+ args.each{ |arg| s << arg.to_s }
+ do_write(s)
+ nil
+ end
+
+ def printf(s, *args)
+ do_write(s % args)
+ nil
+ end
+
+ def flush
+ osync = @sync
+ @sync = true
+ do_write ""
+ @sync = osync
+ end
+
+ def close
+ flush
+ sysclose
+ end
+end
diff --git a/lib/net/https.rb b/lib/net/https.rb
new file mode 100644
index 0000000..e1c43e5
--- /dev/null
+++ b/lib/net/https.rb
@@ -0,0 +1,146 @@
+=begin
+
+= https.rb -- SSL/TLS enhancement for Net::HTTP.
+
+ Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
+
+ This program requires Net 1.2.0 or higher version.
+ You can get it from RAA or Ruby's CVS repository.
+
+ $IPR: https.rb,v 1.5 2001/07/15 22:24:05 gotoyuzo Exp $
+
+== class Net::HTTP
+
+== Example
+
+Simple HTTP client is here:
+
+ require 'net/http'
+ host, port, path = "localhost", 80, "/"
+ if %r!http://(.*?)(?::(\d+))?(/.*)! =~ ARGV[0]
+ host = $1
+ port = $2.to_i if $2
+ path = $3
+ end
+ h = Net::HTTP.new(host, port)
+ h.get2(path){ |resp| print resp.body }
+
+It can be replaced by follow one:
+
+ require 'net/https'
+ host, port, path = "localhost", 80, "/"
+ if %r!(https?)://(.*?)(?::(\d+))?(/.*)! =~ ARGV[0]
+ scheme = $1
+ host = $2
+ port = $3 ? $3.to_i : ((scheme == "http") ? 80 : 443)
+ path = $4
+ end
+ h = Net::HTTP.new(host, port)
+ h.use_ssl = true if scheme == "https" # enable SSL/TLS
+ h.get2(path){ |resp| print resp.body }
+
+=== Instance Methods
+
+: use_ssl
+ returns ture if use SSL/TLS with HTTP.
+
+: use_ssl=((|true_or_false|))
+ sets use_ssl.
+
+: peer_cert
+ return the X.509 certificates the server presented.
+
+: key=((|path|))
+ Sets private key file to use in PEM format.
+ Key_file is not required if the cert_file bundles private key.
+
+: cert=((|path|))
+ Sets pathname of a X.509 certification file in PEM format.
+
+: ca_file=((|path|))
+ Sets path of a CA certification file in PEM format.
+ The file can contrain several CA certificats.
+
+: ca_path=((|path|))
+ Sets path of a CA certification directory containing certifications
+ in PEM format.
+
+: verify_mode=((|mode|))
+ Sets the flags for server the certification verification at
+ begining of SSL/TLS session.
+
+: verify_callback=((|proc|))
+ Sets the verify callback for the server certification verification.
+
+: verify_depth=((|num|))
+ Sets the maximum depth for the certificate chain verification.
+
+=end
+
+require 'net/protocols'
+require 'net/http'
+
+module Net
+ class HTTP
+ protocol_param :socket_type, ::Net::NetPrivate::SSLSocket
+
+ attr_accessor :use_ssl
+ attr_writer :key, :cert, :ca_file, :ca_path, :timeout
+ attr_writer :verify_mode, :verify_callback, :verify_depth
+ attr_reader :peer_cert
+
+ class Conn < ::Net::NetPrivate::HTTPRequest
+ REQUEST_HAS_BODY=false
+ RESPONSE_HAS_BODY=false
+ METHOD="connect"
+
+ def initialize
+ super nil, nil
+ end
+
+ def exec( sock, addr, port, ver )
+ @socket = sock
+ request addr, port, ver
+ @response = get_response(sock)
+ @response
+ end
+
+ def request( addr, port, ver )
+ @socket.writeline sprintf('CONNECT %s:%s HTTP/%s', addr, port, ver)
+ @socket.writeline ''
+ end
+ end
+
+ def on_connect
+ if use_ssl
+ if proxy?
+ resp = Conn.new.exec(@socket, @address, @port, "1.0")
+ if resp.code != '200'
+ raise resp.message
+ end
+ end
+ @socket.key = @key_file
+ @socket.cert = @cert_file
+ @socket.ca_file = @ca_file
+ @socket.ca_path = @ca_path
+ @socket.verify_mode = @verify_mode
+ @socket.verify_callback = @verify_callback
+ @socket.verify_depth = @verify_depth
+ @socket.timeout = @timeout
+ @socket.ssl_connect
+ @peer_cert = socket.peer_cert
+ end
+ end
+
+ module ProxyMod
+ def edit_path( path )
+ if use_ssl
+ 'https://' + addr_port + path
+ else
+ 'http://' + addr_port + path
+ end
+ end
+ end
+
+ end
+end
diff --git a/lib/net/protocols.rb b/lib/net/protocols.rb
new file mode 100644
index 0000000..349407c
--- /dev/null
+++ b/lib/net/protocols.rb
@@ -0,0 +1,51 @@
+=begin
+
+= protocols.rb -- SSL/TLS enhancement for Net.
+
+ Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
+
+ This program requires Net 1.2.0 or higher version.
+ You can get it from RAA or Ruby's CVS repository.
+
+ $IPR: protocols.rb,v 1.1 2001/06/17 14:30:22 gotoyuzo Exp $
+
+=end
+
+require 'net/protocol'
+require 'forwardable'
+require 'openssl'
+
+module Net
+ module NetPrivate
+
+ class SSLSocket < Socket
+ extend Forwardable
+
+ def_delegators(:@socket,
+ :key=, :cert=, :ca_file=, :ca_path=,
+ :verify_mode=, :verify_callback=, :verify_depth=,
+ :timeout=)
+
+ def initialize(addr, port, otime = nil, rtime = nil, pipe = nil)
+ super
+ @raw_socket = @socket
+ @socket = OpenSSL::SSL::SSLSocket.new(@socket, @cert_file, @key_file)
+ end
+
+ def reopen(tout=nil)
+ super
+ @raw_socket = @socket
+ @socket = OpenSSL::SSL::SSLSocket.new(@socket, @cert_file, @key_file)
+ end
+
+ def close
+ super
+ @raw_socket.close
+ end
+
+ def peer_cert; @socket.peer_cert; end
+ def ssl_connect; @socket.connect; end
+
+ end
+ end
+end
diff --git a/lib/net/telnets.rb b/lib/net/telnets.rb
new file mode 100644
index 0000000..923181f
--- /dev/null
+++ b/lib/net/telnets.rb
@@ -0,0 +1,224 @@
+=begin
+
+= https.rb -- SSL/TLS enhancement for Net::Telnet.
+
+ Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
+
+ $IPR: telnets.rb,v 1.5 2001/09/13 16:42:50 gotoyuzo Exp $
+
+== class Net::Telnet
+
+This class will initiate SSL/TLS session automaticaly if the server
+sent OPT_STARTTLS. Some options are added for SSL/TLS.
+
+ host = Net::Telnet::new({
+ "Host" => "localhost",
+ "Port" => "telnets",
+ # follows are new options.
+ 'Cert' => "user.crt",
+ 'Key' => "user.key",
+ 'CAFile' => "/some/where/certs/casert.pem",
+ 'CAPath' => "/some/where/caserts",
+ 'VerifyMode' => SSL::VERIFY_PEER,
+ 'VerifyCallback' => verify_proc
+ })
+
+This class is expected to be a superset of usual Net::Telnet.
+
+=end
+
+require "net/telnet"
+require "OpenSSL"
+
+module Net
+ class Telnet
+ attr_reader :ssl
+
+ OPT_STARTTLS = 46.chr # "\056" # "\x2e" # Start TLS
+ TLS_FOLLOWS = 1.chr # "\001" # "\x01" # FOLLOWS (for STARTTLS)
+
+ alias preprocess_orig preprocess
+
+ def ssl?; @ssl; end
+
+ 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"]
+
+ string.gsub(/#{IAC}(
+ [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
+ [#{DO}#{DONT}#{WILL}#{WONT}][#{OPT_BINARY}-#{OPT_EXOPL}]|
+ #{SB}[#{OPT_BINARY}-#{OPT_EXOPL}]
+ (#{IAC}#{IAC}|[^#{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)
+ elsif OPT_STARTTLS[0] == $1[1]
+ self.write(IAC + WILL + OPT_STARTTLS)
+ self.write(IAC + SB + OPT_STARTTLS + TLS_FOLLOWS + IAC + SE)
+ 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
+ ''
+ elsif SB[0] == $1[0] # respond to "IAC SB xxx IAC SE"
+ if OPT_STARTTLS[0] == $1[1] && TLS_FOLLOWS[0] == $2[0]
+ @sock = OpenSSL::SSL::SSLSocket.new(@sock)
+ @sock.cert = @options['cert'] || @options['Cert']
+ @sock.key = @options['key'] || @options['Key']
+ @sock.ca_file = @options['ca_file'] || @options['CAFile']
+ @sock.ca_path = @options['ca_path'] || @options['CAPath']
+ @sock.timeout = @options['timeout'] || @options['Timeout']
+ @sock.verify_mode =
+ @options['verify_mode'] || @options['VerifyMode']
+ @sock.verify_callback =
+ @options['verify_callback'] || @options['VerifyCallback']
+ @sock.verify_depth =
+ @options['verify_depth'] || @options['VerifyDepth']
+ @sock.connect
+ @ssl = true
+ end
+ ''
+ else
+ ''
+ end
+ end
+ end # preprocess
+
+ alias waitfor_org waitfor
+
+ 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 = '' unless @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 = @rest + @sock.sysread(1024 * 1024)
+ @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
+ if @options["Telnetmode"]
+ pos = 0
+ catch(:next){
+ while true
+ case c[pos]
+ when IAC[0]
+ case c[pos+1]
+ when DO[0], DONT[0], WILL[0], WONT[0]
+ throw :next unless c[pos+2]
+ pos += 3
+ when SB[0]
+ ret = detect_sub_negotiation(c, pos)
+ throw :next unless ret
+ pos = ret
+ when nil
+ throw :next
+ else
+ pos += 2
+ end
+ when nil
+ throw :next
+ else
+ pos += 1
+ end
+ end
+ }
+
+ buf = preprocess(c[0...pos])
+ @rest = c[pos..-1]
+ end
+ @log.print(buf) if @options.has_key?("Output_log")
+ line.concat(buf)
+ yield buf if block_given?
+ rescue EOFError # End of file reached
+ if line == ''
+ line = nil
+ yield nil if block_given?
+ end
+ break
+ end
+ end
+ line
+ end
+
+ private
+
+ def detect_sub_negotiation(data, pos)
+ return nil if data.length < pos+6 # IAC SB x param IAC SE
+ pos += 3
+ while true
+ case data[pos]
+ when IAC[0]
+ if data[pos+1] == SE[0]
+ pos += 2
+ return pos
+ else
+ pos += 2
+ end
+ when nil
+ return nil
+ else
+ pos += 1
+ end
+ end
+ end
+
+ end
+end
diff --git a/lib/openssl.rb b/lib/openssl.rb
new file mode 100644
index 0000000..1d6f72f
--- /dev/null
+++ b/lib/openssl.rb
@@ -0,0 +1,97 @@
+#!/usr/bin/env ruby
+
+require 'openssl.so'
+require 'buffering'
+
+module OpenSSL
+ module PKey
+ class DSA
+ def sign(digest, data)
+ unless self.private?
+ raise OpenSSL::PKey::DSAError, "Cannot sign with public key!"
+ end
+ unless digest.kind_of? OpenSSL::Digest::ANY
+ raise TypeError, "digest alg needed! (got #{digest.class.name})"
+ end
+ txt = ""
+ if data.kind_of? String
+ txt = data
+ else
+ begin
+ txt = data.to_s
+ rescue
+ raise TypeError, "string needed! (got #{data.class.name})"
+ end
+ end
+ self.sign_digest digest.update(txt).digest
+ end #sign
+ def verify(digest, signature, data)
+ unless digest.kind_of? OpenSSL::Digest::ANY
+ raise TypeError, "digest alg needed! (got #{digest.class.name})"
+ end
+ txt = ""
+ if data.kind_of? String
+ txt = data
+ else
+ begin
+ txt = data.to_s
+ rescue
+ raise TypeError, "string needed! (got #{data.class.name})"
+ end
+ end
+ unless signature.type == String
+ raise TypeError, "Signature as String expected (got #{sign.class.name})"
+ end
+ self.verify_digest(digest.update(txt).digest, signature)
+ end #verify
+ end #DSA
+ class RSA
+ def sign(digest, data)
+ unless self.private?
+ raise OpenSSL::PKey::RSAError, "Cannot sign with public key!"
+ end
+ unless digest.kind_of? OpenSSL::Digest::ANY
+ raise TypeError, "digest alg needed! (got #{digest.class.name})"
+ end
+ txt = ""
+ if data.kind_of? String
+ txt = data
+ else
+ begin
+ txt = data.to_s
+ rescue
+ raise TypeError, "string needed! (got #{data.class.name})"
+ end
+ end
+ self.private_encrypt digest.update(txt).digest
+ end #sign
+ def verify(digest, signature, data)
+ unless digest.kind_of? OpenSSL::Digest::ANY
+ raise TypeError, "digest alg needed! (got #{digest.class.name})"
+ end
+ txt = ""
+ if data.kind_of? String
+ txt = data
+ else
+ begin
+ txt = data.to_s
+ rescue
+ raise TypeError, "string needed! (got #{data.class.name})"
+ end
+ end
+ unless signature.type == String
+ raise TypeError, "Signature as String expected (got #{sign.class.name})"
+ end
+ hash_s = self.public_decrypt signature
+ hash_d = digest.update(txt).digest
+ hash_s == hash_d
+ end #verify
+ end #RSA
+ end #PKey
+ module SSL
+ class SSLSocket
+ include Buffering
+ end #SSLSocket
+ end #SSL
+end #OpenSSL
+