class PacketThief::Handlers::AbstractSSLHandler
Parent class for both SSLServer
and SSLClient
.
TODO: get_peer_cert, get_peername, etc.
Attributes
The OpenSSL::SSL::SSLContext. Modify this in post_init
or in the initializing code block to add certificates, etc.
The TCPSocket that the SSLSocket will be created from. It is added by initialize.
Public Class Methods
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 29 def initialize(tcpsocket) logdebug "initialize" # Set up initial values @tcpsocket = tcpsocket @ctx = OpenSSL::SSL::SSLContext.new @close_after_writing = false @state = :new end
Public Instance Methods
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 192 def close_connection detach @sslsocket.close if @sslsocket and not @sslsocket.closed? @tcpsocket.close if not @tcpsocket.closed? # unbind end
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 199 def close_connection_after_writing @close_after_writing = true # if we aren't waiting to write, then we can flush and close. if not notify_writable? @sslsocket.flush close_connection end end
Calls accept_nonblock/connect_nonblock, read_nonblock, or write_nonblock based on the current state of the connection.
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 62 def notify_readable logdebug "notify_readable", :state => @state case @state when :initialized attempt_connection when :ready_to_read attempt_read when :write_needs_to_read attempt_write end end
We only care about notify_writable
if we are waiting to write for some reason.
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 76 def notify_writable logdebug "notify_writable", :state => @state notify_writable = false # disable it now. if we still need it, we'll renabled it. case @state when :initialized attempt_connection when :read_needs_to_write attempt_read when :write_needs_to_write attempt_write end # if we waiting to close and are not longer waiting to write, we can flush and close the connection. if @close_after_writing and not notify_writable? @sslsock.flush close_connection end end
Override this to do something with the unecrypted data.
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 235 def receive_data(data) logdebug "receive_data:", :data => data end
Call this to send data to the other end of the connection.
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 187 def send_data(data) logdebug "send_data:", :data => data attempt_write(data) end
Creates sslsocket from tcpsocket and ctx, and initializes the handler's internal state. Called from the class method that creates the object, after post_init
and the optional code block.
@note (SSLClient
only) If @sni_hostname exists on the handler at this point, it will be added to the SSLSocket in order to enable sending a hostname in the SNI TLS extension.
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 46 def tls_begin logdebug "tls begin", :sni_hostname => @sni_hostname @sslsocket = OpenSSL::SSL::SSLSocket.new(@tcpsocket, @ctx) if @sni_hostname if @sslsocket.respond_to? :hostname @sslsocket.hostname = @sni_hostname else logwarn "#{@sslsocket.class} does not support setting an SNI hostname! This requires Ruby 1.9.x built against OpenSSL with SNI support.", :ruby_version => RUBY_VERSION end end @state = :initialized end
Called right after accept_nonblock fails for some unknown reason. The only parameter contains the OpenSSL::SSL::SSLError object that was thrown.
The connection will be closed after this.
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 230 def tls_failed_handshake(e) logerror "tls_failed_handshake: Failed to accept: #{e} (#{e.class})" end
Called right after the SSL handshake succeeds. This is your “new” post_init
.
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 221 def tls_successful_handshake logdebug "Succesful handshake!" end
Override this to do something when the socket is finished.
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 240 def unbind logdebug "unbind" end
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 142 def write_buffer @write_buffer ||= "" end
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 147 def write_buffer=(rhs) @write_buffer = rhs end
Private Instance Methods
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 96 def attempt_connection begin # Client usess connect_nonblock, while server uses accept_nonblock. connection_action @state = :ready_to_read tls_successful_handshake attempt_write if write_buffer.length > 0 rescue IO::WaitReadable # accept_nonblock needs to wait until it can read again. notify_readable = true rescue IO::WaitWritable # accept_nonblock needs to wait until it can write again. notify_writable = true rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED => e # ssl handshake failed. Likely due to client rejecting our certificate! tls_failed_handshake(e) close_connection end end
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 117 def attempt_read begin data = @sslsocket.read_nonblock 4096 # much more than a network packet... receive_data(data) notify_writable = false rescue EOFError, Errno::ECONNRESET # remote closed. time to wrap up close_connection rescue IO::WaitReadable # we had no data to read. notify_readable = true rescue IO::WaitWritable # we ran out of buffer to send (yes, SSLSocket#read_nonblock can # trigger this) @state = :read_needs_to_write notify_writable = true rescue OpenSSL::SSL::SSLError => e logerror "attempt_read: #{e} (#{e.class})" close_connection else @state = :ready_to_read end end
# File lib/packetthief/handlers/abstract_ssl_handler.rb, line 152 def attempt_write(data=nil) logdebug "attempt_write" write_buffer << data if data # do not attempt to write until we are ready! return if @state == :initialized or @state == :new begin count_written = @sslsocket.write_nonblock write_buffer rescue IO::WaitWritable notify_writable = true rescue IO::WaitReadable @state = :write_needs_to_read rescue OpenSSL::SSL::SSLError, IOError => e logerror "attempt_write: #{e} (#{e.class})" close_connection else # shrink the buf # # byteslice was added in ruby 1.9.x. in ruby 1.8.7, bytesize is # aliased to length, implying that a character coresponds to a # byte. @write_buffer = if write_buffer.respond_to?(:byteslice) write_buffer.byteslice(count_written..-1) else write_buffer.slice(count_written..-1) end # if we didn't write everything, wait for writable. notify_writable = true if write_buffer.bytesize > 0 end end