comparison mod_ircd/mod_ircd.in.lua @ 487:8bdab5489653

mod_ircd: code cleanup, added full logic for changing nicks (locally it works no traces), removed many comment lines (there was an over abundance of 'em they're in the .old_annotate file)
author Marco Cirillo <maranda@lightwitch.org>
date Fri, 02 Dec 2011 01:03:06 +0000
parents f8cc2be7e16a
children 4885ca74515c
comparison
equal deleted inserted replaced
486:b84493ef1d1d 487:8bdab5489653
2 -- Squish verse into this dir, then squish them into one, which you move 2 -- Squish verse into this dir, then squish them into one, which you move
3 -- and rename to mod_ircd.lua in your prosody modules/plugins dir. 3 -- and rename to mod_ircd.lua in your prosody modules/plugins dir.
4 -- 4 --
5 -- IRC spec: 5 -- IRC spec:
6 -- http://tools.ietf.org/html/rfc2812 6 -- http://tools.ietf.org/html/rfc2812
7
7 local _module = module 8 local _module = module
8 module = _G.module 9 module = _G.module
9 local module = _module 10 local module = _module
10 -- 11 local client_xmlns = "jabber:client"
12
11 local component_jid, component_secret, muc_server, port_number = 13 local component_jid, component_secret, muc_server, port_number =
12 module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000); 14 module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000);
13 15
14 if not muc_server then 16 if not muc_server then
15 module:log ("error", "You need to set the MUC server! halting.") 17 module:log ("error", "You need to set the MUC server! halting.")
18 20
19 package.loaded["util.sha1"] = require "util.encodings"; 21 package.loaded["util.sha1"] = require "util.encodings";
20 local verse = require "verse" 22 local verse = require "verse"
21 require "verse.component" 23 require "verse.component"
22 require "socket" 24 require "socket"
23 c = verse.new();--verse.logger()) 25 c = verse.new();
24 c:add_plugin("groupchat"); 26 c:add_plugin("groupchat");
25 27
26 local function verse2prosody(e) 28 local function verse2prosody(e)
27 return c:event("stanza", e.stanza) or true; 29 return c:event("stanza", e.stanza) or true;
28 end 30 end
30 module:hook("message/full", verse2prosody); 32 module:hook("message/full", verse2prosody);
31 module:hook("presence/bare", verse2prosody); 33 module:hook("presence/bare", verse2prosody);
32 module:hook("presence/full", verse2prosody); 34 module:hook("presence/full", verse2prosody);
33 c.type = "component"; 35 c.type = "component";
34 c.send = core_post_stanza; 36 c.send = core_post_stanza;
35
36 -- This plugin is actually a verse based component, but that mode is currently commented out
37
38 -- Add some hooks for debugging
39 --c:hook("opened", function () print("Stream opened!") end);
40 --c:hook("closed", function () print("Stream closed!") end);
41 --c:hook("stanza", function (stanza) print("Stanza:", stanza) end);
42
43 -- This one prints all received data
44 --c:hook("incoming-raw", print, 1000);
45 --c:hook("stanza", print, 1000);
46 --c:hook("outgoing-raw", print, 1000);
47
48 -- Print a message after authentication
49 --c:hook("authentication-success", function () print("Logged in!"); end);
50 --c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end);
51
52 -- Print a message and exit when disconnected
53 --c:hook("disconnected", function () print("Disconnected!"); os.exit(); end);
54
55 -- Now, actually start the connection:
56 --c.connect_host = "127.0.0.1"
57 --c:connect_component(component_jid, component_secret);
58 37
59 local jid = require "util.jid"; 38 local jid = require "util.jid";
60 local nodeprep = require "util.encodings".stringprep.nodeprep; 39 local nodeprep = require "util.encodings".stringprep.nodeprep;
61 40
62 local function utf8_clean (s) 41 local function utf8_clean (s)
126 return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " "); 105 return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " ");
127 end 106 end
128 107
129 local function irc2muc(channel, nick) 108 local function irc2muc(channel, nick)
130 local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; 109 local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
131 return jid.join(room, muc_server, nick) 110 if not nick then
111 return jid.join(room, muc_server);
112 else
113 return jid.join(room, muc_server, nick);
114 end
132 end 115 end
133 local function muc2irc(room) 116 local function muc2irc(room)
134 local channel, _, nick = jid.split(room); 117 local channel, _, nick = jid.split(room);
135 return "#"..channel, nick; 118 return "#"..channel, nick;
136 end 119 end
181 session = { conn = conn, host = component_jid, reset_stream = function () end, 164 session = { conn = conn, host = component_jid, reset_stream = function () end,
182 close = irc_close_session, log = logger.init("irc"..(conn.id or "1")), 165 close = irc_close_session, log = logger.init("irc"..(conn.id or "1")),
183 rooms = {}, 166 rooms = {},
184 roster = {} }; 167 roster = {} };
185 sessions[conn] = session; 168 sessions[conn] = session;
169
186 function session.data(data) 170 function session.data(data)
187 local parts = parse_line(data); 171 local parts = parse_line(data);
188 module:log("debug", require"util.serialization".serialize(parts)); 172 module:log("debug", require"util.serialization".serialize(parts));
189 local command = table.remove(parts, 1); 173 local command = table.remove(parts, 1);
190 if not command then 174 if not command then
205 else 189 else
206 session.send{from=muc_server, "421", session.nick, command, "Unknown command"}; 190 session.send{from=muc_server, "421", session.nick, command, "Unknown command"};
207 return module:log("debug", "Unknown command: %s", command); 191 return module:log("debug", "Unknown command: %s", command);
208 end 192 end
209 end 193 end
194
210 function session.send(data) 195 function session.send(data)
211 if type(data) == "string" then 196 if type(data) == "string" then
212 return conn:write(data.."\r\n"); 197 return conn:write(data.."\r\n");
213 elseif type(data) == "table" then 198 elseif type(data) == "table" then
214 local line = build_line(data); 199 local line = build_line(data);
215 module:log("debug", line); 200 module:log("debug", line);
216 conn:write(line.."\r\n"); 201 conn:write(line.."\r\n");
217 end 202 end
218 end 203 end
219 end 204 end
205
220 if data then 206 if data then
221 session.data(data); 207 session.data(data);
222 end 208 end
223 end 209 end
224 210
225 function irc_listener.ondisconnect(conn, error) 211 function irc_listener.ondisconnect(conn, error)
226 local session = sessions[conn]; 212 local session = sessions[conn];
213
227 if session then 214 if session then
228 for _, room in pairs(session.rooms) do 215 for _, room in pairs(session.rooms) do
229 room:leave("Disconnected"); 216 room:leave("Disconnected");
230 end 217 end
231 if session.nick then 218 if session.nick then
236 end 223 end
237 end 224 end
238 sessions[conn] = nil; 225 sessions[conn] = nil;
239 end 226 end
240 227
228 local function nick_inuse(nick)
229 if nicks[nick] then return true else return false end
230 end
231 local function change_nick_st(jid, room_jid, newnick)
232 return st.presence({ xmlns = xmlns_client, from = jid, to = room_jid, type = "unavailable" }):tag("status"):text("Changing nickname to "..newnick):up();
233 end
234
241 function commands.NICK(session, args) 235 function commands.NICK(session, args)
242 if session.nick then
243 session.send{from = muc_server, "484", "*", nick, "I'm afraid I can't let you do that"};
244 --TODO Loop throug all rooms and change nick, with help from Verse.
245 return;
246 end
247 local nick = args[1]; 236 local nick = args[1];
248 nick = nick:gsub("[^%w_]",""); 237 nick = nick:gsub("[^%w_]","");
249 if nicks[nick] then 238 local full_jid = jid.join(nick, component_jid, "ircd");
250 session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; 239
240 if session.nick and not nick_inuse(nick) then -- changing nick
241 local oldnick = session.nick;
242 local old_full_jid = session.full_jid;
243 local old_ar_last = jids[old_full_jid]["ar_last"];
244 local old_nicks_changing = jids[old_full_jid]["nicks_changing"];
245
246 -- update and replace session data
247 session.nick = nick;
248 session.full_jid = full_jid;
249 jids[old_full_jid] = nil; nicks[oldnick] = nil;
250 nicks[nick] = session;
251 jids[full_jid] = session;
252 jids[full_jid]["ar_last"] = old_ar_last;
253 jids[full_jid]["nicks_changing"] = old_nicks_changing;
254
255 session.send{from=oldnick, "NICK", nick};
256
257 -- broadcast changes if required, todo: something better then forcing parting and rejoining
258 if session.rooms then
259 for id, room in pairs(session.rooms) do
260 session.nicks_changing[session.nick] = oldnick;
261 room:send(change_nick_st(old_full_jid, room.jid.."/"..oldnick, session.nick));
262 commands.JOIN(session, { id });
263 end
264 session.nicks_changing[session.nick] = nil;
265 end
266
251 return; 267 return;
252 end 268 elseif nick_inuse(nick) then
253 local full_jid = jid.join(nick, component_jid, "ircd"); 269 session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; return;
270 end
271
254 jids[full_jid] = session; 272 jids[full_jid] = session;
255 jids[full_jid]["ar_last"] = {}; 273 jids[full_jid]["ar_last"] = {};
274 jids[full_jid]["nicks_changing"] = {};
256 nicks[nick] = session; 275 nicks[nick] = session;
257 session.nick = nick; 276 session.nick = nick;
258 session.full_jid = full_jid; 277 session.full_jid = full_jid;
259 session.type = "c2s"; 278 session.type = "c2s";
260 279
274 293
275 session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting, 294 session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting,
276 -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set") 295 -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set")
277 end 296 end
278 297
279 function commands.USER(session, params) 298 function commands.USER(session, params) -- To be done.
280 -- FIXME
281 -- Empty command for now
282 end 299 end
283 300
284 local function mode_map(am, rm, nicks) 301 local function mode_map(am, rm, nicks)
285 local rnick; 302 local rnick;
286 local c_modes; 303 local c_modes;
292 309
293 function commands.JOIN(session, args) 310 function commands.JOIN(session, args)
294 local channel = args[1]; 311 local channel = args[1];
295 if not channel then return end 312 if not channel then return end
296 local room_jid = irc2muc(channel); 313 local room_jid = irc2muc(channel);
297 print(session.full_jid); 314
298 if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end 315 if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end
299 local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } ); 316 local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } );
300 if not room then 317 if not room then
301 return ":"..muc_server.." ERR :Could not join room: "..err 318 return ":"..muc_server.." ERR :Could not join room: "..err
302 end 319 end
320
303 session.rooms[channel] = room; 321 session.rooms[channel] = room;
304 room.channel = channel;
305 room.session = session; 322 room.session = session;
306 session.send{from=session.nick, "JOIN", channel}; 323
307 if room.subject then 324 if session.nicks_changing[session.nick] then -- my own nick is changing
308 session.send{from=muc_server, 332, session.nick, channel ,room.subject}; 325 commands.NAMES(session, channel);
326 else
327 session.send{from=session.nick, "JOIN", channel};
328 if room.subject then
329 session.send{from=muc_server, 332, session.nick, channel, room.subject};
330 end
331 commands.NAMES(session, channel);
309 end 332 end
310 commands.NAMES(session, channel);
311 333
312 room:hook("subject-changed", function(changed) 334 room:hook("subject-changed", function(changed)
313 session.send((":%s TOPIC %s :%s"):format(changed.by.nick, channel, changed.to or "")); 335 session.send((":%s TOPIC %s :%s"):format(changed.by.nick, channel, changed.to or ""));
314 end); 336 end);
315 337
330 local c_modes; 352 local c_modes;
331 local rnick; 353 local rnick;
332 if ar.nick and not jids[session.full_jid].ar_last[ar.room_jid][ar.nick] then jids[session.full_jid].ar_last[ar.room_jid][ar.nick] = {} end 354 if ar.nick and not jids[session.full_jid].ar_last[ar.room_jid][ar.nick] then jids[session.full_jid].ar_last[ar.room_jid][ar.nick] = {} end
333 local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user") 355 local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user")
334 if x_ar then 356 if x_ar then
335 local xar_item = x_ar:get_child("item") 357 local xar_item = x_ar:get_child("item")
336 if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then 358 if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then
337 if xar_item.attr.affiliation and xar_item.attr.role then 359 if xar_item.attr.affiliation and xar_item.attr.role then
338 if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and 360 if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and
339 not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then 361 not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then
340 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation 362 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
356 end 378 end
357 379
358 c:hook("groupchat/joined", function(room) 380 c:hook("groupchat/joined", function(room)
359 local session = room.session or jids[room.opts.source]; 381 local session = room.session or jids[room.opts.source];
360 local channel = "#"..room.jid:match("^(.*)@"); 382 local channel = "#"..room.jid:match("^(.*)@");
361 session.send{from=session.nick.."!"..session.nick, "JOIN", channel}; 383
362 if room.topic then
363 session.send{from=muc_server, 332, room.topic};
364 end
365 commands.NAMES(session, channel)
366 room:hook("occupant-joined", function(nick) 384 room:hook("occupant-joined", function(nick)
367 session.send{from=nick.nick.."!"..nick.nick, "JOIN", channel}; 385 if session.nicks_changing[nick.nick] then
386 session.send{from=session.nicks_changing[nick.nick], "NICK", nick.nick};
387 session.nicks_changing[nick.nick] = nil;
388 else
389 session.send{from=nick.nick, "JOIN", channel};
390 end
368 end); 391 end);
369 room:hook("occupant-left", function(nick) 392 room:hook("occupant-left", function(nick)
370 jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; -- ugly 393 if jids[session.full_jid] then jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; end
371 session.send{from=nick.nick.."!"..nick.nick, "PART", channel}; 394 local status_code =
395 nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and
396 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status") and
397 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status").attr.code;
398
399 if status_code == "303" then
400 local newnick =
401 nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and
402 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status") and
403 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status"):get_child("item") and
404 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status"):get_child("item").attr.nick;
405
406 session.nicks_changing[newnick] = nick.nick; return;
407 end
408 session.send{from=nick.nick, "PART", channel};
372 end); 409 end);
373 end); 410 end);
374 411
375 function commands.NAMES(session, channel) 412 function commands.NAMES(session, channel)
376 local nicks = { }; 413 local nicks = { };
414 if type(channel) == "table" then channel = channel[1] end
377 local room = session.rooms[channel]; 415 local room = session.rooms[channel];
416
378 local symbols_map = { 417 local symbols_map = {
379 owner = "~", 418 owner = "~",
380 administrator = "&", 419 administrator = "&",
381 moderator = "@", 420 moderator = "@",
382 member = "+" 421 member = "+"
464 function commands.WHO(session, args) 503 function commands.WHO(session, args)
465 local channel = args[1]; 504 local channel = args[1];
466 if session.rooms[channel] then 505 if session.rooms[channel] then
467 local room = session.rooms[channel] 506 local room = session.rooms[channel]
468 for nick in pairs(room.occupants) do 507 for nick in pairs(room.occupants) do
469 --n=MattJ 91.85.191.50 irc.freenode.net MattJ H :0 Matthew Wild
470 session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick} 508 session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick}
471 end 509 end
472 session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"}; 510 session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"};
473 end 511 end
474 end 512 end
475 513
476 function commands.MODE(session, args) -- FIXME 514 function commands.MODE(session, args) -- Empty command
477 -- emptied for the time being, until something sane which works is available.
478 end 515 end
479 516
480 function commands.QUIT(session, args) 517 function commands.QUIT(session, args)
481 session.send{"ERROR", "Closing Link: "..session.nick}; 518 session.send{"ERROR", "Closing Link: "..session.nick};
482 for _, room in pairs(session.rooms) do 519 for _, room in pairs(session.rooms) do
486 nicks[session.nick] = nil; 523 nicks[session.nick] = nil;
487 sessions[session.conn] = nil; 524 sessions[session.conn] = nil;
488 session:close(); 525 session:close();
489 end 526 end
490 527
491 function commands.RAW(session, data) 528 function commands.RAW(session, data) -- Empty command
492 --c:send(data)
493 end 529 end
494 530
495 local function desetup() 531 local function desetup()
496 require "net.connlisteners".deregister("irc"); 532 require "net.connlisteners".deregister("irc");
497 end 533 end
498 534
499 --c:hook("ready", function () 535 require "net.connlisteners".register("irc", irc_listener);
500 require "net.connlisteners".register("irc", irc_listener); 536 require "net.connlisteners".start("irc");
501 require "net.connlisteners".start("irc");
502 --end);
503 537
504 module:hook("module-unloaded", desetup) 538 module:hook("module-unloaded", desetup)
505 539
506
507 --print("Starting loop...")
508 --verse.loop()