/*
* 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.server.xmppsession;
import tigase.auth.TigaseSaslProvider;
import tigase.conf.Configurable;
import tigase.db.AuthRepository;
import tigase.db.NonAuthUserRepository;
import tigase.db.NonAuthUserRepositoryImpl;
import tigase.db.RepositoryFactory;
import tigase.db.TigaseDBException;
import tigase.db.UserRepository;
import tigase.disco.XMPPService;
import tigase.server.AbstractMessageReceiver;
import tigase.server.Command;
import tigase.server.Iq;
import tigase.server.Message;
import tigase.server.Packet;
import tigase.server.Permissions;
import tigase.server.ReceiverTimeoutHandler;
import tigase.server.XMPPServer;
import tigase.server.script.CommandIfc;
import tigase.stats.StatisticsList;
import tigase.sys.OnlineJidsReporter;
import tigase.sys.TigaseRuntime;
import tigase.util.ProcessingThreads;
import tigase.util.QueueItem;
import tigase.util.TigaseStringprepException;
import tigase.util.WorkerThread;
import tigase.vhosts.VHostItem;
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.PacketErrorTypeException;
import tigase.xmpp.ProcessorFactory;
import tigase.xmpp.StanzaType;
import tigase.xmpp.XMPPException;
import tigase.xmpp.XMPPImplIfc;
import tigase.xmpp.XMPPPacketFilterIfc;
import tigase.xmpp.XMPPPostprocessorIfc;
import tigase.xmpp.XMPPPreprocessorIfc;
import tigase.xmpp.XMPPProcessor;
import tigase.xmpp.XMPPProcessorIfc;
import tigase.xmpp.XMPPResourceConnection;
import tigase.xmpp.XMPPSession;
import tigase.xmpp.XMPPStopListenerIfc;
import static tigase.server.xmppsession.SessionManagerConfig.*;
import java.security.Security;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.Bindings;
/**
* Class SessionManager
*
*
* Created: Tue Nov 22 07:07:11 2005
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class SessionManager extends AbstractMessageReceiver implements Configurable,
SessionManagerHandler, OnlineJidsReporter {
protected static final String ADMIN_COMMAND_NODE = "http://jabber.org/protocol/admin";
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log = Logger.getLogger(SessionManager.class.getName());
private long authTimeouts = 0;
private AuthRepository auth_repository = null;
private long closedConnections = 0;
private DefaultHandlerProc defHandlerProc = null;
private PacketDefaultHandler defPacketHandler = null;
private String defPluginsThreadsPool = "default-threads-pool";
private int maxUserConnections = 0;
private int maxUserSessions = 0;
private NonAuthUserRepository naUserRepository = null;
private SessionCloseProc sessionCloseProc = null;
private SessionOpenProc sessionOpenProc = null;
private SMResourceConnection smResourceConnection = null;
private long totalUserConnections = 0;
private long totalUserSessions = 0;
private UserRepository user_repository = null;
private Set<String> trusted = new ConcurrentSkipListSet<String>();
private boolean skipPrivacy = false;
private Set<XMPPImplIfc> allPlugins = new ConcurrentSkipListSet<XMPPImplIfc>();
private Map<String, XMPPStopListenerIfc> stopListeners =
new ConcurrentHashMap<String, XMPPStopListenerIfc>(10);
/**
* A Map with bare user JID as a key and a user session object as a value.
*/
private ConcurrentHashMap<BareJID, XMPPSession> sessionsByNodeId =
new ConcurrentHashMap<BareJID, XMPPSession>(100000);
private Map<String, ProcessingThreads<ProcessorWorkerThread>> workerThreads =
new ConcurrentHashMap<String, ProcessingThreads<ProcessorWorkerThread>>(32);
private Map<String, XMPPProcessorIfc> processors =
new ConcurrentHashMap<String, XMPPProcessorIfc>(32);
private Map<String, XMPPPreprocessorIfc> preProcessors =
new ConcurrentHashMap<String, XMPPPreprocessorIfc>(10);
private Map<String, XMPPPostprocessorIfc> postProcessors =
new ConcurrentHashMap<String, XMPPPostprocessorIfc>(10);
private Map<String, Map<String, Object>> plugin_config =
new ConcurrentHashMap<String, Map<String, Object>>(20);
private Map<String, XMPPPacketFilterIfc> outFilters =
new ConcurrentHashMap<String, XMPPPacketFilterIfc>(10);
/**
* A Map with connectionID as a key and an object with all the user connection
* data as a value
*/
protected ConcurrentHashMap<JID, XMPPResourceConnection> connectionsByFrom =
new ConcurrentHashMap<JID, XMPPResourceConnection>(100000);
private ConnectionCheckCommandHandler connectionCheckCommandHandler =
new ConnectionCheckCommandHandler();
/**
* Method description
*
*
* @param jid
*
* @return
*/
@Override
public boolean containsJid(BareJID jid) {
return sessionsByNodeId.containsKey(jid);
}
/**
* Method description
*
*
* @param jid
*
* @return
*/
@Override
public JID[] getConnectionIdsForJid(BareJID jid) {
if (skipPrivacy()) {
XMPPSession session = sessionsByNodeId.get(jid);
if (session != null) {
return session.getConnectionIds();
}
}
return null;
}
/**
* Method description
*
*
* @param params
*
* @return
*/
@Override
public Map<String, Object> getDefaults(Map<String, Object> params) {
Map<String, Object> props = super.getDefaults(params);
SessionManagerConfig.getDefaults(props, params);
return props;
}
/**
* Method description
*
*
* @return
*/
@Override
public String getDiscoCategoryType() {
return "sm";
}
/**
* Method description
*
*
* @return
*/
@Override
public String getDiscoDescription() {
return "Session manager";
}
/**
* Method description
*
*
* @param from
*
* @return
*/
@Override
public List<Element> getDiscoFeatures(JID from) {
List<Element> features = new LinkedList<Element>();
List<Element> tmp = super.getDiscoFeatures(from);
if (tmp != null) {
features.addAll(tmp);
}
for (XMPPProcessorIfc proc_t : processors.values()) {
Element[] discoFeatures = proc_t.supDiscoFeatures(null);
if (discoFeatures != null) {
features.addAll(Arrays.asList(discoFeatures));
} // end of if (discoFeatures != null)
}
return features;
}
/**
* Method description
*
*
* @param node
* @param jid
* @param from
*
* @return
*/
@Override
public Element getDiscoInfo(String node, JID jid, JID from) {
if ((jid != null)
&& (getName().equals(jid.getLocalpart()) || isLocalDomain(jid.toString()))) {
Element query = super.getDiscoInfo(node, jid, from);
if (query == null) {
query = new Element("query");
query.setXMLNS(XMPPService.INFO_XMLNS);
}
if (node == null) {
for (XMPPProcessorIfc proc_t : processors.values()) {
Element[] discoFeatures = proc_t.supDiscoFeatures(null);
if (discoFeatures != null) {
query.addChildren(Arrays.asList(discoFeatures));
} // end of if (discoFeatures != null)
}
}
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Found disco info: {0}",
((query != null) ? query.toString() : null));
}
return query;
}
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Not found disco info for node: {0}, jid: {1}", new Object[] {
node, jid });
}
return null;
}
/**
* Method description
*
*
* @param jid
*
* @return
*/
public XMPPResourceConnection getResourceConnection(JID jid) {
XMPPSession session = getSession(jid.getBareJID());
if (session != null) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Session not null, getting resource for jid: {0}", jid);
}
return session.getResourceConnection(jid);
} // end of if (session != null)
// Maybe this is a call for the server session?
if (isLocalDomain(jid.toString(), false)) {
return smResourceConnection;
}
return null;
}
private long calcAverage(long[] timings) {
long res = 0;
for (long ppt : timings) {
res += ppt;
}
long processingTime = res / timings.length;
return processingTime;
}
/**
* Method description
*
*
* @param list
*/
@Override
public void getStatistics(StatisticsList list) {
super.getStatistics(list);
if (list.checkLevel(Level.FINEST)) {
list.add(getName(), "Registered accounts", user_repository.getUsersCount(),
Level.FINEST);
}
list.add(getName(), "Open user connections", connectionsByFrom.size(), Level.INFO);
list.add(getName(), "Maximum user connections", maxUserConnections, Level.INFO);
list.add(getName(), "Total user connections", totalUserConnections, Level.FINER);
list.add(getName(), "Closed user connections", closedConnections, Level.FINER);
list.add(getName(), "Open user sessions", sessionsByNodeId.size(), Level.INFO);
list.add(getName(), "Maximum user sessions", maxUserSessions, Level.FINE);
list.add(getName(), "Total user sessions", totalUserSessions, Level.FINER);
list.add(getName(), "Authentication timouts", authTimeouts, Level.INFO);
int totalQueuesWait = list.getValue(getName(), "Total queues wait", 0);
long totalQueuesOverflow = list.getValue(getName(), "Total queues overflow", 0l);
for (Map.Entry<String, ProcessingThreads<ProcessorWorkerThread>> procent : workerThreads
.entrySet()) {
ProcessingThreads<ProcessorWorkerThread> proc = procent.getValue();
totalQueuesWait += proc.getTotalQueueSize();
totalQueuesOverflow += proc.getDroppedPackets();
if (list
.checkLevel(Level.INFO, proc.getTotalQueueSize() + proc.getDroppedPackets())) {
list.add(
getName(),
"Processor: " + procent.getKey(),
", Queue: " + proc.getTotalQueueSize() + ", AvTime: "
+ proc.getAverageProcessingTime() + ", Runs: " + proc.getTotalRuns()
+ ", Lost: " + proc.getDroppedPackets(), Level.INFO);
}
}
list.add(getName(), "Total queues wait", totalQueuesWait, Level.INFO);
list.add(getName(), "Total queues overflow", totalQueuesOverflow, Level.INFO);
// private long[] defPrepTime = new long[maxIdx];
// private long[] prepTime = new long[maxIdx];
// private long[] defForwTime = new long[maxIdx];
// private long[] walkTime = new long[maxIdx];
// private long[] postTime = new long[maxIdx];
// list.add(getName(), "Average defPrepTime on last " + defPrepTime.length
// + " runs [ms]", calcAverage(defPrepTime), Level.FINE);
// list.add(getName(), "Average prepTime on last " + prepTime.length +
// " runs [ms]",
// calcAverage(prepTime), Level.FINE);
// list.add(getName(), "Average defForwTime on last " + defForwTime.length
// + " runs [ms]", calcAverage(defForwTime), Level.FINE);
// list.add(getName(), "Average walkTime on last " + walkTime.length +
// " runs [ms]",
// calcAverage(walkTime), Level.FINE);
// list.add(getName(), "Average postTime on last " + postTime.length +
// " runs [ms]",
// calcAverage(postTime), Level.FINE);
for (Map.Entry<String, long[]> tmEntry : postTimes.entrySet()) {
list.add(getName(),
"Average " + tmEntry.getKey() + " on last " + tmEntry.getValue().length
+ " runs [ms]", calcAverage(tmEntry.getValue()), Level.FINE);
}
for (XMPPImplIfc plugin : allPlugins) {
plugin.getStatistics(list);
}
}
/**
* Method description
*
*
* @param userId
* @param conn
*/
@Override
public void handleLogin(BareJID userId, XMPPResourceConnection conn) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "handleLogin called for: {0}, conn_id: {1}", new Object[] {
userId, conn });
}
registerNewSession(userId, conn);
}
/**
* Method description
*
*
* @param userId
* @param conn
*/
@Override
public void handleLogout(BareJID userId, XMPPResourceConnection conn) {
XMPPSession session = sessionsByNodeId.get(userId);
if ((session != null) && (session.getActiveResourcesSize() <= 1)) {
sessionsByNodeId.remove(userId);
} // end of if (session.getActiveResourcesSize() == 0)
try {
connectionsByFrom.remove(conn.getConnectionId());
Packet cmd =
Command.CLOSE.getPacket(getComponentId(), conn.getConnectionId(),
StanzaType.set, conn.nextStanzaId());
String error = (String) conn.getSessionData(XMPPResourceConnection.ERROR_KEY);
if (error != null) {
Element err_el = new Element(error);
err_el.setXMLNS("urn:ietf:params:xml:ns:xmpp-streams");
cmd.getElement().getChild("command").addChild(err_el);
}
fastAddOutPacket(cmd);
} catch (NoConnectionIdException ex) {
log.log(Level.WARNING, "Connection ID not set for session: {0}", conn);
}
}
/**
* Method description
*
*
* @param conn
*/
@Override
public void handlePresenceSet(XMPPResourceConnection conn) {
}
/**
* Method description
*
*
* @param conn
*/
@Override
public void handleResourceBind(XMPPResourceConnection conn) {
}
/**
* Method description
*
*
* @return
*/
@Override
public boolean handlesLocalDomains() {
return true;
}
/**
* Method description
*
*
* @return
*/
@Override
public boolean hasCompleteJidsInfo() {
return true;
}
/**
* Method description
*
*
* @param binds
*/
@Override
public void initBindings(Bindings binds) {
super.initBindings(binds);
binds.put(CommandIfc.AUTH_REPO, auth_repository);
binds.put(CommandIfc.USER_CONN, connectionsByFrom);
binds.put(CommandIfc.USER_REPO, user_repository);
binds.put(CommandIfc.USER_SESS, sessionsByNodeId);
}
/**
* Method description
*
*
* @param domain
* @param includeComponents
*
* @return
*/
@Override
public boolean isLocalDomain(String domain, boolean includeComponents) {
if (includeComponents) {
return isLocalDomainOrComponent(domain);
} else {
return isLocalDomain(domain);
}
}
/**
* Method description
*
*
* @param packet
*/
@Override
public void processPacket(final Packet packet) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Received packet: {0}", packet.toStringSecure());
}
if (packet.isCommand() && processCommand(packet)) {
packet.processedBy("SessionManager");
// No more processing is needed for command packet
return;
} // end of if (pc.isCommand())
XMPPResourceConnection conn = getXMPPResourceConnection(packet);
if ((conn == null) && (isBrokenPacket(packet)) || processAdminsOrDomains(packet)) {
return;
}
processPacket(packet, conn);
}
/**
* Method description
*
*
* @return
*/
@Override
public int processingInThreads() {
return Runtime.getRuntime().availableProcessors() * 8;
}
@Override
public int processingOutThreads() {
return Runtime.getRuntime().availableProcessors() * 8;
}
/**
* Method description
*
*
* @param name
*/
@Override
public void setName(String name) {
super.setName(name);
TigaseRuntime.getTigaseRuntime().addOnlineJidsReporter(this);
}
/**
* Method description
*
*
* @param props
*/
@Override
public void setProperties(Map<String, Object> props) {
super.setProperties(props);
Security.insertProviderAt(new TigaseSaslProvider(), 6);
if (props.get(SKIP_PRIVACY_PROP_KEY) != null) {
skipPrivacy = (Boolean) props.get(SKIP_PRIVACY_PROP_KEY);
}
if (props.get(TRUSTED_PROP_KEY) != null) {
String[] trusted_tmp = (String[]) props.get(TRUSTED_PROP_KEY);
if (trusted_tmp != null) {
for (String trust : trusted_tmp) {
trusted.add(trust);
}
}
}
if (props.size() == 1) {
// If props.size() == 1, it means this is a single property update
// and this component does not support single property change for the rest
// of it's settings
return;
}
defPacketHandler = new PacketDefaultHandler();
// Is there shared user repository instance? If so I want to use it:
user_repository = (UserRepository) props.get(SHARED_USER_REPO_PROP_KEY);
if (user_repository != null) {
log.log(Level.CONFIG, "Using shared repository instance: {0}", user_repository
.getClass().getName());
} else {
Map<String, String> user_repo_params = new LinkedHashMap<String, String>(10);
for (Map.Entry<String, Object> entry : props.entrySet()) {
if (entry.getKey().startsWith(USER_REPO_PARAMS_NODE)) {
// Split the key to configuration nodes separated with '/'
String[] nodes = entry.getKey().split("/");
// The plugin ID part may contain many IDs separated with comma ','
if (nodes.length > 1) {
user_repo_params.put(nodes[1], entry.getValue().toString());
}
}
}
try {
// String cls_name = (String) props.get(USER_REPO_CLASS_PROP_KEY);
String res_uri = (String) props.get(USER_REPO_URL_PROP_KEY);
user_repository =
RepositoryFactory.getUserRepository(null, res_uri, user_repo_params);
log.log(Level.CONFIG, "Initialized {0} as user repository: {1}", new Object[] {
null, res_uri });
} catch (Exception e) {
log.log(Level.SEVERE, "Can't initialize user repository: ", e);
} // end of try-catch
}
auth_repository = (AuthRepository) props.get(SHARED_AUTH_REPO_PROP_KEY);
if (auth_repository != null) {
log.log(Level.CONFIG, "Using shared auth repository instance: {0}", auth_repository
.getClass().getName());
} else {
Map<String, String> auth_repo_params = new LinkedHashMap<String, String>(10);
for (Map.Entry<String, Object> entry : props.entrySet()) {
if (entry.getKey().startsWith(AUTH_REPO_PARAMS_NODE)) {
// Split the key to configuration nodes separated with '/'
String[] nodes = entry.getKey().split("/");
// The plugin ID part may contain many IDs separated with comma ','
if (nodes.length > 1) {
auth_repo_params.put(nodes[1], entry.getValue().toString());
}
}
}
try {
String res_uri = (String) props.get(AUTH_REPO_URL_PROP_KEY);
auth_repository =
RepositoryFactory.getAuthRepository(null, res_uri, auth_repo_params);
log.log(Level.CONFIG, "Initialized {0} as auth repository: {1}", new Object[] {
null, res_uri });
} catch (Exception e) {
log.log(Level.SEVERE, "Can't initialize auth repository: ", e);
} // end of try-catch
}
naUserRepository =
new NonAuthUserRepositoryImpl(user_repository, getDefHostName(),
Boolean.parseBoolean((String) props.get(AUTO_CREATE_OFFLINE_USER_PROP_KEY)));
LinkedHashMap<String, Integer> plugins_concurrency =
new LinkedHashMap<String, Integer>(20);
String[] plugins_conc = ((String) props.get(PLUGINS_CONCURRENCY_PROP_KEY)).split(",");
log.log(Level.CONFIG, "Loading concurrency plugins list: {0}",
Arrays.toString(plugins_conc));
if ((plugins_conc != null) && (plugins_conc.length > 0)) {
for (String plugc : plugins_conc) {
log.log(Level.CONFIG, "Loading: {0}", plugc);
if (!plugc.trim().isEmpty()) {
String[] pc = plugc.split("=");
try {
int conc = Integer.parseInt(pc[1]);
plugins_concurrency.put(pc[0], conc);
log.log(Level.CONFIG, "Concurrency for plugin: {0} set to: {1}",
new Object[] { pc[0], conc });
} catch (Exception e) {
log.log(Level.WARNING, "Plugin concurrency parsing error for: " + plugc
+ ", ", e);
}
}
}
}
try {
String sm_threads_pool = (String) props.get(SM_THREADS_POOL_PROP_KEY);
if (!sm_threads_pool.equals(SM_THREADS_POOL_PROP_VAL)) {
String[] threads_pool_params = sm_threads_pool.split(":");
int def_pool_size = 100;
if (threads_pool_params.length > 1) {
try {
def_pool_size = Integer.parseInt(threads_pool_params[1]);
} catch (Exception e) {
log.log(Level.WARNING,
"Incorrect threads pool size: {0}, setting default to 100",
threads_pool_params[1]);
def_pool_size = 100;
}
}
ProcessorWorkerThread worker = new ProcessorWorkerThread();
ProcessingThreads<ProcessorWorkerThread> pt =
new ProcessingThreads<ProcessorWorkerThread>(worker, def_pool_size,
maxInQueueSize, defPluginsThreadsPool);
workerThreads.put(defPluginsThreadsPool, pt);
log.log(Level.CONFIG, "Created a default thread pool: {0}", def_pool_size);
}
String[] plugins = (String[]) props.get(PLUGINS_PROP_KEY);
log.log(Level.CONFIG, "Loaded plugins list: {0}", Arrays.toString(plugins));
// maxPluginsNo = plugins.length;
processors.clear();
for (String plug_id : plugins) {
log.log(Level.CONFIG, "Loading and configuring plugin: {0}", plug_id);
XMPPImplIfc plugin = addPlugin(plug_id, plugins_concurrency.get(plug_id));
if (plugin != null) {
Map<String, Object> plugin_settings = getPluginSettings(plug_id, props);
if (plugin_settings.size() > 0) {
if (log.isLoggable(Level.CONFIG)) {
log.log(Level.CONFIG, "Plugin configuration: {0}", plugin_settings);
}
plugin_config.put(plug_id, plugin_settings);
}
try {
plugin.init(plugin_settings);
} catch (TigaseDBException ex) {
log.log(Level.SEVERE, "Problem initializing plugin: " + plugin.id(), ex);
}
allPlugins.add(plugin);
}
} // end of for (String comp_id: plugins)
} catch (Exception e) {
log.log(Level.SEVERE, "Problem with component initialization: " + getName(), e);
}
smResourceConnection =
new SMResourceConnection(null, user_repository, auth_repository, this);
registerNewSession(getComponentId().getBareJID(), smResourceConnection);
}
/**
* Method description
*
*
* @return
*/
public boolean skipPrivacy() {
return skipPrivacy;
}
protected void addOutPackets(Packet packet, XMPPResourceConnection conn,
Queue<Packet> results) {
for (XMPPPacketFilterIfc outfilter : outFilters.values()) {
outfilter.filter(packet, conn, naUserRepository, results);
} // end of for (XMPPPostprocessorIfc postproc: postProcessors)
addOutPackets(results);
}
protected boolean addTrusted(JID jid) {
return trusted.add(jid.getBareJID().toString());
}
protected void closeConnection(JID connectionId, boolean closeOnly) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Stream closed from: {0}", connectionId);
}
XMPPResourceConnection connection = connectionsByFrom.remove(connectionId);
if (connection != null) {
// Make sure no other stuff happen on the connection while it is being
// closed. The best example is handleLogin, it happens they are called
// concurrently and this is where things go wrong....
synchronized (connection) {
connection.putSessionData(XMPPResourceConnection.CLOSING_KEY,
XMPPResourceConnection.CLOSING_KEY);
closeSession(connection, closeOnly);
}
} else {
log.log(Level.FINE, "Can not find resource connection for connectionId: {0}",
connectionId);
// Let's make sure there is no stale XMPPResourceConnection in some
// XMPPSession
// object which may cause problems and packets sent to nowhere.
// This might an expensive operation though....
log.log(Level.INFO, "Trying to find and remove stale XMPPResourceConnection: {0}",
connectionId);
for (XMPPSession session : sessionsByNodeId.values()) {
connection = session.getResourceForConnectionId(connectionId);
if (connection != null) {
log.log(Level.WARNING, "Found stale XMPPResourceConnection: {0}, removing...",
connection);
session.removeResourceConnection(connection);
break;
}
}
} // end of if (conn != null) else
}
protected void closeSession(XMPPResourceConnection conn, boolean closeOnly) {
if (!closeOnly) {
Queue<Packet> results = new ArrayDeque<Packet>(50);
for (XMPPStopListenerIfc stopProc : stopListeners.values()) {
stopProc.stopped(conn, results, plugin_config.get(stopProc.id()));
} // end of for ()
addOutPackets(null, conn, results);
}
try {
if (conn.isAuthorized()) {
JID userJid = conn.getJID();
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Closing connection for: {0}", userJid);
}
XMPPSession session = conn.getParentSession();
if (session != null) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Found parent session for: {0}", userJid);
}
if (session.getActiveResourcesSize() <= 1) {
session = sessionsByNodeId.remove(userJid.getBareJID());
if (session == null) {
log.log(Level.INFO, "UPS can't remove, session not found in map: {0}",
userJid);
} else {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Number of user sessions: {0}",
sessionsByNodeId.size());
}
} // end of else
auth_repository.logout(userJid.getBareJID());
} else {
if (log.isLoggable(Level.FINER)) {
StringBuilder sb = new StringBuilder(100);
for (XMPPResourceConnection res_con : session.getActiveResources()) {
sb.append(", res=").append(res_con.getResource());
}
log.log(
Level.FINER,
"Number of connections is {0} for the user: {1}{2}",
new Object[] { session.getActiveResourcesSize(), userJid, sb.toString() });
}
} // end of else
} // end of if (session.getActiveResourcesSize() == 0)
}
} catch (NotAuthorizedException e) {
log.log(Level.INFO, "Closed not authorized session: {0}", e);
} catch (Exception e) {
log.log(Level.WARNING, "Exception closing session... ", e);
}
++closedConnections;
conn.streamClosed();
}
protected XMPPResourceConnection createUserSession(JID conn_id, String domain)
throws TigaseStringprepException {
XMPPResourceConnection connection =
new XMPPResourceConnection(conn_id, user_repository, auth_repository, this);
VHostItem vitem = null;
if (domain != null) {
vitem = getVHostItem(domain);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Setting hostname {0} for connection: {1}, VHostItem: {2}",
new Object[] { domain, conn_id, vitem });
}
}
if (vitem == null) {
// This shouldn't generally happen. Must mean misconfiguration.
if (log.isLoggable(Level.INFO)) {
log.log(Level.INFO,
"Can't get VHostItem for domain: {0}, using default one instead: {1}",
new Object[] { domain, getDefHostName() });
}
vitem = new VHostItem(getDefHostName().getDomain());
}
connection.setDomain(vitem.getUnmodifiableVHostItem());
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Domain set for connectionId {0}", conn_id);
}
connectionsByFrom.put(conn_id, connection);
int currSize = connectionsByFrom.size();
if (currSize > maxUserConnections) {
maxUserConnections = currSize;
}
++totalUserConnections;
return connection;
}
protected boolean delTrusted(JID jid) {
return trusted.remove(jid.getBareJID().toString());
}
protected boolean fastAddOutPacket(Packet packet) {
return addOutPacket(packet);
}
@Override
public boolean addOutPacket(Packet packet) {
// We actually have to set packetFrom address to the session manager ID
// to make sure the connection manager for instance can report problems back
// This cause other problems with packets processing which have to be
// resolved anyway
if (packet.getPacketFrom() == null) {
packet.setPacketFrom(getComponentId());
}
return super.addOutPacket(packet);
}
@Override
protected Integer getMaxQueueSize(int def) {
return def * 10;
}
protected XMPPSession getSession(BareJID jid) {
return sessionsByNodeId.get(jid);
}
protected XMPPResourceConnection getXMPPResourceConnection(JID connId) {
return connectionsByFrom.get(connId);
}
protected XMPPResourceConnection getXMPPResourceConnection(Packet p) {
XMPPResourceConnection conn = null;
JID from = p.getPacketFrom();
if (from != null) {
conn = connectionsByFrom.get(from);
if (conn != null) {
return conn;
}
}
// It might be a message _to_ some user on this server
// so let's look for established session for this user...
JID to = p.getStanzaTo();
if (to != null) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Searching for resource connection for: " + to);
}
conn = getResourceConnection(to);
} else {
// Hm, not sure what should I do now....
// Maybe I should treat it as message to admin....
log.log(Level.INFO,
"Message without TO attribute set, don''t know what to do wih this: {0}", p);
} // end of else
return conn;
}
protected boolean isBrokenPacket(Packet p) {
// TODO: check this out to make sure it does not lead to an infinite
// processing loop These are most likely packets generated inside the SM to
// other users who are offline, like presence updates.
if (getComponentId().equals(p.getPacketFrom()) && p.getPacketTo() == null) {
return false;
}
if (p.getFrom() == null) {
// This is actually a broken packet and we can't even return an error
// for it, so just log it and drop it.
log.log(Level.FINE, "Broken packet: {0}", p.toStringSecure());
return true;
}
if (!p.getFrom().equals(p.getStanzaFrom())
&& (!p.isCommand() || (p.isCommand() && (p.getCommand() == Command.OTHER)))) {
// Sometimes (Bosh) connection is gone and this is an error packet
// sent back to the original sender. This original sender might be
// not local....
if ((p.getStanzaFrom() != null) && !isLocalDomain(p.getStanzaFrom().getDomain())) {
// ok just forward it there....
p.setPacketFrom(null);
p.setPacketTo(null);
fastAddOutPacket(p);
return true;
}
// It doesn't look good, there should really be a connection for
// this packet....
// returning error back...
log.log(Level.FINE, "Broken packet: {0}", p.toStringSecure());
// we do not want to send presence error packets here...
if ((p.getElemName() == Iq.ELEM_NAME) || (p.getElemName() == Message.ELEM_NAME)) {
try {
Packet error =
Authorization.SERVICE_UNAVAILABLE.getResponseMessage(p,
"Service not available.", true);
error.setPacketTo(p.getFrom());
fastAddOutPacket(error);
} catch (PacketErrorTypeException e) {
log.log(Level.FINE, "Packet is error type already: {0}", p.toStringSecure());
}
}
return true;
}
return false;
}
protected boolean isTrusted(JID jid) {
if (trusted.contains(jid.getBareJID().toString())) {
return true;
}
return isAdmin(jid);
}
protected boolean isTrusted(String jid) {
if (trusted.contains(jid)) {
return true;
}
return false;
}
protected XMPPResourceConnection loginUserSession(JID conn_id, String domain,
BareJID user_id, String resource, String xmpp_sessionId) {
try {
XMPPResourceConnection conn = createUserSession(conn_id, domain);
conn.setSessionId(xmpp_sessionId);
user_repository.setData(user_id, "tokens", xmpp_sessionId, conn_id.toString());
Authorization auth = conn.loginToken(user_id, xmpp_sessionId, conn_id.toString());
if (auth == Authorization.AUTHORIZED) {
handleLogin(user_id, conn);
if (resource != null) {
conn.setResource(resource);
}
} else {
connectionsByFrom.remove(conn_id);
return null;
}
return conn;
} catch (Exception ex) {
log.log(Level.WARNING, "Problem logging user: " + user_id + "/" + resource, ex);
}
return null;
}
protected boolean processAdminsOrDomains(Packet packet) {
if ((packet.getStanzaFrom() == null) && (packet.getPacketFrom() != null)) {
// The packet, probably did not went through the first state of processing
// yet.
return false;
}
JID to = packet.getStanzaTo();
if ((to != null) && isLocalDomain(to.toString())) {
if (packet.getElemName() == "message") {
// Yes this packet is for admin....
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Packet for admin: {0}", packet);
}
sendToAdmins(packet);
packet.processedBy("admins-or-domains");
return true;
} else {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Packet for hostname, should be handled elsewhere: {0}",
packet);
}
}
} // end of if (isInRoutings(to))
return false;
}
protected boolean processCommand(Packet pc) {
if ((pc.getStanzaTo() == null)
|| !(getComponentId().equals(pc.getStanzaTo()) || isLocalDomain(pc.getStanzaTo()
.toString()))) {
return false;
}
Iq iqc = (Iq) pc;
boolean processing_result = false;
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "{0} command from: {1}", new Object[] {
iqc.getCommand().toString(), iqc.getFrom() });
}
XMPPResourceConnection connection = connectionsByFrom.get(iqc.getFrom());
switch (iqc.getCommand()) {
case CLOSE: {
log.log(Level.WARNING, "Unexpected packet: {0}", pc);
processing_result = true;
}
break;
case STREAM_OPENED: {
// Response is sent from the thread when opening user session is
// completed.
// fastAddOutPacket(pc.okResult((String) null, 0));
ProcessingThreads<ProcessorWorkerThread> pt =
workerThreads.get(sessionOpenProc.id());
if (pt == null) {
pt = workerThreads.get(defPluginsThreadsPool);
}
pt.addItem(sessionOpenProc, iqc, connection);
processing_result = true;
}
break;
case GETFEATURES: {
if (iqc.getType() == StanzaType.get) {
List<Element> features = getFeatures(connectionsByFrom.get(iqc.getFrom()));
Packet result = iqc.commandResult(null);
Command.setData(result, features);
addOutPacket(result);
} // end of if (pc.getType() == StanzaType.get)
processing_result = true;
}
break;
case STREAM_CLOSED: {
fastAddOutPacket(iqc.okResult((String) null, 0));
ProcessingThreads<ProcessorWorkerThread> pt =
workerThreads.get(sessionCloseProc.id());
if (pt == null) {
pt = workerThreads.get(defPluginsThreadsPool);
}
pt.addItem(sessionCloseProc, iqc, connection);
// closeConnection(pc.getFrom(), false);
processing_result = true;
}
break;
case STREAM_CLOSED_UPDATE: {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "{0} processing comment, connection: {1}", new Object[] {
iqc.getCommand(), ((connection != null) ? connection : " is null") });
}
// Note! We don't send response to this packet....
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "{0} adding to the processor: {1}",
new Object[] { iqc.getCommand(),
((connection != null) ? connection : " is null") });
}
if (connection == null) {
// Hm, the user connection does not exist here but
// the XMPPSession thinks it still does, a quick fix should
// be enough.
// TODO: investigate why this happens at all, an exception
// during connection close processing????
JID stanzaFrom = iqc.getStanzaFrom();
if (stanzaFrom == null) {
// This is wrong
log.log(Level.WARNING, "Stream close update without an user JID: {0}", iqc);
} else {
XMPPSession xs = sessionsByNodeId.get(stanzaFrom.getBareJID());
if (xs == null) {
log.log(Level.INFO,
"Stream close for the user session which does not exist", iqc);
} else {
XMPPResourceConnection xcr =
xs.getResourceForConnectionId(iqc.getPacketFrom());
if (xcr == null) {
log.log(Level.INFO,
"Stream close for the resource connection which does not exist", iqc);
} else {
xs.removeResourceConnection(xcr);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "{0} removed resource connection: {1}",
new Object[] { iqc.getCommand(), xcr });
}
}
}
}
} else {
ProcessingThreads<ProcessorWorkerThread> pt =
workerThreads.get(sessionCloseProc.id());
if (pt == null) {
pt = workerThreads.get(defPluginsThreadsPool);
}
pt.addItem(sessionCloseProc, iqc, connection);
}
// closeConnection(pc.getFrom(), false);
processing_result = true;
}
break;
case USER_STATUS:
try {
if (isTrusted(iqc.getStanzaFrom())
|| isTrusted(iqc.getStanzaFrom().getDomain())) {
String av = Command.getFieldValue(pc, "available");
boolean available = !((av != null) && av.equalsIgnoreCase("false"));
if (available) {
Packet presence = null;
Element p = iqc.getElement().getChild("command").getChild("presence");
if (p != null) {
// + // use this hack to break XMLNS
// + Element el = new Element("presence");
// + el.setChildren(p.getChildren());
Element elem = p.clone();
elem.setXMLNS("jabber:client");
presence = Packet.packetInstance(elem);
}
connection = connectionsByFrom.get(iqc.getStanzaFrom());
if (connection == null) {
JID user_jid = JID.jidInstance(Command.getFieldValue(iqc, "jid"));
connection =
loginUserSession(iqc.getStanzaFrom(), user_jid.getDomain(),
user_jid.getBareJID(), user_jid.getResource(), "USER_STATUS");
connection.putSessionData("jingle", "active");
fastAddOutPacket(iqc.okResult((String) null, 0));
if (presence == null) {
presence =
Packet.packetInstance(new Element("presence", new Element[] {
new Element("priority", "-1"),
new Element("c",
new String[] { "node", "ver", "ext", "xmlns" },
new String[] { "http://www.google.com/xmpp/client/caps",
XMPPServer.getImplementationVersion(), "voice-v1",
"http://jabber.org/protocol/caps" }) }, null, null));
}
} else {
// addOutPacket(Authorization.CONFLICT.getResponseMessage(pc,
// "The user resource already exists.", true));
if (log.isLoggable(Level.FINEST)) {
log.finest("USER_STATUS set to true for user who is already available: "
+ iqc.toStringSecure());
}
}
if (presence != null) {
presence.setPacketFrom(iqc.getStanzaFrom());
presence.setPacketTo(getComponentId());
addOutPacket(presence);
}
} else {
connection = connectionsByFrom.remove(iqc.getStanzaFrom());
if (connection != null) {
closeSession(connection, false);
addOutPacket(iqc.okResult((String) null, 0));
} else {
addOutPacket(Authorization.ITEM_NOT_FOUND.getResponseMessage(iqc,
"The user resource you want to remove does not exist.", true));
log.info("Can not find resource connection for packet: "
+ iqc.toStringSecure());
}
}
} else {
try {
addOutPacket(Authorization.FORBIDDEN.getResponseMessage(iqc,
"Only trusted entity can do it.", true));
} catch (PacketErrorTypeException e) {
log.warning("Packet error type when not expected: " + iqc.toStringSecure());
}
}
} catch (Exception e) {
try {
addOutPacket(Authorization.UNDEFINED_CONDITION.getResponseMessage(iqc,
"Unexpected error occured during the request: " + e, true));
} catch (Exception ex) {
ex.printStackTrace();
}
log.log(Level.WARNING, "USER_STATUS session creation error: ", e);
}
processing_result = true;
break;
case OTHER:
if (getComponentId().equals(iqc.getStanzaTo())
&& getComponentId().equals(iqc.getPacketFrom())) {
// No such command available. This prevents from an infinite loop in
// case there is no implementation to hadle such a command
try {
addOutPacket(Authorization.FEATURE_NOT_IMPLEMENTED.getResponseMessage(iqc,
"There is no implementation for such command on the server.", true));
} catch (Exception ex) {
ex.printStackTrace();
}
log.log(Level.WARNING,
"There is no implementation for such command on the server: " + iqc);
processing_result = true;
}
break;
default:
if (getComponentId().equals(iqc.getStanzaTo())
&& getComponentId().equals(iqc.getPacketFrom())) {
// No such command available. This prevents from an infinite loop in
// case there is no implementation to hadle such a command
try {
addOutPacket(Authorization.FEATURE_NOT_IMPLEMENTED.getResponseMessage(iqc,
"There is no implementation for such command on the server.", true));
} catch (Exception ex) {
ex.printStackTrace();
}
log.log(Level.WARNING,
"There is no implementation for such command on the server: " + iqc);
processing_result = true;
}
break;
} // end of switch (pc.getCommand())
return processing_result;
}
private int tIdx = 0;
private int maxIdx = 100;
// private long[] defPrepTime = new long[maxIdx];
// private long[] prepTime = new long[maxIdx];
// private long[] defForwTime = new long[maxIdx];
// private long[] walkTime = new long[maxIdx];
// private long[] postTime = new long[maxIdx];
private Map<String, long[]> postTimes = new ConcurrentSkipListMap<String, long[]>();
protected void processPacket(Packet packet, XMPPResourceConnection conn) {
long startTime = System.currentTimeMillis();
int idx = tIdx;
tIdx = (tIdx + 1) % maxIdx;
// long defPrepTm = 0;
// long prepTm = 0;
// long defForwTm = 0;
// long walkTm = 0;
// long postTm = 0;
// TODO: check if this is really necessary, seems to be even harmful in some
// cases like when the error is generated as a response to a bad packet.
packet.setPacketTo(getComponentId());
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "processing packet: {0}, connection: {1}", new Object[] {
packet.toStringSecure(), conn });
}
Queue<Packet> results = new ArrayDeque<Packet>(2);
boolean stop = false;
if (!stop) {
if (defPacketHandler.preprocess(packet, conn, naUserRepository, results)) {
packet.processedBy("filter-foward");
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Packet preprocessed: {0}", packet.toStringSecure());
if (results.size() > 0) {
for (Packet p : results) {
log.log(Level.FINEST, "Preprocess result: {0}", p.toStringSecure());
}
}
}
addOutPackets(packet, conn, results);
return;
}
}
// defPrepTm = System.currentTimeMillis() - startTime;
// Preprocess..., all preprocessors get all messages to look at.
// I am not sure if this is correct for now, let's try to do it this
// way and maybe change it later.
// If any of them returns true - it means processing should stop now.
// That is needed for preprocessors like privacy lists which should
// block certain packets.
if (!stop) {
for (XMPPPreprocessorIfc preproc : preProcessors.values()) {
stop |=
preproc.preProcess(packet, conn, naUserRepository, results,
plugin_config.get(preproc.id()));
if (stop && log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Packet blocked by: {0}, packet{1}", new Object[] {
preproc.id(), packet });
break;
}
} // end of for (XMPPPreprocessorIfc preproc: preProcessors)
}
// prepTm = System.currentTimeMillis() - startTime;
if (!stop) {
if (defPacketHandler.forward(packet, conn, naUserRepository, results)) {
packet.processedBy("filter-foward");
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Packet forwarded: {0}", packet);
}
addOutPackets(packet, conn, results);
return;
}
}
// defForwTm = System.currentTimeMillis() - startTime;
if (!stop) {
walk(packet, conn, packet.getElement(), results);
}
// walkTm = System.currentTimeMillis() - startTime;
if (!stop) {
for (XMPPPostprocessorIfc postproc : postProcessors.values()) {
String plug_id = postproc.id();
long[] postProcTime = null;
synchronized (postTimes) {
postProcTime = postTimes.get(plug_id);
if (postProcTime == null) {
postProcTime = new long[maxIdx];
postTimes.put(plug_id, postProcTime);
}
}
long stTime = System.currentTimeMillis();
postproc.postProcess(packet, conn, naUserRepository, results,
plugin_config.get(postproc.id()));
postProcTime[idx] = System.currentTimeMillis() - stTime;
} // end of for (XMPPPostprocessorIfc postproc: postProcessors)
} // end of if (!stop)
// postTm = System.currentTimeMillis() - startTime;
if (!stop
&& !packet.wasProcessed()
&& ((packet.getStanzaTo() == null) || (!isLocalDomain(packet.getStanzaTo()
.toString())))) {
if (defPacketHandler.canHandle(packet, conn)) {
ProcessingThreads<ProcessorWorkerThread> pt =
workerThreads.get(defHandlerProc.id());
if (pt == null) {
pt = workerThreads.get(defPluginsThreadsPool);
}
pt.addItem(defHandlerProc, packet, conn);
packet.processedBy(defHandlerProc.id());
}
}
setPermissions(conn, results);
addOutPackets(packet, conn, results);
if (packet.wasProcessed() || processAdminsOrDomains(packet)) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Packet processed by: {0}", packet.getProcessorsIds()
.toString());
}
} else {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Packet not processed: {0}", packet.toStringSecure());
}
Packet error = null;
if (stop
|| ((conn == null) && (packet.getStanzaFrom() != null)
&& (packet.getStanzaTo() != null)
&& !packet.getStanzaTo().equals(getComponentId()) && ((packet.getElemName() == Iq.ELEM_NAME) || (packet
.getElemName() == Message.ELEM_NAME)))) {
try {
error =
Authorization.SERVICE_UNAVAILABLE.getResponseMessage(packet,
"Service not available.", true);
} catch (PacketErrorTypeException e) {
log.log(Level.FINE, "Service not available. Packet is error type already: {0}",
packet.toStringSecure());
}
} else {
if ((packet.getStanzaFrom() != null) || (conn != null)) {
try {
error =
Authorization.FEATURE_NOT_IMPLEMENTED.getResponseMessage(packet,
"Feature not supported yet.", true);
} catch (PacketErrorTypeException e) {
log.log(Level.FINE,
"Feature not supported yet. Packet is error type already: {0}",
packet.toStringSecure());
}
}
}
if (error != null) {
if (error.getStanzaTo() != null) {
conn = getResourceConnection(error.getStanzaTo());
} // end of if (error.getElemTo() != null)
try {
if (conn != null) {
error.setPacketTo(conn.getConnectionId());
} // end of if (conn != null)
addOutPacket(error);
} catch (NoConnectionIdException e) {
// Hm, strange, SM own session?
log.log(Level.WARNING, "Error packet to the SM''s own session: {0}", error);
}
}
} // end of else
// defPrepTime[idx] = defPrepTm;
// prepTime[idx] = prepTm;
// defForwTime[idx] = defForwTm;
// walkTime[idx] = walkTm;
// postTime[idx] = postTm;
}
protected void registerNewSession(BareJID userId, XMPPResourceConnection conn) {
synchronized (conn) {
if (conn.getSessionData(XMPPResourceConnection.CLOSING_KEY) != null) {
// The user just closed the connection, ignore....
return;
}
XMPPSession session = sessionsByNodeId.get(userId);
if (session == null) {
session = new XMPPSession(userId.getLocalpart());
sessionsByNodeId.put(userId, session);
int currSize = sessionsByNodeId.size();
if (currSize > maxUserSessions) {
maxUserSessions = currSize;
}
++totalUserSessions;
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Created new XMPPSession for: {0}", userId);
}
} else {
// Check all other connections whether they are still alive....
List<XMPPResourceConnection> connections = session.getActiveResources();
if (connections != null) {
for (XMPPResourceConnection connection : connections) {
if (connection != conn) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Checking connection: {0}", connection);
}
try {
addOutPacketWithTimeout(Command.CHECK_USER_CONNECTION.getPacket(
getComponentId(), connection.getConnectionId(), StanzaType.get, UUID
.randomUUID().toString()), connectionCheckCommandHandler, 30l,
TimeUnit.SECONDS);
} catch (NoConnectionIdException ex) {
// This actually should not happen... might be a bug:
log.log(Level.WARNING, "This should not happen, check it out!, ", ex);
}
}
}
}
}
try {
session.addResourceConnection(conn);
} catch (TigaseStringprepException ex) {
log.log(Level.INFO, "Stringprep problem for resource connection: {0}", conn);
handleLogout(userId, conn);
}
}
}
protected void sendToAdmins(Packet packet) {
for (BareJID admin : admins) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Sending packet to admin: {0}", admin);
}
Packet admin_pac = packet.copyElementOnly();
admin_pac.initVars(packet.getStanzaFrom(), JID.jidInstance(admin));
processPacket(admin_pac);
}
}
private XMPPImplIfc addPlugin(String plug_id, Integer conc)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
XMPPImplIfc result = null;
XMPPProcessorIfc proc = null;
if (plug_id.equals(sessionOpenProcId)) {
sessionOpenProc = new SessionOpenProc();
proc = sessionOpenProc;
}
if (plug_id.equals(sessionCloseProcId)) {
sessionCloseProc = new SessionCloseProc();
proc = sessionCloseProc;
}
if (plug_id.equals(defaultHandlerProcId)) {
defHandlerProc = new DefaultHandlerProc();
proc = defHandlerProc;
}
if (proc == null) {
proc = ProcessorFactory.getProcessor(plug_id);
}
boolean loaded = false;
if (proc != null) {
int concurrency =
((conc != null) ? conc : ((proc != null) ? proc.concurrentQueuesNo() : 0));
System.out.println("Loading plugin: " + plug_id + "=" + concurrency + " ...");
// If there is not default processors thread pool or the processor does
// have thread pool specific settings create a separate thread pool
// for the processor
if ((workerThreads.get(defPluginsThreadsPool) == null) || (conc != null)) {
ProcessorWorkerThread worker = new ProcessorWorkerThread();
ProcessingThreads<ProcessorWorkerThread> pt =
new ProcessingThreads<ProcessorWorkerThread>(worker, concurrency,
maxInQueueSize, proc.id());
workerThreads.put(proc.id(), pt);
log.log(Level.CONFIG, "Created thread pool: {0}, queue: {1} for plugin id: {2}",
new Object[] { concurrency, maxInQueueSize, proc.id() });
}
processors.put(proc.id(), proc);
log.log(Level.CONFIG, "Added processor: {0} for plugin id: {1}", new Object[] {
proc.getClass().getSimpleName(), proc.id() });
loaded = true;
result = proc;
}
XMPPPreprocessorIfc preproc = ProcessorFactory.getPreprocessor(plug_id);
if (preproc != null) {
preProcessors.put(plug_id, preproc);
log.log(Level.CONFIG, "Added preprocessor: {0} for plugin id: {1}", new Object[] {
preproc.getClass().getSimpleName(), plug_id });
loaded = true;
result = preproc;
}
XMPPPostprocessorIfc postproc = ProcessorFactory.getPostprocessor(plug_id);
if (postproc != null) {
postProcessors.put(plug_id, postproc);
log.log(Level.CONFIG, "Added postprocessor: {0} for plugin id: {1}", new Object[] {
postproc.getClass().getSimpleName(), plug_id });
loaded = true;
result = postproc;
}
XMPPStopListenerIfc stoplist = ProcessorFactory.getStopListener(plug_id);
if (stoplist != null) {
stopListeners.put(plug_id, stoplist);
log.log(Level.CONFIG, "Added stopped processor: {0} for plugin id: {1}",
new Object[] { stoplist.getClass().getSimpleName(), plug_id });
loaded = true;
result = stoplist;
}
XMPPPacketFilterIfc filterproc = ProcessorFactory.getPacketFilter(plug_id);
if (filterproc != null) {
outFilters.put(plug_id, filterproc);
log.log(Level.CONFIG, "Added packet filter: {0} for plugin id: {1}", new Object[] {
filterproc.getClass().getSimpleName(), plug_id });
loaded = true;
result = filterproc;
}
if (!loaded) {
log.log(Level.WARNING, "No implementation found for plugin id: {0}", plug_id);
} // end of if (!loaded)
return result;
}
private List<Element> getFeatures(XMPPResourceConnection session) {
List<Element> results = new LinkedList<Element>();
for (XMPPProcessorIfc proc_t : processors.values()) {
Element[] features = proc_t.supStreamFeatures(session);
if (features != null) {
results.addAll(Arrays.asList(features));
} // end of if (features != null)
} // end of for ()
return results;
}
private Map<String, Object>
getPluginSettings(String plug_id, Map<String, Object> props) {
Map<String, Object> plugin_settings = new ConcurrentHashMap<String, Object>(10);
// First set all options common for all plugins and then set all options
// specific to the
// plugin to make sure specific options can overwrite common options
for (Map.Entry<String, Object> entry : props.entrySet()) {
if (entry.getKey().startsWith(PLUGINS_CONF_PROP_KEY)) {
// Split the key to configuration nodes separated with '/'
String[] nodes = entry.getKey().split("/");
// Settings option for all plugins
if (nodes.length == 2) {
plugin_settings.put(nodes[1], entry.getValue());
log.log(Level.CONFIG, "Adding a common plugins option: {0} = {1}",
new Object[] { nodes[1], entry.getValue() });
}
}
}
// Now set plugin specific options
for (Map.Entry<String, Object> entry : props.entrySet()) {
if (entry.getKey().startsWith(PLUGINS_CONF_PROP_KEY)) {
// Split the key to configuration nodes separated with '/'
String[] nodes = entry.getKey().split("/");
// The plugin ID part may contain many IDs separated with comma ','
if (nodes.length > 2) {
String[] ids = nodes[1].split(",");
Arrays.sort(ids);
if (Arrays.binarySearch(ids, plug_id) >= 0) {
plugin_settings.put(nodes[2], entry.getValue());
log.log(Level.CONFIG, "Adding a specific plugins option [{0}]: {1} = {2}",
new Object[] { plug_id, nodes[1], entry.getValue() });
}
}
}
}
return plugin_settings;
}
private void setPermissions(XMPPResourceConnection conn, Queue<Packet> results) {
Permissions perms = Permissions.NONE;
if (conn != null) {
perms = Permissions.LOCAL;
if (conn.isAuthorized()) {
perms = Permissions.AUTH;
if (conn.isAnonymous()) {
perms = Permissions.ANONYM;
} else {
try {
JID id = conn.getJID();
if (isTrusted(id)) {
perms = Permissions.TRUSTED;
}
if (isAdmin(id)) {
perms = Permissions.ADMIN;
}
} catch (NotAuthorizedException e) {
perms = Permissions.NONE;
}
}
}
}
for (Packet res : results) {
res.setPermissions(perms);
}
}
private void walk(final Packet packet, final XMPPResourceConnection connection,
final Element elem, final Queue<Packet> results) {
for (XMPPProcessorIfc proc_t : processors.values()) {
String xmlns = elem.getXMLNS();
if (xmlns == null) {
xmlns = "jabber:client";
}
XMPPProcessorIfc processor = proc_t;
if (processor.isSupporting(elem.getName(), xmlns)) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "XMPPProcessorIfc: {0} ({1}" + ")" + "\n Request: "
+ "{2}, conn: {3}", new Object[] { processor.getClass().getSimpleName(),
processor.id(), packet, connection });
}
ProcessingThreads<ProcessorWorkerThread> pt = workerThreads.get(processor.id());
if (pt == null) {
pt = workerThreads.get(defPluginsThreadsPool);
}
if (pt.addItem(processor, packet, connection)) {
packet.processedBy(processor.id());
} else {
// proc_t.debugQueue();
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Can not add packet: {0} to processor: {1} internal queue full.",
new Object[] { packet.toStringSecure(), pt.getName() });
}
}
} // end of if (proc.isSupporting(elem.getName(), elem.getXMLNS()))
} // end of for ()
Collection<Element> children = elem.getChildren();
if (children != null) {
for (Element child : children) {
walk(packet, connection, child, results);
} // end of for (Element child: children)
} // end of if (children != null)
}
private class AuthenticationTimer extends TimerTask {
private JID connId = null;
private AuthenticationTimer(JID connId) {
this.connId = connId;
}
/**
* Method description
*
*/
@Override
public void run() {
XMPPResourceConnection conn = connectionsByFrom.get(connId);
if (conn != null) {
synchronized (conn) {
if (!conn.isAuthorized()) {
conn.putSessionData(XMPPResourceConnection.AUTHENTICATION_TIMEOUT_KEY,
XMPPResourceConnection.AUTHENTICATION_TIMEOUT_KEY);
connectionsByFrom.remove(connId);
++authTimeouts;
log.log(Level.INFO,
"Authentication timeout expired, closing connection: {0}", connId);
fastAddOutPacket(Command.CLOSE.getPacket(getComponentId(), connId,
StanzaType.set, conn.nextStanzaId()));
}
}
}
}
}
private class ConnectionCheckCommandHandler implements ReceiverTimeoutHandler {
/**
* Method description
*
*
* @param packet
* @param response
*/
@Override
public void responseReceived(Packet packet, Packet response) {
if (response.getType() == StanzaType.error) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER,
"Connection checker error received, closing connection: {0}",
packet.getTo());
}
// The connection is not longer active, closing the user session here.
closeConnection(packet.getTo(), false);
}
}
/**
* Method description
*
*
* @param packet
*/
@Override
public void timeOutExpired(Packet packet) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER,
"Connection checker timeout expired, closing connection: {0}", packet.getTo());
}
closeConnection(packet.getTo(), false);
}
}
private class DefaultHandlerProc extends XMPPProcessor implements XMPPProcessorIfc {
/**
* Method description
*
*
* @return
*/
@Override
public int concurrentQueuesNo() {
return 4;
}
/**
* Method description
*
*
* @return
*/
@Override
public String id() {
return defaultHandlerProcId;
}
/**
* Method description
*
*
* @param packet
* @param session
* @param repo
* @param results
* @param settings
*
* @throws XMPPException
*/
@Override
public void process(Packet packet, XMPPResourceConnection session,
NonAuthUserRepository repo, Queue<Packet> results, Map<String, Object> settings)
throws XMPPException {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Executing default packet handler for: {0}", packet);
}
defPacketHandler.process(packet, session, repo, results);
}
}
private class ProcessorWorkerThread extends WorkerThread {
private ArrayDeque<Packet> local_results = new ArrayDeque<Packet>(100);
// ~--- get methods --------------------------------------------------------
/**
* Method description
*
*
*
* @return
*/
@Override
public WorkerThread getNewInstance() {
ProcessorWorkerThread worker = new ProcessorWorkerThread();
return worker;
}
// ~--- methods ------------------------------------------------------------
/**
* Method description
*
*
* @param item
*/
@Override
public void process(QueueItem item) {
XMPPProcessorIfc processor = item.getProcessor();
try {
processor.process(item.getPacket(), item.getConn(), naUserRepository,
local_results, plugin_config.get(processor.id()));
if (item.getConn() != null) {
setPermissions(item.getConn(), local_results);
}
addOutPackets(item.getPacket(), item.getConn(), local_results);
} catch (PacketErrorTypeException e) {
log.log(Level.INFO, "Already error packet, ignoring: {0}", item.getPacket()
.toStringSecure());
} catch (XMPPException e) {
log.log(Level.WARNING, "Exception during packet processing: "
+ item.getPacket().toStringSecure(), e);
}
}
}
private class SessionCloseProc extends XMPPProcessor implements XMPPProcessorIfc {
/**
* Method description
*
*
* @return
*/
@Override
public int concurrentQueuesNo() {
return 4;
}
/**
* Method description
*
*
* @return
*/
@Override
public String id() {
return sessionCloseProcId;
}
/**
* Method description
*
*
* @param packet
* @param session
* @param repo
* @param results
* @param settings
*
* @throws XMPPException
*/
@Override
public void process(Packet packet, XMPPResourceConnection session,
NonAuthUserRepository repo, Queue<Packet> results, Map<String, Object> settings)
throws XMPPException {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Executing connection close for: {0}", packet);
}
closeConnection(packet.getFrom(), false);
}
}
private class SessionOpenProc extends XMPPProcessor implements XMPPProcessorIfc {
/**
* Method description
*
*
* @return
*/
@Override
public int concurrentQueuesNo() {
return 4;
}
/**
* Method description
*
*
* @return
*/
@Override
public String id() {
return sessionOpenProcId;
}
/**
* Method description
*
*
* @param packet
* @param session
* @param repo
* @param results
* @param settings
*
* @throws XMPPException
*/
@Override
public void process(Packet packet, XMPPResourceConnection session,
NonAuthUserRepository repo, Queue<Packet> results, Map<String, Object> settings)
throws XMPPException {
XMPPResourceConnection conn = session;
// It might be existing opened stream after TLS/SASL authorization
// If not, it means this is new stream
if (conn == null) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Adding resource connection for: {0}", packet.getFrom());
}
final String hostname = Command.getFieldValue(packet, "hostname");
try {
conn = createUserSession(packet.getFrom(), hostname);
} catch (TigaseStringprepException ex) {
log.log(Level.WARNING,
"Incrrect hostname, did not pass stringprep processing: {0}", hostname);
return;
}
addTimerTask(new AuthenticationTimer(packet.getFrom()), 2, TimeUnit.MINUTES);
} else {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Stream opened for existing session, authorized: {0}",
conn.isAuthorized());
}
} // end of else
conn.setSessionId(Command.getFieldValue(packet, "session-id"));
conn.setDefLang(Command.getFieldValue(packet, "xml:lang"));
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Setting session-id {0} for connection: {1}", new Object[] {
conn.getSessionId(), conn });
}
fastAddOutPacket(packet.okResult((String) null, 0));
}
}
}
// ~ Formatted in Sun Code Convention
// ~ Formatted by Jindent --- http://www.jindent.com