mirror of
https://github.com/TalAloni/SMBLibrary.git
synced 2025-08-05 15:31:29 +02:00
Added Support for NotifyChange and Cancel if the underlying file store supports it
This commit is contained in:
parent
7e3df3f694
commit
5b4207aedd
10 changed files with 331 additions and 17 deletions
|
@ -5,6 +5,7 @@ namespace SMBLibrary
|
|||
{
|
||||
STATUS_SUCCESS = 0x00000000,
|
||||
STATUS_PENDING = 0x00000103,
|
||||
STATUS_NOTIFY_CLEANUP = 0x0000010B,
|
||||
STATUS_NOTIFY_ENUM_DIR = 0x0000010C,
|
||||
SEC_I_CONTINUE_NEEDED = 0x00090312,
|
||||
STATUS_OBJECT_NAME_EXISTS = 0x40000000,
|
||||
|
|
|
@ -185,6 +185,8 @@
|
|||
<Compile Include="RPC\Structures\ResultList.cs" />
|
||||
<Compile Include="RPC\Structures\SyntaxID.cs" />
|
||||
<Compile Include="Server\ConnectionManager.cs" />
|
||||
<Compile Include="Server\ConnectionState\SMB1AsyncContext.cs" />
|
||||
<Compile Include="Server\ConnectionState\SMB2AsyncContext.cs" />
|
||||
<Compile Include="Server\ConnectionState\ConnectionState.cs" />
|
||||
<Compile Include="Server\ConnectionState\OpenFileObject.cs" />
|
||||
<Compile Include="Server\ConnectionState\OpenSearch.cs" />
|
||||
|
|
22
SMBLibrary/Server/ConnectionState/SMB1AsyncContext.cs
Normal file
22
SMBLibrary/Server/ConnectionState/SMB1AsyncContext.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
/* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
|
||||
*
|
||||
* You can redistribute this program and/or modify it under the terms of
|
||||
* the GNU Lesser Public License as published by the Free Software Foundation,
|
||||
* either version 3 of the License, or (at your option) any later version.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SMBLibrary.Server
|
||||
{
|
||||
internal class SMB1AsyncContext
|
||||
{
|
||||
public ushort UID; // User ID
|
||||
public ushort TID; // Tree ID
|
||||
public uint PID; // Process ID
|
||||
public ushort MID; // Multiplex ID
|
||||
public ushort FileID;
|
||||
public SMB1ConnectionState Connection;
|
||||
public object IORequest;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,8 @@ namespace SMBLibrary.Server
|
|||
|
||||
// Key is PID (PID MUST be unique within an SMB connection)
|
||||
private Dictionary<uint, ProcessStateObject> m_processStateList = new Dictionary<uint, ProcessStateObject>();
|
||||
|
||||
private List<SMB1AsyncContext> m_pendingRequests = new List<SMB1AsyncContext>();
|
||||
|
||||
public SMB1ConnectionState(ConnectionState state) : base(state)
|
||||
{
|
||||
|
@ -212,5 +214,63 @@ namespace SMBLibrary.Server
|
|||
{
|
||||
m_processStateList.Remove(processID);
|
||||
}
|
||||
|
||||
public SMB1AsyncContext CreateAsyncContext(ushort userID, ushort treeID, uint processID, ushort multiplexID, ushort fileID, SMB1ConnectionState connection)
|
||||
{
|
||||
SMB1AsyncContext context = new SMB1AsyncContext();
|
||||
context.UID = userID;
|
||||
context.TID = treeID;
|
||||
context.MID = multiplexID;
|
||||
context.PID = processID;
|
||||
context.FileID = fileID;
|
||||
context.Connection = connection;
|
||||
lock (m_pendingRequests)
|
||||
{
|
||||
m_pendingRequests.Add(context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public SMB1AsyncContext GetAsyncContext(ushort userID, ushort treeID, uint processID, ushort multiplexID)
|
||||
{
|
||||
lock (m_pendingRequests)
|
||||
{
|
||||
int index = IndexOfAsyncContext(userID, treeID, processID, multiplexID);
|
||||
if (index >= 0)
|
||||
{
|
||||
return m_pendingRequests[index];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void RemoveAsyncContext(SMB1AsyncContext context)
|
||||
{
|
||||
lock (m_pendingRequests)
|
||||
{
|
||||
int index = IndexOfAsyncContext(context.UID, context.TID, context.PID, context.MID);
|
||||
if (index >= 0)
|
||||
{
|
||||
m_pendingRequests.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int IndexOfAsyncContext(ushort userID, ushort treeID, uint processID, ushort multiplexID)
|
||||
{
|
||||
for (int index = 0; index < m_pendingRequests.Count; index++)
|
||||
{
|
||||
SMB1AsyncContext context = m_pendingRequests[index];
|
||||
if (context.UID == userID &&
|
||||
context.TID == treeID &&
|
||||
context.PID == processID &&
|
||||
context.MID == multiplexID)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
22
SMBLibrary/Server/ConnectionState/SMB2AsyncContext.cs
Normal file
22
SMBLibrary/Server/ConnectionState/SMB2AsyncContext.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
/* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
|
||||
*
|
||||
* You can redistribute this program and/or modify it under the terms of
|
||||
* the GNU Lesser Public License as published by the Free Software Foundation,
|
||||
* either version 3 of the License, or (at your option) any later version.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SMBLibrary.SMB2;
|
||||
|
||||
namespace SMBLibrary.Server
|
||||
{
|
||||
internal class SMB2AsyncContext
|
||||
{
|
||||
public ulong AsyncID;
|
||||
public FileID FileID;
|
||||
public SMB2ConnectionState Connection;
|
||||
public ulong SessionID;
|
||||
public uint TreeID;
|
||||
public object IORequest;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,9 @@ namespace SMBLibrary.Server
|
|||
// Key is SessionID
|
||||
private Dictionary<ulong, SMB2Session> m_sessions = new Dictionary<ulong, SMB2Session>();
|
||||
private ulong m_nextSessionID = 1;
|
||||
// Key is AsyncID
|
||||
private Dictionary<ulong, SMB2AsyncContext> m_pendingRequests = new Dictionary<ulong, SMB2AsyncContext>();
|
||||
private ulong m_nextAsyncID = 1;
|
||||
|
||||
public SMB2ConnectionState(ConnectionState state) : base(state)
|
||||
{
|
||||
|
@ -96,5 +99,61 @@ namespace SMBLibrary.Server
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ulong? AllocateAsyncID()
|
||||
{
|
||||
for (ulong offset = 0; offset < UInt64.MaxValue; offset++)
|
||||
{
|
||||
ulong asyncID = (ulong)(m_nextAsyncID + offset);
|
||||
if (asyncID == 0 || asyncID == 0xFFFFFFFF)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!m_pendingRequests.ContainsKey(asyncID))
|
||||
{
|
||||
m_nextAsyncID = (ulong)(asyncID + 1);
|
||||
return asyncID;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public SMB2AsyncContext CreateAsyncContext(FileID fileID, SMB2ConnectionState connection, ulong sessionID, uint treeID)
|
||||
{
|
||||
ulong? asyncID = AllocateAsyncID();
|
||||
if (asyncID == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
SMB2AsyncContext context = new SMB2AsyncContext();
|
||||
context.AsyncID = asyncID.Value;
|
||||
context.FileID = fileID;
|
||||
context.Connection = connection;
|
||||
context.SessionID = sessionID;
|
||||
context.TreeID = treeID;
|
||||
lock (m_pendingRequests)
|
||||
{
|
||||
m_pendingRequests.Add(asyncID.Value, context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public SMB2AsyncContext GetAsyncContext(ulong asyncID)
|
||||
{
|
||||
SMB2AsyncContext context;
|
||||
lock (m_pendingRequests)
|
||||
{
|
||||
m_pendingRequests.TryGetValue(asyncID, out context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public void RemoveAsyncContext(SMB2AsyncContext context)
|
||||
{
|
||||
lock (m_pendingRequests)
|
||||
{
|
||||
m_pendingRequests.Remove(context.AsyncID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,21 @@ namespace SMBLibrary.Server.SMB1
|
|||
{
|
||||
internal static void ProcessNTCancelRequest(SMB1Header header, NTCancelRequest request, ISMBShare share, SMB1ConnectionState state)
|
||||
{
|
||||
SMB1Session session = state.GetSession(header.UID);
|
||||
SMB1AsyncContext context = state.GetAsyncContext(header.UID, header.TID, header.PID, header.MID);
|
||||
if (context != null)
|
||||
{
|
||||
NTStatus status = share.FileStore.Cancel(context.IORequest);
|
||||
OpenFileObject openFile = session.GetOpenFileObject(context.FileID);
|
||||
if (openFile != null)
|
||||
{
|
||||
state.LogToServer(Severity.Information, "Cancel: Requested cancel on '{0}{1}', NTStatus: {2}. PID: {3}. MID: {4}.", share.Name, openFile.Path, status, context.PID, context.MID);
|
||||
}
|
||||
if (status == NTStatus.STATUS_SUCCESS || status == NTStatus.STATUS_CANCELLED)
|
||||
{
|
||||
state.RemoveAsyncContext(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,70 @@ namespace SMBLibrary.Server.SMB1
|
|||
{
|
||||
internal static void ProcessNTTransactNotifyChangeRequest(SMB1Header header, uint maxParameterCount, NTTransactNotifyChangeRequest subcommand, ISMBShare share, SMB1ConnectionState state)
|
||||
{
|
||||
// [MS-CIFS] If the server does not support the NT_TRANSACT_NOTIFY_CHANGE subcommand, it can return an
|
||||
// error response with STATUS_NOT_IMPLEMENTED [..] in response to an NT_TRANSACT_NOTIFY_CHANGE Request.
|
||||
header.Status = NTStatus.STATUS_NOT_IMPLEMENTED;
|
||||
SMB1Session session = state.GetSession(header.UID);
|
||||
OpenFileObject openFile = session.GetOpenFileObject(subcommand.FID);
|
||||
SMB1AsyncContext context = state.CreateAsyncContext(header.UID, header.TID, header.PID, header.MID, subcommand.FID, state);
|
||||
// We wish to make sure that the 'Monitoring started' will appear before the 'Monitoring completed' in the log
|
||||
lock (context)
|
||||
{
|
||||
header.Status = share.FileStore.NotifyChange(out context.IORequest, openFile.Handle, subcommand.CompletionFilter, subcommand.WatchTree, (int)maxParameterCount, OnNotifyChangeCompleted, context);
|
||||
if (header.Status == NTStatus.STATUS_PENDING)
|
||||
{
|
||||
state.LogToServer(Severity.Verbose, "NotifyChange: Monitoring of '{0}{1}' started. PID: {2}. MID: {3}.", share.Name, openFile.Path, context.PID, context.MID);
|
||||
}
|
||||
else if (header.Status == NTStatus.STATUS_NOT_SUPPORTED)
|
||||
{
|
||||
// [MS-CIFS] If the server does not support the NT_TRANSACT_NOTIFY_CHANGE subcommand, it can return an
|
||||
// error response with STATUS_NOT_IMPLEMENTED [..] in response to an NT_TRANSACT_NOTIFY_CHANGE Request.
|
||||
header.Status = NTStatus.STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnNotifyChangeCompleted(NTStatus status, byte[] buffer, object context)
|
||||
{
|
||||
NTTransactNotifyChangeResponse notifyChangeResponse = new NTTransactNotifyChangeResponse();
|
||||
SMB1AsyncContext asyncContext = (SMB1AsyncContext)context;
|
||||
// Wait until the 'Monitoring started' will be written to the log
|
||||
lock (asyncContext)
|
||||
{
|
||||
SMB1ConnectionState connection = asyncContext.Connection;
|
||||
connection.RemoveAsyncContext(asyncContext);
|
||||
SMB1Session session = connection.GetSession(asyncContext.UID);
|
||||
if (session != null)
|
||||
{
|
||||
ISMBShare share = session.GetConnectedTree(asyncContext.TID);
|
||||
OpenFileObject openFile = session.GetOpenFileObject(asyncContext.FileID);
|
||||
if (share != null && openFile != null)
|
||||
{
|
||||
connection.LogToServer(Severity.Verbose, "NotifyChange: Monitoring of '{0}{1}' completed. NTStatus: {2}. PID: {3}. MID: {4}.", share.Name, openFile.Path, status, asyncContext.PID, asyncContext.MID);
|
||||
}
|
||||
}
|
||||
SMB1Header header = new SMB1Header();
|
||||
header.Command = CommandName.SMB_COM_NT_TRANSACT;
|
||||
header.Status = status;
|
||||
header.Flags = HeaderFlags.CaseInsensitive | HeaderFlags.CanonicalizedPaths | HeaderFlags.Reply;
|
||||
// [MS-CIFS] SMB_FLAGS2_UNICODE SHOULD be set to 1 when the negotiated dialect is NT LANMAN.
|
||||
// [MS-CIFS] The Windows NT Server implementation of NT_TRANSACT_NOTIFY_CHANGE always returns the names of changed files in Unicode format.
|
||||
header.Flags2 = HeaderFlags2.Unicode | HeaderFlags2.NTStatusCode;
|
||||
header.UID = asyncContext.UID;
|
||||
header.TID = asyncContext.TID;
|
||||
header.PID = asyncContext.PID;
|
||||
header.MID = asyncContext.MID;
|
||||
notifyChangeResponse.FileNotifyInformationBytes = buffer;
|
||||
|
||||
byte[] responseSetup = notifyChangeResponse.GetSetup();
|
||||
byte[] responseParameters = notifyChangeResponse.GetParameters(false);
|
||||
byte[] responseData = notifyChangeResponse.GetData();
|
||||
List<SMB1Command> responseList = NTTransactHelper.GetNTTransactResponse(responseSetup, responseParameters, responseData, asyncContext.Connection.MaxBufferSize);
|
||||
foreach (SMB1Command response in responseList)
|
||||
{
|
||||
SMB1Message reply = new SMB1Message();
|
||||
reply.Header = header;
|
||||
reply.Commands.Add(response);
|
||||
SMBServer.EnqueueMessage(asyncContext.Connection, reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,16 +15,44 @@ namespace SMBLibrary.Server.SMB2
|
|||
{
|
||||
internal static SMB2Command GetCancelResponse(CancelRequest request, SMB2ConnectionState state)
|
||||
{
|
||||
if (request.Header.IsAsync && request.Header.AsyncID == 0)
|
||||
SMB2Session session = state.GetSession(request.Header.SessionID);
|
||||
if (request.Header.IsAsync)
|
||||
{
|
||||
ErrorResponse response = new ErrorResponse(request.CommandName, NTStatus.STATUS_CANCELLED);
|
||||
response.Header.IsAsync = true;
|
||||
return response;
|
||||
SMB2AsyncContext context = state.GetAsyncContext(request.Header.AsyncID);
|
||||
if (context != null)
|
||||
{
|
||||
ISMBShare share = session.GetConnectedTree(context.TreeID);
|
||||
OpenFileObject openFile = session.GetOpenFileObject(context.FileID);
|
||||
NTStatus status = share.FileStore.Cancel(context.IORequest);
|
||||
if (openFile != null)
|
||||
{
|
||||
state.LogToServer(Severity.Information, "Cancel: Requested cancel on '{0}{1}'. NTStatus: {2}, AsyncID: {3}.", share.Name, openFile.Path, status, context.AsyncID);
|
||||
}
|
||||
if (status == NTStatus.STATUS_SUCCESS || status == NTStatus.STATUS_CANCELLED)
|
||||
{
|
||||
state.RemoveAsyncContext(context);
|
||||
// If the target request is successfully canceled, the target request MUST be failed by sending
|
||||
// an ERROR response packet [..] with the status field of the SMB2 header set to STATUS_CANCELLED.
|
||||
ErrorResponse response = new ErrorResponse(request.CommandName, NTStatus.STATUS_CANCELLED);
|
||||
response.Header.IsAsync = true;
|
||||
response.Header.AsyncID = context.AsyncID;
|
||||
return response;
|
||||
}
|
||||
// [MS-SMB2] If the target request is not successfully canceled [..] no response is sent.
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// [MS-SMB2] If a request is not found [..] no response is sent.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// [MS-SMB2] the SMB2 CANCEL Request MUST use an ASYNC header for canceling requests that have received an interim response.
|
||||
// [MS-SMB2] If the target request is not successfully canceled [..] no response is sent.
|
||||
return null;
|
||||
}
|
||||
|
||||
// [MS-SMB2] If a request is not found [..] no response is sent.
|
||||
// [MS-SMB2] If the target request is not successfully canceled [..] no response is sent.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,12 +15,56 @@ namespace SMBLibrary.Server.SMB2
|
|||
{
|
||||
internal static SMB2Command GetChangeNotifyInterimResponse(ChangeNotifyRequest request, ISMBShare share, SMB2ConnectionState state)
|
||||
{
|
||||
// [MS-SMB2] If the underlying object store does not support change notifications, the server MUST fail this request with STATUS_NOT_SUPPORTED
|
||||
ErrorResponse response = new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
|
||||
// Windows 7 / 8 / 10 will infinitely retry sending ChangeNotify requests if the response does not have SMB2_FLAGS_ASYNC_COMMAND set.
|
||||
// Note: NoRemoteChangeNotify can be set in the registry to prevent the client from sending ChangeNotify requests altogether.
|
||||
response.Header.IsAsync = true;
|
||||
return response;
|
||||
SMB2Session session = state.GetSession(request.Header.SessionID);
|
||||
OpenFileObject openFile = session.GetOpenFileObject(request.FileId);
|
||||
bool watchTree = (request.Flags & ChangeNotifyFlags.WatchTree) > 0;
|
||||
SMB2AsyncContext context = state.CreateAsyncContext(request.FileId, state, request.Header.SessionID, request.Header.TreeID);
|
||||
// We have to make sure that we don't send an interim response after the final response.
|
||||
lock (context)
|
||||
{
|
||||
NTStatus status = share.FileStore.NotifyChange(out context.IORequest, openFile.Handle, request.CompletionFilter, watchTree, (int)request.OutputBufferLength, OnNotifyChangeCompleted, context);
|
||||
if (status == NTStatus.STATUS_PENDING)
|
||||
{
|
||||
state.LogToServer(Severity.Verbose, "NotifyChange: Monitoring of '{0}{1}' started. AsyncID: {2}.", share.Name, openFile.Path, context.AsyncID);
|
||||
}
|
||||
// [MS-SMB2] If the underlying object store does not support change notifications, the server MUST fail this request with STATUS_NOT_SUPPORTED
|
||||
ErrorResponse response = new ErrorResponse(request.CommandName, status);
|
||||
// Windows 7 / 8 / 10 will infinitely retry sending ChangeNotify requests if the response does not have SMB2_FLAGS_ASYNC_COMMAND set.
|
||||
// Note: NoRemoteChangeNotify can be set in the registry to prevent the client from sending ChangeNotify requests altogether.
|
||||
response.Header.IsAsync = true;
|
||||
response.Header.AsyncID = context.AsyncID;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnNotifyChangeCompleted(NTStatus status, byte[] buffer, object context)
|
||||
{
|
||||
SMB2AsyncContext asyncContext = (SMB2AsyncContext)context;
|
||||
// Wait until the interim response has been sent
|
||||
lock (asyncContext)
|
||||
{
|
||||
SMB2ConnectionState connection = asyncContext.Connection;
|
||||
connection.RemoveAsyncContext(asyncContext);
|
||||
SMB2Session session = connection.GetSession(asyncContext.SessionID);
|
||||
if (session != null)
|
||||
{
|
||||
ISMBShare share = session.GetConnectedTree(asyncContext.TreeID);
|
||||
OpenFileObject openFile = session.GetOpenFileObject(asyncContext.FileID);
|
||||
if (share != null && openFile != null)
|
||||
{
|
||||
connection.LogToServer(Severity.Verbose, "NotifyChange: Monitoring of '{0}{1}' completed. NTStatus: {2}. AsyncID: {3}", share.Name, openFile.Path, status, asyncContext.AsyncID);
|
||||
}
|
||||
}
|
||||
|
||||
ChangeNotifyResponse response = new ChangeNotifyResponse();
|
||||
response.Header.Status = status;
|
||||
response.Header.IsAsync = true;
|
||||
response.Header.AsyncID = asyncContext.AsyncID;
|
||||
response.Header.SessionID = asyncContext.SessionID;
|
||||
response.OutputBuffer = buffer;
|
||||
|
||||
SMBServer.EnqueueResponse(connection, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue