1111using System . Security ;
1212using SharpHoundCommonLib . Processors ;
1313using Microsoft . Win32 ;
14+ using System . Threading . Tasks ;
15+ using System . Threading ;
16+ using SharpHoundRPC . NetAPINative ;
17+ using SharpHoundRPC . Shared ;
1418
1519namespace SharpHoundCommonLib {
1620 public static class Helpers {
@@ -135,7 +139,8 @@ public static string DistinguishedNameToDomain(string distinguishedName) {
135139 int idx ;
136140 if ( distinguishedName . ToUpper ( ) . Contains ( "DELETED OBJECTS" ) ) {
137141 idx = distinguishedName . IndexOf ( "DC=" , 3 , StringComparison . Ordinal ) ;
138- } else {
142+ }
143+ else {
139144 idx = distinguishedName . IndexOf ( "DC=" ,
140145 StringComparison . CurrentCultureIgnoreCase ) ;
141146 }
@@ -193,7 +198,8 @@ public static long ConvertFileTimeToUnixEpoch(string ldapTime) {
193198
194199 try {
195200 toReturn = ( long ) Math . Floor ( DateTime . FromFileTimeUtc ( time ) . Subtract ( EpochDiff ) . TotalSeconds ) ;
196- } catch {
201+ }
202+ catch {
197203 toReturn = - 1 ;
198204 }
199205
@@ -209,7 +215,8 @@ public static long ConvertTimestampToUnixEpoch(string ldapTime) {
209215 try {
210216 var dt = DateTime . ParseExact ( ldapTime , "yyyyMMddHHmmss.0K" , CultureInfo . CurrentCulture ) . ToUniversalTime ( ) ;
211217 return ( long ) dt . Subtract ( EpochDiff ) . TotalSeconds ;
212- } catch {
218+ }
219+ catch {
213220 return 0 ;
214221 }
215222 }
@@ -263,19 +270,23 @@ public static RegistryResult GetRegistryKeyData(string target, string subkey, st
263270 data . Value = value ;
264271
265272 data . Collected = true ;
266- } catch ( IOException e ) {
273+ }
274+ catch ( IOException e ) {
267275 log . LogDebug ( e , "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}" ,
268276 target , subkey , subvalue ) ;
269277 data . FailureReason = "Target machine was not found or not connectable" ;
270- } catch ( SecurityException e ) {
278+ }
279+ catch ( SecurityException e ) {
271280 log . LogDebug ( e , "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}" ,
272281 target , subkey , subvalue ) ;
273282 data . FailureReason = "User does not have the proper permissions to perform this operation" ;
274- } catch ( UnauthorizedAccessException e ) {
283+ }
284+ catch ( UnauthorizedAccessException e ) {
275285 log . LogDebug ( e , "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}" ,
276286 target , subkey , subvalue ) ;
277287 data . FailureReason = "User does not have the necessary registry rights" ;
278- } catch ( Exception e ) {
288+ }
289+ catch ( Exception e ) {
279290 log . LogDebug ( e , "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}" ,
280291 target , subkey , subvalue ) ;
281292 data . FailureReason = e . Message ;
@@ -300,7 +311,7 @@ public static IRegistryKey OpenRemoteRegistry(string target) {
300311 CommonOids . ClientAuthentication ,
301312 CommonOids . AnyPurpose
302313 } ;
303-
314+
304315 public static string DumpDirectoryObject ( this IDirectoryObject directoryObject ) {
305316 var builder = new StringBuilder ( ) ;
306317 builder . AppendLine ( "PropertyName : PropertyValue" ) ;
@@ -310,6 +321,109 @@ public static string DumpDirectoryObject(this IDirectoryObject directoryObject)
310321
311322 return builder . ToString ( ) ;
312323 }
324+
325+ /// <summary>
326+ /// Returns a Fail result if a task runs longer than its budgeted time.
327+ /// A cancellation token is passed to the executing function so it may exit cleanly if timeout is reached.
328+ /// </summary>
329+ /// <typeparam name="T"></typeparam>
330+ /// <param name="timeout"></param>
331+ /// <param name="func"></param>
332+ /// <returns></returns>
333+ public static async Task < Result < T > > ExecuteWithTimeout < T > ( TimeSpan timeout , Func < CancellationToken , T > func ) {
334+ var cts = new CancellationTokenSource ( ) ;
335+ var task = Task . Factory . StartNew ( ( ) => func ( cts . Token ) , cts . Token , TaskCreationOptions . LongRunning , TaskScheduler . Default ) ;
336+ await Task . WhenAny ( task , Task . Delay ( timeout , cts . Token ) ) ;
337+ cts . Cancel ( ) ;
338+
339+ if ( task . IsCompleted ) {
340+ try {
341+ return Result < T > . Ok ( await task ) ;
342+ }
343+ catch ( OperationCanceledException ) { }
344+ }
345+
346+ return Result < T > . Fail ( "Timeout" ) ;
347+ }
348+
349+ // These two ExecuteWithTimeout functions should perform equivalently -
350+ // they both create a new task from a function arg
351+ // But where the one below can invoke an async function directly to spawn the Task
352+ // The one above spawns a Task from a synchronous function.
353+ // The caller shouldn't have to worry about which they're using however,
354+ // the compiler should figure it out intrinsically
355+
356+ /// <summary>
357+ /// Returns a Fail result if a task runs longer than its budgeted time.
358+ /// A cancellation token is passed to the executing function so it may exit cleanly if timeout is reached.
359+ /// </summary>
360+ /// <typeparam name="T"></typeparam>
361+ /// <param name="timeout"></param>
362+ /// <param name="func"></param>
363+ /// <returns></returns>
364+ public static async Task < Result < T > > ExecuteWithTimeout < T > ( TimeSpan timeout , Func < CancellationToken , Task < T > > func ) {
365+ var cts = new CancellationTokenSource ( ) ;
366+ var task = func . Invoke ( cts . Token ) ;
367+ await Task . WhenAny ( task , Task . Delay ( timeout , cts . Token ) ) ;
368+ cts . Cancel ( ) ;
369+
370+ if ( task . IsCompleted ) {
371+ try {
372+ return Result < T > . Ok ( await task ) ;
373+ }
374+ catch ( OperationCanceledException ) { }
375+ }
376+
377+ return Result < T > . Fail ( "Timeout" ) ;
378+ }
379+
380+ /// <summary>
381+ /// Returns a Fail result if a task runs longer than its budgeted time.
382+ /// A cancellation token is passed to the executing function so it may exit cleanly if timeout is reached.
383+ /// </summary>
384+ /// <typeparam name="T"></typeparam>
385+ /// <param name="timeout"></param>
386+ /// <param name="func"></param>
387+ /// <returns></returns>
388+ public static async Task < NetAPIResult < T > > ExecuteNetAPIWithTimeout < T > ( TimeSpan timeout , Func < CancellationToken , NetAPIResult < T > > func ) {
389+ var result = await ExecuteWithTimeout ( timeout , func ) ;
390+ if ( result . IsSuccess )
391+ return result . Value ;
392+ else
393+ return NetAPIResult < T > . Fail ( result . Error ) ;
394+ }
395+
396+ /// <summary>
397+ /// Returns a Fail result if a task runs longer than its budgeted time.
398+ /// A cancellation token is passed to the executing function so it may exit cleanly if timeout is reached.
399+ /// </summary>
400+ /// <typeparam name="T"></typeparam>
401+ /// <param name="timeout"></param>
402+ /// <param name="func"></param>
403+ /// <returns></returns>
404+ public static async Task < SharpHoundRPC . Result < T > > ExecuteRPCWithTimeout < T > ( TimeSpan timeout , Func < CancellationToken , SharpHoundRPC . Result < T > > func ) {
405+ var result = await ExecuteWithTimeout ( timeout , func ) ;
406+ if ( result . IsSuccess )
407+ return result . Value ;
408+ else
409+ return SharpHoundRPC . Result < T > . Fail ( result . Error ) ;
410+ }
411+
412+ /// <summary>
413+ /// Returns a Fail result if a task runs longer than its budgeted time.
414+ /// A cancellation token is passed to the executing function so it may exit cleanly if timeout is reached.
415+ /// </summary>
416+ /// <typeparam name="T"></typeparam>
417+ /// <param name="timeout"></param>
418+ /// <param name="func"></param>
419+ /// <returns></returns>
420+ public static async Task < SharpHoundRPC . Result < T > > ExecuteRPCWithTimeout < T > ( TimeSpan timeout , Func < CancellationToken , Task < SharpHoundRPC . Result < T > > > func ) {
421+ var result = await ExecuteWithTimeout ( timeout , func ) ;
422+ if ( result . IsSuccess )
423+ return result . Value ;
424+ else
425+ return SharpHoundRPC . Result < T > . Fail ( result . Error ) ;
426+ }
313427 }
314428
315429 public class ParsedGPLink {
0 commit comments