////////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: Services/Smb/Client/TransportMux.sg
//
// Note:
//
// This file contains the implementation of the SMB Client Transport
// Multiplexer. The mux runs on a single thread, and its purpose is to
// manage access to the TCP/IP connection to the SMB server.
//
using System;
using System.Text;
using System.Collections;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.SingSharp;
using Microsoft.Singularity;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Security;
using NetStack.Contracts;
using Smb.Protocol;
using Smb.PublicChannels;
using Smb.PrivateChannels;
using Smb.Shared;
namespace Smb.Client
{
class TransportMux : ITracked
{
///
/// This method creates a new TransportMux instance.
///
public static void Run(
[Claims]ServiceProviderContract.Exp! namespace_provider,
[Claims]SmbClientControllerContract.Exp! controller,
[Claims]SmbMuxNotifier.Imp! notifier,
string! serverName,
string! resourceName)
{
TransportMux mux = new TransportMux(
namespace_provider,
controller,
notifier,
serverName,
resourceName);
try {
mux.ThreadMain();
} finally {
mux.Dispose();
}
}
TransportMux(
[Claims]ServiceProviderContract.Exp! namespace_provider,
[Claims]SmbClientControllerContract.Exp! controller,
[Claims]SmbMuxNotifier.Imp! notifier,
string! serverName,
string! resourceName)
{
_ServerHostName = serverName;
_ServerResourceName = resourceName;
_notifier = notifier;
_notifier_can_send = true;
_namespace_provider = namespace_provider;
_controller = controller;
_recvBuffer = new byte[400];
_recvDataLength = 0;
}
readonly SmbMuxNotifier.Imp! _notifier;
static bool _dump_received_messages;
static bool _dump_sent_messages;
// These come from the command-line when the service was started.
readonly string! _ServerHostName;
readonly string! _ServerResourceName;
// Once we have connected, negotiated, and connected to the tree, this tuple contains
// the values we should use. MuxId will always be zero.
MuxTuple _treeTuple;
// I don't like this flag.
// If set, it means a protocol violation occurred, and so we need to tear down the connection.
// I want a cleaner way to do this.
bool _connection_failed;
// key: MuxTuple
// value: Transactor
readonly Hashtable/**/! _transactors = new Hashtable();
///
/// This endpoint map contains transactor endpoints that are in the "Ready" state.
/// We listen for the Request message in this state, meaning a transactor client
/// (which is anything that can issue SMB transactions, such as a DirectoryServiceContract
/// worker thread or a FileContract worker thread) wants to start a request.
///
EMap! _readyTransactors =
new EMap();
///
/// The TCP connection to the SMB server. In the Disconnected state, this field is null.
/// In all other states (except possibly during state transitions), this field is non-null.
///
TcpConnectionContract.Imp _connection;
// This endpoint is connected to SmbClientManager. This thread receives control
// requests on the endpoint.
readonly SmbClientControllerContract.Exp! _controller;
// This endpoint represents our registration of the SMB namespace with the global
// Singularity directory service. This is how we receive filesystem requests.
readonly ServiceProviderContract.Exp! _namespace_provider;
// The set of clients that have opened our published service control channel.
// This allows clients to send control requests to the SMB client.
ESet! _control_clients = new ESet();
// The last known status (snapshot) of the connection.
ClientStatusSnapshot _mux_status;
// Sequence number, identifies version of _mux_state
long _mux_status_version;
// Sequence number, identifies the last version of _mux_state we have sent to main thread
long _notifier_mux_status_version;
// If true, then _notifier is in a state that allows us to send a message.
bool _notifier_can_send;
// If true, SmbClientManager has requested that this process stop.
bool _service_stopping;
int _MaxSmbRequestLength = 1024;
// The credentials that we actually selected and used during authentication.
// This is set during SessionSetupAndConnectBasic.
string _SelectedCredentialsName;
// This value is returned from the server to the client in response to the NEGOTIATE message.
string _ReceivedServerDomainNameFromNegotiate;
string _ReceivedServerMachineName;
// These values are received from the server in response to the SESSION SETUP message.
string _ReceivedServerOperatingSystem;
string _ReceivedServerPrimaryDomainName;
// This could just as well be called the DisconnectedStateLoop.
void ThreadMain()
{
expose(this)
{
_service_stopping = false;
// bool want_connect = false;
bool want_connect = true;
try
{
SetMuxStatusDisconnected(SmbReasonDisconnected.Idle);
for (;;)
{
if (want_connect) {
want_connect = false;
goto start_connect;
}
//
// At this point, we are in the Disconnected state.
// New transactors can be created.
//
disconnected_state:
Disconnect();
_connection_failed = false;
for (;;)
{
if (_service_stopping) {
DebugLine("TransportMux - main thread exiting");
return;
}
switch receive
{
case _notifier.Ack():
AckNotifier();
break;
case _controller.ChannelClosed():
DebugLine("Controller channel has closed!!");
goto thread_quit;
case _controller.Bind(ServiceContract.Exp:Start! exp):
ControllerBind(exp);
break;
case _controller.Stop():
DebugLine("Received Stop request on controller channel");
_service_stopping = true;
Disconnect();
return;
case _namespace_provider.Connect(ServiceContract.Exp:Start! exp):
// A client wants to connect to the mount point namespace provider.
// We're not connected to the server right now. If the namespace client
// is a valid client (a supported contract), then we'll try to connect
// to the SMB server in response to this request. If not, we don't
// accept the client, and we don't attempt to connect to the SMB server.
if (NamespaceConnect(_namespace_provider, exp)) {
DebugLine("Namespace client is now connected.");
goto start_connect;
} else {
DebugLine("Namespace client could not be connected.");
}
break;
}
}
//
// At this point, we want to connect to the remote peer.
// Ask the Connector helper class to start connecting for us.
//
start_connect:
if (ConnectAndNegotiate()) {
goto connected_state;
} else {
goto disconnected_state;
}
connected_state:
ConnectedReceiveLoop();
}
thread_quit:
Disconnect();
} catch (Exception ex) {
DebugLine("Exception reached top of thread frame in multiplexer thread!");
Util.ShowExceptionDebug(ex, "TransportMultiplexer failed");
}
}
}
void ControllerBind([Claims]ServiceContract.Exp:Start! exp)
{
expose (this)
{
SmbClientControlContract.Exp typed_exp = exp as SmbClientControlContract.Exp;
if (typed_exp != null) {
if (TraceSwitches.ShowControlMessages) {
DebugLine("CONTROL: A control client has connected");
}
typed_exp.SendSuccess();
_control_clients.Add(typed_exp);
_controller.SendOk();
} else {
if (TraceSwitches.ShowControlMessages) {
DebugLine("CONTROL: A control client connected, but offered an unsupported contract type.");
}
_controller.SendRequestFailed(SmbErrorCode.InternalError, null);
delete exp;
}
}
}
bool NamespaceConnect(ServiceProviderContract.Exp! provider, [Claims]ServiceContract.Exp:Start! exp)
{
DirectoryServiceContract.Exp dir_exp = exp as DirectoryServiceContract.Exp;
if (dir_exp != null) {
if (TraceSwitches.ShowDirectoryMessages ) {
DebugLine("CONTROL: A namespace client has connected");
}
SmbTransactor.Imp transactor = CreateTransactor();
if (transactor == null) {
DebugLine("Failed to bind namespace client because we failed to create a transactor.");
provider.SendNackConnect(exp);
return false;
}
DirectoryClient.StartServiceThread(transactor, dir_exp, "/");
provider.SendAckConnect();
return true;
} else {
DebugLine("CONTROL: A namespace client connected, but offered an unsupported contract.");
provider.SendNackConnect(exp);
return false;
}
}
void Disconnect()
{
expose (this)
{
DebugLine("--- ENTERING DISCONNECTED STATE ---");
// Reject all transactors.
foreach (Transactor! tran in _transactors.Values)
{
tran.Dispose();
}
_transactors.Clear();
_readyTransactors.Dispose();
_readyTransactors = new EMap();
if (_connection != null) {
delete _connection;
_connection = null;
}
_SelectedCredentialsName = null;
_ReceivedServerOperatingSystem = null;
_ReceivedServerDomainNameFromNegotiate = null;
_ReceivedServerPrimaryDomainName = null;
_ReceivedServerMachineName = null;
}
}
void ConnectedReceiveLoop()
{
expose(this)
{
DebugLine("--- ENTERING CONNECTED STATE ---");
assert _connection != null;
// The TCP channel is now connected. However, we still have a lot of work to do before we can
// communicate with the server. We need to send a SETUP message, and we need to authenticate.
// We do this on another thread.
_connectionCanSend = true;
for (;;)
{
assert _connection != null;
if (_connectionCanSend) {
SendQueuedTransactions();
}
if (_connection_failed) {
DebugLine("_connect_failed is set -- going to disconnect state.");
goto disconnect;
}
switch receive
{
case _namespace_provider.Connect(ServiceContract.Exp:Start! exp):
// A client wants to connect to the mount point namespace provider.
// We're in the connected state, so this is easy.
NamespaceConnect(_namespace_provider, exp);
break;
case _notifier.Ack():
AckNotifier();
break;
case _controller.Bind(ServiceContract.Exp:Start! exp):
ControllerBind(exp);
break;
case _controller.Stop():
DebugLine("Received Stop request on controller channel");
_service_stopping = true;
Disconnect();
return;
break;
case transactor.Request(byte[]! in ExHeap request) in _readyTransactors ~> tran:
StartRequest(transactor, tran, request);
break;
case transactor.ChannelClosed() in _readyTransactors ~> tran:
// A transactor has disconnected.
delete transactor;
DeleteTransactorState(tran);
break;
case transactor.AddTransactor() in _readyTransactors ~> tran:
SmbTransactor.Imp new_transactor = CreateTransactor();
if (new_transactor != null) {
transactor.SendAckAddTransactor(new_transactor);
} else {
transactor.SendNakAddTransactor();
}
_readyTransactors.Add(transactor, tran);
break;
case _connection.NoData():
// This is not an error. It just means that the read poll did not return any data.
// Because this is part of a polling cycle, we don't bother printing any debug spew.
assert !_connectionCanSend;
_connectionCanSend = true;
// DebugLine("connection.NoData: no data available from TCP");
break;
case _connection.NoMoreData():
assert !_connectionCanSend;
_connectionCanSend = true;
DebugLine("connection.NoMoreData: peer has closed our receive path");
SetMuxStatusDisconnected(SmbReasonDisconnected.TransportFailure);
goto disconnect;
case _connection.CantSend():
assert !_connectionCanSend;
_connectionCanSend = true;
DebugLine("connection.CantSend: peer has closed our transmit path");
SetMuxStatusDisconnected(SmbReasonDisconnected.TransportFailure);
goto disconnect;
case _connection.Data(byte[]! in ExHeap data):
assert !_connectionCanSend;
_connectionCanSend = true;
// DebugLine("connection.Data: length = " + data.Length);
ProcessReceivedData(data, data.Length);
break;
case _connection.DataIsAvailable(bool available):
assert !_connectionCanSend;
_connectionCanSend = true;
// DebugLine("connection.DataIsAvailable(" + available + ")");
break;
case _connection.OK():
// from write result
// DebugLine("connection.OK()");
_connectionCanSend = true;
break;
}
}
disconnect:
assert _connection != null;
delete _connection;
_connection = null;
}
}
void SetMuxStatus(ClientStatusSnapshot! status)
{
if (TraceSwitches.ShowMuxStatusChanges)
DebugLine("SetMuxState: conn state = " + SmbDebug.EnumToString(status.ConnectionStatus));
_mux_status = status;
++_mux_status_version;
SendToNotifier();
}
void SetMuxStatus(SmbClientConnectionStatus connectionStatus)
{
ClientStatusSnapshot status = new ClientStatusSnapshot();
status.ConnectionStatus = connectionStatus;
status.ReasonDisconnected = SmbReasonDisconnected.Invalid;
status.ServerMachineName = null;
status.ServerDomainName = null;
status.ServerOperatingSystem = null;
SetMuxStatus(status);
}
void SetMuxStatusConnected()
{
ClientStatusSnapshot status = new ClientStatusSnapshot();
status.ConnectionStatus = SmbClientConnectionStatus.Connected;
status.ReasonDisconnected = SmbReasonDisconnected.Invalid;
status.ServerMachineName = _ReceivedServerMachineName;
status.ServerDomainName = _ReceivedServerPrimaryDomainName;
status.ServerOperatingSystem = _ReceivedServerOperatingSystem;
status.ActiveCredentialsName = _SelectedCredentialsName;
SetMuxStatus(status);
}
void SetMuxStatusDisconnected(SmbReasonDisconnected reason)
{
ClientStatusSnapshot status = new ClientStatusSnapshot();
status.ConnectionStatus = SmbClientConnectionStatus.Disconnected;
status.ReasonDisconnected = reason;
status.ServerMachineName = null;
status.ServerDomainName = null;
status.ServerOperatingSystem = null;
status.ActiveCredentialsName = null;
SetMuxStatus(status);
}
void SetMuxStatusDisconnected(SmbReasonDisconnected reason, Exception! ex)
{
ClientStatusSnapshot status = new ClientStatusSnapshot();
status.ConnectionStatus = SmbClientConnectionStatus.Disconnected;
status.ReasonDisconnected = reason;
status.ServerMachineName = null;
status.ServerDomainName = null;
status.ServerOperatingSystem = null;
SetMuxStatus(status);
// -XXX- Currently, we don't do anything with the exception.
// -XXX- Would be good to log it, report it, etc.
DebugLine("Mux status -> Disconnected");
Util.ShowExceptionDebug(ex);
}
// This method checks to see if we can (and should) send any notification messages
// to the notifier channel. The "main" thread holds the exp end of the notifier
// channel, and this thread (the mux) sends notifications to the main thread.
void SendToNotifier()
{
expose (this) {
if (!_notifier_can_send) {
// The _notifier endpoint is busy.
if (TraceSwitches.ShowNotifierMessages)
DebugLine("SendToNotifier - notifier is busy, can't send right now");
return;
}
if (_notifier_mux_status_version < _mux_status_version) {
assert _mux_status != null;
if (TraceSwitches.ShowNotifierMessages)
DebugLine("Sending StatusChanged to notifier");
_notifier.SendStatusChanged(_mux_status.ToExchange());
_notifier_can_send = false;
_notifier_mux_status_version = _mux_status_version;
return;
}
}
}
void AckNotifier()
{
if (TraceSwitches.ShowNotifierMessages) {
DebugLine("Received Ack from notifier channel");
}
assert !_notifier_can_send;
_notifier_can_send = true;
SendToNotifier();
}
byte[]! _recvBuffer = new byte[0x800];
int _recvDataLength = 0;
const int SmbFramingHeaderLength = 4; // length is in bytes
void ProcessReceivedData(
[Claims]byte[]! in ExHeap buffer,
int bufferLength)
requires bufferLength >= 0;
requires bufferLength <= buffer.Length;
{
// Check to see whether the fragment buffer contains any data.
// If it does, then we have already received part of an SMB message, but not all of it.
// Assemble the full message before continuing.
//
// If _recvBuffer contains any data (_recvDataLength > 0), then that existing
// partial message has its start aligned with _recvBuffer. So we never have to
// deal with finding the start of a message.
if (_recvDataLength != 0) {
int concatLength = bufferLength + _recvDataLength;
if (TraceSwitches.ShowSmbReassembly) {
DebugLine("concatenating fragment: previous fragment length = 0x{0:x} recv'd data length = 0x{1:x} total length = 0x{2:x}",
_recvDataLength, bufferLength, concatLength);
}
byte[]! in ExHeap concatBuffer = new[ExHeap] byte[concatLength];
Bitter.FromByteArray(concatBuffer, 0, _recvDataLength, _recvBuffer, 0);
Bitter.Copy(concatBuffer, _recvDataLength, bufferLength, buffer, 0);
delete buffer;
buffer = concatBuffer;
bufferLength = concatLength;
_recvDataLength = 0;
}
// scan from 0 to bufferLength within buffer
int parseOffset = 0;
for (;;) {
assert parseOffset <= bufferLength;
int remaining = bufferLength - parseOffset;
if (remaining == 0) {
if (TraceSwitches.ShowSmbReassembly)
DebugLine("no more data in receive buffer");
_receiveBufferPool.Recycle(buffer);
return;
}
bool too_short;
int nextMessageOffset;
int smbLength;
if (SmbFramingHeaderLength < 4) {
if (TraceSwitches.ShowSmbReassembly)
DebugLine("too little data to parse -- storing in fragment buffer");
too_short = true;
smbLength = -1;
nextMessageOffset = -1;
} else {
smbLength = (buffer[parseOffset + 1] << 16)
+ (buffer[parseOffset + 2] << 8)
+ (buffer[parseOffset + 3]);
nextMessageOffset = parseOffset + SmbFramingHeaderLength + smbLength;
if (nextMessageOffset > bufferLength) {
// not enough data for a complete message
too_short = true;
} else {
too_short = false;
}
}
if (too_short) {
if (remaining > _recvBuffer.Length) {
_recvBuffer = new byte[remaining + 0x100];
}
if (TraceSwitches.ShowSmbReassembly)
DebugLine("too little data to parse -- storing 0x{0:x} bytes in fragment buffer", remaining);
Bitter.ToByteArray(buffer, parseOffset, remaining, _recvBuffer, 0);
_recvDataLength = remaining;
_receiveBufferPool.Recycle(buffer);
return;
}
try {
ProcessReceivedMessage(buffer, parseOffset, smbLength + SmbFramingHeaderLength);
} catch (Exception ex) {
Util.ShowExceptionDebug(ex, "Error occurred while parsing received SMB message.");
}
assert nextMessageOffset <= buffer.Length;
assert nextMessageOffset > parseOffset;
if (nextMessageOffset == bufferLength) {
// DebugLine("SMB message ended on TCP message boundary (this is the easy case)");
_receiveBufferPool.Recycle(buffer);
return;
}
parseOffset = nextMessageOffset;
}
}
void Transactor_EnterReceiveState(
/*[Claims]*/ Transactor! tran,
TransactorState tstate)
requires tstate == TransactorState.WaitingReceiveResponse
|| tstate == TransactorState.WaitingReceivePrimaryTransactionResponse
|| tstate == TransactorState.WaitingReceiveSecondaryTransactionResponse;
{
tran.State = tstate;
}
/**
This method processes a single SMB message that has been received from the SMB server.
The caller of this method (ProcessReceivedData) has already located the SMB message
within a (potentially) larger buffer, and has verified the 'SMB' 0xFF signature.
The purpose of this method is to match up a received SMB message to our internal state
for that request (a Transactor), and to advance the state of that transactor. Depending
on the kind of request, the request will either be completed immediately (within the
scope of this method call), or will require further processing.
The buffer containing this SMB message, and potentially other SMB messages.
This method does not claim ownership of the buffer, so we must copy any data
out of the buffer. Zero-copy is desirable, but is for future study.
The index of the first byte of the SMB message within 'buffer'. The message begins with the
SMB "framing" header (4 bytes).
The length of the SMB message within the buffer. This value is NOT the same as the decoded
length value stored in the first four bytes (the framing header).
*/
void ProcessReceivedMessage(byte[]! in ExHeap buffer, int offset, int length)
{
if (TraceSwitches.ShowSmbMessageDetails) {
DebugLine("------------------ RECEIVED SMB MESSAGE --------------------------");
SmbDebug.DumpMessage(buffer, offset, length);
}
if (length < SmbProtocol.SmbHeaderSize) {
DebugLine("WARNING! Protocol violation: Received an SMB header indicates a message that is too small to be meaningful, length = ", length);
DebugLine("The message will be ignored, and the connection will otherwise not be affected.");
return;
}
ref SmbHeader header = ref buffer[offset];
if (!(header.Signature0 == 0xff
&& header.Signature1 == (byte)'S'
&& header.Signature2 == (byte)'M'
&& header.Signature3 == (byte)'B'))
{
DebugLine("ERROR! Protocol violation: Received an SMB message with an invalid SMB signature.");
DebugLine("This usually means the client and server have gotten out of sync on message boundaries.");
DebugLine("The connection will be terminated.");
_connection_failed = true;
return;
}
if (TraceSwitches.ShowSmbMessageOneLineSummaries) {
string! cmdname = SmbDebug.GetSmbMessageSummary(buffer, offset);
if (header.IsError) {
DebugLine("Received <-- " + cmdname + " ERROR: " + header.GetErrorText());
} else {
DebugLine("Received <-- " + cmdname);
}
}
//
// Find the transactor that sent this request by its mux tuple.
// In most circumstances, the mux tuple of a response will match exactly the mux tuple of the request.
// However, for some requests, only part of the tuple will match. This is really annoying.
//
Transactor tran = FindMatchingTransactor(ref header);
if (tran == null) {
// FindMatchingTransactor has already complained enough.
return;
}
Transactor_ProcessReceivedMessage(tran, buffer, offset, length);
}
void Transactor_ProcessReceivedMessage(
[Claims]Transactor! tran,
byte[]! in ExHeap buffer, int offset, int length)
{
Transactor.TrackedData tracked = tran.AcquireData();
try {
ref SmbHeader header = ref buffer[offset];
switch (tran.State)
{
case TransactorState.Ready:
DebugLine(tran, "Received SMB message, but this transactor is idle!");
DebugLine(tran, "Ignoring message.");
break;
case TransactorState.WaitingSendRequest:
case TransactorState.WaitingSendPrimaryTransactionRequest:
case TransactorState.WaitingSendSecondaryTransactionRequest:
DebugLine(tran, "Received SMB message, but this transactor is not expecting a message yet!");
DebugLine(tran, "The transactor is in a 'send' state, not a receive state.");
DebugLine(tran, "I'll probably regret this later, but the message will be ignored.");
break;
case TransactorState.WaitingReceiveResponse:
{
// We have received the response for a "simple" SMB request/response exchange.
// Next, route the received message to the holder of the import endpoint of the
// transactor, and mark our transactor state as "ready".
// DebugLine("Received SMB message matching simple SMB request.");
SmbTransactor.Exp! exp = (!)tracked.AcquireEndpoint();
Transactor_SendResponse_Copy(tran, exp, buffer, offset, length);
Transactor_EnterReadyState(exp, tran);
break;
}
case TransactorState.WaitingReceiveInterimTransactionResponse:
{
// This transactor is performing a SMB_TRANSACTION2 request. The primary transaction
// request has been sent, and now we have received the interim server response. If this
// response is an error, then the transaction fails immediately. If this response is a
// "go ahead" code, then we have permission from the server to send the rest of the
// transaction's request fragments (the "secondary transaction requests").
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, "Received interim server response");
if (header.IsError) {
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, " Server indicates ERROR -- transaction fails immediately.");
SmbTransactor.Exp! exp = (!)tracked.AcquireEndpoint();
Transactor_SendResponse_Copy(tran, exp, buffer, offset, length);
Transactor_EnterReadyState(exp, tran);
} else {
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, " Server says go ahead with request fragments");
Transactor_EnqueueForSend(tran, TransactorState.WaitingSendSecondaryTransactionRequest);
}
break;
}
case TransactorState.WaitingReceivePrimaryTransactionResponse:
{
// We've received the primary response to a SMB_TRANSACTION2 exchange.
// This response may have further responses (fragments), or may be the only response.
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, "Received Primary Transaction Response");
if (header.IsError) {
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, " Response indicates ERROR");
// DebugStub.Break();
//
// We're receiving SMB TRANSACTION2 responses from XP that don't match anything rational,
// when the transaction fails. So for now, we treat any primary transaction response
// that has an error code in the SMB header as the only fragment that we will receive.
//
SmbTransactor.Exp! exp = (!)tracked.AcquireEndpoint();
Transactor_SendResponse_Copy(tran, exp, buffer, offset, length);
Transactor_EnterReadyState(exp, tran);
break;
}
if (length < sizeof(SmbTransaction2Response)) {
// This message is too small to be a valid transaction response.
// It may be an error response from an older SMB server.
if (TraceSwitches.ShowTransactionFragmentationReassembly) {
DebugLine(tran, " Response is too short to be a valid TRANSACTION2 response!");
DebugLine(tran, " Completing request immediately.");
}
SmbTransactor.Exp! exp = (!)tracked.AcquireEndpoint();
Transactor_SendResponse_Copy(tran, exp, buffer, offset, length);
Transactor_EnterReadyState(exp, tran);
} else {
ref SmbTransaction2Response response = ref buffer[offset];
int totalParameterCount = ByteOrder.LeToHost(response.Transaction.TotalParameterCount);
int totalDataCount = ByteOrder.LeToHost(response.Transaction.TotalDataCount);
int parameterOffset = ByteOrder.LeToHost(response.Transaction.ParameterOffset);
int dataOffset = ByteOrder.LeToHost(response.Transaction.DataOffset);
int parameterCount = ByteOrder.LeToHost(response.Transaction.ParameterCount);
int dataCount = ByteOrder.LeToHost(response.Transaction.DataCount);
if (totalParameterCount > parameterCount || totalDataCount > dataCount) {
if (TraceSwitches.ShowTransactionFragmentationReassembly) {
DebugLine(tran, " Response indicates server will fragment transaction response.");
DebugLine(tran, " Parameters: {0}/{1} Data: {2}/{3}",
parameterCount, totalParameterCount, dataCount, totalDataCount);
}
// Compute the length of a buffer that will contain the entire, assembled
// response message.
// int assembledResponseLength = Util.Align4(sizeof(SmbTransaction2Response)) + totalParameterCount + totalDataCount;
int hypotheticalParameterEnd = SmbHeader.FramingHeaderLength + parameterOffset + totalParameterCount;
int hypotheticalDataEnd = SmbHeader.FramingHeaderLength + dataOffset + totalDataCount;
int hypotheticalMessageLength = Util.Align4(Math.Max(hypotheticalParameterEnd, hypotheticalDataEnd));
int assembledResponseLength = hypotheticalMessageLength;
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, " Reassembly buffer length: " + assembledResponseLength);
if (assembledResponseLength < length) {
// Uhhhhh, this doesn't make any sense!
DebugLine(tran, "*** Internal error! Reassembly buffer is shorter than the first packet buffer?!");
DebugStub.Break();
SmbTransactor.Exp! exp = (!)tracked.AcquireEndpoint();
Transactor_SendRequestFailed(tran, exp, SmbTransactionError.InternalError);
Transactor_EnterReadyState(exp, tran);
break;
}
// Allocate the buffer used to reassemble the entire transaction response.
byte[]! in ExHeap reassemblyBuffer = new[ExHeap] byte[assembledResponseLength];
Bitter.Copy(reassemblyBuffer, 0, length, buffer, offset);
#if INCREDIBLY_NOISY
if (TraceSwitches.ShowTransactionFragmentationReassemblyVerbose) {
DebugLine(tran, "Contents of primary transaction response:");
Util.DumpBuffer(buffer, offset, length);
DebugLine(tran, "Contents of parameter fragment portion:");
Util.DumpBuffer(buffer, offset + SmbHeader.FramingHeaderLength + parameterOffset, parameterCount);
DebugLine(tran, "Contents of data fragment portion:");
Util.DumpBuffer(buffer, offset + SmbHeader.FramingHeaderLength + dataOffset, dataCount);
}
#endif
// Set up the transactor state for reassembly.
expose(tracked) {
delete tracked.ResponseReassemblyBuffer;
tracked.ResponseReassemblyBuffer = reassemblyBuffer;
}
tracked.Transaction_ReceivedParameterBytes = parameterCount;
tracked.Transaction_ReceivedDataBytes = dataCount;
tracked.Transaction_ReceivedFragmentCount = 1;
tracked.Transaction_DataOffset = SmbHeader.FramingHeaderLength + dataOffset;
tracked.Transaction_ParameterOffset = SmbHeader.FramingHeaderLength + parameterOffset;
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, " Waiting for more fragments");
Transactor_EnterReceiveState(tran, TransactorState.WaitingReceiveSecondaryTransactionResponse);
// DebugStub.Break();
} else {
if (TraceSwitches.ShowTransactionFragmentationReassembly) {
DebugLine(tran, " Response indicates no fragmentation necessary");
DebugLine(tran, " Completing request immediately.");
}
SmbTransactor.Exp! exp = (!)tracked.AcquireEndpoint();
Transactor_SendResponse_Copy(tran, exp, buffer, offset, length);
Transactor_EnterReadyState(exp, tran);
}
}
break;
}
case TransactorState.WaitingReceiveSecondaryTransactionResponse:
{
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, "Received Secondary Transaction Response");
// Is this the last one? Are we done yet?
ref SmbTransaction2Response response = ref buffer[offset];
int totalParameterCount = ByteOrder.LeToHost(response.Transaction.TotalParameterCount);
int totalDataCount = ByteOrder.LeToHost(response.Transaction.TotalDataCount);
int parameterCount = ByteOrder.LeToHost(response.Transaction.ParameterCount);
int dataCount = ByteOrder.LeToHost(response.Transaction.DataCount);
int parameterDisplacement = ByteOrder.LeToHost(response.Transaction.ParameterDisplacement);
int dataDisplacement = ByteOrder.LeToHost(response.Transaction.DataDisplacement);
int parameterOffset = ByteOrder.LeToHost(response.Transaction.ParameterOffset);
int dataOffset = ByteOrder.LeToHost(response.Transaction.DataOffset);
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, "Reassembling fragment #" + tracked.Transaction_ReceivedFragmentCount);
// DebugStub.Break();
tracked.Transaction_ReceivedFragmentCount++;
expose (tracked)
{
assert tracked.ResponseReassemblyBuffer != null;
// -XXX- Need to validate the offsets/lengths/etc.
// -XXX- Bitter.Copy will validate them, but will throw exceptions.
// DebugStub.Break();
if (parameterCount != 0) {
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, " Reassembled {0}/{1} parameter bytes at offset {2}", parameterCount, totalParameterCount, parameterDisplacement);
Bitter.Copy(
tracked.ResponseReassemblyBuffer,
tracked.Transaction_ParameterOffset + parameterDisplacement,
parameterCount,
buffer,
offset + SmbHeader.FramingHeaderLength + parameterOffset);
if (TraceSwitches.ShowTransactionFragmentationReassemblyVerbose) {
DebugLine(tran, "Contents of parameter fragment portion:");
Util.DumpBuffer(buffer, offset + SmbHeader.FramingHeaderLength + parameterOffset, parameterCount);
}
tracked.Transaction_ReceivedParameterBytes += parameterCount;
}
if (dataCount != 0) {
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, " Reassembled {0}/{1} data bytes at offset {2}", dataCount, totalDataCount, dataDisplacement);
Bitter.Copy(
tracked.ResponseReassemblyBuffer,
tracked.Transaction_DataOffset + dataDisplacement,
dataCount,
buffer,
offset + SmbHeader.FramingHeaderLength + dataOffset);
if (TraceSwitches.ShowTransactionFragmentationReassemblyVerbose) {
DebugLine(tran, "Contents of data fragment portion:");
Util.DumpBuffer(buffer, offset + SmbHeader.FramingHeaderLength + dataOffset, dataCount);
}
tracked.Transaction_ReceivedDataBytes += dataCount;
}
}
bool last = false;
if (parameterCount == 0 && dataCount == 0) {
// Ummmm, the server is crazy.
DebugLine(tran, "*** Received transaction fragment with both ParameterCount == 0 and DataCount == 0!");
DebugLine(tran, "*** I think this is a protocol violation. Completing the transaction.");
last = true;
}
if (tracked.Transaction_ReceivedParameterBytes >= totalParameterCount
&& tracked.Transaction_ReceivedDataBytes >= totalDataCount)
{
if (TraceSwitches.ShowTransactionFragmentationReassembly) {
DebugLine(tran, "We have apparently received all of the transaction response fragments.");
}
if (TraceSwitches.ShowTransactionFragmentationReassemblyVerbose) {
DebugLine(tran, "Reassembled transaction response:");
expose (tracked) { Util.DumpBuffer(tracked.ResponseReassemblyBuffer); }
}
// DebugStub.Break();
last = true;
}
if (last) {
if (TraceSwitches.ShowTransactionFragmentationReassembly)
DebugLine(tran, " Last fragment!");
byte[] in ExHeap! reassembledResponseBuffer = (!)tracked.ExtractResponseReassemblyBuffer();
SmbTransactor.Exp! exp = (!)tracked.AcquireEndpoint();
Transactor_SendResponse_ClaimsBuffer(tran, exp, reassembledResponseBuffer);
Transactor_EnterReadyState(exp, tran);
} else {
Transactor_EnterReceiveState(tran, TransactorState.WaitingReceiveSecondaryTransactionResponse);
}
break;
}
default:
DebugLine(tran, "INTERNAL ERROR -- TRANSACTOR IS IN UNRECOGNIZED STATE!");
DebugStub.Break();
break;
}
} finally {
tran.ReleaseData(tracked);
}
}
void Transactor_SendResponse_ClaimsBuffer(Transactor! tran, SmbTransactor.Exp! exp, [Claims]byte[]! in ExHeap response)
{
exp.SendResponse(response);
}
void Transactor_SendRequestFailed(Transactor! tran, SmbTransactor.Exp! exp, SmbTransactionError error)
{
DebugLine(tran, "Sending RequestFailed: " + error.ToString());
exp.SendRequestFailed(error);
}
void Transactor_SendResponse_Copy(Transactor! tran, SmbTransactor.Exp! exp, byte[]! in ExHeap buffer, int offset, int length)
{
byte[]! in ExHeap response = new[ExHeap] byte[length];
Bitter.Copy(response, 0, length, buffer, offset);
Transactor_SendResponse_ClaimsBuffer(tran, exp, response);
}
void DumpTransactorTable()
{
DebugLine("Contents of _transactors:");
foreach (DictionaryEntry entry in _transactors)
{
MuxTuple tuple = (MuxTuple)entry.Key;
Transactor! tran = (!)(Transactor)entry.Value;
if (tuple != tran.Tuple) {
DebugLine("!!! Transactor key mismatch! Key={0} tran.Tuple={1}", tuple.ToString(), tran.Tuple.ToString());
DebugStub.Break();
}
DebugLine(" " + tran.ToString());
}
}
Transactor FindMatchingTransactor(ref SmbHeader header)
{
MuxTuple tuple = MuxTuple.FromSmbHeader(header);
object boxed_tuple = (object)tuple;
Transactor tran = (Transactor)_transactors[boxed_tuple];
if (tran != null) {
return tran;
}
#if DEBUG
DebugLine("!!! Received SMB message, but the mux tuple does not match any active transactor! tuple=" + MuxTuple.FromSmb(ref header).ToString());
DumpTransactorTable();
DebugStub.Break();
// for dbg only
DebugLine("tuple hash code: 0x{0:x8}", tuple.GetHashCode());
DebugLine("Key search:");
foreach (object! key in _transactors.Keys)
{
bool is_equal_1 = key.Equals(boxed_tuple);
bool is_equal_2 = boxed_tuple.Equals(key);
assert is_equal_1 == is_equal_2;
DebugLine(" key: hash code 0x{0:x8} tuple {1} equal? {2} {3}", key.GetHashCode(), key.ToString(),
Util.YesNoString(is_equal_1),
Util.YesNoString(is_equal_2));
}
#endif
return null;
}
const int SmbMessageMinimumLength = 0x1a;
// 0: 4-byte signature: 0xff 'S' 'M' 'B'
// 4: 1-byte command, 3-byte status
// 8: 1-byte status, 1-byte flags, 2-byte FLAGS2
// c: 8-byte "EXTRA"
// 12: tid
// 14: pid
// 16: uid
// 18: mid
// 1a: -- end --
const int NativeSmbPort = 445;
// if true, then we can send a message on the TCP connection channel.
// if false, then we can only wait for a response.
bool _connectionCanSend;
const int ReceiveBufferSize = 0x400;
// contains _TRef, each of which contains a buffer
// all of the slots within [0,_freeReceiveBufferCount) are non-null, and contain a valid
// (acquirable) byte[] buffer.
//
// the slots from _freeReceiveBuffers[_freeReceiveBufferCount] to _freeReceiveBuffers[length - 1]
// are either null, or have been acquired.
readonly BufferPool! _receiveBufferPool = new BufferPool(0x40, 0x400);
// Because TcpConnectionContract is half-duplex, we have to poll.
const int PollTimeoutMs = 100;
void SendQueuedTransactions()
// requires _connection != null;
{
// if we cannot send on the connection, then this method can't do anything.
expose(this) {
if (_connection == null)
return;
}
if (!_connectionCanSend)
return;
while (_connectionCanSend && _queuedTransactors.Count != 0)
{
Transactor! tran = (!)(Transactor)_queuedTransactors.Dequeue();
assert tran.InQueue;
tran.InQueue = false;
Transactor_SendData(tran);
}
if (_connectionCanSend) {
//
// Issue a read (receive). Because the TcpConnectionContract is half-duplex, we have to use polling
// with timeouts to multiplex send/receive path. Yick.
//
bool wait = true;
// if (_queuedTransactors.Count != 0)
// wait = false;
expose (this) {
if (wait) {
// blocking read
// DebugLine("issuing blocking read (SendPollRead)");
_connection.SendPollRead(PollTimeoutMs);
} else {
// non-blocking read
// DebugLine("issuing nonblocking read (SendRead)");
_connection.SendRead();
}
}
_connectionCanSend = false;
}
}
void Transactor_SendData(Transactor! tran)
requires _connectionCanSend;
{
Transactor.TrackedData tracked = tran.AcquireData();
switch (tran.State)
{
case TransactorState.WaitingSendRequest:
{
// It's a "simple" request. Just send the request body.
// Ownership of the buffer moves from the transactor client, to the mux, to TCP/IP.
byte[]! in ExHeap request;
expose (tracked) {
assert tracked.RequestBuffer != null;
request = tracked.RequestBuffer;
tracked.RequestBuffer = null;
}
if (TraceSwitches.ShowSmbMessageDetails) {
DebugLine(tran, "sending request to TCP/IP, length = " + request.Length);
SmbDebug.DumpMessage(request, 0, request.Length);
}
if (TraceSwitches.ShowSmbMessageOneLineSummaries) {
DebugLine(tran, "Sending --> " + SmbDebug.GetSmbMessageSummary(request, 0));
}
SendData(request);
Transactor_EnterReceiveState(tran, TransactorState.WaitingReceiveResponse);
break;
}
case TransactorState.WaitingSendPrimaryTransactionRequest:
{
// It's a "transaction" style request. Next, we need to determine whether the
// entire transaction request can fit inside a single SMB message. If so, the
// next step is to wait for the transaction response. If not, then the next
// step is to wait for the "interim server response".
expose (tracked) {
assert tracked.RequestBuffer != null;
if (tracked.RequestBuffer.Length <= _MaxSmbRequestLength) {
// The transaction request is small enough to fit in a single SMB request.
byte[]! in ExHeap request = tracked.RequestBuffer;
tracked.RequestBuffer = null;
SendData(request);
Transactor_EnterReceiveState(tran, TransactorState.WaitingReceivePrimaryTransactionResponse);
} else {
// Need to segment the request.
// int primaryRequestLength = _MaxSmbRequestLength;
DebugLine("Transaction segmentation not yet implemented");
DebugStub.Break();
}
}
break;
}
case TransactorState.WaitingSendSecondaryTransactionRequest:
DebugLine("Transaction segmentation not yet implemented");
DebugStub.Break();
break;
}
tran.ReleaseData(tracked);
}
void SendData([Claims]byte[]! in ExHeap buffer)
{
expose(this)
{
assert _connectionCanSend;
assert _connection != null;
_connection.SendWrite(buffer);
// Since we have sent a message on the TCP/IP channel, we cannot send any more
// messages until we receive a message.
_connectionCanSend = false;
}
}
// Contains Transactor.
// All instances in this list are in the state "Active", and are waiting to be transmitted.
readonly Queue/**/! _queuedTransactors = new Queue();
void Transactor_EnqueueForSend(
Transactor! tran,
TransactorState tstate)
requires tstate == TransactorState.WaitingSendRequest
|| tstate == TransactorState.WaitingSendPrimaryTransactionRequest
|| tstate == TransactorState.WaitingSendSecondaryTransactionRequest;
requires !tran.InQueue;
{
if (!(tstate == TransactorState.WaitingSendRequest
|| tstate == TransactorState.WaitingSendPrimaryTransactionRequest
|| tstate == TransactorState.WaitingSendSecondaryTransactionRequest)) {
DebugLine(tran, "ILLEGAL STATE FOR ENQUEUING A TRANSACTOR FOR SEND!");
DebugStub.Break();
throw new Exception("Illegal state for enqueuing a transactor for send!");
}
assert !tran.InQueue;
tran.InQueue = true;
tran.State = tstate;
_queuedTransactors.Enqueue(tran);
}
// Examines the request to start a transaction, validates some parameters and information about the request.
// Moves a request from the idle to the queued state.
void StartRequest(
[Claims]SmbTransactor.Exp:WaitingRequestResponse! tran_exp,
[Claims]Transactor! tran,
[Claims]byte[]! in ExHeap request)
requires tran.State == TransactorState.Ready;
{
int length = request.Length;
if (length < sizeof(SmbHeader)) { // SmbProtocol.SmbHeaderSize) {
DebugLine("SMB message is too small to be valid.");
goto validation_failed;
}
ref SmbHeader header = ref request[0];
int smbLength = length - 4;
header.FrameLength0 = 0;
header.FrameLength1 = (byte)((smbLength >> 16) & 0xff);
header.FrameLength2 = (byte)((smbLength >> 8) & 0xff);
header.FrameLength3 = (byte)(smbLength & 0xff);
//
// Fill in the mux tuple.
// The transactor client doesn't need to do this.
//
header.TreeId = (ushort)tran.Tuple.TreeId;
header.ProcessId = (ushort)tran.Tuple.ProcessId;
header.UserId = (ushort)tran.Tuple.UserId;
header.MuxId = (ushort)tran.Tuple.MuxId;
header.Signature0 = 0xff;
header.Signature1 = (byte)'S';
header.Signature2 = (byte)'M';
header.Signature3 = (byte)'B';
header.Flags1 = (byte)tran._smbFlag1;
header.Flags2 = (ushort)tran._smbFlag2;
// DebugLine(tran, "idle -> queued, request length = " + length.ToString());
tran.Command = (SmbCommand)header.Command;
Transactor.TrackedData tracked = tran.AcquireData();
TransactorState nextstate;
if (tran.Command == SmbCommand.Transaction2) {
// SMB defines the SMB_TRANSACTION2 command, which has a more complex message pattern
// than the simple request/response pattern that most commands use. SMB allows for
// fragmentation of SMB_TRANSACTION2 requests and responses.
// DebugLine("Starting SMB_TRANSACTION2 request");
tracked.TransactionRequestFragmentByteIndex = -1;
nextstate = TransactorState.WaitingSendPrimaryTransactionRequest;
} else {
tracked.TransactionRequestFragmentByteIndex = -1;
nextstate = TransactorState.WaitingSendRequest;
}
expose (tracked) {
// Transfer ownership of the SmbTransactor.Exp endpoint to the Transactor instance.
delete tracked.Exp;
tracked.Exp = tran_exp;
// Transfer ownership of the request buffer from the caller to the Transactor instance.
delete tracked.RequestBuffer;
tracked.RequestBuffer = request;
tracked.RequestLength = length;
}
tran.ReleaseData(tracked);
// Queue this transactor for access to the send path of the socket.
Transactor_EnqueueForSend(tran, nextstate);
return;
validation_failed:
DebugLine(tran, "Rejecting request -- parameter validation failed");
DebugStub.Break();
tran_exp.SendRequestFailed(SmbTransactionError.InvalidParameters);
delete request;
Transactor_EnterReadyState(tran_exp, tran);
return;
}
void Transactor_EnterReadyState([Claims]SmbTransactor.Exp! exp, Transactor! tran)
{
expose(this)
{
// assert tran.Exp == null;
tran.State = TransactorState.Ready;
_readyTransactors.Add(exp, tran);
}
}
void DeleteTransactorState(Transactor! tran)
{
// DebugLine(tran, "deleting transactor");
object boxed_tuple = (object)tran.Tuple;
_transactors.Remove(boxed_tuple);
// -XXX- Need to scan the send queue!
tran.Dispose();
// DebugLine(tran, "deleted transactor");
}
int _nextMuxId;
int AllocateMuxId()
{
int firstMuxId = _nextMuxId;
for (;;) {
int muxId = _nextMuxId;
_nextMuxId++;
if (_nextMuxId > 0xff)
_nextMuxId = 1;
MuxTuple tuple = this._treeTuple;
tuple.MuxId = muxId;
object boxed_tuple = tuple;
if (_transactors[boxed_tuple] == null) {
// DebugLine("Allocated mux ID: " + muxId);
return muxId;
}
if (_nextMuxId == firstMuxId) {
DebugLine("No mux IDs are available.");
return -1;
}
}
}
// This method runs on the mux thread, and it handles the SmbTransportMux.AddTransactor message.
// This message is sent by other threads to this thread, in order to request that the mux create
// a new transactor. A transactor is anything that can send requests to the mux, and then wait
// for responses (a transaction).
//
// This method checks the hashtable of transactors to see if there are any tuple collisions
SmbTransactor.Imp CreateTransactor()
{
SmbTransactor.Imp! transactor_imp;
SmbTransactor.Exp! transactor_exp;
SmbTransactor.NewChannel(out transactor_imp, out transactor_exp);
if (CreateTransactor(transactor_exp)) {
return transactor_imp;
} else {
delete transactor_imp;
return null;
}
}
bool CreateTransactor([Claims]SmbTransactor.Exp! transactor_exp)
{
expose (this)
{
int muxId = AllocateMuxId();
if (muxId == -1) {
DebugLine("Failed to create transactor -- no mux IDs are available.");
delete transactor_exp;
return false;
}
MuxTuple tuple = _treeTuple;
tuple.MuxId = muxId;
object boxed_tuple = (object)tuple;
if (_transactors[boxed_tuple] != null) {
DebugLine("Internal error - mux tuple is in use, even though we called AllocateMuxId!");
delete transactor_exp;
return false;
}
Transactor tran = new Transactor(tuple);
tran._smbFlag1 = (SmbFlag1)0;
tran._smbFlag2 = SmbFlag2.KnowsLongNames
| SmbFlag2.AllPathsAreLongNames
| SmbFlag2.UsingNtStatus
| SmbFlag2.UnicodeStrings;
_transactors[boxed_tuple] = tran;
Transactor_EnterReadyState(transactor_exp, tran);
if (TraceSwitches.ShowTransactorMessages)
DebugLine(tran, "Transactor created");
return true;
}
}
void DebugLine(string! msg)
{
SmbDebug.WriteLine("MUX: " + msg);
}
void DebugLine(string! format, params object[] args)
{
DebugLine(String.Format(format, args));
}
void DebugLine(Transactor! tran, string msg)
{
SmbDebug.WriteLine(tran.DebugPrefix + msg);
}
void DebugLine(Transactor! tran, string format, params object[] args)
{
DebugLine(tran, String.Format(format, args));
}
const string OSName = "MSR Singularity";
const string LanManName = "smbclient";
SmbFlag1 _flags1;
SmbFlag2 _flags2;
int _treeId;
int _processId;
int _muxId;
int _userId;
TcpConnectionContract.Imp:ReadyState! CreateTcpConnection()
{
//
// We invoke this synchronously, so we trust the TCP/IP stack not to delay us for an inordinate time.
//
TcpConnectionContract.Imp:ReadyState! connection = NetUtil.CreateTcpConnection();
return connection;
}
/*
This method handles connecting to an SMB server and negotiating for access to the file service.
It handles name resolution, connecting the transport layer (TCP/IP), authentication, and connecting
to the SMB file service. Once this method returns (successfully), the SMB connection is ready
for general use by transactor clients.
This method (any any methods it calls) do need to pay attention to certain messages from the
SmbClientManager process. Currently, there are several important call paths (such as WaitResponse)
which do NOT receive any messages from the control client. This needs to be fixed.
*/
bool ConnectAndNegotiate()
{
expose(this)
{
DebugLine("--- CONNECTING TO SERVER ---");
SetMuxStatus(SmbClientConnectionStatus.TransportConnecting);
if (_connection != null) {
delete _connection;
_connection = null;
}
//
// -XXX- I *really* want a better solution than this. I don't want to block this thread
// -XXX- while waiting for TCP/IP to connect.
//
try {
TcpConnectionContract.Imp:Connected! connection = NetUtil.ConnectAny(_ServerHostName, NativeSmbPort);
delete _connection;
_connection = connection;
_connectionCanSend = true;
} catch(Exception ex) {
DebugLine("Failed to connect to server.");
Util.ShowExceptionDebug(ex);
SetMuxStatusDisconnected(SmbReasonDisconnected.TransportConnectFailed);
return false;
}
_flags1 =
SmbFlag1.CaseInsensitive
| SmbFlag1.CanonicalizedPaths;
_flags2 =
SmbFlag2.KnowsLongNames
| SmbFlag2.KnowsExtendedAttributes
| SmbFlag2.AllPathsAreLongNames
// | SmbFlag2.AwareOfExtendedSecurity
| SmbFlag2.UsingNtStatus
| SmbFlag2.UnicodeStrings;
_treeId = 1;
_processId = 0xffef;
_userId = 0;
_muxId = 0;
SetMuxStatus(SmbClientConnectionStatus.Negotiating);
try {
Negotiate();
} catch(Exception ex) {
SetMuxStatusDisconnected(SmbReasonDisconnected.NegotiationFailed, ex);
Disconnect();
return false;
}
SetMuxStatus(SmbClientConnectionStatus.Authenticating);
try {
SessionSetupAndConnectBasic();
} catch(Exception ex) {
SetMuxStatusDisconnected(SmbReasonDisconnected.AuthenticationFailed, ex);
Disconnect();
return false;
}
try {
TreeConnect();
} catch(Exception ex) {
SetMuxStatusDisconnected(SmbReasonDisconnected.ResourceConnectFailed, ex);
Disconnect();
return false;
}
DebugLine("Negotiation completed successfully");
SetMuxStatusConnected();
SmbMuxTuple tree;
tree.ProcessId = _processId;
tree.UserId = _userId;
tree.MuxId = _muxId;
tree.TreeId = _treeId;
_treeTuple = tree;
return true;
}
}
void TreeConnect()
{
ByteWriter data = new ByteWriter();
string! unc = Program.UncPath;
// yes, SMB mixes ASCII and UNICODE! yes, it drives me crazy!
// "password", really just alignment padding
data.WriteZero(1);
WriteSmbStringUnicode(data, unc, true);
// "?????" means "any type of service"
WriteSmbStringAscii(data, "?????", true);
int messageLength = sizeof(SmbTreeConnectAndX) + data.Length;
byte[]! in ExHeap request_encoded = new[ExHeap] byte[messageLength];
try {
ref SmbTreeConnectAndX request = ref request_encoded[0];
request = new SmbTreeConnectAndX();
PrepareHeader(ref request.Header, SmbCommand.TreeConnectAndX, 4);
request.AndXCommand = 0xff; // no AndX
request.AndXOffset = 0;
request.AndXReserved = 0;
request.ByteCount = (ushort)data.Length;
request.PasswordLength = 1;
data.CopyTo(request_encoded, sizeof(SmbTreeConnectAndX));
} catch {
delete request_encoded;
throw;
}
if (TraceSwitches.ShowNegotiation)
DebugLine("Sending TreeConnectAndX");
byte[]! in ExHeap response_encoded = ExecuteSynchronousCommand(request_encoded);
try {
ref SmbHeader response = ref response_encoded[0];
if (response.IsError) {
DebugLine("Received TREE CONNECT response - FAILURE");
DebugLine("Error: " + response.GetErrorText());
throw new Exception("Tree connect FAILED: " + response.GetErrorText());
}
if (TraceSwitches.ShowNegotiation)
DebugLine("Received TREE CONNECT response - successful");
// I don't like this. We receive a different mux tuple from the server in response
// to TreeConnectAndX (the tree ID is different; it has been assigned by the server).
// So for now, we just change our tree ID. This means that the SMB client is restricted
// to servicing a single tree connection. I want this to change in the future, so that
// a single SMB client connection can carry requests for multiple different tree connections.
// This may be a dream, maybe even a bad one, but I want readers to be aware of this issue.
this._treeId = response.TreeId;
} finally {
delete response_encoded;
}
}
void PrepareHeader(ref SmbHeader header, SmbCommand command, int parameterCount)
requires parameterCount >= 0;
requires parameterCount < 256;
{
header.SetSignature();
header.Command = (byte)command;
header.ParameterCount = (byte)parameterCount;
header.TreeId = (ushort)_treeId;
header.UserId = (ushort)_userId;
header.ProcessId = (ushort)_processId;
header.MuxId = (ushort)_muxId;
header.Flags1 = (byte)_flags1;
header.Flags2 = (ushort)_flags2;
}
// This method waits to receive one SMB message from the server.
// This is for use ONLY during initial negotiation.
byte[]! in ExHeap WaitResponse()
// requires _connection != null;
{
expose(this)
{
assert _connection != null;
for (;;) {
if (_connectionCanSend) {
// Yes, we "poll" with a time-out.
// I don't like this, but it's our only option until we have full duplex channels,
// or a 2-channel TCP implementation.
_connection.SendPollRead(100);
_connectionCanSend = false;
}
switch receive
{
case _connection.Data(byte[]! in ExHeap data):
assert !_connectionCanSend;
_connectionCanSend = true;
// DebugLine("Received data on socket: length = " + data.Length);
byte[] in ExHeap response = ProcessReceivedData(data);
if (response != null)
return response;
break;
case _connection.ChannelClosed():
throw new Exception("The TCP channel closed before receiving a message.");
case _connection.ConnectionClosed():
throw new Exception("The TCP connection closed before receiving a message.");
case _connection.NoData():
// We get one of these for EVERY polled read.
// Which is quite noisy.
// DebugLine("no data from socket");
_connectionCanSend = true;
break;
case _notifier.Ack():
AckNotifier();
break;
// -XXX- Should add more handlers here, to pay attention to control messages.
}
}
}
}
byte[] in ExHeap ProcessReceivedData([Claims]byte[]! in ExHeap buffer)
{
// Check to see whether the fragment buffer contains any data.
// If it does, then we have already received part of an SMB message, but not all of it.
// Assemble the full message before continuing.
if (_recvDataLength != 0)
{
// Already have some data.
// Concatenate the existing data and the newly-received data, and attempt to parse.
// This is inefficient.
int concatLength = buffer.Length + _recvDataLength;
DebugLine("concatenating fragment: previous fragment length = 0x{0:x} recv'd data length = 0x{1:x} total length = 0x{2:x}",
_recvDataLength, buffer.Length, concatLength);
byte[]! in ExHeap concatBuffer = new[ExHeap] byte[concatLength];
// Bitter.Copy(concatBuffer, 0, _recvDataLength, _recvBuffer, 0);
Bitter.FromByteArray(concatBuffer, 0, _recvDataLength, _recvBuffer, 0);
Bitter.Copy(concatBuffer, _recvDataLength, buffer.Length, buffer, 0);
_recvDataLength = 0;
delete buffer;
buffer = concatBuffer;
}
//
// Have we received enough data to parse a complete message?
//
if (buffer.Length >= sizeof(SmbHeader)) {
ref SmbHeader header = ref buffer[0];
int messageLength = header.TotalMessageLength;
int remaining = buffer.Length - messageLength;
if (remaining > 0) {
DebugLine("Buffer received from TCP contains SMB message (plus " + remaining + " bytes)");
byte[]! in ExHeap response = new[ExHeap] byte[messageLength];
Bitter.Copy(response, 0, messageLength, buffer, 0);
// Store the remaining data in the fragment buffer.
AppendReceiveBuffer(buffer, messageLength, remaining);
delete buffer;
ValidateSmbMessage(response);
return response;
} else if (remaining == 0) {
// Exact fit.
// This will probably be common.
// DebugLine("Buffer received from TCP contains exactly one SMB message (easy case).");
ValidateSmbMessage(buffer);
return buffer;
} else {
// Not enough data for a complete message.
// Store it in the fragment buffer.
DebugLine("Buffer received from TCP does not contain the entire SMB message (need " + (-remaining) + " bytes).");
AppendReceiveBuffer(buffer, 0, buffer.Length);
delete buffer;
return null;
}
} else {
// Not enough data for a complete message.
// Store it in the fragment buffer.
DebugLine("Buffer received from TCP does not contain the entire SMB message (header is incomplete).");
AppendReceiveBuffer(buffer, 0, buffer.Length);
delete buffer;
return null;
}
}
/*
This method copies data from a buffer to the end of the "fragment buffer" in the response context.
*/
void AppendReceiveBuffer(
byte[]! in ExHeap appendData,
int appendOffset,
int length)
requires length >= 0;
requires appendOffset >= 0;
requires appendOffset <= appendData.Length;
{
if (length == 0)
return;
int finalLength = _recvDataLength + length;
if (finalLength > _recvBuffer.Length)
{
int newLength = finalLength * 3 / 2 + 0x20;
DebugLine("Growing receive buffer from " + _recvBuffer.Length + " to " + newLength + " bytes");
byte[] newRecvBuffer = new byte[newLength];
Buffer.BlockCopy(_recvBuffer, 0, newRecvBuffer, 0, _recvDataLength);
// delete _recvBuffer;
_recvBuffer = newRecvBuffer;
}
assert _recvDataLength <= _recvBuffer.Length;
assert finalLength <= _recvBuffer.Length;
Bitter.ToByteArray(appendData, appendOffset, length, _recvBuffer, _recvDataLength);
_recvDataLength = finalLength;
}
void ValidateSmbMessage(byte[]! in ExHeap buffer)
{
if (buffer.Length < sizeof(SmbHeader))
throw new Exception("The message is too short to be a valid SMB message.");
ref SmbHeader header = ref buffer[0];
int messageLength = header.TotalMessageLength;
if (!header.IsSignatureValid()) {
DebugLine("ERROR! Protocol violation: Received an SMB message with an invalid SMB signature.");
DebugLine("This usually means the client and server have gotten out of sync on message boundaries.");
DebugLine("The connection will be terminated.");
throw new Exception("The message does not have a valid SMB signature.");
}
if (buffer.Length < messageLength)
throw new Exception("The message is too short to contain the message whose length is specified in the message header.");
}
#if false
void NegotiateUsingExtendedSecurity()
{
//
// Build the NTLM challenge and send it encapsulated within a SessionSetupAndTreeConnect request.
//
byte[]! hello = _supplicant.GetNegotiate();
DebugLine("Sending NTLM 'hello' blob:");
Util.DumpBuffer(hello);
SendSessionSetupAndConnect(transactor, hello);
//
// Next, we wait for the response to the SessionSetupAndTreeConnect.
// It should contain the NTLM challenge.
//
DebugLine("Waiting for NTLM challenge...");
byte[]! security_challenge;
WaitSessionSetupResponse(transactor, out security_challenge);
DebugLine("Received NTLM challenge:");
Util.DumpBuffer(security_challenge);
DebugStub.Break();
//
// Have the security context generate the response and send it in another SessionSetupAndX request.
//
byte[]! security_response = _supplicant.GetResponse(security_challenge);
DebugLine("Sending this NTLM response:");
Util.DumpBuffer(security_response);
SendSessionSetupAndConnect(transactor, security_response);
//
// Now wait for the final response.
//
byte[]! security_message_ignored;
WaitSessionSetupResponse(transactor, out security_message_ignored);
DebugLine("Final security response received.");
}
#endif
/*
This method sends the SMB NEGOTIATE message to the server, and waits for the response.
If the message cannot be sent, or the response received indicates an error, then this
method will throw an exception.
*/
void Negotiate()
{
string![] dialects = { SmbDialect.NTLM012 };
ByteWriter! data = new ByteWriter();
foreach (string dialect in dialects)
{
data.Write(2);
byte[]! dialectbytes = Encoding.ASCII.GetBytes(dialect);
data.Write(dialectbytes);
data.Write(0);
}
SmbNegotiateRequest request = new SmbNegotiateRequest();
PrepareHeader(ref request.Header, SmbCommand.Negotiate, 0);
request.ByteCount = (ushort)data.Length;
byte[]! in ExHeap encoded_request = new[ExHeap] byte[sizeof(SmbNegotiateRequest) + data.Length];
ref SmbNegotiateRequest ref_request = ref encoded_request[0];
ref_request = request;
data.CopyTo(encoded_request, sizeof(SmbNegotiateRequest));
DebugLine("Sending NEGOTIATE request");
byte[]! in ExHeap response_encoded = ExecuteSynchronousCommand(encoded_request);
try {
ref SmbHeader header = ref response_encoded[0];
if (header.IsError) {
DebugLine("Received NEGOTIATE RESPONSE - server indicates FAILURE");
DebugLine("Error: " + header.GetErrorText());
throw new Exception("SMB server rejected NEGOTIATE: " + header.GetErrorText());
}
if (TraceSwitches.ShowNegotiation) {
DebugLine("Received NEGOTIATE RESPONSE - successful");
}
if (header.ParameterCount != 17) {
DebugLine("NEGOTIATE response contains the wrong number of parameter words! Cannot use!");
throw new Exception("NEGOTIATE response contains the wrong number of parameter words");
}
ref SmbNegotiateResponse17 response = ref response_encoded[0];
//DebugLine("EncryptionKeyLength = " + response.EncryptionKeyLength.ToString());
byte[]! response_data = new byte[response.ByteCount];
Bitter.ToByteArray(response_encoded, sizeof(SmbNegotiateResponse17), response.ByteCount, response_data, 0);
ByteReader! response_data_reader = new ByteReader(response_data);
byte[]! key = response_data_reader.ReadBytes(response.EncryptionKeyLength);
this._serverLmChallenge = key;
string! serverDomainName = ReadStringUnicode(response_data_reader);
string! serverHostName = ReadStringUnicode(response_data_reader);
// DebugLine("Server Domain Name = " + serverDomainName);
// DebugLine("Server Host Name = " + serverHostName);
_ReceivedServerDomainNameFromNegotiate = serverDomainName;
_ReceivedServerMachineName = serverHostName;
} finally {
delete response_encoded;
}
}
static string! ReadStringUnicode(ByteReader! reader)
{
StringBuilder buffer = new StringBuilder();
for (; ; )
{
byte low = reader.ReadByte();
byte high = reader.ReadByte();
if (low == 0 && high == 0)
return buffer.ToString();
char c = (char)(low + (((ushort)high) << 8));
buffer.Append(c);
}
}
// This field contains the LanMan (LM) challenge, received from the server.
// We use this value to compute the challenge response, which is what the client
// uses to prove knowledge of the user's password without revealing the password
// in the clear.
byte[] _serverLmChallenge;
#if false
void SendSessionSetupAndConnect(SmbTransactor.Imp! transactor, byte[]! security_blob)
requires security_blob.Length <= ushort.MaxValue;
{
// build header
// don't bother with setting tuple in header, mux will set that for us
SmbSessionSetupAndXRequest13 request = new SmbSessionSetupAndXRequest13();
request.Header.Command = (byte)SmbCommand.SessionSetupAndX;
request.Header.Flags1 = (byte)_flags1;
request.Header.Flags2 = (ushort)_flags2;
// build parameters block
request.Header.ParameterCount = 13;
request.AndXCommand = 0xff;
request.AndXPadding = 0;
request.AndXOffset = 0;
request.MaxBufferSize = 2048;
request.MaxMpxCount = 10;
request.VcNumber = 1;
request.SessionKey = 0;
request.CaseInsensitivePasswordLength = (ushort)lm_response.Length;
request.CaseSensitivePasswordLength = (ushort)nt_response.Length;
request.Reserved = 0;
SmbCapability capabilities =
SmbCapability.Unicode
| SmbCapability.LargeFiles
| SmbCapability.NtSmbs
| SmbCapability.NtFind
| SmbCapability.NtStatus
| SmbCapability.Level2Oplocks;
request.Capabilities = (uint)capabilities;
// build message data
ByteWriter data = new ByteWriter();
data.Write(security_blob);
WriteSmbUnicodeString(data, SmbClientParameters.Username, true);
WriteSmbUnicodeString(data, SmbClientParameters.DomainName, true);
WriteSmbUnicodeString(data, OSName);
WriteSmbUnicodeString(data, LanManName);
int data_length = data.Length;
request.ByteCount = (ushort)data.Length;
int total_length = sizeof(SmbSessionSetupAndXRequest13)
+ data_length; // security_blob.Length;
byte[]! in ExHeap msg = new[ExHeap] byte[total_length];
try {
ref SmbSessionSetupAndXRequest13 request_ref = ref msg[0];
// store values into the ex buffer
request_ref = request;
data.CopyTo(msg, sizeof(SmbSessionSetupAndXRequest13));
DebugLine("Sending SessionSetupAndX message:");
} catch {
delete msg;
throw;
}
transactor.SendRequest(msg, msg.Length);
}
#endif
byte[]! GetResponse(NtlmSupplicantContract.Imp! ntlm_supplicant, NtlmResponseType type, byte[]! challenge)
{
byte[]! in ExHeap exchallenge = Bitter.FromByteArray(challenge);
ntlm_supplicant.SendGetResponse(exchallenge, type);
switch receive {
case ntlm_supplicant.Response(exresponse):
byte[]! response = Bitter.ToByteArray(exresponse);
delete exresponse;
return response;
case ntlm_supplicant.RequestFailed(error):
throw new CredentialsManagerException("NTLM supplicant failed to return response.", error);
}
}
// This method contacts the Credentials Manager service.
// Therefore, it CAN block indefinitely, depending on behavior outside of this process.
// Should review this.
void GetChallengeResponses(
out string! username,
out string! domain,
out byte[]! nt_response,
out byte[]! lm_response)
{
// We have already received the NTLM challenge (a simple 8-byte nonce) from the SMB
// server in the response to the NEGOTIATE message.
byte[] challenge = _serverLmChallenge;
if (challenge == null)
throw new Exception("The server LM challenge is null. Failed to sample challenge from negotiation response.");
NtlmSupplicantContract.Imp! ntlm_supplicant;
NtlmSupplicantContract.Exp! ntlm_supplicant_exp;
NtlmSupplicantContract.NewChannel(out ntlm_supplicant, out ntlm_supplicant_exp);
string! credentialsName;
string! tag;
try {
try {
string creds = Program.CredentialsName;
if (creds == null || creds.Length == 0) {
// The user did not provide any explicit credentials to use for this SMB client.
// All the Credentials Manager to select the credentials to use.
if (TraceSwitches.ShowAuthenticationMessages) {
DebugLine("No explicitly-selected credentials provided. Using Credentials Manager to select credentials...");
DebugLine("Creating NTLM supplicant...");
}
//
// -XXX- NOTE! We received two values from the server in the response to the NEGOTIATE message.
// -XXX- We should be careful with those values; since they came from the network, they could
// -XXX- contain any garbage, potentially evil. We pass these to the CM service. So either
// -XXX- the SMB client process and/or the CM service should perform some extreme skepticism
// -XXX- on these values.
//
CredentialsManager.CreateSupplicantForProtocol(
"smb",
(!)_ReceivedServerMachineName, // _ServerHostName,
AuthenticationProtocolNames.Ntlm,
(!)_ReceivedServerDomainNameFromNegotiate,
ntlm_supplicant_exp,
out credentialsName,
out tag);
if (TraceSwitches.ShowAuthenticationMessages)
DebugLine("Created NTLM supplicant, selected credentials: " + credentialsName);
} else {
// The user provided explicit credentials for this SMB client.
// Use the Credentials Manager to create a supplicant, but don't use it
// to select the credentials to use.
if (TraceSwitches.ShowAuthenticationMessages)
DebugLine("Explicit credentials: " + Program.CredentialsName);
credentialsName = Program.CredentialsName;
tag = Program.CredentialsTag;
CredentialsManager.CreateSupplicant(
AuthenticationProtocolNames.Ntlm,
credentialsName,
tag,
ntlm_supplicant_exp);
if (TraceSwitches.ShowAuthenticationMessages)
DebugLine("Supplicant successfully created.");
}
ntlm_supplicant.RecvSuccess();
_SelectedCredentialsName = credentialsName;
} catch (Exception ex) {
throw new Exception("Negotiation failed, because an authentication supplicant could not be created.", ex);
}
int separator = credentialsName.IndexOf('\\');
if (separator != -1) {
username = credentialsName.Substring(separator + 1);
domain = credentialsName.Substring(0, separator);
} else {
username = credentialsName;
domain = ".";
}
lm_response = GetResponse(ntlm_supplicant, NtlmResponseType.LanMan, challenge);
nt_response = GetResponse(ntlm_supplicant, NtlmResponseType.WindowsNt, challenge);
} finally {
delete ntlm_supplicant;
}
}
void SessionSetupAndConnectBasic()
{
// DebugLine("Using basic NTLM negotiation (not NTLMSSP)");
byte[]! lm_response;
byte[]! nt_response;
string! username;
string! domain;
GetChallengeResponses(
out username,
out domain,
out nt_response,
out lm_response);
// build header
// don't bother with setting tuple in header, mux will set that for us
SmbSessionSetupAndXRequest13 request = new SmbSessionSetupAndXRequest13();
PrepareHeader(ref request.Header, SmbCommand.SessionSetupAndX, 13);
// build parameters block
request.AndXCommand = 0xff;
request.AndXPadding = 0;
request.AndXOffset = 0;
request.MaxBufferSize = 2048;
request.MaxMpxCount = 10;
request.VcNumber = 1;
request.SessionKey = 0;
request.CaseInsensitivePasswordLength = (ushort)lm_response.Length;
request.CaseSensitivePasswordLength = (ushort)nt_response.Length;
request.Reserved = 0;
SmbCapability capabilities =
SmbCapability.Unicode
| SmbCapability.LargeFiles
| SmbCapability.NtSmbs
| SmbCapability.NtFind
| SmbCapability.NtStatus
| SmbCapability.Level2Oplocks;
request.Capabilities = (uint)capabilities;
// build message data
ByteWriter data = new ByteWriter();
// data.Write(security_blob);
data.Write(lm_response);
data.Write(nt_response);
data.Write((byte)0); // alignment padding
WriteSmbStringUnicode(data, username, true);
WriteSmbStringUnicode(data, domain, true);
WriteSmbStringUnicode(data, OSName, true);
WriteSmbStringUnicode(data, LanManName, true);
int data_length = data.Length;
//DebugLine("data block:");
//Util.DumpBuffer(data.ToArray());
request.ByteCount = (ushort)data.Length;
int total_length = sizeof(SmbSessionSetupAndXRequest13)
+ data_length; // security_blob.Length;
request.Header.TotalMessageLength = total_length;
byte[]! in ExHeap request_encoded = new[ExHeap] byte[total_length];
try {
ref SmbSessionSetupAndXRequest13 request_ref = ref request_encoded[0];
// store values into the ex buffer
request_ref = request;
data.CopyTo(request_encoded, sizeof(SmbSessionSetupAndXRequest13));
} catch {
delete request_encoded;
throw;
}
if (TraceSwitches.ShowNegotiationVerbose)
DebugLine("Sending SESSION SETUP");
byte[]! in ExHeap response_encoded = ExecuteSynchronousCommand(request_encoded);
try {
ref SmbHeader responseHeader = ref response_encoded[0];
if (responseHeader.IsError) {
// The usual reason is that authentication failed (bogus credentials).
// DebugStub.Break();
DebugLine("Received SESSION SETUP response message, and server indicates FAILURE.");
DebugLine("Error: " + responseHeader.GetErrorText());
throw new Exception("The SessionSetupAndTreeConnect message failed. The server rejected the request with an error: " + responseHeader.GetErrorText());
}
if (TraceSwitches.ShowNegotiation)
DebugLine("Received SESSION SETUP response (successful)");
// ref SmbSessionSetupResponse response = ref response_encoded[0];
ExByteReader reader = new ExByteReader(0, response_encoded.Length);
reader.Skip(sizeof(SmbSessionSetupAndXResponse) + 1); // +1 for alignment
string! nativeOs = reader.ReadStringUnicode(response_encoded);
string! nativeLanMan = reader.ReadStringUnicode(response_encoded);
string! primaryDomain = reader.ReadStringUnicode(response_encoded);
_ReceivedServerOperatingSystem = nativeOs;
_ReceivedServerPrimaryDomainName = primaryDomain;
// The response contains a DIFFERENT user ID (in the mux tuple) than the request.
// This is how we learn the user ID, so that we can send it in future requests.
_userId = responseHeader.UserId;
if (TraceSwitches.ShowNegotiationVerbose) {
DebugLine(" Native OS: " + nativeOs);
DebugLine(" Native Lan Man: " + nativeLanMan);
DebugLine(" Primary Domain: " + primaryDomain);
DebugLine(" UserID = 0x{0:x4}", responseHeader.UserId);
}
} finally {
delete response_encoded;
}
}
static void WriteSmbStringUnicode(ByteWriter! writer, string! text, bool terminate)
{
// writer.Write((byte)SmbEncodingFormat.String);
// writer.Write((byte)0);
for (int i = 0; i < text.Length; i++) {
char c = text[i];
writer.Write((byte)(c & 0xff));
writer.Write((byte)(c >> 8));
}
if (terminate)
{
writer.Write((byte)0);
writer.Write((byte)0);
}
}
static void WriteSmbStringAscii(ByteWriter! writer, string! text, bool terminate)
{
// writer.Write((byte)SmbEncodingFormat.String);
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
writer.Write((byte)(c & 0xff));
}
if (terminate)
{
writer.Write((byte)0);
}
}
byte[]! in ExHeap ExecuteSynchronousCommand([Claims]byte[]! in ExHeap request)
{
SendSmbMessage(request);
return WaitResponse();
}
void SendSmbMessage([Claims]byte[]! in ExHeap request)
// requires _connection != null;
{
if (request.Length < sizeof(SmbHeader))
throw new Exception("Message is too short to be a valid SMB message.");
ref SmbHeader header = ref request[0];
header.SetSignature();
header.TotalMessageLength = request.Length;
SendDataSynchronous(request);
}
void SendDataSynchronous([Claims]byte[]! in ExHeap data)
// requires _connection != null;
{
expose(this)
{
assert _connection != null;
_connection.SendWrite(data);
switch receive
{
case _connection.OK():
break;
case _connection.ChannelClosed():
throw new Exception("TCP channel closed");
case _connection.ConnectionClosed():
throw new Exception("TCP connection closed");
}
}
}
enum SmbEncodingFormat
{
DataBlock = 1,
Dialect = 2, // null terminated string, always 8-bit encoding, never unicode
Pathname = 3, // null terminated string
String = 4, // null terminated string
}
void ITracked.Release() {}
void ITracked.Acquire() {}
void ITracked.Expose() {}
void ITracked.UnExpose() {}
public void Dispose()
{
_readyTransactors.Dispose();
delete _namespace_provider;
delete _connection;
delete _notifier;
_control_clients.Dispose();
delete _controller;
}
}
}