Class: Babylon::ClientConnection

Inherits:
XmppConnection
  • Object
show all
Defined in:
lib/babylon/client_connection.rb

Overview

ClientConnection is in charge of the XMPP connection for a Regular XMPP Client. So far, SASL Plain authenticationonly is supported Upon stanza reception, and depending on the status (connected… etc), this component will handle or forward the stanzas.

Instance Attribute Summary collapse

Attributes inherited from XmppConnection

#host, #jid, #port

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from XmppConnection

max_stanza_size, max_stanza_size=, #post_init, #send_xml, #unbind

Constructor Details

#initialize(params) ⇒ ClientConnection

Creates a new ClientConnection and waits for data in the stream



13
14
15
16
# File 'lib/babylon/client_connection.rb', line 13

def initialize(params)
  super(params)
  @state = :wait_for_stream
end

Instance Attribute Details

#binding_iq_idObject (readonly)

Returns the value of attribute binding_iq_id.



9
10
11
# File 'lib/babylon/client_connection.rb', line 9

def binding_iq_id
  @binding_iq_id
end

#session_iq_idObject (readonly)

Returns the value of attribute session_iq_id.



9
10
11
# File 'lib/babylon/client_connection.rb', line 9

def session_iq_id
  @session_iq_id
end

Class Method Details

.connect(params, handler = nil) ⇒ Object

Connects the ClientConnection based on SRV records for the jid’s domain, if no host or port has been specified. In any case, we give priority to the specified host and port.



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
# File 'lib/babylon/client_connection.rb', line 21

def self.connect(params, handler = nil)
  return super(params, handler) if params["host"] && params["port"]

  begin
    srv = []
    Resolv::DNS.open { |dns|
      # If ruby version is too old and SRV is unknown, this will raise a NameError
      # which is caught below
      host_from_jid = params["jid"].split("/").first.split("@").last
      Babylon.logger.debug {
        "RESOLVING: _xmpp-client._tcp.#{host_from_jid} (SRV)"
      }
      srv = dns.getresources("_xmpp-client._tcp.#{host_from_jid}", Resolv::DNS::Resource::IN::SRV)
    }
    # Sort SRV records: lowest priority first, highest weight first
    srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }
    # And now, for each record, let's try to connect.
    srv.each { |record|
      begin
        params["host"] = record.target.to_s
        params["port"] = Integer(record.port)
        super(params, handler)
        # Success
        break
      rescue NotConnected
        # Try next SRV record
      end
    }
  rescue NameError
    Babylon.logger.debug {
      "Resolv::DNS does not support SRV records. Please upgrade to ruby-1.8.3 or later! \n#{$!} : #{$!.backtrace.join("\n")}"
    }
  end
end

Instance Method Details

#connection_completedObject

Connection_completed is called when the connection (socket) has been established and is in charge of “building” the XML stream to establish the XMPP connection itself. We use a “tweak” here to send only the starting tag of stream:stream



75
76
77
78
# File 'lib/babylon/client_connection.rb', line 75

def connection_completed
  super
  send_xml(stream_stanza)
end

#receive_stanza(stanza) ⇒ Object

Called upon stanza reception Marked as connected when the client has been SASLed, authenticated, biund to a resource and when the session has been created



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
# File 'lib/babylon/client_connection.rb', line 83

def receive_stanza(stanza)
    case @state
    when :connected
      super # Can be dispatched

    when :wait_for_stream_authenticated
      if stanza.name == "stream:stream" && stanza.attributes['id']
        @state = :wait_for_bind
      end

    when :wait_for_stream
      if stanza.name == "stream:stream" && stanza.attributes['id']
        @state = :wait_for_auth_mechanisms
      end

    when :wait_for_auth_mechanisms
      if stanza.name == "stream:features"
        if stanza.at("starttls") # we shall start tls
          doc = Nokogiri::XML::Document.new
          starttls = Nokogiri::XML::Node.new("starttls", doc)
          doc.add_child(starttls)
          starttls["xmlns"] = "urn:ietf:params:xml:ns:xmpp-tls"
          send_xml(starttls.to_s)
          @state = :wait_for_proceed
        elsif stanza.at("mechanisms") # tls is ok
          if stanza.at("mechanisms").children.map() { |m| m.text }.include? "PLAIN"
            doc = Nokogiri::XML::Document.new
            auth = Nokogiri::XML::Node.new("auth", doc)
            doc.add_child(auth)
            auth['mechanism'] = "PLAIN"
            auth["xmlns"] = "urn:ietf:params:xml:ns:xmpp-sasl"
            auth.content = Base64::encode64([jid, jid.split("@").first, @password].join("\000")).gsub(/\s/, '')
            send_xml(auth.to_s)
            @state = :wait_for_success
          end
        end
      end

    when :wait_for_success
      if stanza.name == "success" # Yay! Success
        @state = :wait_for_stream_authenticated
        @parser.reset
        send_xml(stream_stanza)
      elsif stanza.name == "failure"
        if stanza.at("bad-auth") || stanza.at("not-authorized")
          raise AuthenticationError
        else
        end
      else
        # Hum Failure...
      end

    when :wait_for_bind
      if stanza.name == "stream:features"
        if stanza.at("bind")
          doc = Nokogiri::XML::Document.new
          # Let's build the binding_iq
          @binding_iq_id = Integer(rand(10000000))
          iq = Nokogiri::XML::Node.new("iq", doc)
          doc.add_child(iq)
          iq["type"] = "set"
          iq["id"] = binding_iq_id.to_s
          bind = Nokogiri::XML::Node.new("bind", doc)
          bind["xmlns"] = "urn:ietf:params:xml:ns:xmpp-bind"
          iq.add_child(bind)
          resource = Nokogiri::XML::Node.new("resource", doc)
          if jid.split("/").size == 2 
            resource.content = (@jid.split("/").last)
          else
            resource.content = "babylon_client_#{binding_iq_id}"
          end
          bind.add_child(resource)
          send_xml(iq.to_s)
          @state = :wait_for_confirmed_binding
        end
      end

    when :wait_for_confirmed_binding
      if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) ==  binding_iq_id
        if stanza.at("jid")
          @jid = stanza.at("jid").text
        end
        # And now, we must initiate the session
        @session_iq_id = Integer(rand(10000))
        doc = Nokogiri::XML::Document.new
        iq = Nokogiri::XML::Node.new("iq", doc)
        doc.add_child(iq)
        iq["type"] = "set"
        iq["id"] = session_iq_id.to_s
        session = Nokogiri::XML::Node.new("session", doc)
        session["xmlns"] = "urn:ietf:params:xml:ns:xmpp-session"
        iq.add_child(session)
        send_xml(iq.to_s)
        @state = :wait_for_confirmed_session
      end

    when :wait_for_confirmed_session
      if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == session_iq_id
        # And now, send a presence!
        doc = Nokogiri::XML::Document.new
        presence = Nokogiri::XML::Node.new("presence", doc)
        send_xml(presence.to_s)
        begin
          @handler.on_connected(self) if @handler and @handler.respond_to?("on_connected")
        rescue
          Babylon.logger.error {
            "on_connected failed : #{$!}\n#{$!.backtrace.join("\n")}"
          }
        end
        @state = :connected
      end

    when :wait_for_proceed
      start_tls() # starting TLS
      @state = :wait_for_stream
      @parser.reset
      send_xml stream_stanza
    end
end

#stream_namespaceObject

Namespace of the client



205
206
207
# File 'lib/babylon/client_connection.rb', line 205

def stream_namespace
  "jabber:client"
end

#stream_stanzaObject

Builds the stream stanza for this client



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/babylon/client_connection.rb', line 58

def stream_stanza
  doc = Nokogiri::XML::Document.new
  stream = Nokogiri::XML::Node.new("stream:stream", doc)
  doc.add_child(stream)
  stream["xmlns"] = stream_namespace
  stream["xmlns:stream"] = "http://etherx.jabber.org/streams"
  stream["to"] = jid.split("/").first.split("@").last
  stream["version"] = "1.0"
  paste_content_here = Nokogiri::XML::Node.new("paste_content_here", doc)
  stream.add_child(paste_content_here)
  doc.to_xml.split('<paste_content_here/>').first
end