diff options
Diffstat (limited to 'main.lua')
-rw-r--r-- | main.lua | 300 |
1 files changed, 92 insertions, 208 deletions
@@ -1,6 +1,6 @@ require "luarocks.require" -require "copas" +server = require "server" require "socket" require "ssl" require "lxp" @@ -10,8 +10,10 @@ function log(type, area, message) end require "core.stanza_dispatch" +init_xmlhandlers = require "core.xmlhandlers" require "core.rostermanager" require "core.offlinemessage" +require "core.usermanager" require "util.stanza" require "util.jid" @@ -24,7 +26,7 @@ local format = string.format; local st = stanza; ------------------------------ -users = {}; +sessions = {}; hosts = { ["localhost"] = { type = "local"; @@ -40,236 +42,118 @@ hosts = { local hosts, users = hosts, users; -local ssl_ctx, msg = ssl.newcontext { mode = "server", protocol = "sslv23", key = "/home/matthew/ssl_cert/server.key", +--local ssl_ctx, msg = ssl.newcontext { mode = "server", protocol = "sslv23", key = "/home/matthew/ssl_cert/server.key", +-- certificate = "/home/matthew/ssl_cert/server.crt", capath = "/etc/ssl/certs", verify = "none", } +-- +--if not ssl_ctx then error("Failed to initialise SSL/TLS support: "..tostring(msg)); end + + +local ssl_ctx = { mode = "server", protocol = "sslv23", key = "/home/matthew/ssl_cert/server.key", certificate = "/home/matthew/ssl_cert/server.crt", capath = "/etc/ssl/certs", verify = "none", } - -if not ssl_ctx then error("Failed to initialise SSL/TLS support: "..tostring(msg)); end function connect_host(host) hosts[host] = { type = "remote", sendbuffer = {} }; end -function handler(conn) - local copas_receive, copas_send = copas.receive, copas.send; - local reqdata, sktmsg; - local session = { sendbuffer = { external = {} }, conn = conn, notopen = true, priority = 0 } - - - -- Logging functions -- - - local mainlog, log = log; - do - local conn_name = tostring(conn):match("%w+$"); - log = function (type, area, message) mainlog(type, conn_name, message); end - end - local print = function (...) log("info", "core", t_concatall({...}, "\t")); end - session.log = log; - - -- -- -- - - -- Send buffers -- - - local sendbuffer = session.sendbuffer; - local send = function (data) return t_insert(sendbuffer, tostring(data)); end; - local send_to = function (to, stanza) - local node, host, resource = jid.split(to); - print("Routing stanza to "..to..":", node, host, resource); - if not hosts[host] then - print(" ...but host offline, establishing connection"); - connect_host(host); - t_insert(hosts[host].sendbuffer, stanza); -- This will be sent when s2s connection succeeds - elseif hosts[host].connected then - print(" ...putting in our external send buffer"); - t_insert(sendbuffer.external, { node = node, host = host, resource = resource, data = stanza}); - print(" ...there are now "..tostring(#sendbuffer.external).." stanzas in the external send buffer"); +local function send_to(session, to, stanza) + local node, host, resource = jid.split(to); + if not hosts[host] then + -- s2s + elseif hosts[host].type == "local" then + print(" ...is to a local user") + local destuser = hosts[host].sessions[node]; + if destuser and destuser.sessions then + if not destuser.sessions[resource] then + local best_session; + for resource, session in pairs(destuser.sessions) do + if not best_session then best_session = session; + elseif session.priority >= best_session.priority and session.priority >= 0 then + best_session = session; end end - session.send, session.send_to = send, send_to; - - -- -- -- - print("Client connected"); - conn = ssl.wrap(copas.wrap(conn), ssl_ctx); - - do - local succ, msg - conn:settimeout(15) - while not succ do - succ, msg = conn:dohandshake() - if not succ then - print("SSL: "..tostring(msg)); - if msg == 'wantread' then - socket.select({conn}, nil) - elseif msg == 'wantwrite' then - socket.select(nil, {conn}) + if not best_session then + offlinemessage.new(node, host, stanza); else - -- other error + print("resource '"..resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'"); + resource = best_session.resource; end end + if destuser.sessions[resource] == session then + log("warn", "core", "Attempt to send stanza to self, dropping..."); + else + print("...sending...", tostring(stanza)); + --destuser.sessions[resource].conn.write(tostring(data)); + print(" to conn ", destuser.sessions[resource].conn); + destuser.sessions[resource].conn.write(tostring(stanza)); + print("...sent") + end + elseif stanza.name == "message" then + print(" ...will be stored offline"); + offlinemessage.new(node, host, stanza); + elseif stanza.name == "iq" then + print(" ...is an iq"); + session.send(st.reply(stanza) + :tag("error", { type = "cancel" }) + :tag("service-unavailable", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" })); end + print(" ...done routing"); end - print("SSL handshake complete"); - -- XML parser initialisation -- +end - local parser; - local stanza; +function handler(conn, data, err) + local session = sessions[conn]; - local stanza_dispatch = init_stanza_dispatcher(session); + if not session then + sessions[conn] = { conn = conn, notopen = true, priority = 0 }; + session = sessions[conn]; - local xml_handlers = {}; - - do - local ns_stack = { "" }; - local curr_ns = ""; - local curr_tag; - function xml_handlers:StartElement(name, attr) - curr_ns,name = name:match("^(.+):(%w+)$"); - print("Tag received:", name, tostring(curr_ns)); - if not stanza then - if session.notopen then - if name == "stream" then - session.host = attr.to or error("Client failed to specify destination hostname"); - session.version = attr.version or 0; - session.streamid = m_random(1000000, 99999999); - print(session, session.host, "Client opened stream"); - send("<?xml version='1.0'?>"); - send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' >", session.streamid, session.host)); - --send("<stream:features>"); - --send("<mechanism>PLAIN</mechanism>"); - --send [[<register xmlns="http://jabber.org/features/iq-register"/> ]] - --send("</stream:features>"); - log("info", "core", "Stream opened successfully"); - session.notopen = nil; - return; - end - error("Client failed to open stream successfully"); - end - if name ~= "iq" and name ~= "presence" and name ~= "message" then - error("Client sent invalid top-level stanza"); - end - stanza = st.stanza(name, { to = attr.to, type = attr.type, id = attr.id, xmlns = curr_ns }); - curr_tag = stanza; - else - attr.xmlns = curr_ns; - stanza:tag(name, attr); - end - end - function xml_handlers:CharacterData(data) - if data:match("%S") then - stanza:text(data); - end - end - function xml_handlers:EndElement(name) - curr_ns,name = name:match("^(.+):(%w+)$"); - --print("<"..name.."/>", tostring(stanza), tostring(#stanza.last_add < 1), tostring(stanza.last_add[#stanza.last_add].name)); - if (not stanza) or #stanza.last_add < 0 or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then error("XML parse error in client stream"); end - -- Complete stanza - print(name, tostring(#stanza.last_add)); - if #stanza.last_add == 0 then - stanza_dispatch(stanza); - stanza = nil; - else - stanza:up(); - end - end ---[[ function xml_handlers:StartNamespaceDecl(namespace) - table.insert(ns_stack, namespace); - curr_ns = namespace; - log("debug", "parser", "Entering namespace "..tostring(curr_ns)); - end - function xml_handlers:EndNamespaceDecl(namespace) - table.remove(ns_stack); - log("debug", "parser", "Leaving namespace "..tostring(curr_ns)); - curr_ns = ns_stack[#ns_stack]; - log("debug", "parser", "Entering namespace "..tostring(curr_ns)); + -- Logging functions -- + + local mainlog, log = log; + do + local conn_name = tostring(conn):match("%w+$"); + log = function (type, area, message) mainlog(type, conn_name, message); end end -]] - end - parser = lxp.new(xml_handlers, ":"); + local print = function (...) log("info", "core", t_concatall({...}, "\t")); end + session.log = log; - -- -- -- + -- -- -- - -- Main loop -- - print "Receiving..." - reqdata = copas_receive(conn, 1); - print "Received" - while reqdata do - parser:parse(reqdata); - if #sendbuffer.external > 0 then - -- Stanzas queued to go to other places, from us - -- ie. other local users, or remote hosts that weren't connected before - print(#sendbuffer.external.." stanzas queued for other recipients, sending now..."); - for n, packet in pairs(sendbuffer.external) do - if not hosts[packet.host] then - connect_host(packet.host); - t_insert(hosts[packet.host].sendbuffer, packet.data); - elseif hosts[packet.host].type == "local" then - print(" ...is to a local user") - local destuser = hosts[packet.host].sessions[packet.node]; - if destuser and destuser.sessions then - if not destuser.sessions[packet.resource] then - local best_resource; - for resource, session in pairs(destuser.sessions) do - if not best_session then best_session = session; - elseif session.priority >= best_session.priority and session.priority >= 0 then - best_session = session; - end - end - if not best_session then - offlinemessage.new(packet.node, packet.host, packet.data); - else - print("resource '"..packet.resource.."' was not online, have chosen to send to '"..best_session.username.."@"..best_session.host.."/"..best_session.resource.."'"); - packet.resource = best_session.resource; - end - end - if destuser.sessions[packet.resource] == session then - log("warn", "core", "Attempt to send stanza to self, dropping..."); - else - print("...sending..."); - copas_send(destuser.sessions[packet.resource].conn, tostring(packet.data)); - print("...sent") - end - elseif packet.data.name == "message" then - print(" ...will be stored offline"); - offlinemessage.new(packet.node, packet.host, packet.data); - elseif packet.data.name == "iq" then - print(" ...is an iq"); - send(st.reply(packet.data) - :tag("error", { type = "cancel" }) - :tag("service-unavailable", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" })); - end - print(" ...removing from send buffer"); - sendbuffer.external[n] = nil; - end - end - end + -- Send buffers -- + + local send = function (data) print("Sending...", tostring(data)); conn.write(tostring(data)); end; + session.send, session.send_to = send, send_to; + + print("Client connected"); - if #sendbuffer > 0 then - for n, data in ipairs(sendbuffer) do - print "Sending..." - copas_send(conn, data); - print "Sent" - sendbuffer[n] = nil; + session.stanza_dispatch = init_stanza_dispatcher(session); + session.xml_handlers = init_xmlhandlers(session); + session.parser = lxp.new(session.xml_handlers, ":"); + + function session.disconnect(err) + print("Disconnected: "..err); end end - print "Receiving..." - repeat - reqdata, sktmsg = copas_receive(conn, 1); - if sktmsg == 'wantread' then - print("Received... wantread"); - --socket.select({conn}, nil) - --print("Socket ready now..."); - elseif sktmsg then - print("Received socket message:", sktmsg); - end - until reqdata or sktmsg == "closed"; - print("Received", tostring(reqdata)); + if data then + session.parser:parse(data); end - log("info", "core", "Client disconnected, connection closed"); + + --log("info", "core", "Client disconnected, connection closed"); +end + +function disconnect(conn, err) + sessions[conn].disconnect(err); end -server = socket.bind("*", 5223) -assert(server, "Failed to bind to socket") -copas.addserver(server, handler) +print("ssl_ctx:", type(ssl_ctx)); + +setmetatable(_G, { __index = function (t, k) print("WARNING: ATTEMPT TO READ A NIL GLOBAL!!!", k); error("Attempt to read a non-existent global. Naughty boy.", 2); end, __newindex = function (t, k, v) print("ATTEMPT TO SET A GLOBAL!!!!", tostring(k).." = "..tostring(v)); error("Attempt to set a global. Naughty boy.", 2); end }) --]][][[]][]; + + +local protected_handler = function (...) local success, ret = pcall(handler, ...); if not success then print("ERROR on "..tostring((select(1, ...)))..": "..ret); end end; + +print( server.add( { listener = protected_handler, disconnect = disconnect }, 5222, "*", 1, nil ) ) -- server.add will send a status message +print( server.add( { listener = protected_handler, disconnect = disconnect }, 5223, "*", 1, ssl_ctx ) ) -- server.add will send a status message -copas.loop(); +server.loop(); |