Comparison

plugins/muc/muc.lib.lua @ 6094:db2faeb151b6

plugins/muc/muc.lib: Factor `handle_to_occupant` out into many functions
author daurnimator <quae@daurnimator.com>
date Thu, 20 Feb 2014 16:50:18 -0500
parent 6093:9a7eaf0a35b6
child 6095:7900ebc544ce
comparison
equal deleted inserted replaced
6093:9a7eaf0a35b6 6094:db2faeb151b6
402 return from_nick, to_jid, id; 402 return from_nick, to_jid, id;
403 end 403 end
404 end 404 end
405 end 405 end
406 406
407 function room_mt:handle_presence_error_to_occupant(origin, stanza)
408 local current_nick = self._jid_nick[stanza.attr.from];
409 if not current_nick then
410 return true -- discard
411 end
412 log("debug", "kicking %s from %s", current_nick, self.jid);
413 return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza))
414 end
415
416 function room_mt:handle_unavailable_to_occupant(origin, stanza)
417 local from = stanza.attr.from;
418 local current_nick = self._jid_nick[from];
419 if not current_nick then
420 return true; -- discard
421 end
422 local pr = get_filtered_presence(stanza);
423 pr.attr.from = current_nick;
424 log("debug", "%s leaving %s", current_nick, self.jid);
425 self._jid_nick[from] = nil;
426 local occupant = self._occupants[current_nick];
427 local new_jid = next(occupant.sessions);
428 if new_jid == from then new_jid = next(occupant.sessions, new_jid); end
429 if new_jid then
430 local jid = occupant.jid;
431 occupant.jid = new_jid;
432 occupant.sessions[from] = nil;
433 pr.attr.to = from;
434 pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
435 :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up()
436 :tag("status", {code='110'}):up();
437 self:_route_stanza(pr);
438 if jid ~= new_jid then
439 pr = st.clone(occupant.sessions[new_jid])
440 :tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
441 :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"});
442 pr.attr.from = current_nick;
443 self:broadcast_except_nick(pr, current_nick);
444 end
445 else
446 occupant.role = 'none';
447 self:broadcast_presence(pr, from);
448 self._occupants[current_nick] = nil;
449 end
450 return true;
451 end
452
453 function room_mt:handle_occupant_presence(origin, stanza)
454 local from = stanza.attr.from;
455 local pr = get_filtered_presence(stanza);
456 local current_nick = stanza.attr.to
457 pr.attr.from = current_nick;
458 log("debug", "%s broadcasted presence", current_nick);
459 self._occupants[current_nick].sessions[from] = pr;
460 self:broadcast_presence(pr, from);
461 return true;
462 end
463
464 function room_mt:handle_change_nick(origin, stanza, current_nick, to)
465 local from = stanza.attr.from;
466 local occupant = self._occupants[current_nick];
467 local is_multisession = next(occupant.sessions, next(occupant.sessions));
468 if self._occupants[to] or is_multisession then
469 log("debug", "%s couldn't change nick", current_nick);
470 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
471 reply.tags[1].attr.code = "409";
472 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
473 return true;
474 else
475 local data = self._occupants[current_nick];
476 local to_nick = select(3, jid_split(to));
477 log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to);
478 local p = st.presence({type='unavailable', from=current_nick});
479 self:broadcast_presence(p, from, '303', to_nick);
480 self._occupants[current_nick] = nil;
481 self._occupants[to] = data;
482 self._jid_nick[from] = to;
483 local pr = get_filtered_presence(stanza);
484 pr.attr.from = to;
485 self._occupants[to].sessions[from] = pr;
486 self:broadcast_presence(pr, from);
487 return true;
488 end
489 end
490
491 function room_mt:handle_join(origin, stanza)
492 local from, to = stanza.attr.from, stanza.attr.to;
493 log("debug", "%s joining as %s", from, to);
494 if not next(self._affiliations) then -- new room, no owners
495 self._affiliations[jid_bare(from)] = "owner";
496 if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then
497 self.locked = nil; -- Older groupchat protocol doesn't lock
498 end
499 elseif self.locked then -- Deny entry
500 origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
501 return true;
502 end
503 local affiliation = self:get_affiliation(from);
504 local role = self:get_default_role(affiliation)
505 if role then -- new occupant
506 local is_merge = not not self._occupants[to]
507 if not is_merge then
508 self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}};
509 else
510 self._occupants[to].sessions[from] = get_filtered_presence(stanza);
511 end
512 self._jid_nick[from] = to;
513 self:send_occupant_list(from);
514 local pr = get_filtered_presence(stanza);
515 pr.attr.from = to;
516 pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
517 :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up();
518 if not is_merge then
519 self:broadcast_except_nick(pr, to);
520 end
521 pr:tag("status", {code='110'}):up();
522 if self._data.whois == 'anyone' then
523 pr:tag("status", {code='100'}):up();
524 end
525 if self.locked then
526 pr:tag("status", {code='201'}):up();
527 end
528 pr.attr.to = from;
529 self:_route_stanza(pr);
530 self:send_history(from, stanza);
531 self:send_subject(from);
532 return true;
533 elseif not affiliation then -- registration required for entering members-only room
534 local reply = st.error_reply(stanza, "auth", "registration-required"):up();
535 reply.tags[1].attr.code = "407";
536 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
537 return true;
538 else -- banned
539 local reply = st.error_reply(stanza, "auth", "forbidden"):up();
540 reply.tags[1].attr.code = "403";
541 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
542 return true;
543 end
544 end
545
546 function room_mt:handle_available_to_occupant(origin, stanza)
547 local from, to = stanza.attr.from, stanza.attr.to;
548 local current_nick = self._jid_nick[from];
549 if current_nick then
550 --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence
551 if current_nick == to then -- simple presence
552 return self:handle_occupant_presence(origin, stanza)
553 else -- change nick
554 return self:handle_change_nick(origin, stanza, current_nick, to)
555 end
556 --else -- possible rejoin
557 -- log("debug", "%s had connection replaced", current_nick);
558 -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
559 -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable
560 -- self:handle_to_occupant(origin, stanza); -- resend available
561 --end
562 else -- enter room
563 local new_nick = to;
564 if self._occupants[to] then
565 if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then
566 new_nick = nil;
567 end
568 end
569 local password = stanza:get_child("x", "http://jabber.org/protocol/muc");
570 password = password and password:get_child("password", "http://jabber.org/protocol/muc");
571 password = password and password[1] ~= "" and password[1];
572 if self:get_password() and self:get_password() ~= password then
573 log("debug", "%s couldn't join due to invalid password: %s", from, to);
574 local reply = st.error_reply(stanza, "auth", "not-authorized"):up();
575 reply.tags[1].attr.code = "401";
576 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
577 return true;
578 elseif not new_nick then
579 log("debug", "%s couldn't join due to nick conflict: %s", from, to);
580 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
581 reply.tags[1].attr.code = "409";
582 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
583 return true;
584 else
585 return self:handle_join(origin, stanza)
586 end
587 end
588 end
589
590 function room_mt:handle_presence_to_occupant(origin, stanza)
591 local type = stanza.attr.type;
592 if type == "error" then -- error, kick em out!
593 return self:handle_presence_error_to_occupant(origin, stanza)
594 elseif type == "unavailable" then -- unavailable
595 return self:handle_unavailable_to_occupant(origin, stanza)
596 elseif not type then -- available
597 return self:handle_available_to_occupant(origin, stanza)
598 elseif type ~= 'result' then -- bad type
599 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
600 origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
601 end
602 end
603 return true;
604 end
605
606 function room_mt:handle_private(origin, stanza)
607 local from, to = stanza.attr.from, stanza.attr.to;
608 local current_nick = self._jid_nick[from];
609 local type = stanza.attr.type;
610 local o_data = self._occupants[to];
611 if o_data then
612 log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid);
613 if stanza.name == "iq" then
614 local id = stanza.attr.id;
615 if stanza.attr.type == "get" or stanza.attr.type == "set" then
616 stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza);
617 else
618 stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza);
619 end
620 if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then
621 stanza.attr.to = jid_bare(stanza.attr.to);
622 end
623 if stanza.attr.id then
624 self:_route_stanza(stanza);
625 end
626 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
627 else -- message
628 stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up();
629 stanza.attr.from = current_nick;
630 for jid in pairs(o_data.sessions) do
631 stanza.attr.to = jid;
632 self:_route_stanza(stanza);
633 end
634 stanza.attr.from, stanza.attr.to = from, to;
635 end
636 elseif type ~= "error" and type ~= "result" then -- recipient not in room
637 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
638 end
639 return true;
640 end
641
642 function room_mt:handle_iq_to_occupant(origin, stanza)
643 local from, to = stanza.attr.from, stanza.attr.to;
644 local current_nick = self._jid_nick[from];
645 if not current_nick then
646 local type = stanza.attr.type;
647 if (type == "error" or type == "result") then
648 local id = stanza.attr.id;
649 stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza);
650 if stanza.attr.id then
651 self:_route_stanza(stanza);
652 end
653 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
654 else
655 origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
656 end
657 return true;
658 else
659 return self:handle_private(origin, stanza)
660 end
661 end
662
663 function room_mt:handle_message_to_occupant(origin, stanza)
664 local current_nick = self._jid_nick[stanza.attr.from];
665 local type = stanza.attr.type;
666 if not current_nick then -- not in room
667 if type ~= "error" then
668 origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
669 end
670 return true;
671 end
672 if type == "groupchat" then -- groupchat messages not allowed in PM
673 origin.send(st.error_reply(stanza, "modify", "bad-request"));
674 return true;
675 elseif type == "error" and is_kickable_error(stanza) then
676 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
677 return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable
678 else -- private stanza
679 return self:handle_private(origin, stanza)
680 end
681 end
407 682
408 function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc 683 function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
409 local from, to = stanza.attr.from, stanza.attr.to; 684 local from, to = stanza.attr.from, stanza.attr.to;
410 local room = jid_bare(to); 685 local room = jid_bare(to);
411 local current_nick = self._jid_nick[from]; 686 local current_nick = self._jid_nick[from];
412 local type = stanza.attr.type; 687 local type = stanza.attr.type;
413 log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); 688 log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag());
414 if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end 689 if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end
415 if stanza.name == "presence" then 690 if stanza.name == "presence" then
416 local pr = get_filtered_presence(stanza); 691 return self:handle_presence_to_occupant(origin, stanza)
417 pr.attr.from = current_nick; 692 elseif stanza.name == "iq" then
418 if type == "error" then -- error, kick em out! 693 return self:handle_iq_to_occupant(origin, stanza)
419 if current_nick then 694 elseif stanza.name == "message" then
420 log("debug", "kicking %s from %s", current_nick, room); 695 return self:handle_message_to_occupant(origin, stanza)
421 self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza));
422 end
423 elseif type == "unavailable" then -- unavailable
424 if current_nick then
425 log("debug", "%s leaving %s", current_nick, room);
426 self._jid_nick[from] = nil;
427 local occupant = self._occupants[current_nick];
428 local new_jid = next(occupant.sessions);
429 if new_jid == from then new_jid = next(occupant.sessions, new_jid); end
430 if new_jid then
431 local jid = occupant.jid;
432 occupant.jid = new_jid;
433 occupant.sessions[from] = nil;
434 pr.attr.to = from;
435 pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
436 :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up()
437 :tag("status", {code='110'}):up();
438 self:_route_stanza(pr);
439 if jid ~= new_jid then
440 pr = st.clone(occupant.sessions[new_jid])
441 :tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
442 :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"});
443 pr.attr.from = current_nick;
444 self:broadcast_except_nick(pr, current_nick);
445 end
446 else
447 occupant.role = 'none';
448 self:broadcast_presence(pr, from);
449 self._occupants[current_nick] = nil;
450 end
451 end
452 elseif not type then -- available
453 if current_nick then
454 --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence
455 if current_nick == to then -- simple presence
456 log("debug", "%s broadcasted presence", current_nick);
457 self._occupants[current_nick].sessions[from] = pr;
458 self:broadcast_presence(pr, from);
459 else -- change nick
460 local occupant = self._occupants[current_nick];
461 local is_multisession = next(occupant.sessions, next(occupant.sessions));
462 if self._occupants[to] or is_multisession then
463 log("debug", "%s couldn't change nick", current_nick);
464 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
465 reply.tags[1].attr.code = "409";
466 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
467 else
468 local data = self._occupants[current_nick];
469 local to_nick = select(3, jid_split(to));
470 if to_nick then
471 log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to);
472 local p = st.presence({type='unavailable', from=current_nick});
473 self:broadcast_presence(p, from, '303', to_nick);
474 self._occupants[current_nick] = nil;
475 self._occupants[to] = data;
476 self._jid_nick[from] = to;
477 pr.attr.from = to;
478 self._occupants[to].sessions[from] = pr;
479 self:broadcast_presence(pr, from);
480 else
481 --TODO malformed-jid
482 end
483 end
484 end
485 --else -- possible rejoin
486 -- log("debug", "%s had connection replaced", current_nick);
487 -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
488 -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable
489 -- self:handle_to_occupant(origin, stanza); -- resend available
490 --end
491 else -- enter room
492 local new_nick = to;
493 local is_merge;
494 if self._occupants[to] then
495 if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then
496 new_nick = nil;
497 end
498 is_merge = true;
499 end
500 local password = stanza:get_child("x", "http://jabber.org/protocol/muc");
501 password = password and password:get_child("password", "http://jabber.org/protocol/muc");
502 password = password and password[1] ~= "" and password[1];
503 if self:get_password() and self:get_password() ~= password then
504 log("debug", "%s couldn't join due to invalid password: %s", from, to);
505 local reply = st.error_reply(stanza, "auth", "not-authorized"):up();
506 reply.tags[1].attr.code = "401";
507 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
508 elseif not new_nick then
509 log("debug", "%s couldn't join due to nick conflict: %s", from, to);
510 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
511 reply.tags[1].attr.code = "409";
512 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
513 else
514 log("debug", "%s joining as %s", from, to);
515 if not next(self._affiliations) then -- new room, no owners
516 self._affiliations[jid_bare(from)] = "owner";
517 if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then
518 self.locked = nil; -- Older groupchat protocol doesn't lock
519 end
520 elseif self.locked then -- Deny entry
521 origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
522 return;
523 end
524 local affiliation = self:get_affiliation(from);
525 local role = self:get_default_role(affiliation)
526 if role then -- new occupant
527 if not is_merge then
528 self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}};
529 else
530 self._occupants[to].sessions[from] = get_filtered_presence(stanza);
531 end
532 self._jid_nick[from] = to;
533 self:send_occupant_list(from);
534 pr.attr.from = to;
535 pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
536 :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up();
537 if not is_merge then
538 self:broadcast_except_nick(pr, to);
539 end
540 pr:tag("status", {code='110'}):up();
541 if self._data.whois == 'anyone' then
542 pr:tag("status", {code='100'}):up();
543 end
544 if self.locked then
545 pr:tag("status", {code='201'}):up();
546 end
547 pr.attr.to = from;
548 self:_route_stanza(pr);
549 self:send_history(from, stanza);
550 self:send_subject(from);
551 elseif not affiliation then -- registration required for entering members-only room
552 local reply = st.error_reply(stanza, "auth", "registration-required"):up();
553 reply.tags[1].attr.code = "407";
554 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
555 else -- banned
556 local reply = st.error_reply(stanza, "auth", "forbidden"):up();
557 reply.tags[1].attr.code = "403";
558 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
559 end
560 end
561 end
562 elseif type ~= 'result' then -- bad type
563 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
564 origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
565 end
566 end
567 elseif not current_nick then -- not in room
568 if (type == "error" or type == "result") and stanza.name == "iq" then
569 local id = stanza.attr.id;
570 stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza);
571 if stanza.attr.id then
572 self:_route_stanza(stanza);
573 end
574 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
575 elseif type ~= "error" then
576 origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
577 end
578 elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM
579 origin.send(st.error_reply(stanza, "modify", "bad-request"));
580 elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
581 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
582 self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable
583 else -- private stanza
584 local o_data = self._occupants[to];
585 if o_data then
586 log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid);
587 if stanza.name == "iq" then
588 local id = stanza.attr.id;
589 if stanza.attr.type == "get" or stanza.attr.type == "set" then
590 stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza);
591 else
592 stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza);
593 end
594 if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then
595 stanza.attr.to = jid_bare(stanza.attr.to);
596 end
597 if stanza.attr.id then
598 self:_route_stanza(stanza);
599 end
600 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
601 else -- message
602 stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up();
603 stanza.attr.from = current_nick;
604 for jid in pairs(o_data.sessions) do
605 stanza.attr.to = jid;
606 self:_route_stanza(stanza);
607 end
608 stanza.attr.from, stanza.attr.to = from, to;
609 end
610 elseif type ~= "error" and type ~= "result" then -- recipient not in room
611 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
612 end
613 end 696 end
614 end 697 end
615 698
616 function room_mt:send_form(origin, stanza) 699 function room_mt:send_form(origin, stanza)
617 origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") 700 origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")