package org.jivesoftware.openfire.plugin.gojara.messagefilter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import org.dom4j.Element;
import org.dom4j.Node;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.interceptor.PacketInterceptor;
import org.jivesoftware.openfire.interceptor.PacketRejectedException;
import org.jivesoftware.openfire.plugin.gojara.messagefilter.processors.*;
import org.jivesoftware.openfire.plugin.gojara.sessions.GojaraAdminManager;
import org.jivesoftware.openfire.plugin.gojara.sessions.TransportSessionManager;
import org.jivesoftware.openfire.plugin.gojara.utils.XpathHelper;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
/**
* This is the only Interceptor GoJara uses. Each IQ/Message/Presence is checked if it is of interest to us, so we can
* process it accordingly. This is done in the specific Processors. The Interceptor keeps a set of currently connected
* Transports.
*
* @author axel.frederik.brand
* @author Holger Bergunde
*
*/
public class MainInterceptor implements PacketInterceptor {
private static final Logger Log = LoggerFactory.getLogger(MainInterceptor.class);
private Set<String> activeTransports = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
/**
* For referencing the abstract remote Processors
*/
private Map<String, AbstractRemoteRosterProcessor> packetProcessors = new HashMap<String, AbstractRemoteRosterProcessor>();
private TransportSessionManager tSessionManager = TransportSessionManager.getInstance();
private GojaraAdminManager gojaraAdminmanager = GojaraAdminManager.getInstance();
private XMPPServer server;
private Boolean frozen;
public MainInterceptor() {
Log.info("Created MainInterceptor for GoJara Plugin.");
server = XMPPServer.getInstance();
RosterManager rosterManager = server.getRosterManager();
AbstractRemoteRosterProcessor iqRegisteredProcessor = new DiscoIQRegisteredProcessor();
AbstractRemoteRosterProcessor iqRosterPayloadProcessor = new IQRosterPayloadProcessor(rosterManager);
AbstractRemoteRosterProcessor nonPersistantProcessor = new NonPersistantRosterProcessor(rosterManager);
AbstractRemoteRosterProcessor statisticsProcessor = new StatisticsProcessor();
AbstractRemoteRosterProcessor updateToComponentProcessor = new ClientToComponentUpdateProcessor(activeTransports);
AbstractRemoteRosterProcessor whitelistProcessor = new WhitelistProcessor(activeTransports);
AbstractRemoteRosterProcessor mucfilterProcessor = new MucFilterProcessor();
AbstractRemoteRosterProcessor gojaraAdminProcessor = new GojaraAdminProcessor();
packetProcessors.put("sparkIQRegistered", iqRegisteredProcessor);
packetProcessors.put("iqRosterPayload", iqRosterPayloadProcessor);
packetProcessors.put("handleNonPersistant", nonPersistantProcessor);
packetProcessors.put("statisticsProcessor", statisticsProcessor);
packetProcessors.put("clientToComponentUpdate", updateToComponentProcessor);
packetProcessors.put("whitelistProcessor", whitelistProcessor);
packetProcessors.put("mucfilterProcessor", mucfilterProcessor);
packetProcessors.put("gojaraAdminProcessor", gojaraAdminProcessor);
frozen = false;
}
public boolean addTransport(String subDomain) {
Log.info("Adding " + subDomain + " to watched Transports.");
boolean retval = this.activeTransports.add(subDomain);
if (retval) {
tSessionManager.addTransport(subDomain);
gojaraAdminmanager.testAdminConfiguration(subDomain);
}
return retval;
}
public boolean removeTransport(String subDomain) {
Log.info("Removing " + subDomain + " from watched Transports.");
tSessionManager.removeTransport(subDomain);
gojaraAdminmanager.gatewayUnregistered(subDomain);
return this.activeTransports.remove(subDomain);
}
public void freeze() {
Log.info("Freezing GoJara Maininterceptor.");
frozen = true;
}
/**
* As our Set of Subdomains is a Hash of Strings like icq.domain.tld, if we want to check if a jid CONTAINS a
* watched subdomain we need to iterate over the set. We also return the subdomain as a string so we can use it if
* we find it.
*/
private String searchJIDforSubdomain(String jid) {
if (jid.length() > 0) {
for (String subdomain : activeTransports) {
if (jid.contains(subdomain))
return subdomain;
}
}
return "";
}
/**
* This Interceptor tests if GoJara needs to process this package. We decided to do one global Interceptor so we
* would'nt redundantly test for cases we already checked in previous Interceptors, also we have only one big ugly
* If Structure to maintain instead of several.
*
* @see org.jivesoftware.openfire.interceptor.PacketInterceptor#interceptPacket (org.xmpp.packet.Packet,
* org.jivesoftware.openfire.session.Session, boolean, boolean)
*/
public void interceptPacket(Packet packet, Session session, boolean incoming, boolean processed) throws PacketRejectedException {
if (frozen)
return;
String from = "";
String to = "";
if (!processed || (incoming && processed)) {
try {
if (packet.getFrom() != null)
from = packet.getFrom().toString();
if (packet.getTo() != null)
to = packet.getTo().toString();
} catch (IllegalArgumentException e) {
Log.debug("There was an illegal JID while intercepting Message for GoJara. Not Intercepting it! " + e.getMessage());
return;
}
}
if (incoming && !processed) {
if (packet instanceof IQ) {
IQ iqPacket = (IQ) packet;
Element query = iqPacket.getChildElement();
if (query == null)
return;
// Jabber:IQ:roster Indicates Client to Component update or Rosterpush
else if (query.getNamespaceURI().equals("jabber:iq:roster")) {
if (!activeTransports.contains(from) && to.length() == 0 && iqPacket.getType().equals(IQ.Type.set))
packetProcessors.get("clientToComponentUpdate").process(packet, "", to, from);
else if (from.length() > 0 && activeTransports.contains(from))
packetProcessors.get("iqRosterPayload").process(packet, from, to, from);
}
// SPARK IQ REGISTERED Feature
else if (query.getNamespaceURI().equals("http://jabber.org/protocol/disco#info") && to.length() > 0
&& activeTransports.contains(to) && iqPacket.getType().equals(IQ.Type.get)) {
packetProcessors.get("sparkIQRegistered").process(packet, to, to, from);
}
// JABBER:IQ:LAST - Autoresponse Feature
else if (JiveGlobals.getBooleanProperty("plugin.remoteroster.iqLastFilter", false)
&& query.getNamespaceURI().equals("jabber:iq:last")) {
String to_s = searchJIDforSubdomain(to);
if (to_s.length() > 0 && iqPacket.getType().equals(IQ.Type.get))
throw new PacketRejectedException();
}
}
// Gojara Admin Manager Feature - Intercept responses to ADHOC commands sent via AdminManager
else if (packet instanceof Message && activeTransports.contains(from) && to.contains("gojaraadmin")) {
packetProcessors.get("gojaraAdminProcessor").process(packet, from, to, from);
}
// NONPERSISTANT Feature
else {
if (!JiveGlobals.getBooleanProperty("plugin.remoteroster.persistent", false)) {
if (packet instanceof Presence && activeTransports.contains(from))
packetProcessors.get("handleNonPersistant").process(packet, from, to, from);
}
}
} else if (incoming && processed) {
// We ignore Pings from S2 to S2 itself.
// STATISTICS - Feature
String from_searched = searchJIDforSubdomain(from);
String to_searched = searchJIDforSubdomain(to);
String subdomain = from_searched.length() == 0 ? to_searched : from_searched;
if (!from.equals(to) && subdomain.length() > 0)
packetProcessors.get("statisticsProcessor").process(packet, subdomain, to, from);
// TransportSession Feature
if (packet instanceof Presence && activeTransports.contains(from)) {
Presence presence_packet = (Presence) packet;
if (presence_packet.getType() == null) {
tSessionManager.connectUserTo(from, packet.getTo().getNode().toString());
} else if (presence_packet.getType() != null && presence_packet.getType().equals(Presence.Type.unavailable)) {
tSessionManager.disconnectUserFrom(from, packet.getTo().getNode().toString());
}
}
// TransportSession Feature - track Registrations and unregistrations so we can reset unsuccesfull ones
else if (packet instanceof IQ && activeTransports.contains(to)) {
IQ iqPacket = (IQ) packet;
Element query = iqPacket.getChildElement();
if (query == null)
return;
/*
* Okay, so now we have a IQ Packet with a query xmlns: jabber:iq:register Spark sends registrations and
* unregistrations in such a query, register has a x xmls jabber:iq:gateway:register, unregister is just
* a <remove/> element Gajim sends the whole form with x xmlns jabber:x:data type="submit", where a
* field var=unregister value of 0 is a registration, and unregister = 1 is unregistration
*/
if (query.getNamespaceURI().equals("jabber:iq:register") && iqPacket.getType().equals(IQ.Type.set)) {
// spark + gajim unregister
if (query.element("remove") != null)
tSessionManager.removeRegistrationOfUserFromDB(to, iqPacket.getFrom().getNode().toString());
else if (query.element("x") != null) {
Element xElem = query.element("x");
String xNamespace = xElem.getNamespaceURI();
// spark register
if (xNamespace.equals("jabber:iq:gateway:register"))
tSessionManager.registerUserTo(to, iqPacket.getFrom().getNode().toString());
// Gajim register + presence push
else if (xNamespace.equals("jabber:x:data") && xElem.attribute("type").getText().equals("submit")) {
tSessionManager.registerUserTo(to, iqPacket.getFrom().getNode().toString());
presencePush(packet.getTo(), packet.getFrom(), 150);
}
}
}
}
} else if (!incoming && !processed) {
if (packet instanceof IQ) {
IQ iqPacket = (IQ) packet;
Element query = iqPacket.getChildElement();
if (query == null)
return;
// DISCO#ITEMS - Whitelisting Feature
if (query.getNamespaceURI().equals("http://jabber.org/protocol/disco#items"))
packetProcessors.get("whitelistProcessor").process(packet, "", to, from);
// DISCO#INFO - MUC-Filter-Feature
else if (JiveGlobals.getBooleanProperty("plugin.remoteroster.mucFilter", false)
&& query.getNamespaceURI().equals("http://jabber.org/protocol/disco#info") && from.length() > 0
&& activeTransports.contains(from))
packetProcessors.get("mucfilterProcessor").process(packet, from, to, from);
// GAJIM presence push when connecting... probably doesnt really belong here but by now we would have
// 50processors for every edge case we handle...
// If roster is persistent and we dont block presences no need to do this since presences are being
// pushed by OF anyways
else if (JiveGlobals.getBooleanProperty("plugin.remoteroster.gajimBroadcast", false)) {
if (to.contains("Gajim") && query.getNamespaceURI().equals("jabber:iq:roster") && iqPacket.getType().equals(IQ.Type.result)) {
List<Node> nodes = XpathHelper.findNodesInDocument(iqPacket.getElement().getDocument(), "//roster:item");
for (Node n : nodes) {
String jid = n.valueOf("@jid");
if (activeTransports.contains(jid)) {
JID pushTo = new JID(jid);
presencePush(pushTo, iqPacket.getTo(), 3000);
}
}
}
}
} else if (JiveGlobals.getBooleanProperty("plugin.remoteroster.blockPresences", true) && packet instanceof Presence) {
/*
* We block Presences to users of a subdomain (except transport itself) so OF/S2 wont log you in
* automatically if you have a subdomain user in your roster. This prevents some clients from logging
* automatically, as the clients dont send a specific presence to the transport. We could do a presence
* push if we see them come online, but that would do the same as just not intercepting these presences,
* as the problem is really the client
*/
String to_s = searchJIDforSubdomain(to);
if (to_s.length() > 0 && !activeTransports.contains(to))
throw new PacketRejectedException();
}
}
}
/**
* Just pushes a available presence, we need this for GAJIM Client as it does not push available presence after
* registering We also wait a little so after register transport is on users roster. Really didnt wanted this here
* but doesnt really belong anywhere else
*
* @param to
* @param from
* @param delay MS until the job should be done
*/
private void presencePush(final JID to, final JID from, int delay) {
TimerTask pushPresenceTask = new TimerTask() {
@Override
public void run() {
PacketRouter router = server.getPacketRouter();
Packet presence = new Presence();
presence.setTo(to);
presence.setFrom(from);
router.route(presence);
}
};
Timer timer = new Timer();
timer.schedule(pushPresenceTask, delay);
}
}