ComponentSession.java 9.27 KB
Newer Older
Gaston Dombiak's avatar
Gaston Dombiak committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */
package org.jivesoftware.messenger;

import org.xmpp.packet.Packet;
import org.xmpp.packet.JID;
15
import org.xmpp.packet.StreamError;
16 17
import org.xmpp.component.*;
import org.xmpp.component.ComponentManager;
Gaston Dombiak's avatar
Gaston Dombiak committed
18 19 20 21
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.auth.AuthFactory;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.LocaleUtils;
22
import org.jivesoftware.util.JiveGlobals;
Gaston Dombiak's avatar
Gaston Dombiak committed
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
import org.dom4j.io.XPPPacketReader;
import org.dom4j.Element;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.Writer;
import java.io.IOException;

/**
 * Represents a session between the server and a component.
 *
 * @author Gaston Dombiak
 */
public class ComponentSession extends Session {

    private ExternalComponent component = new ExternalComponent();

    /**
     * Returns a newly created session between the server and a component. The session will be
42
     * created and returned only if all the checkings were correct.<p>
Gaston Dombiak's avatar
Gaston Dombiak committed
43 44
     *
     * A domain will be binded for the new connecting component. This method is following
45
     * the JEP-114 where the domain to bind is sent in the TO attribute of the stream header.
Gaston Dombiak's avatar
Gaston Dombiak committed
46 47
     *
     * @param serverName the name of the server where the session is connecting to.
48
     * @param reader     the reader that is reading the provided XML through the connection.
Gaston Dombiak's avatar
Gaston Dombiak committed
49 50 51 52 53
     * @param connection the connection with the component.
     * @return a newly created session between the server and a component.
     */
    public static Session createSession(String serverName, XPPPacketReader reader,
            Connection connection) throws UnauthorizedException, IOException,
54 55
            XmlPullParserException
    {
Gaston Dombiak's avatar
Gaston Dombiak committed
56 57 58 59
        XmlPullParser xpp = reader.getXPPParser();
        Session session;
        String domain = xpp.getAttributeValue("", "to");

60 61 62 63 64 65 66
        // Get the requested subdomain
        String subdomain = domain;
        int index = domain.indexOf(serverName);
        if (index > -1) {
            subdomain = domain.substring(0, index -1);
        }

Gaston Dombiak's avatar
Gaston Dombiak committed
67 68
        Writer writer = connection.getWriter();
        // Default answer header in case of an error
69
        StringBuilder sb = new StringBuilder();
Gaston Dombiak's avatar
Gaston Dombiak committed
70 71 72 73 74 75 76 77 78 79 80 81
        sb.append("<?xml version='1.0' encoding='");
        sb.append(CHARSET);
        sb.append("'?>");
        sb.append("<stream:stream ");
        sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
        sb.append("xmlns=\"jabber:component:accept\" from=\"");
        sb.append(domain);
        sb.append("\">");

        // Check that a domain was provided in the stream header
        if (domain == null) {
            // Include the bad-format in the response
82 83
            StreamError error = new StreamError(StreamError.Condition.bad_format);
            sb.append(error.toXML());
Gaston Dombiak's avatar
Gaston Dombiak committed
84 85 86 87 88 89 90 91 92 93 94 95
            sb.append("</stream:stream>");
            writer.write(sb.toString());
            writer.flush();
            // Close the underlying connection
            connection.close();
            return null;
        }
        // Check that a secret key was configured in the server
        // TODO Configure the secret key in the Admin Console
        String secretKey = JiveGlobals.getProperty("component.external.secretKey");
        if (secretKey == null) {
            Log.error("Setup for external components is incomplete. Property " +
96
                    "component.external.secretKey does not exist.");
Gaston Dombiak's avatar
Gaston Dombiak committed
97
            // Include the internal-server-error in the response
98 99
            StreamError error = new StreamError(StreamError.Condition.internal_server_error);
            sb.append(error.toXML());
Gaston Dombiak's avatar
Gaston Dombiak committed
100 101 102 103 104 105 106
            sb.append("</stream:stream>");
            writer.write(sb.toString());
            writer.flush();
            // Close the underlying connection
            connection.close();
            return null;
        }
107 108
        // Check that the requested subdomain is not already in use
        if (InternalComponentManager.getInstance().getComponent(subdomain) != null) {
Gaston Dombiak's avatar
Gaston Dombiak committed
109 110
            // Domain already occupied so return a conflict error and close the connection
            // Include the conflict error in the response
111 112
            StreamError error = new StreamError(StreamError.Condition.conflict);
            sb.append(error.toXML());
Gaston Dombiak's avatar
Gaston Dombiak committed
113 114 115 116 117 118 119 120 121 122 123
            sb.append("</stream:stream>");
            writer.write(sb.toString());
            writer.flush();
            // Close the underlying connection
            connection.close();
            return null;
        }

        // Create a ComponentSession for the external component
        session = SessionManager.getInstance().createComponentSession(connection);
        // Set the bind address as the address of the session
124
        session.setAddress(new JID(null, domain , null));
Gaston Dombiak's avatar
Gaston Dombiak committed
125 126 127

        try {
            // Build the start packet response
128
            sb = new StringBuilder();
Gaston Dombiak's avatar
Gaston Dombiak committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
            sb.append("<?xml version='1.0' encoding='");
            sb.append(CHARSET);
            sb.append("'?>");
            sb.append("<stream:stream ");
            sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
            sb.append("xmlns=\"jabber:component:accept\" from=\"");
            sb.append(domain);
            sb.append("\" id=\"");
            sb.append(session.getStreamID().toString());
            sb.append("\">");
            writer.write(sb.toString());
            writer.flush();

            // Perform authentication. Wait for the handshake (with the secret key)
            Element doc = reader.parseDocument().getRootElement();
            String digest = "handshake".equals(doc.getName()) ? doc.getStringValue() : "";
            String anticipatedDigest = AuthFactory.createDigest(session.getStreamID().getID(),
                    secretKey);
147
            // Check that the provided handshake (secret key + sessionID) is correct
Gaston Dombiak's avatar
Gaston Dombiak committed
148 149 150
            if (!anticipatedDigest.equalsIgnoreCase(digest)) {
                //  The credentials supplied by the initiator are not valid (answer an error
                // and close the connection)
151
                sb = new StringBuilder();
Gaston Dombiak's avatar
Gaston Dombiak committed
152
                // Include the conflict error in the response
153 154
                StreamError error = new StreamError(StreamError.Condition.not_authorized);
                sb.append(error.toXML());
Gaston Dombiak's avatar
Gaston Dombiak committed
155 156 157 158 159 160 161 162 163 164 165 166 167
                sb.append("</stream:stream>");
                writer.write(sb.toString());
                writer.flush();
                // Close the underlying connection
                connection.close();
                return null;
            }
            else {
                // Component has authenticated fine
                session.setStatus(Session.STATUS_AUTHENTICATED);
                // Send empty handshake element to acknowledge success
                writer.write("<handshake></handshake>");
                writer.flush();
168 169
                // Bind the domain to this component
                ExternalComponent component = ((ComponentSession) session).getExternalComponent();
170
                InternalComponentManager.getInstance().addComponent(subdomain, component);
Gaston Dombiak's avatar
Gaston Dombiak committed
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
                return session;
            }
        }
        catch (Exception e) {
            Log.error("An error occured while creating a ComponentSession", e);
            // Close the underlying connection
            connection.close();
            return null;
        }
    }

    public ComponentSession(String serverName, Connection conn, StreamID id) {
        super(serverName, conn, id);
    }

186
    public void process(Packet packet) throws PacketException {
Gaston Dombiak's avatar
Gaston Dombiak committed
187 188
        // Since ComponentSessions are not being stored in the RoutingTable this messages is very
        // unlikely to be sent
189
        component.processPacket(packet);
Gaston Dombiak's avatar
Gaston Dombiak committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
    }

    public ExternalComponent getExternalComponent() {
        return component;
    }

    /**
     * The ExternalComponent acts as a proxy of the remote connected component. Any Packet that is
     * sent to this component will be delivered to the real component on the other side of the
     * connection.<p>
     *
     * An ExternalComponent will be added as a route in the RoutingTable for each connected
     * external component. This implies that when the server receives a packet whose domain matches
     * the external component services address then a route to the external component will be used
     * and the packet will be forwarded to the component on the other side of the connection.
     */
    public class ExternalComponent implements Component {

208
        public void processPacket(Packet packet) {
Gaston Dombiak's avatar
Gaston Dombiak committed
209 210 211 212 213
            if (conn != null && !conn.isClosed()) {
                try {
                    conn.deliver(packet);
                }
                catch (Exception e) {
214
                    Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
215
                    conn.close();
Gaston Dombiak's avatar
Gaston Dombiak committed
216 217 218 219
                }
            }
        }

220 221 222 223 224 225 226 227 228 229 230
        public String getName() {
            return null;
        }

        public String getDescription() {
            return null;
        }

        public void initialize(JID jid, ComponentManager componentManager) {
        }

231 232 233
        public void start() {
        }

234 235
        public void shutdown() {
        }
236
    }
237
}