1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
# =XMPP4R - XMPP Library for Ruby
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
# Website::http://xmpp4r.github.io
begin
require 'openssl'
rescue LoadError
end
require 'xmpp4r/stream'
module Jabber
##
# The connection class manages the TCP connection to the Jabber server
#
class Connection < Stream
attr_reader :host, :port
# How many seconds to wait for <stream:features/>
# before proceeding
attr_accessor :features_timeout
# Keep-alive interval in seconds, or nil to disable keepalive,
# defaults to 60 (see private method keepalive_loop for
# implementation details)
attr_accessor :keepalive_interval
# Allow TLS negotiation? Defaults to true
attr_accessor :allow_tls
# Optional CA-Path for TLS-handshake
attr_accessor :ssl_capath
# Optional callback for verification of SSL peer
attr_accessor :ssl_verifycb
#whether to use the old and deprecated SSL protocol
#Defaults to false
attr_accessor :use_ssl
class SSLSocketUTF8 < OpenSSL::SSL::SSLSocket
# #readline returns incorrect encodings for UTF8 strings
# because SSLSocket does not support encoding conversions
# This forces a correct encoding, and pervents REXML exceptions
def sysread *args
super.force_encoding ::Encoding::UTF_8
end
end
##
# Create a new connection to the given host and port
def initialize
super()
@host = nil
@port = nil
@allow_tls = defined? OpenSSL
@tls = false
@ssl_capath = nil
@ssl_verifycb = nil
@features_timeout = 10
@keepalive_interval = 60
@use_ssl = false
end
##
# Connect to the Jabber server through a TCP Socket,
# start the Jabber parser,
# invoke to accept_features to wait for TLS,
# start the keep-alive thread
def connect(host, port)
@host = host
@port = port
# Reset is_tls?, so that it works when reconnecting
@tls = false
Jabber::debuglog("CONNECTING:\n#{@host}:#{@port}")
@socket = TCPSocket.new(@host, @port)
# We want to use the old and deprecated SSL protocol (usually on port 5223)
if @use_ssl
ssl = SSLSocketUTF8.new(@socket)
ssl.connect # start SSL session
ssl.sync_close = true
Jabber::debuglog("SSL connection established.")
@socket = ssl
end
start
accept_features
unless @keepalive_interval.nil?
@keepaliveThread = Thread.new do
Thread.current.abort_on_exception = true
keepalive_loop
end
end
end
##
# Closing connection:
# first kill keepaliveThread (but only if it's not me), then call Stream#close!
def close!
@keepaliveThread.kill if @keepaliveThread and @keepaliveThread.alive? and @keepaliveThread != Thread.current
super
@keepaliveThread.kill if @keepaliveThread and @keepaliveThread.alive?
end
def accept_features
begin
Timeout::timeout(@features_timeout) {
Jabber::debuglog("FEATURES: waiting...")
@features_sem.wait
Jabber::debuglog("FEATURES: waiting finished")
}
rescue Timeout::Error
Jabber::debuglog("FEATURES: timed out when waiting, stream peer seems not XMPP compliant")
end
if @allow_tls and not is_tls? and @stream_features['starttls'] == 'urn:ietf:params:xml:ns:xmpp-tls'
begin
starttls
rescue
Jabber::debuglog("STARTTLS:\nFailure: #{$!}")
end
end
end
##
# Start the parser on the previously connected socket
def start
super(@socket)
end
##
# Do a <starttls/>
# (will be automatically done by connect if stream peer supports this)
def starttls
stls = REXML::Element.new('starttls')
stls.add_namespace('urn:ietf:params:xml:ns:xmpp-tls')
reply = nil
send(stls) { |r|
reply = r
true
}
if reply.name != 'proceed'
raise ServerError.new(reply.first_element('error'))
end
# Don't be interrupted
stop
begin
error = nil
# Context/user set-able stuff
ctx = OpenSSL::SSL::SSLContext.new
if @ssl_capath
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
ctx.ca_path = @ssl_capath
else
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
ctx.verify_callback = @ssl_verifycb
# SSL connection establishing
sslsocket = SSLSocketUTF8.new(@socket, ctx)
sslsocket.sync_close = true
Jabber::debuglog("TLSv1: OpenSSL handshake in progress")
sslsocket.connect
# Make REXML believe it's a real socket
class << sslsocket
def kind_of?(o)
o == IO ? true : super
end
end
# We're done and will use it
@tls = true
@socket = sslsocket
rescue
error = $!
ensure
Jabber::debuglog("TLSv1: restarting parser")
start
accept_features
raise error if error
end
end
##
# Have we gone to TLS mode?
# result:: [true] or [false]
def is_tls?
@tls
end
def generate_stream_start(to=nil, from=nil, id=nil, xml_lang="en", xmlns="jabber:client", version="1.0")
stream_start_string = "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
stream_start_string += "xmlns='#{xmlns}' " unless xmlns.nil?
stream_start_string += "to='#{to}' " unless to.nil?
stream_start_string += "from='#{from}' " unless from.nil?
stream_start_string += "id='#{id}' " unless id.nil?
stream_start_string += "xml:lang='#{xml_lang}' " unless xml_lang.nil?
stream_start_string += "version='#{version}' " unless version.nil?
stream_start_string += ">"
stream_start_string
end
private :generate_stream_start
##
# A loop to send "keep alive" data to prevent
# the Jabber connection from closing for inactivity.
#
# This loop sends a single white-space character if
# no other data has been sent in the last @keepalive_interval
# seconds.
def keepalive_loop
loop do
unless is_connected?
Thread.current.kill
end
difference = @last_send + @keepalive_interval - Time.now
if difference <= 0
send(' ')
sleep @keepalive_interval
else
sleep(difference)
end
end
end
private :keepalive_loop
end
end
|