Files
LIAM/LiamNtfs/cNtfsBase.cs
2026-03-18 13:48:24 +01:00

506 lines
17 KiB
C#

using C4IT.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.DirectoryServices.AccountManagement;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace LiamNtfs
{
public class cNtfsBase
{
private const int ErrorSessionCredentialConflict = 1219;
private const int ErrorNotConnected = 2250;
private cNtfsLogonInfo privLogonInfo = null;
private int scanningDepth;
public PrincipalContext adContext = null;
public Exception LastException { get; private set; } = null;
public string LastErrorMessage { get; private set; } = null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetError()
{
LastException = null;
LastErrorMessage = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetErrorException(string Action, Exception E, LogLevels lev = LogLevels.Error)
{
LastException = E;
LastErrorMessage = Action + ": " + E.Message;
cLogManager.LogEntry(Action, lev);
}
private async Task<bool> privLogonAsync(cNtfsLogonInfo LogonInfo)
{
try
{
//TODO: remove dummy delay?
await Task.Delay(0);
ResetError();
var netResource = new NetResource()
{
Scope = ResourceScope.GlobalNetwork,
ResourceType = ResourceType.Disk,
DisplayType = ResourceDisplaytype.Share,
RemoteName = LogonInfo.TargetNetworkName
};
var result = WNetAddConnection2(
netResource,
LogonInfo.UserSecret,
LogonInfo.User,
0);
if (result == ErrorSessionCredentialConflict)
{
var originalResult = result;
var cancelResult = WNetCancelConnection2(LogonInfo.TargetNetworkName, 0, true);
if (cancelResult == 0 || cancelResult == ErrorNotConnected)
{
result = WNetAddConnection2(
netResource,
LogonInfo.UserSecret,
LogonInfo.User,
0);
if (result == 0)
{
cLogManager.LogEntry(
$"NTFS login retry succeeded after SMB conflict on '{LogonInfo.TargetNetworkName}'. Initial add returned {originalResult} ({BuildWin32Message(originalResult)}), cancel returned {cancelResult} ({BuildWin32Message(cancelResult)}).",
LogLevels.Debug);
}
else
{
throw CreateNtfsLoginException(LogonInfo, originalResult, cancelResult, result);
}
}
else
{
throw CreateNtfsLoginException(LogonInfo, originalResult, cancelResult, null);
}
}
if (result != 0)
{
throw CreateNtfsLoginException(LogonInfo, result, null, null);
}
var FSLogon = true;
adContext = new PrincipalContext(ContextType.Domain, LogonInfo.Domain, LogonInfo.User, new NetworkCredential("", LogonInfo.UserSecret).Password);
return FSLogon && adContext != null;
}
catch (Exception E)
{
SetErrorException("exception error while Ntfs login", E, LogLevels.Debug);
cLogManager.LogException(E, LogLevels.Debug);
}
return false;
}
private static Exception CreateNtfsLoginException(cNtfsLogonInfo logonInfo, int addResult, int? cancelResult, int? retryResult)
{
var stages = new List<string>
{
$"WNetAddConnection2={addResult} ({BuildWin32Message(addResult)})"
};
if (cancelResult.HasValue)
stages.Add($"WNetCancelConnection2('{logonInfo?.TargetNetworkName}')={cancelResult.Value} ({BuildWin32Message(cancelResult.Value)})");
if (retryResult.HasValue)
stages.Add($"RetryAdd={retryResult.Value} ({BuildWin32Message(retryResult.Value)})");
var details = string.Join(", ", stages);
var message = $"NTFS login to '{logonInfo?.TargetNetworkName}' for user '{logonInfo?.User}' failed. {details}.";
if (addResult == ErrorSessionCredentialConflict)
{
message += " This usually indicates an existing SMB/DFS session to the same server with a different credential context.";
if (cancelResult == ErrorNotConnected)
message += " The exact DFS/share path was not registered as a disconnectable connection, so the conflicting session is likely held under a different remote name or DFS target.";
}
var nativeResult = retryResult ?? cancelResult ?? addResult;
return new InvalidOperationException(message, new Win32Exception(nativeResult));
}
private static string BuildWin32Message(int errorCode)
{
return new Win32Exception(errorCode).Message;
}
private async Task<bool> privRelogon()
{
if (privLogonInfo == null)
return false;
var RetVal = await privLogonAsync(privLogonInfo);
return RetVal;
}
public async Task<bool> LogonAsync(cNtfsLogonInfo LogonInfo)
{
var RetVal = await privLogonAsync(LogonInfo);
if (RetVal == true)
privLogonInfo = LogonInfo;
return RetVal;
}
[DllImport("mpr.dll")]
private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags);
[DllImport("mpr.dll")]
private static extern int WNetCancelConnection2(string name, int flags,
bool force);
public async Task<cNtfsCollectionBase> RequestFoldersListAsync(string rootPath, int depth)
{
ResetError();
try
{
await Task.Delay(0);
if (depth == 0)
return null;
var Result = privRequestFoldersListAsync(rootPath, depth);
if (Result != null)
{
var RetVal = new cNtfsCollectionBase(Result.Count);
foreach (var Entry in Result)
{
var res = new cNtfsResultFolder((cNtfsResultFolder)Entry);
RetVal.Add(res);
}
return RetVal;
}
}
catch (Exception E)
{
cLogManager.LogException(E);
}
return null;
}
private List<cNtfsResultBase> privRequestFoldersListAsync(string rootPath, int depth)
{
if (String.IsNullOrEmpty(rootPath))
return new List<cNtfsResultBase>();
scanningDepth = depth;
return privRequestFoldersListAsync(new DirectoryInfo(rootPath), depth);
}
private List<cNtfsResultBase> privRequestFoldersListAsync(DirectoryInfo rootPath, int depth, cNtfsResultFolder parent = null)
{
ResetError();
var folders = new List<cNtfsResultBase>();
try
{
if (depth == 0)
return folders;
DirectoryInfo[] directories;
try
{
directories = rootPath.GetDirectories();
}
catch (Exception E)
{
cLogManager.LogEntry($"Could not enumerate directories under '{rootPath.FullName}': {E.Message}", LogLevels.Warning);
cLogManager.LogException(E, LogLevels.Debug);
return folders;
}
foreach (var directory in directories)
{
cNtfsResultFolder folder;
try
{
folder = new cNtfsResultFolder()
{
ID = generateUniquId(directory.FullName),
Path = directory.FullName,
CreatedDate = directory.CreationTimeUtc.ToString("s"),
DisplayName = directory.Name,
Level = scanningDepth - depth + 1,
Parent = parent
};
}
catch (Exception E)
{
cLogManager.LogEntry($"Could not read directory metadata for '{directory.FullName}': {E.Message}", LogLevels.Warning);
cLogManager.LogException(E, LogLevels.Debug);
continue;
}
folders.Add(folder);
if (depth <= 0)
continue;
try
{
var result = privRequestFoldersListAsync(directory, depth - 1, folder);
if (result != null && result.Count > 0)
folders.AddRange(result);
}
catch (Exception E)
{
cLogManager.LogEntry($"Could not scan subtree '{directory.FullName}': {E.Message}", LogLevels.Warning);
cLogManager.LogException(E, LogLevels.Debug);
}
}
return folders;
}
catch (Exception E)
{
cLogManager.LogException(E);
}
return null;
}
public async Task<cNtfsCollectionBase> RequestFoldersListAsync(cNtfsResultBase folder, cNtfsResultBase parent)
{
ResetError();
try
{
await Task.Delay(0);
var Result = new List<cNtfsResultBase>();
if (Result != null)
{
var RetVal = new cNtfsCollectionBase(Result.Count);
foreach (var Entry in Result)
{
var res = new cNtfsResultFolder((cNtfsResultFolder)Entry);
res.Parent = parent;
RetVal.Add(res);
}
return RetVal;
}
}
catch (Exception E)
{
cLogManager.LogException(E);
}
return null;
}
internal static int getDepth(DirectoryInfo root, DirectoryInfo folder)
{
var rootDepth = root.FullName.TrimEnd(Path.DirectorySeparatorChar).Split(System.IO.Path.DirectorySeparatorChar).Length;
var folderDepth = folder.FullName.TrimEnd(Path.DirectorySeparatorChar).Split(System.IO.Path.DirectorySeparatorChar).Length;
return folderDepth - rootDepth;
}
internal static int getDepth(string root, string folder)
{
return getDepth(new DirectoryInfo(root), new DirectoryInfo(folder));
}
internal static string generateUniquId(string name)
{
try
{
var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
var utf8 = new System.Text.UTF8Encoding();
var hash = BitConverter.ToString(md5.ComputeHash(utf8.GetBytes(name)));
hash = hash.ToLower().Replace("-", "");
return hash;
}
catch (Exception E)
{
cLogManager.DefaultLogger.LogException(E);
throw;
}
}
}
public class cNtfsLogonInfo
{
public string Domain;
public string User;
public string UserSecret;
public string TargetNetworkName;
public string TargetGroupPath;
}
public class cNtfsResultBase
{
public string ID { get; set; } = null;
public string DisplayName { get; set; } = null;
public string Path { get; set; } = null;
public int Level { get; set; } = -1;
public cNtfsResultBase()
{ }
public cNtfsResultBase(cNtfsResultBase Result)
{
if (Result == null)
return;
ID = Result.ID;
DisplayName = Result.DisplayName;
Path = Result.Path;
}
}
public class cNtfsResultFolder : cNtfsResultBase
{
public string Description { get; set; } = null;
public string Visibility { get; set; } = null;
public string OwnerADGroup { get; set; } = null;
public string WriteADGroup { get; set; } = null;
public string ReadADGroup { get; set; } = null;
public cNtfsResultBase Parent { get;set;} = null;
public string CreatedDate { get; set; } = DateTime.MinValue.ToString("s");
public cNtfsResultFolder Folders { get; private set; } = null;
public cNtfsResultFolder(cNtfsResultFolder Result) : base(Result)
{
Description = Result.Description;
Visibility = Result.Visibility;
CreatedDate = Result.CreatedDate;
Folders = Result.Folders;
Level = Result.Level;
Parent = Result.Parent;
OwnerADGroup = Result.OwnerADGroup;
WriteADGroup = Result.WriteADGroup;
ReadADGroup = Result.ReadADGroup;
}
public cNtfsResultFolder()
{
}
}
public class cNtfsResultShare : cNtfsResultBase
{
public cNtfsCollectionBase Folders { get; private set; } = null;
public cNtfsResultShare(cNtfsResultBase Result) : base(Result) { }
public cNtfsResultShare()
{
}
public async Task<bool> ResolveFolders(cNtfsBase ntfs)
{
Folders = await ntfs.RequestFoldersListAsync(this.Path, 0);
return Folders != null;
}
}
public class cNtfsCollectionBase : SortedList<string, cNtfsResultBase>
{
public cNtfsCollectionBase() { }
public cNtfsCollectionBase(int n) : base(n) { }
public void Add(cNtfsResultBase Folder)
{
if (!this.ContainsKey(Folder.ID))
this.Add(Folder.ID, Folder);
}
}
public class cFileshareLogonInfo
{
public string Domain;
public string User;
public string UserSecret;
public string TargetNetworkName;
}
[StructLayout(LayoutKind.Sequential)]
public class NetResource
{
public ResourceScope Scope;
public ResourceType ResourceType;
public ResourceDisplaytype DisplayType;
public int Usage;
public string LocalName;
public string RemoteName;
public string Comment;
public string Provider;
}
public enum ResourceScope : int
{
Connected = 1,
GlobalNetwork,
Remembered,
Recent,
Context
};
public enum ResourceType : int
{
Any = 0,
Disk = 1,
Print = 2,
Reserved = 8,
}
public enum ResourceDisplaytype : int
{
Generic = 0x0,
Domain = 0x01,
Server = 0x02,
Share = 0x03,
File = 0x04,
Group = 0x05,
Network = 0x06,
Root = 0x07,
Shareadmin = 0x08,
Directory = 0x09,
Tree = 0x0a,
Ndscontainer = 0x0b
}
public enum NetError : uint
{
NERR_Success = 0,
NERR_BASE = 2100,
NERR_UnknownDevDir = (NERR_BASE + 16),
NERR_DuplicateShare = (NERR_BASE + 18),
NERR_BufTooSmall = (NERR_BASE + 23),
}
public enum SHARE_TYPE : uint
{
STYPE_DISKTREE = 0,
STYPE_PRINTQ = 1,
STYPE_DEVICE = 2,
STYPE_IPC = 3,
STYPE_SPECIAL = 0x80000000,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SHARE_INFO_1
{
public string shi1_netname;
public uint shi1_type;
public string shi1_remark;
public SHARE_INFO_1(string sharename, uint sharetype, string remark)
{
this.shi1_netname = sharename;
this.shi1_type = sharetype;
this.shi1_remark = remark;
}
public override string ToString()
{
return shi1_netname;
}
}
}