IQBindHandler.java 7.55 KB
Newer Older
1
/*
2
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
3
 *
4 5 6 7 8 9 10 11 12 13 14
 * 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.
15 16
 */

17
package org.jivesoftware.openfire.handler;
18

19 20
import gnu.inet.encoding.StringprepException;

21
import org.dom4j.Element;
22
import org.jivesoftware.openfire.IQHandlerInfo;
23
import org.jivesoftware.openfire.RoutingTable;
24 25 26 27
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.UnauthorizedException;
28
import org.jivesoftware.openfire.event.SessionEventDispatcher;
29
import org.jivesoftware.openfire.session.ClientSession;
30
import org.jivesoftware.openfire.session.LocalClientSession;
31 32
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
33
import org.xmpp.packet.IQ;
34
import org.xmpp.packet.JID;
35 36 37 38 39
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
40 41
 * has been binded to the session the entity (i.e. client) is considered a "connected resource".
 * <p>
42 43 44 45
 * 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
46
 * may be kicked or the new session may be rejected.</p>
47 48 49 50 51
 *
 * @author Gaston Dombiak
 */
public class IQBindHandler extends IQHandler {

52 53
	private static final Logger Log = LoggerFactory.getLogger(IQBindHandler.class);

54
    private IQHandlerInfo info;
55 56
    private String serverName;
    private RoutingTable routingTable;
57 58 59 60 61 62

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

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

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

161 162
    @Override
	public void initialize(XMPPServer server) {
163
        super.initialize(server);
164
        routingTable = server.getRoutingTable();
165
        serverName = server.getServerInfo().getXMPPDomain();
166
     }
167

168 169
    @Override
	public IQHandlerInfo getInfo() {
170 171 172
        return info;
    }
}