IQBindHandler.java 7.59 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile$
 * $Revision: $
 * $Date: $
 *
6
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
7
 *
8 9 10 11 12 13 14 15 16 17 18
 * 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.
19 20
 */

21
package org.jivesoftware.openfire.handler;
22

23 24
import gnu.inet.encoding.StringprepException;

25
import org.dom4j.Element;
26
import org.jivesoftware.openfire.IQHandlerInfo;
27
import org.jivesoftware.openfire.RoutingTable;
28 29 30 31
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.UnauthorizedException;
32
import org.jivesoftware.openfire.event.SessionEventDispatcher;
33
import org.jivesoftware.openfire.session.ClientSession;
34
import org.jivesoftware.openfire.session.LocalClientSession;
35 36
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
37
import org.xmpp.packet.IQ;
38
import org.xmpp.packet.JID;
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
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 {

56 57
	private static final Logger Log = LoggerFactory.getLogger(IQBindHandler.class);

58
    private IQHandlerInfo info;
59 60
    private String serverName;
    private RoutingTable routingTable;
61 62 63 64 65 66

    public IQBindHandler() {
        super("Resource Binding handler");
        info = new IQHandlerInfo("bind", "urn:ietf:params:xml:ns:xmpp-bind");
    }

67 68
    @Override
	public IQ handleIQ(IQ packet) throws UnauthorizedException {
69
        LocalClientSession session = (LocalClientSession) sessionManager.getSession(packet.getFrom());
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
        // 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 {
94
                resource = JID.resourceprep(resource);
95 96 97 98 99 100 101 102 103
            }
            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;
            }
        }
104 105
        // Get the token that was generated during the SASL authentication
        AuthToken authToken = session.getAuthToken();
106 107 108 109 110 111 112 113
        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;
        }
114 115 116 117 118 119 120 121
        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
122
            ClientSession oldSession = routingTable.getClientRoute(new JID(username, serverName, resource, true));
123
            if (oldSession != null) {
124 125
                try {
                    int conflictLimit = sessionManager.getConflictKickLimit();
126 127 128 129 130 131 132 133 134 135
                    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) {
136 137 138 139
                        // 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();
140
                    }
141 142 143 144 145 146
                    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;
147 148
                    }
                }
149 150 151
                catch (Exception e) {
                    Log.error("Error during login", e);
                }
152
            }
153 154
            // If the connection was not refused due to conflict, log the user in
            session.setAuthToken(authToken, resource);
155 156 157 158 159
        }

        child.addElement("jid").setText(session.getAddress().toString());
        // Send the response directly since a route does not exist at this point.
        session.process(reply);
160
        // After the client has been informed, inform all listeners as well.
161
        SessionEventDispatcher.dispatchEvent(session, SessionEventDispatcher.EventType.resource_bound);
162 163 164
        return null;
    }

165 166
    @Override
	public void initialize(XMPPServer server) {
167
        super.initialize(server);
168
        routingTable = server.getRoutingTable();
169
        serverName = server.getServerInfo().getXMPPDomain();
170
     }
171

172 173
    @Override
	public IQHandlerInfo getInfo() {
174 175 176
        return info;
    }
}