// This code is derived from jcifs smb client library // Ported by J. Arturo // // 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 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using SharpCifs.Netbios; using SharpCifs.Util; using SharpCifs.Util.Sharpen; using SharpCifs.Util.Transport; namespace SharpCifs.Smb { public class SmbTransport : Transport { internal static readonly byte[] Buf = new byte[0xFFFF]; internal static readonly SmbComNegotiate NegotiateRequest = new SmbComNegotiate(); internal static LogStream LogStatic = LogStream.GetInstance(); internal static Hashtable DfsRoots = null; internal static SmbTransport GetSmbTransport(UniAddress address, int port) { lock (typeof(SmbTransport)) { return GetSmbTransport(address, port, SmbConstants.Laddr, SmbConstants.Lport, null); } } internal static SmbTransport GetSmbTransport(UniAddress address, int port, IPAddress localAddr, int localPort, string hostName) { lock (typeof(SmbTransport)) { SmbTransport conn; lock (SmbConstants.Connections) { if (SmbConstants.SsnLimit != 1) { conn = SmbConstants.Connections .FirstOrDefault(c => c.Matches(address, port, localAddr, localPort, hostName) && (SmbConstants.SsnLimit == 0 || c.Sessions.Count < SmbConstants.SsnLimit)); if (conn != null) { return conn; } } conn = new SmbTransport(address, port, localAddr, localPort); SmbConstants.Connections.Insert(0, conn); } return conn; } } /// /// Clear All Cached Transport-Connections /// public static void ClearCachedConnections() { lock (typeof(SmbTransport)) lock (SmbConstants.Connections) { foreach (var transport in SmbConstants.Connections) { try { transport.Disconnect(true); } catch (Exception) {} } SmbConstants.Connections.Clear(); } } internal class ServerData { internal byte Flags; internal int Flags2; internal int MaxMpxCount; internal int MaxBufferSize; internal int SessionKey; internal int Capabilities; internal string OemDomainName; internal int SecurityMode; internal int Security; internal bool EncryptedPasswords; internal bool SignaturesEnabled; internal bool SignaturesRequired; internal int MaxNumberVcs; internal int MaxRawSize; internal long ServerTime; internal int ServerTimeZone; internal int EncryptionKeyLength; internal byte[] EncryptionKey; internal byte[] Guid; internal ServerData(SmbTransport enclosing) { this._enclosing = enclosing; } private readonly SmbTransport _enclosing; } internal IPAddress LocalAddr; internal int LocalPort; internal UniAddress Address; internal SocketEx Socket; internal int Port; internal int Mid; internal OutputStream Out; internal InputStream In; internal byte[] Sbuf = new byte[512]; internal SmbComBlankResponse Key = new SmbComBlankResponse(); internal long SessionExpiration = Runtime.CurrentTimeMillis() + SmbConstants.SoTimeout; internal List Referrals = new List(); internal SigningDigest Digest; internal List Sessions = new List(); internal ServerData Server; internal int Flags2 = SmbConstants.Flags2; internal int MaxMpxCount = SmbConstants.MaxMpxCount; internal int SndBufSize = SmbConstants.SndBufSize; internal int RcvBufSize = SmbConstants.RcvBufSize; internal int Capabilities = SmbConstants.Capabilities; internal int SessionKey = 0x00000000; internal bool UseUnicode = SmbConstants.UseUnicode; internal string TconHostName; internal SmbTransport(UniAddress address, int port, IPAddress localAddr, int localPort) { Server = new ServerData(this); this.Address = address; this.Port = port; this.LocalAddr = localAddr; this.LocalPort = localPort; } internal virtual SmbSession GetSmbSession() { lock (this) { return GetSmbSession(new NtlmPasswordAuthentication(null, null, null)); } } internal virtual SmbSession GetSmbSession(NtlmPasswordAuthentication auth) { lock (this) { SmbSession ssn; long now; ssn = Sessions.FirstOrDefault(s => s.Matches(auth)); if (ssn != null) { ssn.Auth = auth; return ssn; } if (SmbConstants.SoTimeout > 0 && SessionExpiration < (now = Runtime.CurrentTimeMillis())) { SessionExpiration = now + SmbConstants.SoTimeout; foreach (var session in Sessions.Where(s => s.Expiration < now)) { session.Logoff(false); } } ssn = new SmbSession(Address, Port, LocalAddr, LocalPort, auth); ssn.transport = this; Sessions.Add(ssn); return ssn; } } internal virtual bool Matches(UniAddress address, int port, IPAddress localAddr, int localPort, string hostName) { if (hostName == null) { hostName = address.GetHostName(); } return (TconHostName == null || Runtime.EqualsIgnoreCase(hostName, TconHostName)) && address.Equals(this.Address) && (port == -1 || port == this.Port || (port == 445 && this.Port == 139)) && (localAddr == this.LocalAddr || (localAddr != null && localAddr.Equals(this.LocalAddr))) && localPort == this.LocalPort; } /// internal virtual bool HasCapability(int cap) { try { Connect(SmbConstants.ResponseTimeout); } catch (IOException ioe) { throw new SmbException(ioe.Message, ioe); } return (Capabilities & cap) == cap; } internal virtual bool IsSignatureSetupRequired(NtlmPasswordAuthentication auth) { return (Flags2 & SmbConstants.Flags2SecuritySignatures) != 0 && Digest == null && auth != NtlmPasswordAuthentication.Null && NtlmPasswordAuthentication.Null.Equals(auth) == false; } /// internal virtual void Ssn139() { Name calledName = new Name(Address.FirstCalledName(), 0x20, null); do { Socket = new SocketEx(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //TCPローカルポートは、毎回空いているものを使う。 //https://blogs.msdn.microsoft.com/dgorti/2005/09/18/only-one-usage-of-each-socket-address-protocolnetwork-addressport-is-normally-permitted/ Socket.Bind(new IPEndPoint(LocalAddr, 0)); Socket.Connect(new IPEndPoint(IPAddress.Parse(Address.GetHostAddress()), 139), SmbConstants.ConnTimeout); Socket.SoTimeOut = SmbConstants.SoTimeout; Out = Socket.GetOutputStream(); In = Socket.GetInputStream(); SessionServicePacket ssp = new SessionRequestPacket(calledName, NbtAddress.GetLocalName()); Out.Write(Sbuf, 0, ssp.WriteWireFormat(Sbuf, 0)); if (Readn(In, Sbuf, 0, 4) < 4) { try { //Socket.`Close` method deleted //Socket.Close(); Socket.Dispose(); } catch (IOException) { } throw new SmbException("EOF during NetBIOS session request"); } switch (Sbuf[0] & 0xFF) { case SessionServicePacket.PositiveSessionResponse: { if (Log.Level >= 4) { Log.WriteLine("session established ok with " + Address); } return; } case SessionServicePacket.NegativeSessionResponse: { int errorCode = In.Read() & 0xFF; switch (errorCode) { case NbtException.CalledNotPresent: case NbtException.NotListeningCalled: { //Socket.`Close` method deleted //Socket.Close(); Socket.Dispose(); break; } default: { Disconnect(true); throw new NbtException(NbtException.ErrSsnSrvc, errorCode); } } break; } case -1: { Disconnect(true); throw new NbtException(NbtException.ErrSsnSrvc, NbtException.ConnectionRefused); } default: { Disconnect(true); throw new NbtException(NbtException.ErrSsnSrvc, 0); } } } while ((calledName.name = Address.NextCalledName()) != null); throw new IOException("Failed to establish session with " + Address); } /// private void Negotiate(int port, ServerMessageBlock resp) { lock (Sbuf) { if (port == 139) { Ssn139(); } else { if (port == -1) { port = SmbConstants.DefaultPort; } // 445 Socket = new SocketEx(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //TCPローカルポートは、毎回空いているものを使う。 //https://blogs.msdn.microsoft.com/dgorti/2005/09/18/only-one-usage-of-each-socket-address-protocolnetwork-addressport-is-normally-permitted/ Socket.Bind(new IPEndPoint(LocalAddr, 0)); Socket.Connect(new IPEndPoint(IPAddress.Parse(Address.GetHostAddress()), port), // <- 445 SmbConstants.ConnTimeout); Socket.SoTimeOut = SmbConstants.SoTimeout; Out = Socket.GetOutputStream(); In = Socket.GetInputStream(); } if (++Mid == 32000) { Mid = 1; } NegotiateRequest.Mid = Mid; int n = NegotiateRequest.Encode(Sbuf, 4); Encdec.Enc_uint32be(n & 0xFFFF, Sbuf, 0); if (Log.Level >= 4) { Log.WriteLine(NegotiateRequest); if (Log.Level >= 6) { Hexdump.ToHexdump(Log, Sbuf, 4, n); } } Out.Write(Sbuf, 0, 4 + n); Out.Flush(); if (PeekKey() == null) { throw new IOException("transport closed in negotiate"); } int size = Encdec.Dec_uint16be(Sbuf, 2) & 0xFFFF; if (size < 33 || (4 + size) > Sbuf.Length) { throw new IOException("Invalid payload size: " + size); } Readn(In, Sbuf, 4 + 32, size - 32); resp.Decode(Sbuf, 4); if (Log.Level >= 4) { Log.WriteLine(resp); if (Log.Level >= 6) { Hexdump.ToHexdump(Log, Sbuf, 4, n); } } } } /// public virtual void Connect() { try { base.Connect(SmbConstants.ResponseTimeout); } catch (TransportException te) { throw new SmbException("Failed to connect: " + Address, te); } } /// protected internal override void DoConnect() { SmbComNegotiateResponse resp = new SmbComNegotiateResponse(Server); try { Negotiate(Port, resp); } catch (ConnectException) { Port = (Port == -1 || Port == SmbConstants.DefaultPort) ? 139 : SmbConstants.DefaultPort; Negotiate(Port, resp); } if (resp.DialectIndex > 10) { throw new SmbException("This client does not support the negotiated dialect."); } if ( (Server.Capabilities & SmbConstants.CapExtendedSecurity) != SmbConstants.CapExtendedSecurity && Server.EncryptionKeyLength != 8 && SmbConstants.LmCompatibility == 0 ) { throw new SmbException("Unexpected encryption key length: " + Server.EncryptionKeyLength); } TconHostName = Address.GetHostName(); if (Server.SignaturesRequired || (Server.SignaturesEnabled && SmbConstants.Signpref)) { Flags2 |= SmbConstants.Flags2SecuritySignatures; } else { Flags2 &= 0xFFFF ^ SmbConstants.Flags2SecuritySignatures; } MaxMpxCount = Math.Min(MaxMpxCount, Server.MaxMpxCount); if (MaxMpxCount < 1) { MaxMpxCount = 1; } SndBufSize = Math.Min(SndBufSize, Server.MaxBufferSize); Capabilities &= Server.Capabilities; if ((Server.Capabilities & SmbConstants.CapExtendedSecurity) == SmbConstants.CapExtendedSecurity) { Capabilities |= SmbConstants.CapExtendedSecurity; } // & doesn't copy high bit if ((Capabilities & SmbConstants.CapUnicode) == 0) { // server doesn't want unicode if (SmbConstants.ForceUnicode) { Capabilities |= SmbConstants.CapUnicode; } else { UseUnicode = false; Flags2 &= 0xFFFF ^ SmbConstants.Flags2Unicode; } } } /// protected internal override void DoDisconnect(bool hard) { try { foreach (var ssn in Sessions) { ssn.Logoff(hard); } Out.Close(); In.Close(); //Socket.`Close` method deleted //Socket.Close(); Socket.Shutdown(SocketShutdown.Both); Socket.Dispose(); } finally { Digest = null; Socket = null; TconHostName = null; } } /// protected internal override void MakeKey(ServerMessageBlock request) { if (++Mid == 32000) { Mid = 1; } request.Mid = Mid; } /// protected internal override ServerMessageBlock PeekKey() { int n; do { if ((n = Readn(In, Sbuf, 0, 4)) < 4) { return null; } } while (Sbuf[0] == 0x85); if ((n = Readn(In, Sbuf, 4, 32)) < 32) { return null; } if (Log.Level >= 4) { Log.WriteLine("New data read: " + this); Hexdump.ToHexdump(Log, Sbuf, 4, 32); } for (;;) { if (Sbuf[0] == 0x00 && Sbuf[1] == 0x00 && Sbuf[4] == 0xFF && Sbuf[5] == 'S' && Sbuf[6] == 'M' && Sbuf[7] == 'B') { break; } for (int i = 0; i < 35; i++) { Sbuf[i] = Sbuf[i + 1]; } int b; if ((b = In.Read()) == -1) { return null; } Sbuf[35] = unchecked((byte)b); } Key.Mid = Encdec.Dec_uint16le(Sbuf, 34) & 0xFFFF; return Key; } /// protected internal override void DoSend(ServerMessageBlock request) { lock (Buf) { ServerMessageBlock smb = request; int n = smb.Encode(Buf, 4); Encdec.Enc_uint32be(n & 0xFFFF, Buf, 0); if (Log.Level >= 4) { do { Log.WriteLine(smb); } while (smb is AndXServerMessageBlock && (smb = ((AndXServerMessageBlock)smb).Andx) != null); if (Log.Level >= 6) { Hexdump.ToHexdump(Log, Buf, 4, n); } } Out.Write(Buf, 0, 4 + n); } } /// protected internal virtual void DoSend0(ServerMessageBlock request) { try { DoSend(request); } catch (IOException ioe) { if (Log.Level > 2) { Runtime.PrintStackTrace(ioe, Log); } try { Disconnect(true); } catch (IOException ioe2) { Runtime.PrintStackTrace(ioe2, Log); } throw; } } /// protected internal override void DoRecv(Response response) { ServerMessageBlock resp = (ServerMessageBlock)response; resp.UseUnicode = UseUnicode; resp.ExtendedSecurity = (Capabilities & SmbConstants.CapExtendedSecurity) == SmbConstants.CapExtendedSecurity; lock (Buf) { Array.Copy(Sbuf, 0, Buf, 0, 4 + SmbConstants.HeaderLength); int size = Encdec.Dec_uint16be(Buf, 2) & 0xFFFF; if (size < (SmbConstants.HeaderLength + 1) || (4 + size) > RcvBufSize) { throw new IOException("Invalid payload size: " + size); } int errorCode = Encdec.Dec_uint32le(Buf, 9) & unchecked((int)(0xFFFFFFFF)); if (resp.Command == ServerMessageBlock.SmbComReadAndx && (errorCode == 0 || errorCode == unchecked((int)(0x80000005))) ) { // overflow indicator normal for pipe SmbComReadAndXResponse r = (SmbComReadAndXResponse)resp; int off = SmbConstants.HeaderLength; Readn(In, Buf, 4 + off, 27); off += 27; resp.Decode(Buf, 4); int pad = r.DataOffset - off; if (r.ByteCount > 0 && pad > 0 && pad < 4) { Readn(In, Buf, 4 + off, pad); } if (r.DataLength > 0) { Readn(In, r.B, r.Off, r.DataLength); } } else { Readn(In, Buf, 4 + 32, size - 32); resp.Decode(Buf, 4); if (resp is SmbComTransactionResponse) { ((SmbComTransactionResponse)resp).Current(); } } if (Digest != null && resp.ErrorCode == 0) { Digest.Verify(Buf, 4, resp); } if (Log.Level >= 4) { Log.WriteLine(response); if (Log.Level >= 6) { Hexdump.ToHexdump(Log, Buf, 4, size); } } } } /// protected internal override void DoSkip() { int size = Encdec.Dec_uint16be(Sbuf, 2) & 0xFFFF; if (size < 33 || (4 + size) > RcvBufSize) { In.Skip(In.Available()); } else { In.Skip(size - 32); } } /// internal virtual void CheckStatus(ServerMessageBlock req, ServerMessageBlock resp) { resp.ErrorCode = SmbException.GetStatusByCode(resp.ErrorCode); switch (resp.ErrorCode) { case NtStatus.NtStatusOk: { break; } case NtStatus.NtStatusAccessDenied: case NtStatus.NtStatusWrongPassword: case NtStatus.NtStatusLogonFailure: case NtStatus.NtStatusAccountRestriction: case NtStatus.NtStatusInvalidLogonHours: case NtStatus.NtStatusInvalidWorkstation: case NtStatus.NtStatusPasswordExpired: case NtStatus.NtStatusAccountDisabled: case NtStatus.NtStatusAccountLockedOut: case NtStatus.NtStatusTrustedDomainFailure: { throw new SmbAuthException(resp.ErrorCode); } case NtStatus.NtStatusPathNotCovered: { if (req.Auth == null) { throw new SmbException(resp.ErrorCode, null); } DfsReferral dr = GetDfsReferrals(req.Auth, req.Path, 1); if (dr == null) { throw new SmbException(resp.ErrorCode, null); } SmbFile.Dfs.Insert(req.Path, dr); throw dr; } case unchecked((int)(0x80000005)): { break; } case NtStatus.NtStatusMoreProcessingRequired: { break; } default: { throw new SmbException(resp.ErrorCode, null); } } if (resp.VerifyFailed) { throw new SmbException("Signature verification failed."); } } /// internal virtual void Send(ServerMessageBlock request, ServerMessageBlock response) { Connect(); request.Flags2 |= Flags2; request.UseUnicode = UseUnicode; request.Response = response; if (request.Digest == null) { request.Digest = Digest; } try { if (response == null) { DoSend0(request); return; } if (request is SmbComTransaction) { response.Command = request.Command; SmbComTransaction req = (SmbComTransaction)request; SmbComTransactionResponse resp = (SmbComTransactionResponse)response; req.MaxBufferSize = SndBufSize; resp.Reset(); try { BufferCache.GetBuffers(req, resp); req.Current(); if (req.MoveNext()) { SmbComBlankResponse interim = new SmbComBlankResponse(); Sendrecv(req, interim, SmbConstants.ResponseTimeout); if (interim.ErrorCode != 0) { CheckStatus(req, interim); } req.Current(); } else { MakeKey(req); } lock (this) { response.Received = false; resp.IsReceived = false; try { ResponseMap.Put(req, resp); do { DoSend0(req); } while (req.MoveNext() && req.Current() != null); long timeout = SmbConstants.ResponseTimeout; resp.Expiration = Runtime.CurrentTimeMillis() + timeout; while (resp.MoveNext()) { Runtime.Wait(this, timeout); timeout = resp.Expiration - Runtime.CurrentTimeMillis(); if (timeout <= 0) { throw new TransportException( this + " timedout waiting for response to " + req); } } if (response.ErrorCode != 0) { CheckStatus(req, resp); } } catch (Exception ie) { if (ie is SmbException) { throw; } else { throw new TransportException(ie); } } finally { //Sharpen.Collections.Remove(response_map, req); ResponseMap.Remove(req); } } } finally { BufferCache.ReleaseBuffer(req.TxnBuf); BufferCache.ReleaseBuffer(resp.TxnBuf); } } else { response.Command = request.Command; Sendrecv(request, response, SmbConstants.ResponseTimeout); } } catch (SmbException se) { throw; } catch (IOException ioe) { throw new SmbException(ioe.Message, ioe); } CheckStatus(request, response); } public override string ToString() { return base.ToString() + "[" + Address + ":" + Port + "]"; } internal virtual void DfsPathSplit(string path, string[] result) { int ri = 0; int rlast = result.Length - 1; int i = 0; int b = 0; int len = path.Length; do { if (ri == rlast) { result[rlast] = Runtime.Substring(path, b); return; } if (i == len || path[i] == '\\') { result[ri++] = Runtime.Substring(path, b, i); b = i + 1; } } while (i++ < len); while (ri < result.Length) { result[ri++] = string.Empty; } } /// internal virtual DfsReferral GetDfsReferrals(NtlmPasswordAuthentication auth, string path, int rn) { SmbTree ipc = GetSmbSession(auth).GetSmbTree("IPC$", null); Trans2GetDfsReferralResponse resp = new Trans2GetDfsReferralResponse(); ipc.Send(new Trans2GetDfsReferral(path), resp); if (resp.NumReferrals == 0) { return null; } if (rn == 0 || resp.NumReferrals < rn) { rn = resp.NumReferrals; } DfsReferral dr = new DfsReferral(); string[] arr = new string[4]; long expiration = Runtime.CurrentTimeMillis() + Dfs.Ttl * 1000; int di = 0; for (;;) { dr.ResolveHashes = auth.HashesExternal; dr.Ttl = resp.Referrals[di].Ttl; dr.Expiration = expiration; if (path.Equals(string.Empty)) { dr.Server = Runtime.Substring(resp.Referrals[di].Path, 1).ToLower(); } else { 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; } return dr.Next; } /// internal virtual DfsReferral[] __getDfsReferrals(NtlmPasswordAuthentication auth, string path, int rn) { SmbTree ipc = GetSmbSession(auth).GetSmbTree("IPC$", null); Trans2GetDfsReferralResponse resp = new Trans2GetDfsReferralResponse(); ipc.Send(new Trans2GetDfsReferral(path), resp); if (rn == 0 || resp.NumReferrals < rn) { rn = resp.NumReferrals; } DfsReferral[] drs = new DfsReferral[rn]; string[] arr = new string[4]; long expiration = Runtime.CurrentTimeMillis() + Dfs.Ttl * 1000; for (int di = 0; di < drs.Length; di++) { DfsReferral dr = new DfsReferral(); dr.ResolveHashes = auth.HashesExternal; dr.Ttl = resp.Referrals[di].Ttl; dr.Expiration = expiration; if (path.Equals(string.Empty)) { dr.Server = Runtime.Substring(resp.Referrals[di].Path, 1).ToLower(); } else { DfsPathSplit(resp.Referrals[di].Node, arr); dr.Server = arr[1]; dr.Share = arr[2]; dr.Path = arr[3]; } dr.PathConsumed = resp.PathConsumed; drs[di] = dr; } return drs; } } }