changeset 111:3de60860adca

mod_ircd: Initial commit of a wonderfully hacky but working IRC->XMPP interface for Prosody
author Matthew Wild <mwild1@gmail.com>
date Wed, 23 Dec 2009 14:22:02 +0000
parents e02281edc273
children d1168a454107
files mod_ircd/mod_ircd.lua
diffstat 1 files changed, 178 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_ircd/mod_ircd.lua	Wed Dec 23 14:22:02 2009 +0000
@@ -0,0 +1,178 @@
+local irc_listener = { default_port = 6667, default_mode = "*l" };
+
+local sessions = {};
+local commands = {};
+
+local nicks = {};
+
+local st = require "util.stanza";
+
+local conference_server = module:get_option("conference_server") or "conference.jabber.org";
+
+local function irc_close_session(session)
+	session.conn:close();
+end
+
+function irc_listener.onincoming(conn, data)
+	local session = sessions[conn];
+	if not session then
+		session = { conn = conn, host = module.host, reset_stream = function () end,
+			close = irc_close_session, log = logger.init("irc"..(conn.id or "1")),
+			roster = {} };
+		sessions[conn] = session;
+		function session.data(data)
+			module:log("debug", "Received: %s", data);
+			local command, args = data:match("^%s*([^ ]+) *(.*)%s*$");
+			if not command then
+				module:log("warn", "Invalid command: %s", data);
+				return;
+			end
+			command = command:upper();
+			module:log("debug", "Received command: %s", command);
+			if commands[command] then
+				local ret = commands[command](session, args);
+				if ret then
+					session.send(ret.."\r\n");
+				end
+			end
+		end
+		function session.send(data)
+			module:log("debug", "sending: %s", data);
+			return conn:write(data.."\r\n");
+		end
+	end
+	if data then
+		session.data(data);
+	end
+end
+
+function irc_listener.ondisconnect(conn, error)
+	module:log("debug", "Client disconnected");
+	sessions[conn] = nil;
+end
+
+function commands.NICK(session, nick)
+	nick = nick:match("^[%w_]+");
+	if nicks[nick] then
+		session.send(":"..session.host.." 433 * The nickname "..nick.." is already in use");
+		return;
+	end
+	nicks[nick] = session;
+	session.nick = nick;
+	session.full_jid = nick.."@"..module.host.."/ircd";
+	session.type = "c2s";
+	module:log("debug", "Client bound to %s", session.full_jid);
+	session.send(":"..session.host.." 001 "..session.nick.." :Welcome to XMPP via the "..session.host.." gateway "..session.nick);
+end
+
+local joined_mucs = {};
+function commands.JOIN(session, channel)
+	if not joined_mucs[channel] then
+		joined_mucs[channel] = { occupants = {}, sessions = {} };
+	end
+	joined_mucs[channel].sessions[session] = true;
+	local join_stanza = st.presence({ from = session.full_jid, to = channel:gsub("^#", "").."@"..conference_server.."/"..session.nick });
+	core_process_stanza(session, join_stanza);
+	session.send(":"..session.nick.." JOIN :"..channel);
+	session.send(":"..session.host.." 332 "..session.nick.." "..channel.." :Connection in progress...");
+	session.send(":"..session.host.." 353 "..session.nick.." = "..channel.." :"..session.nick);
+	session.send(":"..session.host.." 366 "..session.nick.." "..channel.." :End of /NAMES list.");
+end
+
+function commands.PART(session, channel)
+	local channel, part_message = channel:match("^(%S+) :(.+)$");
+	core_process_stanza(session, st.presence{ type = "unavailable", from = session.full_jid,
+		to = channel:gsub("^#", "").."@"..conference_server.."/"..session.nick }:tag("status"):text(part_message));
+end
+
+function commands.PRIVMSG(session, message)
+	local who, message = message:match("^(%S+) :(.+)$");
+	if joined_mucs[who] then
+		core_process_stanza(session, st.message{to=who:gsub("^#", "").."@"..conference_server, type="groupchat"}:tag("body"):text(message));
+	end
+end
+
+function commands.WHO(session, channel)
+	if joined_mucs[channel] then
+		for nick in pairs(joined_mucs[channel].occupants) do
+			--n=MattJ 91.85.191.50 irc.freenode.net MattJ H :0 Matthew Wild
+			session.send(":"..session.host.." 352 "..session.nick.." "..channel.." "..nick.." "..nick.." "..session.host.." "..nick.." H :0 "..nick);
+		end
+		session.send(":"..session.host.." 315 "..session.nick.." "..channel.. " :End of /WHO list");
+	end
+end
+
+function commands.MODE(session, channel)
+	session.send(":"..session.host.." 324 "..session.nick.." "..channel.." +J"); 
+end
+
+--- Component (handle stanzas from the server for IRC clients)
+function irc_component(origin, stanza)
+	local from, from_bare = stanza.attr.from, jid.bare(stanza.attr.from);
+	local from_node = "#"..jid.split(stanza.attr.from);
+	
+	if joined_mucs[from_node] and from_bare == from then
+		-- From room itself
+		local joined_muc = joined_mucs[from_node];
+		if stanza.name == "message" then
+			local subject = stanza:get_child("subject");
+			if subject then
+				local subject_text = subject:get_text();
+				for session in pairs(joined_muc.sessions) do
+					session.send(":"..session.host.." 332 "..session.nick.." "..from_node.." :"..subject_text);
+				end
+			end
+		end
+	elseif joined_mucs[from_node] then
+		-- From room occupant
+		local joined_muc = joined_mucs[from_node];
+		local nick = select(3, jid.split(from)):gsub(" ", "_");
+		if stanza.name == "presence" then
+			local what;
+			if not stanza.attr.type then
+				if joined_muc.occupants[nick] then
+					return;
+				end
+				joined_muc.occupants[nick] = true;
+				what = "JOIN";
+			else
+				joined_muc.occupants[nick] = nil;
+				what = "PART";
+			end
+			for session in pairs(joined_muc.sessions) do
+				if nick ~= session.nick then
+					session.send(":"..nick.."!"..nick.." "..what.." :"..from_node);
+				end
+			end
+		elseif stanza.name == "message" then
+			local body = stanza:get_child("body");
+			local hasdelay = stanza:get_child("delay", "urn:xmpp:delay");
+			if body then
+				for session in pairs(joined_muc.sessions) do
+					if nick ~= session.nick or hasdelay then
+						session.send(":"..nick.." PRIVMSG "..from_node.." :"..body:get_text());
+					end
+				end
+			end
+		end
+	end
+end
+
+require "core.componentmanager".register_component(module.host, irc_component);
+
+prosody.events.add_handler("server-stopping", function (shutdown)
+	module:log("debug", "Closing IRC connections prior to shutdown");
+	for channel, joined_muc in pairs(joined_mucs) do
+		for session in pairs(joined_muc.sessions) do
+			core_process_stanza(session,
+				st.presence{ type = "unavailable", from = session.full_jid,
+					to = channel:gsub("^#", "").."@"..conference_server.."/"..session.nick }
+					:tag("status")
+						:text("Connection closed: Server is shutting down"..(shutdown.reason and (": "..shutdown.reason) or "")));
+			session:close();
+		end
+	end
+end);
+
+require "net.connlisteners".register("irc", irc_listener);
+require "net.connlisteners".start("irc", { port = module:get_option("port") });