/* * Tigase Jabber/XMPP Server * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. * * $Rev$ * Last modified by $Author$ * $Date$ */ package tigase.xmpp.impl; //~--- non-JDK imports -------------------------------------------------------- import tigase.db.NonAuthUserRepository; import tigase.db.TigaseDBException; import tigase.server.Packet; import tigase.xml.Element; import tigase.xmpp.Authorization; import tigase.xmpp.BareJID; import tigase.xmpp.JID; import tigase.xmpp.NoConnectionIdException; import tigase.xmpp.NotAuthorizedException; import tigase.xmpp.StanzaType; import tigase.xmpp.XMPPException; import tigase.xmpp.XMPPPacketFilterIfc; import tigase.xmpp.XMPPPreprocessorIfc; import tigase.xmpp.XMPPProcessor; import tigase.xmpp.XMPPProcessorIfc; import tigase.xmpp.XMPPResourceConnection; import tigase.xmpp.impl.roster.RosterAbstract; import tigase.xmpp.impl.roster.RosterFactory; import static tigase.xmpp.impl.Privacy.*; //~--- JDK imports ------------------------------------------------------------ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; //~--- classes ---------------------------------------------------------------- /** * Describe class JabberIqPrivacy here. * * * Created: Mon Oct 9 18:18:11 2006 * * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @version $Rev$ */ public class JabberIqPrivacy extends XMPPProcessor implements XMPPProcessorIfc, XMPPPreprocessorIfc, XMPPPacketFilterIfc { /** * Private logger for class instances. */ private static Logger log = Logger.getLogger(JabberIqPrivacy.class.getName()); private static final String XMLNS = "jabber:iq:privacy"; private static final String ID = XMLNS; private static final String[] ELEMENTS = { "query" }; private static final String[] XMLNSS = { XMLNS }; private static final Element[] DISCO_FEATURES = { new Element("feature", new String[] { "var" }, new String[] { XMLNS }) }; private static final String PRIVACY_INIT_KEY = "privacy-init"; private static final String LIST_EL_NAME = "list"; private static final String DEFAULT_EL_NAME = "default"; private static final String ACTIVE_EL_NAME = "active"; private static final String PRESENCE_IN_EL_NAME = "presence-in"; private static final String PRESENCE_OUT_EL_NAME = "presence-out"; private static final String PRESENCE_EL_NAME = "presence"; private static RosterAbstract roster_util = RosterFactory.getRosterImplementation(true); private static final Comparator<Element> compar = new Comparator<Element>() { @Override public int compare(Element el1, Element el2) { String or1 = el1.getAttribute(ORDER); String or2 = el2.getAttribute(ORDER); return or1.compareTo(or2); } }; //~--- constant enums ------------------------------------------------------- private enum ITEM_ACTION { allow, deny } private enum ITEM_SUBSCRIPTIONS { both, to, from, none } private enum ITEM_TYPE { jid, group, subscription, all } //~--- methods -------------------------------------------------------------- /** * Method description * * * @param packet * @param session * @param repo * @param results */ @Override public void filter(Packet packet, XMPPResourceConnection session, NonAuthUserRepository repo, Queue<Packet> results) { if ((session == null) ||!session.isAuthorized() || (results == null) || (results.size() == 0)) { return; } for (Iterator<Packet> it = results.iterator(); it.hasNext(); ) { Packet res = it.next(); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Checking outbound packet: {0}", res); } // Always allow presence unavailable to go, privacy lists packets and // all other which are allowed by privacy rules if ((res.getType() == StanzaType.unavailable) || res.isXMLNS("/iq/query", XMLNS) || allowed(res, session)) { continue; } if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Packet not allowed to go, removing: {0}", res); } it.remove(); } } /** * Method description * * * @return */ @Override public String id() { return ID; } /** * <code>preProcess</code> method checks only incoming stanzas * so it doesn't check for presence-out at all. * * @param packet a <code>Packet</code> value * @param session a <code>XMPPResourceConnection</code> value * @param repo a <code>NonAuthUserRepository</code> value * @param results * @param settings * @return a <code>boolean</code> value */ @Override public boolean preProcess(Packet packet, XMPPResourceConnection session, NonAuthUserRepository repo, Queue<Packet> results, Map<String, Object> settings) { if ((session == null) ||!session.isAuthorized() || packet.isXMLNS("/iq/query", XMLNS)) { return false; } // end of if (session == null) return !allowed(packet, session); } /** * Method description * * * @param packet * @param session * @param repo * @param results * @param settings * * @throws XMPPException */ @Override public void process(final Packet packet, final XMPPResourceConnection session, final NonAuthUserRepository repo, final Queue<Packet> results, final Map<String, Object> settings) throws XMPPException { if (session == null) { return; } // end of if (session == null) try { StanzaType type = packet.getType(); switch (type) { case get : processGetRequest(packet, session, results); break; case set : processSetRequest(packet, session, results); break; case result : case error : // Ignore break; default : results.offer(Authorization.BAD_REQUEST.getResponseMessage(packet, "Request type is incorrect", false)); break; } // end of switch (type) } catch (NotAuthorizedException e) { log.log(Level.WARNING, "Received privacy request but user session is not authorized yet: {0}", packet); results.offer(Authorization.NOT_AUTHORIZED.getResponseMessage(packet, "You must authorize session first.", true)); } catch (TigaseDBException e) { log.log(Level.WARNING, "Database proble, please contact admin: {0}", e); results.offer(Authorization.INTERNAL_SERVER_ERROR.getResponseMessage(packet, "Database access problem, please contact administrator.", true)); } } /** * Method description * * * @param session * * @return */ @Override public Element[] supDiscoFeatures(final XMPPResourceConnection session) { return DISCO_FEATURES; } /** * Method description * * * @return */ @Override public String[] supElements() { return ELEMENTS; } /** * Method description * * * @return */ @Override public String[] supNamespaces() { return XMLNSS; } private boolean allowed(Packet packet, XMPPResourceConnection session) { try { // If this is a preprocessing phase, always allow all packets to // make it possible for the client to communicate with the server. if (session.getConnectionId().equals(packet.getPacketFrom())) { return true; } Element list = Privacy.getActiveList(session); if ((list == null) && (session.getSessionData(PRIVACY_INIT_KEY) == null)) { // First mark the session as privacy lists loaded for it, this way if there // is an exception thrown during database call for this user we won't // call it again for the same user. session.putSessionData(PRIVACY_INIT_KEY, ""); String lName = Privacy.getDefaultList(session); if (lName != null) { Privacy.setActiveList(session, lName); list = Privacy.getActiveList(session); } // end of if (lName != null) } // end of if (lName == null) if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Active privcy list: {0}", list); } if (list != null) { List<Element> items = list.getChildren(); if (items != null) { Collections.sort(items, compar); for (Element item : items) { boolean type_matched = false; boolean elem_matched = false; ITEM_TYPE type = ITEM_TYPE.all; if (item.getAttribute(TYPE) != null) { type = ITEM_TYPE.valueOf(item.getAttribute(TYPE)); } // end of if (item.getAttribute(TYPE) != null) String value = item.getAttribute(VALUE); BareJID sessionUserId = session.getBareJID(); JID jid = packet.getStanzaFrom(); boolean packetIn = true; if ((jid == null) || sessionUserId.equals(jid.getBareJID())) { jid = packet.getStanzaTo(); packetIn = false; } if (jid != null) { switch (type) { case jid : type_matched = jid.toString().contains(value); break; case group : String[] groups = roster_util.getBuddyGroups(session, jid); if (groups != null) { for (String group : groups) { if (type_matched = group.equals(value)) { break; } // end of if (group.equals(value)) } // end of for (String group: groups) } break; case subscription : ITEM_SUBSCRIPTIONS subscr = ITEM_SUBSCRIPTIONS.valueOf(value); switch (subscr) { case to : type_matched = roster_util.isSubscribedTo(session, jid); break; case from : type_matched = roster_util.isSubscribedFrom(session, jid); break; case none : type_matched = ( !roster_util.isSubscribedFrom(session, jid) &&!roster_util.isSubscribedTo(session, jid)); break; case both : type_matched = (roster_util.isSubscribedFrom(session, jid) && roster_util.isSubscribedTo(session, jid)); break; default : break; } // end of switch (subscr) break; case all : default : type_matched = true; break; } // end of switch (type) } else { if (type == ITEM_TYPE.all) { type_matched = true; } } // end of if (from != null) else if ( !type_matched) { continue; } // end of if (!type_matched) List<Element> elems = item.getChildren(); if ((elems == null) || (elems.size() == 0)) { elem_matched = true; } else { for (Element elem : elems) { if ( (packet.getElemName() == PRESENCE_EL_NAME) && ((packetIn && (elem.getName() == PRESENCE_IN_EL_NAME)) || ( !packetIn && (elem.getName() == PRESENCE_OUT_EL_NAME))) && ((packet.getType() == null) || (packet.getType() == StanzaType.unavailable)) ) { elem_matched = true; break; } if (packetIn && (elem.getName() == packet.getElemName())) { elem_matched = true; break; } // end of if (elem.getName().equals(packet.getElemName())) } // end of for (Element elem: elems) } // end of else if ( !elem_matched) { break; } // end of if (!elem_matched) ITEM_ACTION action = ITEM_ACTION.valueOf(item.getAttribute(ACTION)); switch (action) { case allow : return true; case deny : return false; default : break; } // end of switch (action) } // end of for (Element item: items) } // end of if (items != null) } // end of if (lName != null) } catch (NoConnectionIdException e) { // Always allow, this is server dummy session } catch (NotAuthorizedException e) { // results.offer(Authorization.NOT_AUTHORIZED.getResponseMessage(packet, // "You must authorize session first.", true)); } catch (TigaseDBException e) { log.log(Level.WARNING, "Database problem, please notify the admin. {0}", e); } return true; } private void processGetRequest(final Packet packet, final XMPPResourceConnection session, final Queue<Packet> results) throws NotAuthorizedException, XMPPException, TigaseDBException { List<Element> children = packet.getElemChildren("/iq/query"); if ((children == null) || (children.size() == 0)) { String[] lists = Privacy.getLists(session); if (lists != null) { StringBuilder sblists = new StringBuilder(100); for (String list : lists) { sblists.append("<list name=\"").append(list).append("\"/>"); } String list = Privacy.getDefaultList(session); if (list != null) { sblists.append("<default name=\"").append(list).append("\"/>"); } // end of if (defList != null) list = Privacy.getActiveListName(session); if (list != null) { sblists.append("<active name=\"").append(list).append("\"/>"); } // end of if (defList != null) results.offer(packet.okResult(sblists.toString(), 1)); } else { results.offer(packet.okResult((String) null, 1)); } // end of if (buddies != null) else } else { if (children.size() > 1) { results.offer(Authorization.BAD_REQUEST.getResponseMessage(packet, "You can retrieve only one list at a time.", true)); } else { Element eList = Privacy.getList(session, children.get(0).getAttribute("name")); if (eList != null) { results.offer(packet.okResult(eList, 1)); } else { results.offer(Authorization.ITEM_NOT_FOUND.getResponseMessage(packet, "Requested list not found.", true)); } // end of if (eList != null) else } // end of else } // end of else } private void processSetRequest(final Packet packet, final XMPPResourceConnection session, final Queue<Packet> results) throws NotAuthorizedException, XMPPException, TigaseDBException { List<Element> children = packet.getElemChildren("/iq/query"); if ((children != null) && (children.size() == 1)) { Element child = children.get(0); if (child.getName() == LIST_EL_NAME) { // Broken privacy implementation sends list without name set // instead of sending BAD_REQUEST error I can just assign // 'default' name here. String name = child.getAttribute(NAME); if ((name == null) || (name.length() == 0)) { child.setAttribute(NAME, "default"); } // end of if (name == null || name.length() == 0) List<Element> items = child.getChildren(); if (items == null || items.isEmpty()) { boolean inUse = name.equals(getDefaultList(session)); if (!inUse) { for (XMPPResourceConnection activeSession : session.getActiveSessions()) { inUse |= name.equals(Privacy.getActiveList(session)); } } if (inUse) { results.offer(Authorization.CONFLICT.getResponseMessage(packet, null, true)); } else { Privacy.removeList(session, child); results.offer(packet.okResult((String) null, 0)); } } else { Privacy.addList(session, child); results.offer(packet.okResult((String) null, 0)); } } // end of if (child.getName().equals("list)) if (child.getName() == DEFAULT_EL_NAME) { Privacy.setDefaultList(session, child); results.offer(packet.okResult((String) null, 0)); } // end of if (child.getName().equals("list)) if (child.getName() == ACTIVE_EL_NAME) { // User selects a different active list String listName = child.getAttribute(NAME); Element list = Privacy.getList(session, listName); if ((listName != null) && (list == null)) { results.offer(Authorization.ITEM_NOT_FOUND.getResponseMessage(packet, "Selected list not found on the server", true)); } else { // This is either declining of active list use or setting a new active list Privacy.setActiveList(session, child.getAttribute(NAME)); results.offer(packet.okResult((String) null, 0)); } } // end of if (child.getName().equals("list)) } else { results.offer(Authorization.BAD_REQUEST.getResponseMessage(packet, "Only 1 element is allowed in privacy set request.", true)); } // end of else } } // JabberIqPrivacy //~ Formatted in Sun Code Convention //~ Formatted by Jindent --- http://www.jindent.com