| 1 | local verse = require "verse"; |
| 2 | local stream_mt = verse.stream_mt; |
| 3 | |
| 4 | local jid_split = require "prosody.util.jid".split; |
| 5 | local st = require "prosody.util.stanza"; |
| 6 | local sha1 = require "prosody.util.hashes".sha1; |
| 7 | |
| 8 | -- Shortcuts to save having to load util.stanza |
| 9 | verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply = |
| 10 | st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply; |
| 11 | |
| 12 | local new_xmpp_stream = require "prosody.util.xmppstream".new; |
| 13 | |
| 14 | local xmlns_stream = "http://etherx.jabber.org/streams"; |
| 15 | local xmlns_component = "jabber:component:accept"; |
| 16 | |
| 17 | local stream_callbacks = { |
| 18 | stream_ns = xmlns_stream, |
| 19 | stream_tag = "stream", |
| 20 | default_ns = xmlns_component }; |
| 21 | |
| 22 | function stream_callbacks.streamopened(stream, attr) |
| 23 | stream.stream_id = attr.id; |
| 24 | if not stream:event("opened", attr) then |
| 25 | stream.notopen = nil; |
| 26 | end |
| 27 | return true; |
| 28 | end |
| 29 | |
| 30 | function stream_callbacks.streamclosed(stream) |
| 31 | return stream:event("closed"); |
| 32 | end |
| 33 | |
| 34 | function stream_callbacks.handlestanza(stream, stanza) |
| 35 | if stanza.attr.xmlns == xmlns_stream then |
| 36 | return stream:event("stream-"..stanza.name, stanza); |
| 37 | elseif stanza.attr.xmlns or stanza.name == "handshake" then |
| 38 | return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza); |
| 39 | end |
| 40 | |
| 41 | return stream:event("stanza", stanza); |
| 42 | end |
| 43 | |
| 44 | function stream_mt:connect_component(jid, pass) |
| 45 | self.jid, self.password = jid, pass; |
| 46 | self.username, self.host, self.resource = jid_split(jid); |
| 47 | |
| 48 | -- Component stream methods |
| 49 | function self:reset() |
| 50 | if self.stream then |
| 51 | self.stream:reset(); |
| 52 | else |
| 53 | self.stream = new_xmpp_stream(self, stream_callbacks); |
| 54 | end |
| 55 | self.notopen = true; |
| 56 | return true; |
| 57 | end |
| 58 | |
| 59 | function self:reopen() |
| 60 | self:reset(); |
| 61 | self:send(st.stanza("stream:stream", { to = self.jid, ["xmlns:stream"]='http://etherx.jabber.org/streams', |
| 62 | xmlns = xmlns_component, version = "1.0" }):top_tag()); |
| 63 | end |
| 64 | |
| 65 | local _base_close = self.close; |
| 66 | function self:close(reason) |
| 67 | if not self.notopen then |
| 68 | self:send("</stream:stream>"); |
| 69 | end |
| 70 | return _base_close(self, reason); |
| 71 | end |
| 72 | |
| 73 | function self:send_iq(iq, callback) |
| 74 | local id = self:new_id(); |
| 75 | self.tracked_iqs[id] = callback; |
| 76 | iq.attr.id = id; |
| 77 | self:send(iq); |
| 78 | end |
| 79 | |
| 80 | function self:new_id() |
| 81 | self.curr_id = self.curr_id + 1; |
| 82 | return tostring(self.curr_id); |
| 83 | end |
| 84 | |
| 85 | function self.data(conn, data) |
| 86 | local ok, err = self.stream:feed(data); |
| 87 | if ok then return; end |
| 88 | stream:debug("Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " ")); |
| 89 | stream:close("xml-not-well-formed"); |
| 90 | end |
| 91 | |
| 92 | self:hook("incoming-raw", function (data) return self.data(self.conn, data); end); |
| 93 | |
| 94 | self.curr_id = 0; |
| 95 | |
| 96 | self.tracked_iqs = {}; |
| 97 | self:hook("stanza", function (stanza) |
| 98 | local id, type = stanza.attr.id, stanza.attr.type; |
| 99 | if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then |
| 100 | self.tracked_iqs[id](stanza); |
| 101 | self.tracked_iqs[id] = nil; |
| 102 | return true; |
| 103 | end |
| 104 | end); |
| 105 | |
| 106 | self:hook("stanza", function (stanza) |
| 107 | local ret; |
| 108 | if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then |
| 109 | if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then |
| 110 | local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; |
| 111 | if xmlns then |
| 112 | ret = self:event("iq/"..xmlns, stanza); |
| 113 | if not ret then |
| 114 | ret = self:event("iq", stanza); |
| 115 | end |
| 116 | end |
| 117 | if ret == nil then |
| 118 | self:send(verse.error_reply(stanza, "cancel", "service-unavailable")); |
| 119 | return true; |
| 120 | end |
| 121 | else |
| 122 | ret = self:event(stanza.name, stanza); |
| 123 | end |
| 124 | end |
| 125 | return ret; |
| 126 | end, -1); |
| 127 | |
| 128 | self:hook("opened", function (attr) |
| 129 | print(self.jid, self.stream_id, attr.id); |
| 130 | local token = sha1(self.stream_id..pass, true); |
| 131 | |
| 132 | self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token)); |
| 133 | self:hook("stream/"..xmlns_component, function (stanza) |
| 134 | if stanza.name == "handshake" then |
| 135 | self:event("authentication-success"); |
| 136 | end |
| 137 | end); |
| 138 | end); |
| 139 | |
| 140 | local function stream_ready() |
| 141 | self:event("ready"); |
| 142 | end |
| 143 | self:hook("authentication-success", stream_ready, -1); |
| 144 | |
| 145 | -- Initialise connection |
| 146 | self:connect(self.connect_host or self.host, self.connect_port or 5347); |
| 147 | self:reopen(); |
| 148 | end |