////////////////////////////////////////////////////////////////////////////////
//
// 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) {
if (TraceSwitches.ShowNotifierMessages)
DebugLine("Sending StatusChanged to notifier");
assert _mux_status != null;
_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 respones 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 NYI");
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 negotation.
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;
}
}
}