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

jcifs.smb.SmbTransport Maven / Gradle / Ivy

There is a newer version: 2.1.10
Show newest version
/* jcifs smb client library in Java
 * Copyright (C) 2005  "Michael B. Allen" 
 *                  "Eric Glass" 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package jcifs.smb;


import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import org.apache.log4j.Logger;

import jcifs.CIFSContext;
import jcifs.RuntimeCIFSException;
import jcifs.SmbConstants;
import jcifs.netbios.Name;
import jcifs.netbios.NbtException;
import jcifs.netbios.SessionRequestPacket;
import jcifs.netbios.SessionServicePacket;
import jcifs.netbios.UniAddress;
import jcifs.util.Encdec;
import jcifs.util.Hexdump;
import jcifs.util.transport.Request;
import jcifs.util.transport.Response;
import jcifs.util.transport.Transport;
import jcifs.util.transport.TransportException;


/**
 * 
 */
public class SmbTransport extends Transport implements SmbConstants {

    private static Logger log = Logger.getLogger(SmbTransport.class);

    class ServerData {

        byte sflags;
        int sflags2;
        int smaxMpxCount;
        int maxBufferSize;
        int sessKey;
        int scapabilities;
        String oemDomainName;
        int securityMode;
        int security;
        boolean encryptedPasswords;
        boolean signaturesEnabled;
        boolean signaturesRequired;
        int maxNumberVcs;
        int maxRawSize;
        long serverTime;
        int serverTimeZone;
        int encryptionKeyLength;
        byte[] encryptionKey;
        byte[] guid;
    }

    private final SmbComNegotiate NEGOTIATE_REQUEST;
    private boolean smb2 = false;
    InetAddress localAddr;
    int localPort;
    UniAddress address;
    Socket socket;
    int port, mid;
    OutputStream out;
    InputStream in;
    byte[] sbuf = new byte[512]; /* small local buffer */
    SmbComBlankResponse key;
    long sessionExpiration;
    SigningDigest digest = null;
    List sessions = new LinkedList<>();
    ServerData server = new ServerData();
    /* Negotiated values */
    int flags2;
    int maxMpxCount;
    int snd_buf_size;
    int rcv_buf_size;
    int capabilities;
    int sessionKey = 0x00000000;
    boolean useUnicode;
    String tconHostName = null;

    private CIFSContext transportContext;
    boolean signingEnforced;

    private Object socketLock = new Object();


    SmbTransport ( CIFSContext tc, SmbComNegotiate nego, UniAddress address, int port, InetAddress localAddr, int localPort, boolean forceSigning ) {
        this.transportContext = tc;
        this.key = new SmbComBlankResponse(tc.getConfig());
        this.NEGOTIATE_REQUEST = nego;
        this.flags2 = this.NEGOTIATE_REQUEST.flags2;
        this.signingEnforced = forceSigning || this.getTransportContext().getConfig().isSigningEnforced();
        this.sessionExpiration = System.currentTimeMillis() + tc.getConfig().getSessionTimeout();
        this.capabilities = tc.getConfig().getCapabilities();
        this.address = address;
        this.port = port;
        this.localAddr = localAddr;
        this.localPort = localPort;

        this.maxMpxCount = tc.getConfig().getMaxMpxCount();
        this.snd_buf_size = tc.getConfig().getSendBufferSize();
        this.rcv_buf_size = tc.getConfig().getRecieveBufferSize();
        this.useUnicode = tc.getConfig().isUseUnicode();
        setNoIdleTimeout(tc.getConfig().isIdleTimeoutDisabled());
    }


    SmbComNegotiate getNegotiateRequest () {
        return this.NEGOTIATE_REQUEST;
    }


    /**
     * @return the context associated with this transport connection
     */
    public CIFSContext getTransportContext () {
        return this.transportContext;
    }


    /**
     * @return the server's encryption key
     */
    public byte[] getServerEncryptionKey () {
        if ( this.server == null ) {
            return null;
        }
        return this.server.encryptionKey;
    }


    /**
     * 
     * @param tf
     *            context to use
     * @return a session for the context
     */
    public synchronized SmbSession getSmbSession ( CIFSContext tf ) {
        SmbSession ssn;
        long now;

        if ( log.isDebugEnabled() ) {
            log.debug("Currently " + this.sessions.size() + " session(s) active for " + this);
        }

        ListIterator iter = this.sessions.listIterator();
        while ( iter.hasNext() ) {
            ssn = iter.next();
            if ( ssn.matches(tf) ) {
                if ( log.isTraceEnabled() ) {
                    log.trace("Reusing existing session " + ssn);
                }
                return ssn;
            }
            else if ( log.isTraceEnabled() ) {
                log.trace("Existing session " + ssn + " does not match " + tf.getCredentials());
            }
        }

        /* logoff old sessions */
        if ( tf.getConfig().getSessionTimeout() > 0 && this.sessionExpiration < ( now = System.currentTimeMillis() ) ) {
            this.sessionExpiration = now + tf.getConfig().getSessionTimeout();
            iter = this.sessions.listIterator();
            while ( iter.hasNext() ) {
                ssn = iter.next();
                if ( ssn.getExpiration() != null && ssn.getExpiration() < now ) {
                    if ( log.isDebugEnabled() ) {
                        log.debug("Closing session after timeout " + ssn);
                    }
                    ssn.logoff(false);
                }
            }
        }
        ssn = new SmbSession(tf, this, this.address, this.port, this.localAddr, this.localPort);
        if ( log.isDebugEnabled() ) {
            log.debug("Establishing new session " + ssn + " on " + this.name);
        }
        this.sessions.add(ssn);
        return ssn;
    }


    boolean matches ( UniAddress addr, int prt, InetAddress laddr, int lprt, String hostName ) {
        if ( hostName == null )
            hostName = addr.getHostName();
        return ( this.tconHostName == null || hostName.equalsIgnoreCase(this.tconHostName) ) && addr.equals(this.address)
                && ( prt == 0 || prt == this.port ||
                        /* port 139 is ok if 445 was requested */
                        ( prt == 445 && this.port == 139 ) )
                && ( laddr == this.localAddr || ( laddr != null && laddr.equals(this.localAddr) ) ) && lprt == this.localPort;
    }


    /**
     * @param cap
     * @return whether the given capability was negotiated
     * @throws SmbException
     */
    public boolean hasCapability ( int cap ) throws SmbException {
        try {
            connect(this.transportContext.getConfig().getResponseTimeout());
        }
        catch ( IOException ioe ) {
            throw new SmbException(ioe.getMessage(), ioe);
        }
        return ( this.capabilities & cap ) == cap;
    }


    boolean isSignatureSetupRequired () {
        return ( this.signingEnforced || ( this.flags2 & SmbConstants.FLAGS2_SECURITY_SIGNATURES ) != 0 ) && this.digest == null;
    }


    void ssn139 () throws IOException {
        Name calledName = new Name(this.transportContext.getConfig(), this.address.firstCalledName(), 0x20, null);
        do {
            this.socket = new Socket();
            if ( this.localAddr != null )
                this.socket.bind(new InetSocketAddress(this.localAddr, this.localPort));
            this.socket.connect(new InetSocketAddress(this.address.getHostAddress(), 139), this.transportContext.getConfig().getConnTimeout());
            this.socket.setSoTimeout(this.transportContext.getConfig().getSoTimeout());

            this.out = this.socket.getOutputStream();
            this.in = this.socket.getInputStream();

            SessionServicePacket ssp = new SessionRequestPacket(calledName, this.transportContext.getNameServiceClient().getLocalName());
            this.out.write(this.sbuf, 0, ssp.writeWireFormat(this.sbuf, 0));
            if ( readn(this.in, this.sbuf, 0, 4) < 4 ) {
                try {
                    this.socket.close();
                }
                catch ( IOException ioe ) {
                    log.debug("Failed to close socket", ioe);
                }
                throw new SmbException("EOF during NetBIOS session request");
            }
            switch ( this.sbuf[ 0 ] & 0xFF ) {
            case SessionServicePacket.POSITIVE_SESSION_RESPONSE:
                if ( log.isDebugEnabled() )
                    log.debug("session established ok with " + this.address);
                return;
            case SessionServicePacket.NEGATIVE_SESSION_RESPONSE:
                int errorCode = this.in.read() & 0xFF;
                switch ( errorCode ) {
                case NbtException.CALLED_NOT_PRESENT:
                case NbtException.NOT_LISTENING_CALLED:
                    this.socket.close();
                    break;
                default:
                    disconnect(true);
                    throw new NbtException(NbtException.ERR_SSN_SRVC, errorCode);
                }
                break;
            case -1:
                disconnect(true);
                throw new NbtException(NbtException.ERR_SSN_SRVC, NbtException.CONNECTION_REFUSED);
            default:
                disconnect(true);
                throw new NbtException(NbtException.ERR_SSN_SRVC, 0);
            }
        }
        while ( ( calledName.name = this.address.nextCalledName(this.getTransportContext()) ) != null );

        throw new IOException("Failed to establish session with " + this.address);
    }


    private SmbComNegotiateResponse negotiate ( int prt ) throws IOException {
        /*
         * We cannot use Transport.sendrecv() yet because
         * the Transport thread is not setup until doConnect()
         * returns and we want to supress all communication
         * until we have properly negotiated.
         */
        synchronized ( this.sbuf ) {
            if ( prt == 139 ) {
                ssn139();
            }
            else {
                if ( prt == 0 )
                    prt = DEFAULT_PORT; // 445

                this.socket = new Socket();
                if ( this.localAddr != null )
                    this.socket.bind(new InetSocketAddress(this.localAddr, this.localPort));
                this.socket.connect(new InetSocketAddress(this.address.getHostAddress(), prt), this.transportContext.getConfig().getConnTimeout());
                this.socket.setSoTimeout(this.transportContext.getConfig().getSoTimeout());

                this.out = this.socket.getOutputStream();
                this.in = this.socket.getInputStream();
            }

            if ( ++this.mid == 32000 )
                this.mid = 1;
            this.NEGOTIATE_REQUEST.mid = this.mid;
            int n = this.NEGOTIATE_REQUEST.encode(this.sbuf, 4);
            Encdec.enc_uint32be(n & 0xFFFF, this.sbuf, 0); /* 4 byte ssn msg header */

            if ( log.isTraceEnabled() ) {
                log.trace(this.NEGOTIATE_REQUEST);
                log.trace(Hexdump.toHexString(this.sbuf, 4, n));
            }

            this.out.write(this.sbuf, 0, 4 + n);
            this.out.flush();
            /*
             * Note the Transport thread isn't running yet so we can
             * read from the socket here.
             */
            try {
                this.socket.setSoTimeout(this.transportContext.getConfig().getConnTimeout());
                if ( peekKey() == null ) /* try to read header */
                    throw new IOException("transport closed in negotiate");
            }
            finally {
                this.socket.setSoTimeout(this.transportContext.getConfig().getSoTimeout());
            }
            int size = Encdec.dec_uint16be(this.sbuf, 2) & 0xFFFF;
            if ( size < 33 || ( 4 + size ) > this.sbuf.length ) {
                throw new IOException("Invalid payload size: " + size);
            }
            readn(this.in, this.sbuf, 4 + 32, size - 32);

            SmbComNegotiateResponse resp;

            if ( !this.smb2 ) {
                resp = new SmbComNegotiateResponse(this.getTransportContext().getConfig(), this.server);
            }
            else {
                throw new RuntimeCIFSException("SMB2 not yet supported");
            }

            resp.decode(this.sbuf, 4);

            if ( log.isTraceEnabled() ) {
                log.trace(resp);
                log.trace(Hexdump.toHexString(this.sbuf, 4, n));
            }

            return resp;
        }
    }


    /**
     * Connect the transport
     * 
     * @throws SmbException
     */
    public void connect () throws SmbException {
        try {
            super.connect(this.transportContext.getConfig().getResponseTimeout());
        }
        catch ( TransportException te ) {
            throw new SmbException("Failed to connect: " + this.address, te);
        }
    }


    @Override
    protected void doConnect () throws IOException {
        /*
         * Negotiate Protocol Request / Response
         */
        if ( log.isDebugEnabled() ) {
            log.debug("Connecting in state " + this.state);
        }

        SmbComNegotiateResponse resp;
        try {
            resp = negotiate(this.port);
        }
        catch ( IOException ce ) {
            this.port = ( this.port == 0 || this.port == DEFAULT_PORT ) ? 139 : DEFAULT_PORT;
            resp = negotiate(this.port);
        }

        if ( resp.dialectIndex > 10 ) {
            throw new SmbException("This client does not support the negotiated dialect.");
        }
        if ( ( this.server.scapabilities & CAP_EXTENDED_SECURITY ) != CAP_EXTENDED_SECURITY && this.server.encryptionKeyLength != 8
                && this.getTransportContext().getConfig().getLanManCompatibility() == 0 ) {
            throw new SmbException("Unexpected encryption key length: " + this.server.encryptionKeyLength);
        }

        boolean serverRequireSig = this.server.signaturesRequired;
        boolean serverEnableSig = this.server.signaturesEnabled;
        if ( log.isDebugEnabled() ) {
            log.debug(
                "Signature negotiation enforced " + this.signingEnforced + " (server " + serverRequireSig + ") enabled "
                        + this.getTransportContext().getConfig().isSigningEnabled() + " (server " + serverEnableSig + ")");
        }

        /* Adjust negotiated values */
        this.tconHostName = this.address.getHostName();
        if ( this.signingEnforced && !serverEnableSig ) {
            throw new SmbException("Signatures are required but the server does not support them");
        }
        else if ( this.signingEnforced || serverRequireSig || ( serverEnableSig && this.getTransportContext().getConfig().isSigningEnabled() ) ) {
            this.flags2 |= SmbConstants.FLAGS2_SECURITY_SIGNATURES;
            if ( this.signingEnforced || this.server.signaturesRequired ) {
                this.flags2 |= SmbConstants.FLAGS2_SECURITY_REQUIRE_SIGNATURES;
            }
        }
        else {
            this.flags2 &= 0xFFFF ^ SmbConstants.FLAGS2_SECURITY_SIGNATURES;
            this.flags2 &= 0xFFFF ^ SmbConstants.FLAGS2_SECURITY_REQUIRE_SIGNATURES;
        }

        if ( log.isDebugEnabled() ) {
            log.debug(
                "Signing " + ( ( this.flags2 & SmbConstants.FLAGS2_SECURITY_SIGNATURES ) != 0 ? "enabled " : "not-enabled " )
                        + ( ( this.flags2 & SmbConstants.FLAGS2_SECURITY_REQUIRE_SIGNATURES ) != 0 ? "required" : "not-required" ));
        }

        this.maxMpxCount = Math.min(this.maxMpxCount, this.server.smaxMpxCount);
        if ( this.maxMpxCount < 1 )
            this.maxMpxCount = 1;
        this.snd_buf_size = Math.min(this.snd_buf_size, this.server.maxBufferSize);
        this.capabilities &= this.server.scapabilities;
        if ( ( this.server.scapabilities & CAP_EXTENDED_SECURITY ) == CAP_EXTENDED_SECURITY )
            this.capabilities |= CAP_EXTENDED_SECURITY; // & doesn't copy high bit

        if ( this.getTransportContext().getConfig().isUseUnicode() || this.getTransportContext().getConfig().isForceUnicode() ) {
            this.capabilities |= SmbConstants.CAP_UNICODE;
        }

        if ( ( this.capabilities & SmbConstants.CAP_UNICODE ) == 0 ) {
            // server doesn't want unicode
            if ( this.getTransportContext().getConfig().isForceUnicode() ) {
                this.capabilities |= SmbConstants.CAP_UNICODE;
                this.useUnicode = true;
            }
            else {
                this.useUnicode = false;
                this.flags2 &= 0xFFFF ^ SmbConstants.FLAGS2_UNICODE;
            }
        }
        else {
            this.useUnicode = this.getTransportContext().getConfig().isUseUnicode();
        }

        if ( this.useUnicode ) {
            log.debug("Unicode is enabled");
        }
        else {
            log.debug("Unicode is disabled");
        }
    }


    @Override
    protected synchronized void doDisconnect ( boolean hard ) throws IOException {
        ListIterator iter = this.sessions.listIterator();

        if ( log.isDebugEnabled() ) {
            log.debug("Disconnecting transport " + this);
        }

        try {
            if ( log.isTraceEnabled() ) {
                log.trace("Currently " + this.sessions.size() + " session(s) active for " + this);
            }
            while ( iter.hasNext() ) {
                SmbSession ssn = iter.next();
                try {
                    ssn.logoff(hard);
                }
                catch ( Exception e ) {
                    log.debug("Failed to close session", e);
                }
                finally {
                    iter.remove();
                }
            }

            if ( this.socket != null ) {
                this.socket.shutdownOutput();
                this.out.close();
                this.in.close();
                this.socket.close();
                log.trace("Socket closed");
            }
            else {
                log.trace("Not yet initialized");
            }
        }
        catch ( Exception e ) {
            log.debug("Exception in disconnect", e);
        }
        finally {
            this.digest = null;
            this.socket = null;
            this.tconHostName = null;
            this.transportContext.getTransportPool().removeTransport(this);
        }
    }


    @Override
    protected void makeKey ( Request request ) throws IOException {
        /* The request *is* the key */
        if ( ++this.mid == 32000 )
            this.mid = 1;
        ( (ServerMessageBlock) request ).mid = this.mid;
    }


    @Override
    protected Request peekKey () throws IOException {
        do {
            if ( ( readn(this.in, this.sbuf, 0, 4) ) < 4 )
                return null;
        }
        while ( this.sbuf[ 0 ] == (byte) 0x85 ); /* Dodge NetBIOS keep-alive */
        /* read smb header */
        if ( ( readn(this.in, this.sbuf, 4, 32) ) < 32 )
            return null;

        if ( log.isTraceEnabled() ) {
            log.trace("New data read: " + this);
            log.trace(Hexdump.toHexString(this.sbuf, 4, 32));
        }

        for ( ;; ) {
            /*
             * 01234567
             * 00SSFSMB
             * 0 - 0's
             * S - size of payload
             * FSMB - 0xFF SMB magic #
             */

            if ( this.sbuf[ 0 ] == (byte) 0x00 && this.sbuf[ 1 ] == (byte) 0x00 && ( this.sbuf[ 4 ] == (byte) 0xFF || this.sbuf[ 4 ] == (byte) 0xFE )
                    && this.sbuf[ 5 ] == (byte) 'S' && this.sbuf[ 6 ] == (byte) 'M' && this.sbuf[ 7 ] == (byte) 'B' ) {
                if ( this.sbuf[ 4 ] == (byte) 0xFE ) {
                    this.smb2 = true;
                }
                break; /* all good */
            }
            /* out of phase maybe? */
            /* inch forward 1 byte and try again */
            for ( int i = 0; i < 35; i++ ) {
                this.sbuf[ i ] = this.sbuf[ i + 1 ];
            }
            int b;
            if ( ( b = this.in.read() ) == -1 )
                return null;
            this.sbuf[ 35 ] = (byte) b;
        }

        this.key.mid = Encdec.dec_uint16le(this.sbuf, 34) & 0xFFFF;

        /*
         * Unless key returned is null or invalid Transport.loop() always
         * calls doRecv() after and no one else but the transport thread
         * should call doRecv(). Therefore it is ok to expect that the data
         * in sbuf will be preserved for copying into BUF in doRecv().
         */

        return this.key;
    }


    @Override
    protected void doSend ( Request request ) throws IOException {

        ServerMessageBlock smb = (ServerMessageBlock) request;
        byte[] buffer = this.getTransportContext().getBufferCache().getBuffer();
        try {
            int n = smb.encode(buffer, 4);
            Encdec.enc_uint32be(n & 0xFFFF, buffer, 0); /* 4 byte session message header */
            if ( log.isTraceEnabled() ) {
                do {
                    log.trace(smb);
                }
                while ( smb instanceof AndXServerMessageBlock && ( smb = ( (AndXServerMessageBlock) smb ).andx ) != null );
                log.trace(Hexdump.toHexString(buffer, 4, n));

            }
            /*
             * For some reason this can sometimes get broken up into another
             * "NBSS Continuation Message" frame according to WireShark
             */
            synchronized ( this.socketLock ) {
                this.out.write(buffer, 0, 4 + n);
            }
        }
        finally {
            this.getTransportContext().getBufferCache().releaseBuffer(buffer);
        }
    }


    protected void doSend0 ( Request request ) throws IOException {
        try {
            doSend(request);
        }
        catch ( IOException ioe ) {
            log.warn("send failed", ioe);
            try {
                disconnect(true);
            }
            catch ( IOException ioe2 ) {
                ioe.addSuppressed(ioe2);
                log.error("disconnect failed", ioe2);
            }
            throw ioe;
        }
    }


    @Override
    protected void doRecv ( Response response ) throws IOException {
        ServerMessageBlock resp = (ServerMessageBlock) response;
        resp.useUnicode = this.useUnicode;
        byte[] buffer = this.getTransportContext().getBufferCache().getBuffer();
        try {
            int size;
            synchronized ( this.socketLock ) {
                System.arraycopy(this.sbuf, 0, buffer, 0, 4 + HEADER_LENGTH);
                size = Encdec.dec_uint16be(buffer, 2) & 0xFFFF;
                if ( size < ( HEADER_LENGTH + 1 ) || ( 4 + size ) > this.rcv_buf_size ) {
                    throw new IOException("Invalid payload size: " + size);
                }
                int errorCode = Encdec.dec_uint32le(buffer, 9) & 0xFFFFFFFF;
                if ( resp.command == ServerMessageBlock.SMB_COM_READ_ANDX && ( errorCode == 0 || errorCode == 0x80000005 ) ) {
                    // overflow indicator normal for pipe

                    SmbComReadAndXResponse r = (SmbComReadAndXResponse) resp;
                    int off = HEADER_LENGTH;
                    /* WordCount thru dataOffset always 27 */
                    readn(this.in, buffer, 4 + off, 27);
                    off += 27;
                    resp.decode(buffer, 4);
                    /* EMC can send pad w/o data */
                    int pad = r.dataOffset - off;
                    if ( r.byteCount > 0 && pad > 0 && pad < 4 )
                        readn(this.in, buffer, 4 + off, pad);

                    if ( r.dataLength > 0 ) {
                        readn(this.in, r.b, r.off, r.dataLength); /* read direct */
                    }
                }
                else {
                    readn(this.in, buffer, 4 + 32, size - 32);
                    resp.decode(buffer, 4);
                    if ( resp instanceof SmbComTransactionResponse ) {
                        ( (SmbComTransactionResponse) resp ).nextElement();
                    }
                }
            }

            /*
             * Verification fails (w/ W2K3 server at least) if status is not 0. This
             * suggests MS doesn't compute the signature (correctly) for error responses
             * (perhaps for DOS reasons).
             */
            if ( this.digest != null && resp.errorCode == 0 ) {
                this.digest.verify(buffer, 4, resp);
                if ( resp.verifyFailed ) {
                    throw new TransportException("Signature verification failed");
                }
            }

            if ( log.isTraceEnabled() ) {
                log.trace(response);
                log.trace(Hexdump.toHexString(buffer, 4, size));
            }
        }
        catch ( Exception e ) {
            resp.isError = true;
            resp.exception = e;
            this.notifyAll();
            throw e;
        }
        finally {
            this.getTransportContext().getBufferCache().releaseBuffer(buffer);
        }
    }


    @Override
    protected void doSkip () throws IOException {
        int size = Encdec.dec_uint16be(this.sbuf, 2) & 0xFFFF;
        if ( size < 33 || ( 4 + size ) > this.rcv_buf_size ) {
            /* log message? */
            this.in.skip(this.in.available());
        }
        else {
            this.in.skip(size - 32);
        }
    }


    void checkStatus ( ServerMessageBlock req, ServerMessageBlock resp ) throws SmbException {
        if ( resp.errorCode == 0x30002 ) {
            // if using DOS error codes this indicates a DFS referral
            resp.errorCode = NtStatus.NT_STATUS_PATH_NOT_COVERED;
        }
        else {
            resp.errorCode = SmbException.getStatusByCode(resp.errorCode);
        }
        switch ( resp.errorCode ) {
        case NtStatus.NT_STATUS_OK:
            break;
        case NtStatus.NT_STATUS_ACCESS_DENIED:
        case NtStatus.NT_STATUS_WRONG_PASSWORD:
        case NtStatus.NT_STATUS_LOGON_FAILURE:
        case NtStatus.NT_STATUS_ACCOUNT_RESTRICTION:
        case NtStatus.NT_STATUS_INVALID_LOGON_HOURS:
        case NtStatus.NT_STATUS_INVALID_WORKSTATION:
        case NtStatus.NT_STATUS_PASSWORD_EXPIRED:
        case NtStatus.NT_STATUS_ACCOUNT_DISABLED:
        case NtStatus.NT_STATUS_ACCOUNT_LOCKED_OUT:
        case NtStatus.NT_STATUS_TRUSTED_DOMAIN_FAILURE:
            throw new SmbAuthException(resp.errorCode);
        case NtStatus.NT_STATUS_PATH_NOT_COVERED:
            // samba fails to report the proper status for some operations
        case 0xC00000A2: // NT_STATUS_MEDIA_WRITE_PROTECTED
            DfsReferral dr = null;
            if ( !this.getTransportContext().getConfig().isDfsDisabled() ) {
                dr = getDfsReferrals(getTransportContext(), req.path, 1);
            }
            if ( dr == null ) {
                log.debug("Error code: 0x" + Hexdump.toHexString(resp.errorCode, 8));
                throw new SmbException(resp.errorCode, null);
            }

            if ( log.isDebugEnabled() ) {
                log.debug("Got referral " + dr);
            }
            this.getTransportContext().getDfs().cache(getTransportContext(), req.path, dr);
            throw dr;
        case 0x80000005: /* STATUS_BUFFER_OVERFLOW */
            break; /* normal for DCERPC named pipes */
        case NtStatus.NT_STATUS_MORE_PROCESSING_REQUIRED:
            break; /* normal for NTLMSSP */
        default:
            log.debug("Error code: 0x" + Hexdump.toHexString(resp.errorCode, 8) + " for " + req.getClass().getSimpleName());
            throw new SmbException(resp.errorCode, null);
        }
        if ( resp.verifyFailed ) {
            throw new SmbException("Signature verification failed.");
        }
    }


    void send ( ServerMessageBlock request, ServerMessageBlock response, boolean doTimeout ) throws SmbException {

        connect(); /* must negotiate before we can test flags2, useUnicode, etc */

        request.flags2 |= this.flags2;
        request.useUnicode = request.forceUnicode || this.useUnicode;
        if ( request.useUnicode ) {
            request.flags2 |= SmbConstants.FLAGS2_UNICODE;
        }
        request.response = response; /* needed by sign */
        if ( request.digest == null )
            request.digest = this.digest; /* for sign called in encode */

        try {
            if ( log.isTraceEnabled() ) {
                log.trace("Sending " + request);
            }
            if ( response == null ) {
                doSend0(request);
                return;
            }
            else if ( request instanceof SmbComTransaction ) {
                response.command = request.command;
                SmbComTransaction req = (SmbComTransaction) request;
                SmbComTransactionResponse resp = (SmbComTransactionResponse) response;

                req.maxBufferSize = this.snd_buf_size;
                resp.reset();

                try {
                    setupBuffers(req, resp);

                    /*
                     * First request w/ interim response
                     */
                    req.nextElement();
                    if ( req.hasMoreElements() ) {
                        SmbComBlankResponse interim = new SmbComBlankResponse(getTransportContext().getConfig());
                        super.sendrecv(req, interim, doTimeout ? (long) this.transportContext.getConfig().getResponseTimeout() : null);
                        if ( interim.errorCode != 0 ) {
                            checkStatus(req, interim);
                        }
                        req.nextElement();
                    }
                    else {
                        makeKey(req);
                    }

                    synchronized ( this ) {
                        response.received = false;
                        resp.isReceived = false;
                        try {
                            long timeout = this.transportContext.getConfig().getResponseTimeout();
                            if ( doTimeout ) {
                                resp.expiration = System.currentTimeMillis() + timeout;
                            }
                            else {
                                resp.expiration = null;
                            }
                            this.response_map.put(req, resp);

                            /*
                             * Send multiple fragments
                             */

                            do {
                                doSend0(req);
                            }
                            while ( req.hasMoreElements() && req.nextElement() != null );

                            /*
                             * Receive multiple fragments
                             */

                            while ( resp.hasMoreElements() ) {
                                if ( doTimeout ) {
                                    wait(timeout);
                                    timeout = resp.expiration - System.currentTimeMillis();
                                    if ( timeout <= 0 ) {
                                        throw new TransportException(this + " timedout waiting for response to " + req);
                                    }
                                }
                                else {
                                    wait();
                                    if ( log.isTraceEnabled() ) {
                                        log.trace("Wait returned " + this.isDisconnected());
                                    }
                                    if ( this.isDisconnected() ) {
                                        throw new EOFException("Transport closed while waiting for result");
                                    }
                                }
                            }

                            if ( response.errorCode != 0 ) {
                                checkStatus(req, resp);
                            }
                        }
                        catch ( InterruptedException ie ) {
                            throw new TransportException(ie);
                        }
                        finally {
                            this.response_map.remove(req);
                        }
                    }
                }
                finally {
                    this.getTransportContext().getBufferCache().releaseBuffer(req.txn_buf);
                    this.getTransportContext().getBufferCache().releaseBuffer(resp.txn_buf);
                }

            }
            else {
                response.command = request.command;
                super.sendrecv(request, response, doTimeout ? (long) this.transportContext.getConfig().getResponseTimeout() : null);
            }
        }
        catch ( SmbException se ) {
            throw se;
        }
        catch ( IOException ioe ) {
            throw new SmbException(ioe.getMessage(), ioe);
        }

        if ( log.isTraceEnabled() ) {
            log.trace("Response is " + response);
        }
        checkStatus(request, response);
    }


    void setupBuffers ( SmbComTransaction req, SmbComTransactionResponse rsp ) {
        req.txn_buf = getTransportContext().getBufferCache().getBuffer();
        rsp.txn_buf = getTransportContext().getBufferCache().getBuffer();
    }


    @Override
    public String toString () {
        return super.toString() + "[" + this.address + ":" + this.port + ",state=" + this.state + ",signingEnforced=" + this.signingEnforced + "]";
    }


    /* DFS */
    DfsReferral getDfsReferrals ( CIFSContext ctx, String path, int rn ) throws SmbException {
        if ( log.isDebugEnabled() ) {
            log.debug("Resolving DFS path " + path);
        }

        if ( path.length() >= 2 && path.charAt(0) == '\\' && path.charAt(1) == '\\' ) {
            throw new SmbException("Path must not start with double slash: " + path);
        }

        SmbSession sess = getSmbSession(ctx);
        SmbTree ipc = sess.getSmbTree("IPC$", null);

        if ( ( sess.getTransport().flags2 & SmbConstants.FLAGS2_UNICODE ) == 0 ) {
            log.debug("Unicode is disabled");
        }

        Trans2GetDfsReferralResponse resp = new Trans2GetDfsReferralResponse(ctx.getConfig());

        ipc.send(new Trans2GetDfsReferral(ctx.getConfig(), path), resp);

        if ( resp.numReferrals == 0 ) {
            return null;
        }
        else if ( rn == 0 || resp.numReferrals < rn ) {
            rn = resp.numReferrals;
        }

        DfsReferral dr = new DfsReferral();

        String[] arr = new String[4];
        long expiration = System.currentTimeMillis() + ( ctx.getConfig().getDfsTtl() * 1000 );

        int di = 0;
        for ( ;; ) {
            /*
             * NTLM HTTP Authentication must be re-negotiated
             * with challenge from 'server' to access DFS vol.
             */
            if ( ctx.getCredentials() instanceof NtlmPasswordAuthentication ) {
                dr.resolveHashes = ( (NtlmPasswordAuthentication) ctx.getCredentials() ).areHashesExternal();
            }
            dr.ttl = resp.referrals[ di ].ttl;
            dr.rflags = resp.referrals[ di ].rflags;
            dr.expiration = expiration;
            if ( ( dr.rflags & Trans2GetDfsReferralResponse.FLAGS_NAME_LIST_REFERRAL ) == Trans2GetDfsReferralResponse.FLAGS_NAME_LIST_REFERRAL ) {
                if ( resp.referrals[ di ].expandedNames.length > 0 ) {
                    dr.server = resp.referrals[ di ].expandedNames[ 0 ].substring(1).toLowerCase();
                }
                else {
                    dr.server = resp.referrals[ di ].specialName.substring(1).toLowerCase();
                }
                if ( log.isDebugEnabled() ) {
                    log.debug(
                        "Server " + dr.server + " path " + path + " remain " + path.substring(resp.pathConsumed) + " path consumed "
                                + resp.pathConsumed);
                }
            }
            else {
                if ( log.isDebugEnabled() ) {
                    log.debug(
                        "Node " + resp.referrals[ di ].node + " path " + path + " remain " + path.substring(resp.pathConsumed) + " path consumed "
                                + resp.pathConsumed);
                }
                dfsPathSplit(resp.referrals[ di ].node, arr);
                dr.server = arr[ 1 ];
                dr.share = arr[ 2 ];
                dr.path = arr[ 3 ];
            }
            dr.pathConsumed = resp.pathConsumed;

            di++;
            if ( di == rn )
                break;

            dr.append(new DfsReferral());
            dr = dr.next;
        }

        if ( log.isDebugEnabled() ) {
            log.debug("Got referral " + dr);
        }
        return dr.next;
    }


    /*
     * Split DFS path like \fs1.example.com\root5\link2\foo\bar.txt into at
     * most 3 components (not including the first index which is always empty):
     * result[0] = ""
     * result[1] = "fs1.example.com"
     * result[2] = "root5"
     * result[3] = "link2\foo\bar.txt"
     */
    int dfsPathSplit ( String path, String[] result ) {
        int ri = 0, rlast = result.length - 1;
        int i = 0, b = 0, len = path.length();
        int strip = 0;

        do {
            if ( ri == rlast ) {
                result[ rlast ] = path.substring(b);
                return strip;
            }
            if ( i == len || path.charAt(i) == '\\' ) {
                result[ ri++ ] = path.substring(b, i);
                strip++;
                b = i + 1;
            }
        }
        while ( i++ < len );

        while ( ri < result.length ) {
            result[ ri++ ] = "";
        }

        return strip;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy