Skip to content

Commit b7c949c

Browse files
committed
Added support for a FollowLink switch which will allow accessible cmdlets to follow symbolic links. Feature request #29.
1 parent 1418c94 commit b7c949c

4 files changed

Lines changed: 100 additions & 33 deletions

File tree

NtObjectManager/Cmdlets/Accessible/GetAccessibleFileCmdlet.cs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ public class GetAccessibleFileCmdlet : GetAccessiblePathCmdlet<FileAccessRights>
8787

8888
private NtResult<NtFile> OpenFile(string name, NtFile root, FileOpenOptions options)
8989
{
90-
using (ObjectAttributes obja = new ObjectAttributes(name,
91-
AttributeFlags.CaseInsensitive, root))
90+
using (ObjectAttributes obja = new ObjectAttributes(name, GetAttributeFlags(), root))
9291
{
9392
var result = NtFile.Open(obja, GetMaximumAccess(FileAccessRights.Synchronize | FileAccessRights.ReadAttributes | FileAccessRights.ReadControl),
9493
FileShareMode.Read | FileShareMode.Delete, options | FileOpenOptions.SynchronousIoNonAlert, false);
@@ -147,7 +146,7 @@ private void CheckAccessUnderImpersonation(TokenEntry token, AccessMask access_r
147146
using (var result = token.Token.RunUnderImpersonate(() =>
148147
file.ReOpen(FileAccessRights.MaximumAllowed,
149148
FileShareMode.Read | FileShareMode.Delete,
150-
FileOpenOptions.None, false)))
149+
FileOpenOptions.None, GetAttributeFlags(), false)))
151150
{
152151
if ( result.Status.IsSuccess() && IsAccessGranted(result.Result.GrantedAccessMask, access_rights))
153152
{
@@ -207,7 +206,8 @@ private void DumpDirectory(IEnumerable<TokenEntry> tokens, AccessMask access_rig
207206
if (Recurse)
208207
{
209208
using (var result = file.ReOpen(FileAccessRights.Synchronize | FileAccessRights.ReadData | FileAccessRights.ReadAttributes,
210-
FileShareMode.Read | FileShareMode.Delete, options | FileOpenOptions.DirectoryFile | FileOpenOptions.SynchronousIoNonAlert, false))
209+
FileShareMode.Read | FileShareMode.Delete, options | FileOpenOptions.DirectoryFile | FileOpenOptions.SynchronousIoNonAlert,
210+
GetAttributeFlags(), false))
211211
{
212212
if (result.Status.IsSuccess())
213213
{
@@ -230,12 +230,15 @@ private void DumpDirectory(IEnumerable<TokenEntry> tokens, AccessMask access_rig
230230
{
231231
if (new_file.IsSuccess)
232232
{
233-
DumpFile(tokens, access_rights, dir_access_rights,
234-
parent_sd.IsSuccess ? parent_sd.Result : null, new_file.Result);
235-
if (IsDirectoryNoThrow(new_file.Result))
233+
if (FollowPath(new_file.Result, GetFilePath))
236234
{
237-
DumpDirectory(tokens, access_rights, dir_access_rights,
238-
new_file.Result, options, current_depth - 1);
235+
DumpFile(tokens, access_rights, dir_access_rights,
236+
parent_sd.IsSuccess ? parent_sd.Result : null, new_file.Result);
237+
if (IsDirectoryNoThrow(new_file.Result))
238+
{
239+
DumpDirectory(tokens, access_rights, dir_access_rights,
240+
new_file.Result, options, current_depth - 1);
241+
}
239242
}
240243
}
241244
}
@@ -275,23 +278,35 @@ protected override void BeginProcessing()
275278
base.BeginProcessing();
276279
}
277280

281+
private static string GetFilePath(NtFile file)
282+
{
283+
return file.GetNormalizedFileName(false).GetResultOrDefault() ?? file.FileName;
284+
}
285+
278286
private protected override void RunAccessCheckPath(IEnumerable<TokenEntry> tokens, string path)
279287
{
280-
FileOpenOptions options = FileOpenOptions.OpenReparsePoint | (_open_for_backup ? FileOpenOptions.OpenForBackupIntent : FileOpenOptions.None);
288+
FileOpenOptions options = _open_for_backup ? FileOpenOptions.OpenForBackupIntent : FileOpenOptions.None;
289+
if (!FollowLink)
290+
{
291+
options |= FileOpenOptions.OpenReparsePoint;
292+
}
281293
NtType type = NtType.GetTypeByType<NtFile>();
282294
AccessMask access_rights = type.MapGenericRights(Access);
283295
AccessMask dir_access_rights = type.MapGenericRights(DirectoryAccess);
284296
using (var result = OpenFile(path, null, options))
285297
{
286298
NtFile file = result.GetResultOrThrow();
287-
DumpFile(tokens,
288-
access_rights,
289-
dir_access_rights,
290-
null,
291-
result.Result);
292-
if (IsDirectoryNoThrow(result.Result))
299+
if (FollowPath(file, GetFilePath))
293300
{
294-
DumpDirectory(tokens, access_rights, dir_access_rights, file, options, GetMaxDepth());
301+
DumpFile(tokens,
302+
access_rights,
303+
dir_access_rights,
304+
null,
305+
result.Result);
306+
if (IsDirectoryNoThrow(result.Result))
307+
{
308+
DumpDirectory(tokens, access_rights, dir_access_rights, file, options, GetMaxDepth());
309+
}
295310
}
296311
}
297312
}

NtObjectManager/Cmdlets/Accessible/GetAccessibleKeyCmdlet.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class GetAccessibleKeyCmdlet : GetAccessiblePathCmdlet<KeyAccessRights>
5454
{
5555
private NtResult<NtKey> OpenKey(string name, NtObject root, bool open_link, bool open_for_backup)
5656
{
57-
AttributeFlags flags = AttributeFlags.CaseInsensitive;
57+
AttributeFlags flags = GetAttributeFlags();
5858
if (open_link)
5959
{
6060
flags |= AttributeFlags.OpenLink;
@@ -82,7 +82,7 @@ private void CheckAccess(TokenEntry token, NtKey key, AccessMask access_rights,
8282
private void CheckAccessUnderImpersonation(TokenEntry token, AccessMask access_rights, NtKey key)
8383
{
8484
using (ObjectAttributes obj_attributes = new ObjectAttributes(string.Empty,
85-
AttributeFlags.CaseInsensitive | AttributeFlags.OpenLink, key))
85+
GetAttributeFlags() | (FollowLink ? AttributeFlags.None : AttributeFlags.OpenLink), key))
8686
{
8787
using (var result = token.Token.RunUnderImpersonate(() => NtKey.Open(obj_attributes, KeyAccessRights.MaximumAllowed, 0, false)))
8888
{
@@ -131,11 +131,14 @@ private void DumpKey(IEnumerable<TokenEntry> tokens, AccessMask access_rights, b
131131
{
132132
continue;
133133
}
134-
using (var result = OpenKey(subkey, key, true, open_for_backup))
134+
using (var result = OpenKey(subkey, key, !FollowLink, open_for_backup))
135135
{
136136
if (result.IsSuccess)
137137
{
138-
DumpKey(tokens, access_rights, open_for_backup, result.Result, current_depth - 1);
138+
if (FollowPath(result.Result.FullPath))
139+
{
140+
DumpKey(tokens, access_rights, open_for_backup, result.Result, current_depth - 1);
141+
}
139142
}
140143
else
141144
{
@@ -181,7 +184,10 @@ private protected override void RunAccessCheckPath(IEnumerable<TokenEntry> token
181184
using (var result = OpenKey(path, null, false, _open_for_backup))
182185
{
183186
NtKey key = result.GetResultOrThrow();
184-
DumpKey(tokens, result.Result.NtType.MapGenericRights(Access), _open_for_backup, key, GetMaxDepth());
187+
if (FollowPath(key.FullPath))
188+
{
189+
DumpKey(tokens, result.Result.NtType.MapGenericRights(Access), _open_for_backup, key, GetMaxDepth());
190+
}
185191
}
186192
}
187193
}

NtObjectManager/Cmdlets/Accessible/GetAccessibleObjectCmdlet.cs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ private void CheckAccess(TokenEntry token, NtObject obj, NtType type, bool is_di
152152
}
153153
}
154154

155-
private static NtResult<NtObject> ReopenUnderImpersonation(TokenEntry token, NtType type, NtObject obj)
155+
private NtResult<NtObject> ReopenUnderImpersonation(TokenEntry token, NtType type, NtObject obj)
156156
{
157157
using (ObjectAttributes obj_attributes = new ObjectAttributes(string.Empty,
158-
AttributeFlags.CaseInsensitive, obj))
158+
GetAttributeFlags(), obj))
159159
{
160160
return token.Token.RunUnderImpersonate(() => type.Open(obj_attributes, GenericAccessRights.MaximumAllowed, false));
161161
}
@@ -248,17 +248,23 @@ private void DumpDirectory(IEnumerable<TokenEntry> tokens, HashSet<string> type_
248248
continue;
249249
}
250250

251-
if (entry.IsDirectory)
251+
if (entry.IsDirectory || (FollowLink && entry.IsSymbolicLink))
252252
{
253253
using (var new_dir = OpenDirectory(entry.Name, dir))
254254
{
255255
if (new_dir.IsSuccess)
256256
{
257-
DumpDirectory(tokens, type_filter, access_rights, new_dir.Result, current_depth - 1);
257+
if (FollowPath(new_dir.Result.FullPath))
258+
{
259+
DumpDirectory(tokens, type_filter, access_rights, new_dir.Result, current_depth - 1);
260+
}
258261
}
259262
else
260263
{
261-
WriteAccessWarning(dir, entry.Name, new_dir.Status);
264+
if (entry.IsDirectory || new_dir.Status != NtStatus.STATUS_OBJECT_TYPE_MISMATCH)
265+
{
266+
WriteAccessWarning(dir, entry.Name, new_dir.Status);
267+
}
262268
}
263269
}
264270
}
@@ -292,19 +298,18 @@ private void DumpDirectory(IEnumerable<TokenEntry> tokens, HashSet<string> type_
292298
}
293299
}
294300

295-
private static NtResult<NtObject> OpenObject(ObjectDirectoryInformation entry, NtObject root, AccessMask desired_access)
301+
private NtResult<NtObject> OpenObject(ObjectDirectoryInformation entry, NtObject root, AccessMask desired_access)
296302
{
297303
NtType type = entry.NtType;
298-
using (var obja = new ObjectAttributes(entry.Name, AttributeFlags.CaseInsensitive, root))
304+
using (var obja = new ObjectAttributes(entry.Name, GetAttributeFlags(), root))
299305
{
300306
return type.Open(obja, desired_access, false);
301307
}
302308
}
303309

304310
private NtResult<NtDirectory> OpenDirectory(string path, NtObject root)
305311
{
306-
using (ObjectAttributes obja = new ObjectAttributes(path,
307-
AttributeFlags.CaseInsensitive, root))
312+
using (ObjectAttributes obja = new ObjectAttributes(path, GetAttributeFlags(), root))
308313
{
309314
var result = NtDirectory.Open(obja, GetMaximumAccessGeneric(DirectoryAccessRights.Query | DirectoryAccessRights.ReadControl), false);
310315
if (result.IsSuccess || result.Status != NtStatus.STATUS_ACCESS_DENIED)
@@ -342,11 +347,15 @@ private protected override void RunAccessCheckPath(IEnumerable<TokenEntry> token
342347
{
343348
if (result.IsSuccess)
344349
{
345-
DumpDirectory(tokens, GetTypeFilter(), Access, result.Result, GetMaxDepth());
350+
if (FollowPath(result.Result.FullPath))
351+
{
352+
DumpDirectory(tokens, GetTypeFilter(), Access, result.Result, GetMaxDepth());
353+
}
346354
}
347355
else
348356
{
349-
using (var obj = NtObject.OpenWithType(null, path, null, AttributeFlags.CaseInsensitive, GetMaximumAccess(GenericAccessRights.MaximumAllowed), null, false))
357+
using (var obj = NtObject.OpenWithType(null, path, null, GetAttributeFlags(),
358+
GetMaximumAccess(GenericAccessRights.MaximumAllowed), null, false))
350359
{
351360
if (obj.IsSuccess)
352361
{

NtObjectManager/Cmdlets/Accessible/GetAccessiblePathCmdlet.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public abstract class GetAccessiblePathCmdlet<A> : CommonAccessBaseWithAccessCmd
3030
private Func<string, bool>[] _include_filters;
3131
private Func<string, bool>[] _exclude_filters;
3232
private Func<string, bool> _filter;
33+
private HashSet<string> _checked_paths;
3334

3435
/// <summary>
3536
/// <para type="description">Specify a list of native paths to check.</para>
@@ -83,6 +84,42 @@ public abstract class GetAccessiblePathCmdlet<A> : CommonAccessBaseWithAccessCmd
8384
[Parameter(ParameterSetName = "path")]
8485
public string[] Exclude { get; set; }
8586

87+
/// <summary>
88+
/// <para type="description">Specify to follow links in an recursive enumeration.</para>
89+
/// </summary>
90+
[Parameter(ParameterSetName = "path")]
91+
public SwitchParameter FollowLink { get; set; }
92+
93+
/// <summary>
94+
/// <para type="description">Specify the checks should be attempted case sensitively.</para>
95+
/// </summary>
96+
[Parameter(ParameterSetName = "path")]
97+
public SwitchParameter CaseSensitive { get; set; }
98+
99+
private protected AttributeFlags GetAttributeFlags()
100+
{
101+
return CaseSensitive ? AttributeFlags.None : AttributeFlags.CaseInsensitive;
102+
}
103+
104+
private protected bool FollowPath(string path)
105+
{
106+
if (!FollowLink)
107+
return true;
108+
if (_checked_paths == null)
109+
{
110+
_checked_paths = new HashSet<string>(CaseSensitive
111+
? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase);
112+
}
113+
return _checked_paths.Add(path);
114+
}
115+
116+
private protected bool FollowPath<T>(T obj, Func<T, string> get_path)
117+
{
118+
if (!FollowLink)
119+
return true;
120+
return FollowPath(get_path(obj));
121+
}
122+
86123
/// <summary>
87124
/// Convert a Win32 path to a native path.
88125
/// </summary>

0 commit comments

Comments
 (0)