client.lua 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. local verse = require "verse";
  2. local stream = verse.stream_mt;
  3. local jid_split = require "util.jid".split;
  4. local adns = require "net.adns";
  5. local lxp = require "lxp";
  6. local st = require "util.stanza";
  7. local to_ascii = require "util.encodings".idna.to_ascii;
  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. local new_xmpp_stream = require "util.xmppstream".new;
  12. local xmlns_stream = "http://etherx.jabber.org/streams";
  13. local function compare_srv_priorities(a,b)
  14. return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
  15. end
  16. local stream_callbacks = {
  17. stream_ns = xmlns_stream,
  18. stream_tag = "stream",
  19. default_ns = "jabber:client" };
  20. function stream_callbacks.streamopened(stream, attr)
  21. stream.stream_id = attr.id;
  22. if not stream:event("opened", attr) then
  23. stream.notopen = nil;
  24. end
  25. return true;
  26. end
  27. function stream_callbacks.streamclosed(stream)
  28. stream.notopen = true;
  29. if not stream.closed then
  30. stream:send("</stream:stream>");
  31. stream.closed = true;
  32. end
  33. stream:event("closed");
  34. return stream:close("stream closed")
  35. end
  36. function stream_callbacks.handlestanza(stream, stanza)
  37. if stanza.attr.xmlns == xmlns_stream then
  38. return stream:event("stream-"..stanza.name, stanza);
  39. elseif stanza.attr.xmlns then
  40. return stream:event("stream/"..stanza.attr.xmlns, stanza);
  41. end
  42. return stream:event("stanza", stanza);
  43. end
  44. function stream_callbacks.error(stream, e, stanza)
  45. if stream:event(e, stanza) == nil then
  46. local err = stanza:get_child(nil, "urn:ietf:params:xml:ns:xmpp-streams");
  47. local text = stanza:get_child_text("text", "urn:ietf:params:xml:ns:xmpp-streams");
  48. error(err.name..(text and ": "..text or ""));
  49. end
  50. end
  51. function stream:reset()
  52. if self.stream then
  53. self.stream:reset();
  54. else
  55. self.stream = new_xmpp_stream(self, stream_callbacks);
  56. end
  57. self.notopen = true;
  58. return true;
  59. end
  60. function stream:connect_client(jid, pass)
  61. self.jid, self.password = jid, pass;
  62. self.username, self.host, self.resource = jid_split(jid);
  63. -- Required XMPP features
  64. self:add_plugin("tls");
  65. self:add_plugin("sasl");
  66. self:add_plugin("bind");
  67. self:add_plugin("session");
  68. function self.data(conn, data)
  69. local ok, err = self.stream:feed(data);
  70. if ok then return; end
  71. self:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
  72. self:close("xml-not-well-formed");
  73. end
  74. self:hook("connected", function () self:reopen(); end);
  75. self:hook("incoming-raw", function (data) return self.data(self.conn, data); end);
  76. self.curr_id = 0;
  77. self.tracked_iqs = {};
  78. self:hook("stanza", function (stanza)
  79. local id, type = stanza.attr.id, stanza.attr.type;
  80. if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then
  81. self.tracked_iqs[id](stanza);
  82. self.tracked_iqs[id] = nil;
  83. return true;
  84. end
  85. end);
  86. self:hook("stanza", function (stanza)
  87. local ret;
  88. if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then
  89. if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
  90. local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
  91. if xmlns then
  92. ret = self:event("iq/"..xmlns, stanza);
  93. if not ret then
  94. ret = self:event("iq", stanza);
  95. end
  96. end
  97. if ret == nil then
  98. self:send(verse.error_reply(stanza, "cancel", "service-unavailable"));
  99. return true;
  100. end
  101. else
  102. ret = self:event(stanza.name, stanza);
  103. end
  104. end
  105. return ret;
  106. end, -1);
  107. self:hook("outgoing", function (data)
  108. if data.name then
  109. self:event("stanza-out", data);
  110. end
  111. end);
  112. self:hook("stanza-out", function (stanza)
  113. if not stanza.attr.xmlns then
  114. self:event(stanza.name.."-out", stanza);
  115. end
  116. end);
  117. local function stream_ready()
  118. self:event("ready");
  119. end
  120. self:hook("session-success", stream_ready, -1)
  121. self:hook("bind-success", stream_ready, -1);
  122. local _base_close = self.close;
  123. function self:close(reason)
  124. self.close = _base_close;
  125. if not self.closed then
  126. self:send("</stream:stream>");
  127. self.closed = true;
  128. else
  129. return self:close(reason);
  130. end
  131. end
  132. local function start_connect()
  133. -- Initialise connection
  134. self:connect(to_ascii(self.connect_host or self.host), self.connect_port or 5222);
  135. end
  136. if not (self.connect_host or self.connect_port) then
  137. -- Look up SRV records
  138. adns.lookup(function (answer)
  139. if answer then
  140. self.srv_answer = answer;
  141. local srv_hosts = {};
  142. self.srv_hosts = srv_hosts;
  143. for _, record in ipairs(answer) do
  144. table.insert(srv_hosts, record.srv);
  145. end
  146. table.sort(srv_hosts, compare_srv_priorities);
  147. local srv_choice = srv_hosts[1];
  148. self.srv_choice = 1;
  149. if srv_choice then
  150. self.connect_host, self.connect_port = srv_choice.target, srv_choice.port;
  151. self:debug("Best record found, will connect to %s:%d", self.connect_host or self.host, self.connect_port or 5222);
  152. end
  153. self:hook("disconnected", function ()
  154. if self.srv_hosts and self.srv_choice < #self.srv_hosts then
  155. self.srv_choice = self.srv_choice + 1;
  156. local srv_choice = srv_hosts[self.srv_choice];
  157. self.connect_host, self.connect_port = srv_choice.target, srv_choice.port;
  158. start_connect();
  159. return true;
  160. end
  161. end, 1000);
  162. end
  163. start_connect();
  164. end, "_xmpp-client._tcp."..(to_ascii(self.host))..".", "SRV");
  165. else
  166. start_connect();
  167. end
  168. end
  169. function stream:reopen()
  170. self:reset();
  171. self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams',
  172. xmlns = "jabber:client", version = "1.0" }):top_tag());
  173. end
  174. function stream:send_iq(iq, callback)
  175. local id = self:new_id();
  176. self.tracked_iqs[id] = callback;
  177. iq.attr.id = id;
  178. self:send(iq);
  179. end
  180. function stream:new_id()
  181. self.curr_id = self.curr_id + 1;
  182. return tostring(self.curr_id);
  183. end