/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2005-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.handler;
import gnu.inet.encoding.StringprepException;
import org.dom4j.Element;
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.event.SessionEventDispatcher;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.StreamError;
/**
* Binds a resource to the stream so that the client's address becomes a full JID. Once a resource
* has been binded to the session the entity (i.e. client) is considered a "connected resource".<p>
* <p/>
* Clients may specify a desired resource but if none was specified then the server will create
* a random resource for the session. The new resource should be in accordance with ResourcePrep.
* The server will also verify if there are previous sessions from the same user that are already
* using the resource specified by the user. Depending on the server configuration the old session
* may be kicked or the new session may be rejected.
*
* @author Gaston Dombiak
*/
public class IQBindHandler extends IQHandler {
private static final Logger Log = LoggerFactory.getLogger(IQBindHandler.class);
private IQHandlerInfo info;
private String serverName;
private RoutingTable routingTable;
public IQBindHandler() {
super("Resource Binding handler");
info = new IQHandlerInfo("bind", "urn:ietf:params:xml:ns:xmpp-bind");
}
@Override
public IQ handleIQ(IQ packet) throws UnauthorizedException {
LocalClientSession session = (LocalClientSession) sessionManager.getSession(packet.getFrom());
// If no session was found then answer an error (if possible)
if (session == null) {
Log.error("Error during resource binding. Session not found in " +
sessionManager.getPreAuthenticatedKeys() +
" for key " +
packet.getFrom());
// This error packet will probably won't make it through
IQ reply = IQ.createResultIQ(packet);
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(PacketError.Condition.internal_server_error);
return reply;
}
IQ reply = IQ.createResultIQ(packet);
Element child = reply.setChildElement("bind", "urn:ietf:params:xml:ns:xmpp-bind");
// Check if the client specified a desired resource
String resource = packet.getChildElement().elementTextTrim("resource");
if (resource == null || resource.length() == 0) {
// None was defined so use the random generated resource
resource = session.getAddress().getResource();
}
else {
// Check that the desired resource is valid
try {
resource = JID.resourceprep(resource);
}
catch (StringprepException e) {
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(PacketError.Condition.jid_malformed);
// Send the error directly since a route does not exist at this point.
session.process(reply);
return null;
}
}
// Get the token that was generated during the SASL authentication
AuthToken authToken = session.getAuthToken();
if (authToken == null) {
// User must be authenticated before binding a resource
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(PacketError.Condition.not_authorized);
// Send the error directly since a route does not exist at this point.
session.process(reply);
return reply;
}
if (authToken.isAnonymous()) {
// User used ANONYMOUS SASL so initialize the session as an anonymous login
session.setAnonymousAuth();
}
else {
String username = authToken.getUsername().toLowerCase();
// If a session already exists with the requested JID, then check to see
// if we should kick it off or refuse the new connection
ClientSession oldSession = routingTable.getClientRoute(new JID(username, serverName, resource, true));
if (oldSession != null) {
try {
int conflictLimit = sessionManager.getConflictKickLimit();
if (conflictLimit == SessionManager.NEVER_KICK) {
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(PacketError.Condition.conflict);
// Send the error directly since a route does not exist at this point.
session.process(reply);
return null;
}
int conflictCount = oldSession.incrementConflictCount();
if (conflictCount > conflictLimit) {
// Kick out the old connection that is conflicting with the new one
StreamError error = new StreamError(StreamError.Condition.conflict);
oldSession.deliverRawText(error.toXML());
oldSession.close();
}
else {
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(PacketError.Condition.conflict);
// Send the error directly since a route does not exist at this point.
session.process(reply);
return null;
}
}
catch (Exception e) {
Log.error("Error during login", e);
}
}
// If the connection was not refused due to conflict, log the user in
session.setAuthToken(authToken, resource);
}
child.addElement("jid").setText(session.getAddress().toString());
// Send the response directly since a route does not exist at this point.
session.process(reply);
// After the client has been informed, inform all listeners as well.
SessionEventDispatcher.dispatchEvent(session, SessionEventDispatcher.EventType.resource_bound);
return null;
}
@Override
public void initialize(XMPPServer server) {
super.initialize(server);
routingTable = server.getRoutingTable();
serverName = server.getServerInfo().getXMPPDomain();
}
@Override
public IQHandlerInfo getInfo() {
return info;
}
}