All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hierynomus.smbj.connection.Connection Maven / Gradle / Ivy

/*
 * Copyright (C)2016 - SMBJ Contributors
 *
 * 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 com.hierynomus.smbj.connection;

import static com.hierynomus.mssmb2.SMB2Packet.SINGLE_CREDIT_PAYLOAD_SIZE;
import static java.lang.String.format;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import com.hierynomus.mssmb.SMB1PacketFactory;
import com.hierynomus.mssmb2.*;
import com.hierynomus.mssmb2.messages.SMB2Cancel;
import com.hierynomus.protocol.commons.buffer.Buffer;
import com.hierynomus.protocol.commons.concurrent.CancellableFuture;
import com.hierynomus.protocol.commons.concurrent.Futures;
import com.hierynomus.protocol.transport.PacketFactory;
import com.hierynomus.protocol.transport.PacketHandlers;
import com.hierynomus.protocol.transport.PacketReceiver;
import com.hierynomus.protocol.transport.TransportException;
import com.hierynomus.protocol.transport.TransportLayer;
import com.hierynomus.smb.SMBPacket;
import com.hierynomus.smb.SMBPacketData;
import com.hierynomus.smbj.SMBClient;
import com.hierynomus.smbj.SmbConfig;
import com.hierynomus.smbj.auth.AuthenticationContext;
import com.hierynomus.smbj.common.Pooled;
import com.hierynomus.smbj.connection.packet.DeadLetterPacketHandler;
import com.hierynomus.smbj.connection.packet.IncomingPacketHandler;
import com.hierynomus.smbj.connection.packet.SMB1PacketHandler;
import com.hierynomus.smbj.connection.packet.SMB2AsyncResponsePacketHandler;
import com.hierynomus.smbj.connection.packet.SMB2CompoundedPacketHandler;
import com.hierynomus.smbj.connection.packet.SMB2CreditGrantingPacketHandler;
import com.hierynomus.smbj.connection.packet.SMB2IsOutstandingPacketHandler;
import com.hierynomus.smbj.connection.packet.SMB2ProcessResponsePacketHandler;
import com.hierynomus.smbj.connection.packet.SMB2SignatureVerificationPacketHandler;
import com.hierynomus.smbj.connection.packet.SMB3DecryptingPacketHandler;
import com.hierynomus.smbj.event.ConnectionClosed;
import com.hierynomus.smbj.event.SMBEventBus;
import com.hierynomus.smbj.event.SessionLoggedOff;
import com.hierynomus.smbj.paths.DFSPathResolver;
import com.hierynomus.smbj.paths.PathResolver;
import com.hierynomus.smbj.paths.SymlinkPathResolver;
import com.hierynomus.smbj.server.ServerList;
import com.hierynomus.smbj.session.Session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.engio.mbassy.listener.Handler;

/**
 * A connection to a server.
 */
public class Connection extends Pooled implements Closeable, PacketReceiver> {
    private static final Logger logger = LoggerFactory.getLogger(Connection.class);
    private static final DelegatingSMBMessageConverter converter = new DelegatingSMBMessageConverter(new SMB3EncryptedPacketFactory(), new SMB3CompressedPacketFactory(), new SMB2PacketFactory(), new SMB1PacketFactory());
    private IncomingPacketHandler packetHandlerChain;

    private ConnectionContext connectionContext;
    private SessionTable sessionTable = new SessionTable();
    private SessionTable preauthSessionTable = new SessionTable();
    OutstandingRequests outstandingRequests = new OutstandingRequests();
    SequenceWindow sequenceWindow;
    private SMB2MessageConverter smb2Converter = new SMB2MessageConverter();
    private PathResolver pathResolver;

    private final SMBClient client;
    final ServerList serverList;

    private PacketSignatory signatory;
    private PacketEncryptor encryptor;

    public SMBClient getClient() {
        return client;
    }

    private SmbConfig config;
    TransportLayer> transport;
    private final SMBEventBus bus;
    private final ReentrantLock lock = new ReentrantLock();

    public Connection(SmbConfig config, SMBClient client, SMBEventBus bus, ServerList serverList) {
        this.config = config;
        this.client = client;
        this.transport = config.getTransportLayerFactory().createTransportLayer(new PacketHandlers<>(new SMBPacketSerializer(), this, converter), config);
        this.bus = bus;
        this.serverList = serverList;
        init();
    }

    private void init() {
        bus.subscribe(this);
        this.sequenceWindow = new SequenceWindow();
        this.signatory = new PacketSignatory(config.getSecurityProvider());
        this.encryptor = new PacketEncryptor(config.getSecurityProvider());

        this.packetHandlerChain = new SMB3DecryptingPacketHandler(sessionTable, encryptor).setNext(
            new SMB2CompoundedPacketHandler().setNext(
                new SMB2IsOutstandingPacketHandler(outstandingRequests).setNext(
                    new SMB2SignatureVerificationPacketHandler(sessionTable, signatory).setNext(
                        new SMB2CreditGrantingPacketHandler(sequenceWindow).setNext(
                            new SMB2AsyncResponsePacketHandler(outstandingRequests).setNext(
                                new SMB2ProcessResponsePacketHandler(smb2Converter, outstandingRequests).setNext(
                                    new SMB1PacketHandler().setNext(new DeadLetterPacketHandler()))))))));
    }

    public Connection(Connection connection) {
        this.client = connection.client;
        this.config = connection.config;
        this.transport = connection.transport;
        this.bus = connection.bus;
        this.serverList = connection.serverList;
        init();
    }

    public void connect(String hostname, int port) throws IOException {
        if (isConnected()) {
            throw new IllegalStateException(format("This connection is already connected to %s", getRemoteHostname()));
        }
        transport.connect(new InetSocketAddress(hostname, port));
        this.connectionContext = new ConnectionContext(config.getClientGuid(), hostname, port, config);
        new SMBProtocolNegotiator(this, config, connectionContext).negotiateDialect();
        this.signatory.init();
        this.encryptor.init(connectionContext);

        this.pathResolver = new SymlinkPathResolver(PathResolver.LOCAL);
        if (config.isDfsEnabled() && connectionContext.supportsDFS()) {
            this.pathResolver = new DFSPathResolver(pathResolver, config.getTransactTimeout());
        }

        logger.info("Successfully connected to: {}", getRemoteHostname());
    }

    @Override
    public void close() throws IOException {
        close(false);
    }

    /**
     * Close the Connection. If {@code force} is set to true, it forgoes the
     * {@link Session#close()} operation on the open sessions, and it just calls the
     * {@link TransportLayer#disconnect()}.
     *
     * 

* If {@code force} is set to false, the usage counter of the connection reduces with one. If the usage count drops to zero the connection will be closed. *

* * @param force if set, does not nicely terminate the open sessions. * @throws IOException If any error occurred during close-ing. */ public void close(boolean force) throws IOException { if (!force && !release()) { return; } try { if (!force) { for (Session session : sessionTable.activeSessions()) { try { session.close(); } catch (IOException e) { logger.warn("Exception while closing session {}", session.getSessionId(), e); } } } } finally { transport.disconnect(); logger.info("Closed connection to {}", getRemoteHostname()); bus.publish(new ConnectionClosed(connectionContext.getServer().getServerName(), connectionContext.getServer().getPort())); } } /** * Authenticate the user on this connection in order to start a (new) session. * * @return a (new) Session that is authenticated for the user. */ public Session authenticate(AuthenticationContext authContext) { return new SMBSessionBuilder(this, config, new SMBSessionBuilder.SessionFactory() { @Override public Session createSession(AuthenticationContext context) { return new Session(Connection.this, config, context, bus, pathResolver, signatory, encryptor); } }).establish(authContext); } /** * send a packet. * * @param packet SMBPacket to send * @return a Future to be used to retrieve the response packet * @throws TransportException When a transport level error occurred */ public Future send(SMB2Packet packet) throws TransportException { lock.lock(); Future f = null; try { if (!(packet.getPacket() instanceof SMB2Cancel)) { int availableCredits = sequenceWindow.available(); int grantCredits = calculateGrantedCredits(packet, availableCredits); if (availableCredits == 0) { logger.warn( "There are no credits left to send {}, will block until there are more credits available.", packet.getHeader().getMessage()); } long[] messageIds = sequenceWindow.get(grantCredits); packet.getHeader().setMessageId(messageIds[0]); logger.debug("Granted {} (out of {}) credits to {}", grantCredits, availableCredits, packet); packet.getHeader().setCreditRequest(Math .max(SequenceWindow.PREFERRED_MINIMUM_CREDITS - availableCredits - grantCredits, grantCredits)); Request request = new Request(packet.getPacket(), messageIds[0], UUID.randomUUID()); outstandingRequests.registerOutstanding(request); f = request.getFuture(new CancelRequest(request, packet.getHeader().getSessionId())); } transport.write(packet); return f; } finally { lock.unlock(); } } T sendAndReceive(SMB2Packet packet) throws TransportException { return Futures.get(this.send(packet), config.getTransactTimeout(), TimeUnit.MILLISECONDS, TransportException.Wrapper); } private int calculateGrantedCredits(final SMB2Packet packet, final int availableCredits) { final int grantCredits; int maxPayloadSize = packet.getMaxPayloadSize(); int creditsNeeded = creditsNeeded(maxPayloadSize); if (creditsNeeded > 1 && !connectionContext.supportsMultiCredit()) { logger.trace("Connection to {} does not support multi-credit requests.", getRemoteHostname()); grantCredits = 1; } else if (creditsNeeded < availableCredits) { // Scale the credits dynamically grantCredits = creditsNeeded; } else if (creditsNeeded > 1 && availableCredits > 1) { // creditsNeeded >= availableCredits grantCredits = availableCredits - 1; // Keep 1 credit left for a simple request } else { grantCredits = 1; } packet.setCreditsAssigned(grantCredits); return grantCredits; } /** * [MS-SMB2] 3.1.5.2 Calculating the CreditCharge */ private int creditsNeeded(int payloadSize) { return Math.abs((payloadSize - 1) / SINGLE_CREDIT_PAYLOAD_SIZE) + 1; } /** * Returns the negotiated protocol details for this connection. * * @return The negotiated protocol details */ public NegotiatedProtocol getNegotiatedProtocol() { return connectionContext.getNegotiatedProtocol(); } @Override public void handle(SMBPacketData uncheckedPacket) throws TransportException { this.packetHandlerChain.handle(uncheckedPacket); // [MS-SMB2] 3.2.5.1.6 Handling Session Expiration // if (packet.getHeader().getStatus() == NtStatus.STATUS_NETWORK_SESSION_EXPIRED) { // TODO reauthenticate session! // } } @Override public void handleError(Throwable t) { outstandingRequests.handleError(t); try { this.close(); } catch (Exception e) { String exceptionClass = e.getClass().getSimpleName(); logger.debug("{} while closing connection on error, ignoring: {}", exceptionClass, e.getMessage()); } } public String getRemoteHostname() { return connectionContext.getServer().getServerName(); } public boolean isConnected() { return transport.isConnected(); } public ConnectionContext getConnectionContext() { return connectionContext; } @Handler @SuppressWarnings("unused") private void sessionLogoff(SessionLoggedOff loggedOff) { sessionTable.removeSession(loggedOff.getSessionId()); logger.debug("Session << {} >> logged off", loggedOff.getSessionId()); } private static class DelegatingSMBMessageConverter implements PacketFactory> { private PacketFactory[] packetFactories; public DelegatingSMBMessageConverter(PacketFactory... packetFactories) { this.packetFactories = packetFactories; } @Override public SMBPacketData read(byte[] data) throws Buffer.BufferException, IOException { for (PacketFactory packetFactory : packetFactories) { if (packetFactory.canHandle(data)) { return (SMBPacketData) packetFactory.read(data); } } throw new IOException("Unknown packet format received."); } @Override public boolean canHandle(byte[] data) { for (PacketFactory packetFactory : packetFactories) { if (packetFactory.canHandle(data)) { return true; } } return false; } } private class CancelRequest implements CancellableFuture.CancelCallback { private Request request; private long sessionId; public CancelRequest(Request request, long sessionId) { this.request = request; this.sessionId = sessionId; } /** * [MS-SMB2] 3.2.4.24 Application Requests Canceling an Operation */ @Override public void cancel() { SMB2Cancel cancel = new SMB2Cancel(connectionContext.getNegotiatedProtocol().getDialect(), sessionId, request.getMessageId(), request.getAsyncId()); try { sessionTable.find(sessionId).send(cancel); // transport.write(cancel); } catch (TransportException e) { logger.error("Failed to send {}", cancel); } } } SessionTable getSessionTable() { return sessionTable; } SessionTable getPreauthSessionTable() { return preauthSessionTable; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy