LocalSession.java 16.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * 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.
 */

package org.jivesoftware.openfire.session;

import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
25
import java.util.concurrent.atomic.AtomicLong;
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

import javax.net.ssl.SSLSession;

import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.interceptor.InterceptorManager;
import org.jivesoftware.openfire.interceptor.PacketRejectedException;
import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.net.TLSStreamHandler;
import org.jivesoftware.openfire.streammanagement.StreamManager;
import org.jivesoftware.util.LocaleUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.*;

/**
 * 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 {

56
    private static final Logger Log = LoggerFactory.getLogger(LocalSession.class);
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

    /**
     * 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.
     */
    protected Connection conn;

    protected SessionManager sessionManager;

    private String serverName;

    private long startDate = System.currentTimeMillis();

    private long lastActiveDate;
90 91
    private AtomicLong clientPacketCount = new AtomicLong( 0 );
    private AtomicLong serverPacketCount = new AtomicLong( 0 );
92 93

    /**
94 95 96 97
     * Session temporary data. All data stored in this <code>Map</code> disapear when session
     * finishes.
     */
    private final Map<String, Object> sessionData = new HashMap<>();
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275

    /**
     * XEP-0198 Stream Manager
     */
    protected final StreamManager streamManager;

    private final Locale language;

    /**
     * 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.
     * @param language The language to use for this session.
     */
    public LocalSession(String serverName, Connection connection, StreamID streamID, Locale language) {
        if (connection == null) {
            throw new IllegalArgumentException("connection must not be null");
        }
        conn = connection;
        this.streamID = streamID;
        this.serverName = serverName;
        String id = streamID.getID();
        this.address = new JID(null, serverName, id, true);
        this.sessionManager = SessionManager.getInstance();
        this.streamManager = new StreamManager(this);
        this.language = language;
    }

    /**
     * Returns true if the session is detached (that is, if the underlying connection
     * has been closed.
     *
     * @return true if session detached
     */
    public boolean isDetached() {
        return this.conn == null;
    }

    /**
     * Set the session to detached mode, indicating that the underlying connection
     * has been closed.
     */
    public void setDetached() {
        this.sessionManager.addDetached(this);
        this.conn = null;
    }

    /**
     * Reattach the session to a new connection. The connection must already be
     * initialized as a running XML Stream, normally by having run through XEP-0198
     * resumption.
     */
    public void reattach(Connection connection, long h) {
        Connection temp = this.conn;
        this.conn = null;
        if (temp != null && !temp.isClosed()) {
            temp.close();
        }
        this.conn = connection;
        this.conn.reinit(this);
        this.status = STATUS_AUTHENTICATED;
        this.sessionManager.removeDetached(this);
        this.streamManager.onResume(new JID(null, this.serverName, null, true), h);
    }

    /**
      * 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.
      */
    @Override
    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() {
        if (conn == null) {
            try {
                conn.isClosed(); // This generates an NPE deliberately.
            } catch (NullPointerException e) {
                Log.error("Attempt to read connection of detached session: ", e);
            }
        }
        return conn;
    }

    /**
     * Obtain the current status of this session.
     *
     * @return The status code for this session
     */
    @Override
    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) {
        if (status == STATUS_CLOSED && this.streamManager.getResume()) {
            Log.debug("Suppressing close.");
            return;
        }
        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
     */
    @Override
    public StreamID getStreamID() {
        return streamID;
    }

    /**
     * Obtain the name of the server this session belongs to.
     *
     * @return the server name.
     */
    @Override
    public String getServerName() {
        return serverName;
    }

    /**
     * Obtain the date the session was created.
     *
     * @return the session's creation date.
     */
    @Override
    public Date getCreationDate() {
        return new Date(startDate);
    }

    /**
     * Obtain the time the session last had activity.
     *
     * @return The last time the session received activity.
     */
    @Override
    public Date getLastActiveDate() {
        return new Date(lastActiveDate);
    }

    /**
     * Increments the number of packets sent from the client to the server.
     */
    public void incrementClientPacketCount() {
276
        clientPacketCount.incrementAndGet();
277 278 279 280 281 282 283 284
        lastActiveDate = System.currentTimeMillis();
        streamManager.incrementServerProcessedStanzas();
    }

    /**
     * Increments the number of packets sent from the server to the client.
     */
    public void incrementServerPacketCount() {
285
        serverPacketCount.incrementAndGet();
286 287 288 289 290 291 292 293 294 295
        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.
     */
    @Override
    public long getNumClientPackets() {
296
        return clientPacketCount.get();
297 298 299 300 301 302 303 304 305
    }

    /**
     * Obtain the number of packets sent from the server to the client.
     *
     * @return The number of packets sent from the server to the client.
     */
    @Override
    public long getNumServerPackets() {
306
        return serverPacketCount.get();
307 308 309
    }

    /**
310 311 312 313 314 315 316 317
     * 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) {
318 319 320 321 322
        synchronized (sessionData) {
            sessionData.put(key, value);
        }
    }

323 324 325 326 327 328 329 330 331 332
    /**
     * 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) {
333
        synchronized (sessionData) {
334
            return sessionData.get(key);
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
        }
    }

    /**
     * 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) {
        synchronized (sessionData) {
            sessionData.remove(key);
        }
    }

    /**
     * Get XEP-0198 Stream manager for session
     * @return The StreamManager for the session.
     */
    public StreamManager getStreamManager() {
356
        return streamManager;
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
    }

    @Override
    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);
            }
        } 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);
                }
            }
        }
    }

    /**
     * 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;

    @Override
    public void deliverRawText(String text) {
414 415 416 417 418
        if ( conn == null )
        {
            Log.debug( "Unable to deliver raw text in session, as its connection is null. Dropping: " + text );
            return;
        }
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
        conn.deliverRawText(text);
    }

    /**
     * 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();

    @Override
    public void close() {
        if (conn == null) return;
        conn.close();
    }

    @Override
    public boolean validate() {
        return conn.validate();
    }

    @Override
    public boolean isSecure() {
        return conn.isSecure();
    }

    @Override
    public Certificate[] getPeerCertificates() {
        return conn.getPeerCertificates();
    }

    @Override
    public boolean isClosed() {
        return conn.isClosed();
    }

    @Override
    public String getHostAddress() throws UnknownHostException {
458 459 460
        if (conn == null) {
            throw new UnknownHostException("Detached session");
        }
461 462 463 464 465
        return conn.getHostAddress();
    }

    @Override
    public String getHostName() throws UnknownHostException {
466 467 468
        if (conn == null) {
            throw new UnknownHostException("Detached session");
        }
469 470 471 472
        return conn.getHostName();
    }

    @Override
473
    public String toString() {
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
        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;
    }

    /**
     * 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();
    }

    /**
     * Returns a String representing the Cipher Suite Name, or "NONE".
     * @return String
     */
    @Override
    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";
    }

    @Override
    public final Locale getLanguage() {
        return language;
    }
}