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 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 { $"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 privRelogon() { if (privLogonInfo == null) return false; var RetVal = await privLogonAsync(privLogonInfo); return RetVal; } public async Task 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 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 privRequestFoldersListAsync(string rootPath, int depth) { if (String.IsNullOrEmpty(rootPath)) return new List(); scanningDepth = depth; return privRequestFoldersListAsync(new DirectoryInfo(rootPath), depth); } private List privRequestFoldersListAsync(DirectoryInfo rootPath, int depth, cNtfsResultFolder parent = null) { ResetError(); var folders = new List(); 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 RequestFoldersListAsync(cNtfsResultBase folder, cNtfsResultBase parent) { ResetError(); try { await Task.Delay(0); var Result = new List(); 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 ResolveFolders(cNtfsBase ntfs) { Folders = await ntfs.RequestFoldersListAsync(this.Path, 0); return Folders != null; } } public class cNtfsCollectionBase : SortedList { 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; } } }