LocalSession.java 15.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright (C) 2004-2009 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.
 */

Gaston Dombiak's avatar
Gaston Dombiak committed
17 18
package org.jivesoftware.openfire.session;

19
import java.net.UnknownHostException;
20
import java.security.cert.Certificate;
21 22 23 24
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

25 26
import javax.net.ssl.SSLSession;

27
import org.dom4j.Element;
Gaston Dombiak's avatar
Gaston Dombiak committed
28 29 30
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID;
31
import org.jivesoftware.openfire.XMPPServer;
Gaston Dombiak's avatar
Gaston Dombiak committed
32 33 34
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.interceptor.InterceptorManager;
import org.jivesoftware.openfire.interceptor.PacketRejectedException;
35 36
import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.net.TLSStreamHandler;
37
import org.jivesoftware.openfire.streammanagement.StreamManager;
Gaston Dombiak's avatar
Gaston Dombiak committed
38
import org.jivesoftware.util.LocaleUtils;
39 40
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
41
import org.xmpp.packet.*;
Gaston Dombiak's avatar
Gaston Dombiak committed
42 43 44 45 46 47 48 49 50 51 52 53 54

/**
 * The session represents a connection between the server and a client (c2s) or
 * another server (s2s) as well as a connection with a component. Authentication and
 * user accounts are associated with c2s connections while s2s has an optional authentication
 * association but no single user user.<p>
 *
 * Obtain object managers from the session in order to access server resources.
 *
 * @author Gaston Dombiak
 */
public abstract class LocalSession implements Session {

55 56
	private static final Logger Log = LoggerFactory.getLogger(LocalSession.class);

Gaston Dombiak's avatar
Gaston Dombiak committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
    /**
     * The utf-8 charset for decoding and encoding Jabber packet streams.
     */
    protected static String CHARSET = "UTF-8";

    /**
     * The Address this session is authenticated as.
     */
    private JID address;

    /**
     * The stream id for this session (random and unique).
     */
    private StreamID streamID;

    /**
     * The current session status.
     */
    protected int status = STATUS_CONNECTED;

    /**
     * The connection that this session represents.
     */
80
    protected final Connection conn;
Gaston Dombiak's avatar
Gaston Dombiak committed
81 82 83

    protected SessionManager sessionManager;

Daryl Herzmann's avatar
Daryl Herzmann committed
84
    private String serverName;
Gaston Dombiak's avatar
Gaston Dombiak committed
85

86
    private long startDate = System.currentTimeMillis();
Gaston Dombiak's avatar
Gaston Dombiak committed
87 88 89 90 91 92 93 94 95

    private long lastActiveDate;
    private long clientPacketCount = 0;
    private long serverPacketCount = 0;

    /**
	 * Session temporary data. All data stored in this <code>Map</code> disapear when session
	 * finishes.
	 */
96
	private final Map<String, Object> sessionData = new HashMap<>();
Gaston Dombiak's avatar
Gaston Dombiak committed
97

98 99 100
    /**
     * XEP-0198 Stream Manager
     */
101
    protected final StreamManager streamManager;
102

Gaston Dombiak's avatar
Gaston Dombiak committed
103 104 105 106 107 108 109 110
    /**
     * Creates a session with an underlying connection and permission protection.
     *
     * @param serverName domain of the XMPP server where the new session belongs.
     * @param connection The connection we are proxying.
     * @param streamID unique identifier for this session.
     */
    public LocalSession(String serverName, Connection connection, StreamID streamID) {
111 112 113
        if (connection == null) {
            throw new IllegalArgumentException("connection must not be null");
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
114 115 116 117 118 119
        conn = connection;
        this.streamID = streamID;
        this.serverName = serverName;
        String id = streamID.getID();
        this.address = new JID(null, serverName, id, true);
        this.sessionManager = SessionManager.getInstance();
120
        this.streamManager = new StreamManager(conn);
Gaston Dombiak's avatar
Gaston Dombiak committed
121 122 123 124 125 126 127 128 129 130
    }

    /**
      * Obtain the address of the user. The address is used by services like the core
      * server packet router to determine if a packet should be sent to the handler.
      * Handlers that are working on behalf of the server should use the generic server
      * hostname address (e.g. server.com).
      *
      * @return the address of the packet handler.
      */
131
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
    public JID getAddress() {
        return address;
    }

    /**
     * Sets the new address of this session. The address is used by services like the core
     * server packet router to determine if a packet should be sent to the handler.
     * Handlers that are working on behalf of the server should use the generic server
     * hostname address (e.g. server.com).
     *
     * @param address the new address of this session.
     */
    public void setAddress(JID address){
        this.address = address;
    }

    /**
     * Returns the connection associated with this Session.
     *
     * @return The connection for this session
     */
    public Connection getConnection() {
        return conn;
    }

    /**
     * Obtain the current status of this session.
     *
     * @return The status code for this session
     */
162
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
    public int getStatus() {
        return status;
    }

    /**
     * Set the new status of this session. Setting a status may trigger
     * certain events to occur (setting a closed status will close this
     * session).
     *
     * @param status The new status code for this session
     */
    public void setStatus(int status) {
        this.status = status;
    }

    /**
     * Obtain the stream ID associated with this sesison. Stream ID's are generated by the server
     * and should be unique and random.
     *
     * @return This session's assigned stream ID
     */
184
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
185 186 187 188 189 190 191 192 193
    public StreamID getStreamID() {
        return streamID;
    }

    /**
     * Obtain the name of the server this session belongs to.
     *
     * @return the server name.
     */
194
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
195 196 197 198 199 200 201 202 203
    public String getServerName() {
        return serverName;
    }

    /**
     * Obtain the date the session was created.
     *
     * @return the session's creation date.
     */
204
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
205
    public Date getCreationDate() {
206
        return new Date(startDate);
Gaston Dombiak's avatar
Gaston Dombiak committed
207 208 209 210 211 212 213
    }

    /**
     * Obtain the time the session last had activity.
     *
     * @return The last time the session received activity.
     */
214
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
215 216 217 218 219 220 221 222 223 224
    public Date getLastActiveDate() {
        return new Date(lastActiveDate);
    }

    /**
     * Increments the number of packets sent from the client to the server.
     */
    public void incrementClientPacketCount() {
        clientPacketCount++;
        lastActiveDate = System.currentTimeMillis();
225
        streamManager.incrementServerProcessedStanzas();
Gaston Dombiak's avatar
Gaston Dombiak committed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
    }

    /**
     * Increments the number of packets sent from the server to the client.
     */
    public void incrementServerPacketCount() {
        serverPacketCount++;
        lastActiveDate = System.currentTimeMillis();
    }

    /**
     * Obtain the number of packets sent from the client to the server.
     *
     * @return The number of packets sent from the client to the server.
     */
241
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
242 243 244 245 246 247 248 249 250
    public long getNumClientPackets() {
        return clientPacketCount;
    }

    /**
     * Obtain the number of packets sent from the server to the client.
     *
     * @return The number of packets sent from the server to the client.
     */
251
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
252 253 254 255 256 257 258 259 260 261 262 263 264
    public long getNumServerPackets() {
        return serverPacketCount;
    }

    /**
	 * Saves given session data. Data are saved to temporary storage only and are accessible during
	 * this session life only and only from this session instance.
	 *
	 * @param key a <code>String</code> value of stored data key ID.
	 * @param value a <code>Object</code> value of data stored in session.
	 * @see #getSessionData(String)
	 */
	public void setSessionData(String key, Object value) {
265 266 267 268
        synchronized (sessionData) {
            sessionData.put(key, value);
        }
    }
Gaston Dombiak's avatar
Gaston Dombiak committed
269 270 271 272 273 274 275 276 277 278 279

	/**
	 * Retrieves session data. This method gives access to temporary session data only. You can
	 * retrieve earlier saved data giving key ID to receive needed value. Please see
	 * {@link #setSessionData(String, Object)}  description for more details.
	 *
	 * @param key a <code>String</code> value of stored data ID.
	 * @return a <code>Object</code> value of data for given key.
	 * @see #setSessionData(String, Object)
	 */
	public Object getSessionData(String key) {
280 281 282 283
        synchronized (sessionData) {
    		return sessionData.get(key);
        }
    }
Gaston Dombiak's avatar
Gaston Dombiak committed
284 285 286 287 288 289 290 291 292

    /**
     * Removes session data. Please see {@link #setSessionData(String, Object)} description
     * for more details.
     *
     * @param key a <code>String</code> value of stored data ID.
     * @see #setSessionData(String, Object)
     */
    public void removeSessionData(String key) {
293 294 295
        synchronized (sessionData) {
            sessionData.remove(key);
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
296 297
    }

298 299 300 301 302 303 304 305
    /**
     * Get XEP-0198 Stream manager for session
     * @return
     */
    public StreamManager getStreamManager() {
    	return streamManager;
    }

306
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    public void process(Packet packet) {
        // Check that the requested packet can be processed
        if (canProcess(packet)) {
            // Perform the actual processing of the packet. This usually implies sending
            // the packet to the entity
            try {
                // Invoke the interceptors before we send the packet
                InterceptorManager.getInstance().invokeInterceptors(packet, this, false, false);
                deliver(packet);
                // Invoke the interceptors after we have sent the packet
                InterceptorManager.getInstance().invokeInterceptors(packet, this, false, true);
            }
            catch (PacketRejectedException e) {
                // An interceptor rejected the packet so do nothing
            }
            catch (Exception e) {
                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            }
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
        } else {
            // http://xmpp.org/extensions/xep-0016.html#protocol-error
            if (packet instanceof Message) {
                // For message stanzas, the server SHOULD return an error, which SHOULD be <service-unavailable/>.
                Message message = (Message) packet;
                Message result = message.createCopy();
                result.setTo(message.getFrom());
                result.setError(PacketError.Condition.service_unavailable);
                XMPPServer.getInstance().getRoutingTable().routePacket(message.getFrom(), result, true);
            } else if (packet instanceof IQ) {
                // For IQ stanzas of type "get" or "set", the server MUST return an error, which SHOULD be <service-unavailable/>.
                // IQ stanzas of other types MUST be silently dropped by the server.
                IQ iq = (IQ) packet;
                if (iq.getType() == IQ.Type.get || iq.getType() == IQ.Type.set) {
                    IQ result = IQ.createResultIQ(iq);
                    result.setError(PacketError.Condition.service_unavailable);
                    XMPPServer.getInstance().getRoutingTable().routePacket(iq.getFrom(), result, true);
                }
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
        }
    }

    /**
     * Returns true if the specified packet can be delivered to the entity. Subclasses will use different
     * criterias to determine of processing is allowed or not. For instance, client sessions will use
     * privacy lists while outgoing server sessions will always allow this action.
     *
     * @param packet the packet to analyze if it must be blocked.
     * @return true if the specified packet must be blocked.
     */
    abstract boolean canProcess(Packet packet);

    abstract void deliver(Packet packet) throws UnauthorizedException;

359
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
360
    public void deliverRawText(String text) {
361
        conn.deliverRawText(text);
Gaston Dombiak's avatar
Gaston Dombiak committed
362 363 364 365 366 367 368 369 370 371
    }

    /**
     * Returns a text with the available stream features. Each subclass may return different
     * values depending whether the session has been authenticated or not.
     *
     * @return a text with the available stream features or <tt>null</tt> to add nothing.
     */
    public abstract String getAvailableStreamFeatures();

372
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
373
    public void close() {
374
        conn.close();
Gaston Dombiak's avatar
Gaston Dombiak committed
375 376
    }

377
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
378 379 380 381
    public boolean validate() {
        return conn.validate();
    }

382
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
383 384 385 386
    public boolean isSecure() {
        return conn.isSecure();
    }

387
    @Override
388 389 390 391
    public Certificate[] getPeerCertificates() {
        return conn.getPeerCertificates();
    }

392
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
393 394 395 396
    public boolean isClosed() {
        return conn.isClosed();
    }

397
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
398 399 400 401
    public String getHostAddress() throws UnknownHostException {
        return conn.getHostAddress();
    }

402
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
403 404 405 406
    public String getHostName() throws UnknownHostException {
        return conn.getHostName();
    }

407 408
    @Override
	public String toString() {
Gaston Dombiak's avatar
Gaston Dombiak committed
409 410 411 412 413 414 415 416 417 418
        return super.toString() + " status: " + status + " address: " + address + " id: " + streamID;
    }

    protected static int[] decodeVersion(String version) {
        int[] answer = new int[] {0 , 0};
        String [] versionString = version.split("\\.");
        answer[0] = Integer.parseInt(versionString[0]);
        answer[1] = Integer.parseInt(versionString[1]);
        return answer;
    }
419 420 421 422 423 424 425 426 427 428 429 430

    /**
     * Returns true if the other peer of this session presented a self-signed certificate. When
     * using self-signed certificate for server-2-server sessions then SASL EXTERNAL will not be
     * used and instead server-dialback will be preferred for vcerifying the identify of the remote
     * server.
     *
     * @return true if the other peer of this session presented a self-signed certificate.
     */
    public boolean isUsingSelfSignedCertificate() {
        return conn.isUsingSelfSignedCertificate();
    }
431 432 433 434 435

    /**
     * Returns a String representing the Cipher Suite Name, or "NONE".
     * @return String
     */
436
    @Override
437 438 439 440 441 442 443 444 445 446 447 448 449
    public String getCipherSuiteName() {
        SocketConnection s = (SocketConnection)getConnection();
        if (s != null) {
            TLSStreamHandler t = s.getTLSStreamHandler();
            if (t != null) {
                SSLSession ssl = t.getSSLSession();
                if (ssl != null) {
                    return ssl.getCipherSuite();
                }
            }
        }
        return "NONE";
    }
450 451 452

    /**
     * Enables stream management for session
453
     * @param enable XEP-0198 <enable/> element
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
     */
    public void enableStreamMangement(Element enable) {

    	// Do nothing if already enabled
    	if(streamManager.isEnabled()) {
    		return;
    	}

		streamManager.setNamespace(enable.getNamespace().getStringValue());

    	// Ensure that resource binding has occurred
    	if(getAddress().getResource() == null) {
    		streamManager.sendUnexpectedError();
    		return;
    	}

    	streamManager.setEnabled(true);
	}

Gaston Dombiak's avatar
Gaston Dombiak committed
473
}