/* * Copyright (C) 2004-2008 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.openfire.plugin; import java.io.File; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jivesoftware.openfire.PresenceManager; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.container.Plugin; import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.user.User; import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.component.Component; import org.xmpp.component.ComponentManager; import org.xmpp.component.ComponentManagerFactory; import org.xmpp.packet.JID; import org.xmpp.packet.Packet; import org.xmpp.packet.Presence; /** * Plugin that includes a servlet that provides information about users' and components' * presence in the server. For security reasons, the XMPP spec does not allow anyone to see * the presence of any user. Only the users that are subscribed to the presence of other * users may see their presences.<p/> * * However, in order to make the servlet more useful it is possible to configure this plugin * so that anyone or only the users that are subscribed to a user presence may see the presence * of other users.<p/> * * Currently, the servlet provides presence information in two formats: 1) In XML format * and 2) using images.<p> * * The presence plugin is also a component so that it can probe presences of other components. * The new component will use <tt>presence</tt> as the subdomain subdomain. * * @author Gaston Dombiak */ public class PresencePlugin implements Plugin, Component { private static final Logger Log = LoggerFactory.getLogger(PresencePlugin.class); private static final String subdomain = "presence"; private UserManager userManager; private PresenceManager presenceManager; private PluginManager pluginManager; private ComponentManager componentManager; private String hostname; private Map<String, Presence> probedPresence; private JID componentJID; public void initializePlugin(PluginManager manager, File pluginDirectory) { pluginManager = manager; XMPPServer server = XMPPServer.getInstance(); userManager = server.getUserManager(); presenceManager = server.getPresenceManager(); hostname = server.getServerInfo().getXMPPDomain(); probedPresence = new ConcurrentHashMap<String, Presence>(); componentJID = new JID(subdomain + "." + hostname); // Register new component componentManager = ComponentManagerFactory.getComponentManager(); try { componentManager.addComponent(subdomain, this); } catch (Exception e) { Log.error(e.getMessage(), e); } } public void destroyPlugin() { userManager = null; presenceManager = null; // Remove presence plugin component try { componentManager.removeComponent(subdomain); componentManager = null; } catch (Exception e) { Log.error(e.getMessage(), e); } } public String getName() { return pluginManager.getName(this); } public String getDescription() { return pluginManager.getDescription(this); } public void initialize(JID jid, ComponentManager componentManager) { } public void start() { } public void shutdown() { } public void processPacket(Packet packet) { // Check that we are getting an answer to a presence probe if (packet instanceof Presence) { Presence presence = (Presence) packet; if (presence.isAvailable() || presence.getType() == Presence.Type.unavailable || presence.getType() == Presence.Type.error) { // Store answer of presence probes probedPresence.put(presence.getFrom().toString(), presence); } } } /** * Returns true if anyone is allowed to see the presence of other users. False means that * only the users that are subscribed to a user presence will be able to get information * about the user. By default, presence information is not publicly available. * * @return true if anyone is allowed to see the presence of other users. */ public boolean isPresencePublic() { return JiveGlobals.getBooleanProperty("plugin.presence.public", false); } /** * Sets if anyone is allowed to see the presence of other users. A false value means that * only the users that are subscribed to a user presence will be able to get information * about the user. By default, presence information is not publicly available. * * @param presencesPublic if anyone is allowed to see the presence of other users. */ public void setPresencePublic(boolean presencesPublic) { JiveGlobals.setProperty("plugin.presence.public", presencesPublic ? "true" : "false"); } /** * Returns the status message for the unavailable presence. This setting allows * a different string to be used for the status on this presence which is * "Unavailable" by default. * * @return the status message for the unavailable presence. */ public String getUnavailableStatus() { return JiveGlobals.getProperty("plugin.presence.unavailable.status", "Unavailable"); } /** * Sets the status message for the unavailable presence. This setting allows * a different string to be used for the status on this presence which is * "Unavailable" by default. * * @param statusMessage the status message for the unavailable presence. */ public void setUnavailableStatus(String statusMessage) { JiveGlobals.setProperty("plugin.presence.unavailable.status", statusMessage); } /** * Returns the presence of the requested user or <tt>null</tt> if the user is offline. If * presences are not public then the user presence will be returned if and only if the sender * of the request is subscribed to the user presence. * * @param sender the bare JID of the user making the request. * @param jid the bare JID of the entity whose presence is being probed. * @return the presence of the requested user. * @throws UserNotFoundException If presences are not public and the sender is null or the * sender cannot probe the presence of the requested user. Or if the requested user * does not exist in the local server. */ public Presence getPresence(String sender, String jid) throws UserNotFoundException { if (jid == null) { throw new UserNotFoundException("Target JID not found in request"); } JID targetJID = new JID(jid); // Check that the sender is not requesting information of a remote server entity if (targetJID.getDomain() == null || XMPPServer.getInstance().isRemote(targetJID)) { throw new UserNotFoundException("Domain does not matches local server domain"); } if (!hostname.equals(targetJID.getDomain())) { // Sender is requesting information about component presence, so we send a // presence probe to the component. presenceManager.probePresence(componentJID, targetJID); // Wait 30 seconds until we get the probe presence result int count = 0; Presence presence = probedPresence.get(jid); while (presence == null) { if (count > 300) { // After 30 seconds, timeout throw new UserNotFoundException( "Request for component presence has timed-out."); } try { Thread.sleep(100); } catch (InterruptedException e) { // don't care! } presence = probedPresence.get(jid); count++; } // Clean-up probe presence result probedPresence.remove(jid); // Return component presence return presence; } if (targetJID.getNode() == null || !UserManager.getInstance().isRegisteredUser(targetJID.getNode())) { // Sender is requesting presence information of an anonymous user throw new UserNotFoundException("Username is null"); } if (!isPresencePublic()) { if (sender == null) { throw new UserNotFoundException("Sender is null"); } else { // If sender is same as target, then we can see ourselves JID senderJID = new JID(sender); if (!senderJID.getNode().equals(targetJID.getNode()) && !presenceManager.canProbePresence(new JID(sender), targetJID.getNode())) { throw new UserNotFoundException("Sender is not allowed to probe this user"); } } } User user = userManager.getUser(targetJID.getNode()); return presenceManager.getPresence(user); } }