public DirResult ClassifyDir(string dir) { BlockingMq Mq = BlockingMq.GetMq(); DirResult dirResult = new DirResult() { DirPath = dir, Triage = ClassifierRule.Triage, ScanDir = true, }; // check if it matches TextClassifier textClassifier = new TextClassifier(ClassifierRule); TextResult textResult = textClassifier.TextMatch(dir); if (textResult != null) { // if it does, see what we're gonna do with it switch (ClassifierRule.MatchAction) { case MatchAction.Discard: dirResult.ScanDir = false; return(dirResult); case MatchAction.Snaffle: dirResult.Triage = ClassifierRule.Triage; Mq.DirResult(dirResult); return(dirResult); default: Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + "."); return(null); } } return(dirResult); }
public AdData() { Mq = BlockingMq.GetMq(); // figure out domain context if (MyOptions.TargetDomain == null && MyOptions.TargetDc == null) { try { DirectoryContext = new DirectoryContext(DirectoryContextType.Domain, Domain.GetCurrentDomain().Name); } catch (Exception e) { Mq.Error( "Problem figuring out DirectoryContext, you might need to define manually with -d and/or -c."); Mq.Degub(e.ToString()); Mq.Terminate(); } } else if (!String.IsNullOrEmpty(MyOptions.TargetDc)) { DirectoryContext = new DirectoryContext(DirectoryContextType.Domain, MyOptions.TargetDc); } else if (!String.IsNullOrEmpty(MyOptions.TargetDomain)) { DirectoryContext = new DirectoryContext(DirectoryContextType.Domain, MyOptions.TargetDomain); } SetDomainUsersAndComputers(); }
internal bool IsShareReadable(string share) { if (share.EndsWith("IPC$", StringComparison.OrdinalIgnoreCase) || share.EndsWith("print$", StringComparison.OrdinalIgnoreCase)) { return(false); } BlockingMq Mq = BlockingMq.GetMq(); try { string[] files = Directory.GetFiles(share); return(true); } catch (UnauthorizedAccessException) { return(false); } catch (DirectoryNotFoundException) { return(false); } catch (IOException) { return(false); } catch (Exception e) { Mq.Trace("Unhandled exception in IsShareReadable() for share path: " + share + " Full Exception:" + e.ToString()); } return(false); }
public TreeWalker() { Mq = BlockingMq.GetMq(); FileTaskScheduler = SnaffCon.GetFileTaskScheduler(); TreeTaskScheduler = SnaffCon.GetTreeTaskScheduler(); FileScanner = SnaffCon.GetFileScanner(); }
private void HandleOutput() { BlockingMq Mq = BlockingMq.GetMq(); foreach (SnafflerMessage message in Mq.Q.GetConsumingEnumerable()) { ProcessMessage(message); } }
public bool ClassifyShare(string share) { BlockingMq Mq = BlockingMq.GetMq(); // first time we hit sysvol, toggle the flag and keep going. every other time, bail out. if (share.ToLower().EndsWith("sysvol")) { if (MyOptions.ScanSysvol == false) { return(true); } MyOptions.ScanSysvol = false; } ; // same for netlogon if (share.ToLower().EndsWith("netlogon")) { if (MyOptions.ScanNetlogon == false) { return(true); } MyOptions.ScanNetlogon = false; } // check if it matches TextClassifier textClassifier = new TextClassifier(ClassifierRule); TextResult textResult = textClassifier.TextMatch(share); if (textResult != null) { // if it does, see what we're gonna do with it switch (ClassifierRule.MatchAction) { case MatchAction.Discard: return(true); case MatchAction.Snaffle: // in this context snaffle means 'send a report up the queue but don't scan the share' if (IsShareReadable(share)) { ShareResult shareResult = new ShareResult() { Triage = ClassifierRule.Triage, Listable = true, SharePath = share }; Mq.ShareResult(shareResult); } return(true); default: Mq.Error("You've got a misconfigured share ClassifierRule named " + ClassifierRule.RuleName + "."); return(false); } } return(false); }
public SnaffCon(Options options) { MyOptions = options; Mq = BlockingMq.GetMq(); int shareThreads = MyOptions.ShareThreads; int treeThreads = MyOptions.TreeThreads; int fileThreads = MyOptions.FileThreads; ShareTaskScheduler = new BlockingStaticTaskScheduler(shareThreads, MyOptions.MaxShareQueue); TreeTaskScheduler = new BlockingStaticTaskScheduler(treeThreads, MyOptions.MaxTreeQueue); FileTaskScheduler = new BlockingStaticTaskScheduler(fileThreads, MyOptions.MaxFileQueue); }
private void DumpQueue() { BlockingMq Mq = BlockingMq.GetMq(); while (Mq.Q.TryTake(out SnafflerMessage message)) { // emergency dump of queue contents to console Console.WriteLine(message.Message); } if (System.Diagnostics.Debugger.IsAttached) { Console.ReadKey(); } Environment.Exit(1); }
public X509Certificate2 parseCert(string certPath, string password = null) { BlockingMq Mq = BlockingMq.GetMq(); // IT TURNS OUT THAT new X509Certificate2() actually writes a file to a temp path and if you // don't manage it yourself it hits 65,000 temp files and hangs. var tempfile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); File.Copy(certPath, tempfile); X509Certificate2 parsedCert = null; try { if (Path.GetExtension(certPath) == ".pem") { string pemstring = File.ReadAllText(tempfile); byte[] certBuffer = Helpers.GetBytesFromPEM(pemstring, PemStringType.Certificate); byte[] keyBuffer = Helpers.GetBytesFromPEM(pemstring, PemStringType.RsaPrivateKey); if (certBuffer != null) { parsedCert = new X509Certificate2(certBuffer); if (keyBuffer != null) { RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer); parsedCert.PrivateKey = prov; } } else { Mq.Error("Failure parsing " + certPath); } } else { parsedCert = new X509Certificate2(tempfile, password); } } catch (Exception e) { File.Delete(tempfile); throw e; } File.Delete(tempfile); return(parsedCert); }
private void HandleOutput() { BlockingMq Mq = BlockingMq.GetMq(); foreach (SnafflerMessage message in Mq.Q.GetConsumingEnumerable()) { if (Options.LogType == LogType.Plain) { ProcessMessage(message); } else if (Options.LogType == LogType.JSON) { ProcessMessageJSON(message); } } }
public TreeWalker(string shareRoot) { Mq = BlockingMq.GetMq(); FileTaskScheduler = SnaffCon.GetFileTaskScheduler(); if (shareRoot == null) { Mq.Trace("A null made it into TreeWalker. Wtf."); return; } Mq.Trace("About to start a TreeWalker on share " + shareRoot); WalkTree(shareRoot); Mq.Trace("Finished TreeWalking share " + shareRoot); }
public SnaffCon(Options options) { MyOptions = options; Mq = BlockingMq.GetMq(); int shareThreads = MyOptions.ShareThreads; int treeThreads = MyOptions.TreeThreads; int fileThreads = MyOptions.FileThreads; ShareTaskScheduler = new BlockingStaticTaskScheduler(shareThreads, MyOptions.MaxShareQueue); TreeTaskScheduler = new BlockingStaticTaskScheduler(treeThreads, MyOptions.MaxTreeQueue); FileTaskScheduler = new BlockingStaticTaskScheduler(fileThreads, MyOptions.MaxFileQueue); FileScanner = new FileScanner(MyOptions.InterestLevel); TreeWalker = new TreeWalker(); ShareFinder = new ShareFinder(); }
internal bool IsShareReadable(string share) { BlockingMq Mq = BlockingMq.GetMq(); try { string[] files = Directory.GetFiles(share); return(true); } catch (UnauthorizedAccessException) { return(false); } catch (Exception e) { Mq.Trace(e.ToString()); } return(false); }
public static RwStatus CanRw(FileInfo fileInfo) { BlockingMq Mq = BlockingMq.GetMq(); try { RwStatus rwStatus = new RwStatus { CanWrite = CanIWrite(fileInfo), CanRead = CanIRead(fileInfo) }; return(rwStatus); } catch (Exception e) { Mq.Error(e.ToString()); return(new RwStatus { CanWrite = false, CanRead = false });; } }
private DirectorySearch GetDirectorySearcher() { Mq = BlockingMq.GetMq(); // target domain and dc set if ((!string.IsNullOrEmpty(MyOptions.TargetDomain)) && (!string.IsNullOrEmpty(MyOptions.TargetDc))) { Mq.Trace("Target DC and Domain specified: " + MyOptions.TargetDomain + " + " + MyOptions.TargetDc); _targetDc = MyOptions.TargetDc; _targetDomain = MyOptions.TargetDomain; } // no target DC or domain set else { Mq.Trace("Getting current domain from user context."); _currentDomain = Domain.GetCurrentDomain(); _targetDomain = _currentDomain.Name; _targetDc = _targetDomain; } _targetDomainNetBIOSName = GetNetBiosDomainName(); return(new DirectorySearch(_targetDomain, _targetDc)); }
public static Options Parse(string[] args) { BlockingMq Mq = BlockingMq.GetMq(); Options options; // parse the args try { options = ParseImpl(args); if (options == null) { throw new ArgumentException("Unable to correctly parse arguments."); } } catch { Mq.Error("Something went wrong parsing args."); throw; } Mq.Info("Parsed args successfully."); return(options); }
public bool ClassifyShare(string share) { BlockingMq Mq = BlockingMq.GetMq(); // check if the share has a matching classifier TextClassifier textClassifier = new TextClassifier(ClassifierRule); TextResult textResult = textClassifier.TextMatch(share); if (textResult != null) { // if it does, see what we're gonna do with it switch (ClassifierRule.MatchAction) { case MatchAction.Discard: return(true); case MatchAction.Snaffle: // in this context snaffle means 'send a report up the queue but don't scan the share' if (IsShareReadable(share)) { ShareResult shareResult = new ShareResult() { Triage = ClassifierRule.Triage, Listable = true, SharePath = share }; Mq.ShareResult(shareResult); } return(true); default: Mq.Error("You've got a misconfigured share ClassifierRule named " + ClassifierRule.RuleName + "."); return(false); } } return(false); }
public ShareFinder() { Mq = BlockingMq.GetMq(); }
public void Run(string[] args) { // prime the hoststring lazy instantiator hostString(); // print the thing PrintBanner(); // set up the message queue for operation BlockingMq.MakeMq(); // get a handle to the message queue singleton Mq = BlockingMq.GetMq(); // prime the UI handler SnaffCon controller = null; try { // parse cli opts in Options = Config.Parse(args); // set up the TSV output if the flag is set if (Options.LogTSV) { // treat all as strings except LastWriteTime {6} fileResultTemplate = Options.Separator + "{0}" + Options.Separator + "{1}" + Options.Separator + "{2}" + Options.Separator + "{3}" + Options.Separator + "{4}" + Options.Separator + "{5}" + Options.Separator + "{6:u}" + Options.Separator + "{7}" + Options.Separator + "{8}"; shareResultTemplate = Options.Separator + "{0}" + Options.Separator + "{1}"; dirResultTemplate = Options.Separator + "{0}" + Options.Separator + "{1}"; } // otherwise just do the normal thing else { // treat all as strings except LastWriteTime {6} fileResultTemplate = "{{{0}}}<{1}|{2}{3}|{4}|{5}|{6:u}>({7}) {8}"; shareResultTemplate = "{{{0}}}({1})"; dirResultTemplate = "{{{0}}}({1})"; } //------------------------------------------ // set up new fangled logging //------------------------------------------ LoggingConfiguration nlogConfig = new LoggingConfiguration(); ColoredConsoleTarget logconsole = null; FileTarget logfile = null; ParseLogLevelString(Options.LogLevelString); // Targets where to log to: File and Console if (Options.LogToConsole) { logconsole = new ColoredConsoleTarget("logconsole") { DetectOutputRedirected = true, UseDefaultRowHighlightingRules = false, WordHighlightingRules = { new ConsoleWordHighlightingRule("{Green}", ConsoleOutputColor.DarkGreen, ConsoleOutputColor.White), new ConsoleWordHighlightingRule("{Yellow}", ConsoleOutputColor.DarkYellow, ConsoleOutputColor.White), new ConsoleWordHighlightingRule("{Red}", ConsoleOutputColor.DarkRed, ConsoleOutputColor.White), new ConsoleWordHighlightingRule("{Black}", ConsoleOutputColor.Black, ConsoleOutputColor.White), new ConsoleWordHighlightingRule("[Trace]", ConsoleOutputColor.DarkGray, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Degub]", ConsoleOutputColor.Gray, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Info]", ConsoleOutputColor.White, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Error]", ConsoleOutputColor.Magenta, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Fatal]", ConsoleOutputColor.Red, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[File]", ConsoleOutputColor.Green, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Share]", ConsoleOutputColor.Yellow, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule { CompileRegex = true, Regex = @"<.*\|.*\|.*\|.*?>", ForegroundColor = ConsoleOutputColor.Cyan, BackgroundColor = ConsoleOutputColor.Black }, new ConsoleWordHighlightingRule { CompileRegex = true, Regex = @"^\d\d\d\d-\d\d\-\d\d \d\d:\d\d:\d\d [\+-]\d\d:\d\d ", ForegroundColor = ConsoleOutputColor.DarkGray, BackgroundColor = ConsoleOutputColor.Black }, new ConsoleWordHighlightingRule { CompileRegex = true, Regex = @"\((?:[^\)]*\)){1}", ForegroundColor = ConsoleOutputColor.DarkMagenta, BackgroundColor = ConsoleOutputColor.Black } } }; nlogConfig.AddRule(LogLevel, LogLevel.Fatal, logconsole); logconsole.Layout = "${message}"; } if (Options.LogToFile) { logfile = new FileTarget("logfile") { FileName = Options.LogFilePath }; nlogConfig.AddRule(LogLevel, LogLevel.Fatal, logfile); logfile.Layout = "${message}"; } // Apply config LogManager.Configuration = nlogConfig; //------------------------------------------- if (Options.Snaffle && (Options.SnafflePath.Length > 4)) { Directory.CreateDirectory(Options.SnafflePath); } controller = new SnaffCon(Options); Task thing = Task.Factory.StartNew(() => { controller.Execute(); }); while (true) { HandleOutput(); } } catch (Exception e) { Console.WriteLine(e.ToString()); DumpQueue(); } }
public List <string> x509Match(FileInfo fileInfo) { BlockingMq Mq = BlockingMq.GetMq(); string certPath = fileInfo.FullName; List <string> matchReasons = new List <string>(); X509Certificate2 parsedCert = null; bool nopwrequired = false; // TODO - handle if there is no private key, strip out unnecessary stuff from Certificate.cs, make work with pfx style stuff below try { // try to parse it, it'll throw if it needs a password parsedCert = parseCert(certPath); // if it parses we know we didn't need a password nopwrequired = true; } catch (CryptographicException e) { // if it doesn't parse that almost certainly means we need a password Mq.Trace(e.ToString()); // build the list of passwords to try including the filename List <string> passwords = MyOptions.CertPasswords; passwords.Add(Path.GetFileNameWithoutExtension(fileInfo.Name)); // try each of our very obvious passwords foreach (string password in MyOptions.CertPasswords) { try { parsedCert = parseCert(certPath, password); if (password == "") { matchReasons.Add("PasswordBlank"); } else { matchReasons.Add("PasswordCracked: " + password); } } catch (CryptographicException ee) { Mq.Trace("Password " + password + " invalid for cert file " + fileInfo.FullName + " " + ee.ToString()); } } if (matchReasons.Count == 0) { matchReasons.Add("HasPassword"); matchReasons.Add("LookNearbyFor.txtFiles"); } } catch (Exception e) { Mq.Error("Unhandled exception parsing cert: " + fileInfo.FullName + " " + e.ToString()); } if (parsedCert != null) { // check if it includes a private key, if not, who cares? if (parsedCert.HasPrivateKey) { matchReasons.Add("HasPrivateKey"); if (nopwrequired) { matchReasons.Add("NoPasswordRequired"); } matchReasons.Add("Subject:" + parsedCert.Subject); // take a look at the extensions X509ExtensionCollection extensions = parsedCert.Extensions; // this feels dumb but whatever foreach (X509Extension extension in extensions) { AsnEncodedData asndata = new AsnEncodedData(extension.Oid, extension.RawData); string asndataString = asndata.Format(false); if (extension.Oid.FriendlyName == "Basic Constraints") { if (asndataString.Contains("Subject Type=CA")) { matchReasons.Add("IsCACert"); } } if (extension.GetType() == typeof(X509KeyUsageExtension)) { matchReasons.Add((extension as X509KeyUsageExtension).KeyUsages.ToString()); } if (extension.GetType() == typeof(X509EnhancedKeyUsageExtension)) { List <string> ekus = new List <string>(); X509EnhancedKeyUsageExtension ekuExtension = (X509EnhancedKeyUsageExtension)extension; foreach (Oid eku in ekuExtension.EnhancedKeyUsages) { ekus.Add(eku.FriendlyName); } // include the EKUs in the info we're passing to the user string ekustring = String.Join("|", ekus); matchReasons.Add(ekustring); } ; if (extension.Oid.FriendlyName == "Subject Alternative Name") { byte[] sanbytes = extension.RawData; string san = Encoding.UTF8.GetString(sanbytes, 0, sanbytes.Length); matchReasons.Add(asndataString); } } matchReasons.Add("Expiry:" + parsedCert.GetExpirationDateString()); matchReasons.Add("Issuer:" + parsedCert.Issuer); } } return(matchReasons); }
public FileScanner() { Mq = BlockingMq.GetMq(); }
public bool ClassifyFile(FileInfo fileInfo) { BlockingMq Mq = BlockingMq.GetMq(); // figure out what part we gonna look at string stringToMatch = null; switch (ClassifierRule.MatchLocation) { case MatchLoc.FileExtension: stringToMatch = fileInfo.Extension; // special handling to treat files named like 'thing.kdbx.bak' if (stringToMatch == ".bak") { // strip off .bak string subName = fileInfo.Name.Replace(".bak", ""); stringToMatch = Path.GetExtension(subName); // if this results in no file extension, put it back. if (stringToMatch == "") { stringToMatch = ".bak"; } } // this is insane that i have to do this but apparently files with no extension return // this bullshit if (stringToMatch == "") { return(false); } break; case MatchLoc.FileName: stringToMatch = fileInfo.Name; break; case MatchLoc.FilePath: stringToMatch = fileInfo.FullName; break; case MatchLoc.FileLength: if (!SizeMatch(fileInfo)) { return(false); } else { break; } default: Mq.Error("You've got a misconfigured file classifier rule named " + ClassifierRule.RuleName + "."); return(false); } TextResult textResult = null; if (!String.IsNullOrEmpty(stringToMatch)) { TextClassifier textClassifier = new TextClassifier(ClassifierRule); // check if it matches textResult = textClassifier.TextMatch(stringToMatch); if (textResult == null) { // if it doesn't we just bail now. return(false); } } FileResult fileResult; // if it matches, see what we're gonna do with it switch (ClassifierRule.MatchAction) { case MatchAction.Discard: // chuck it. return(true); case MatchAction.Snaffle: // snaffle that bad boy fileResult = new FileResult(fileInfo) { MatchedRule = ClassifierRule, TextResult = textResult }; Mq.FileResult(fileResult); return(true); case MatchAction.CheckForKeys: // do a special x509 dance if (x509PrivKeyMatch(fileInfo)) { fileResult = new FileResult(fileInfo) { MatchedRule = ClassifierRule }; Mq.FileResult(fileResult); } return(true); case MatchAction.Relay: // bounce it on to the next ClassifierRule // TODO concurrency uplift make this a new task on the poolq try { ClassifierRule nextRule = MyOptions.ClassifierRules.First(thing => thing.RuleName == ClassifierRule.RelayTarget); if (nextRule.EnumerationScope == EnumerationScope.ContentsEnumeration) { ContentClassifier nextContentClassifier = new ContentClassifier(nextRule); nextContentClassifier.ClassifyContent(fileInfo); return(true); } else if (nextRule.EnumerationScope == EnumerationScope.FileEnumeration) { FileClassifier nextFileClassifier = new FileClassifier(nextRule); nextFileClassifier.ClassifyFile(fileInfo); return(true); } else { Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + "."); return(false); } } catch (IOException e) { Mq.Trace(e.ToString()); } catch (Exception e) { Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + "."); Mq.Trace(e.ToString()); } return(false); case MatchAction.EnterArchive: // do a special looking inside archive files dance using // https://github.com/adamhathcock/sharpcompress // TODO FUUUUUCK throw new NotImplementedException("Haven't implemented walking dir structures inside archives. Prob needs pool queue."); default: Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + "."); return(false); } }
public void ClassifyContent(FileInfo fileInfo) { BlockingMq Mq = BlockingMq.GetMq(); FileResult fileResult; try { if (MyOptions.MaxSizeToGrep >= fileInfo.Length) { // figure out if we need to look at the content as bytes or as string. switch (ClassifierRule.MatchLocation) { case MatchLoc.FileContentAsBytes: byte[] fileBytes = File.ReadAllBytes(fileInfo.FullName); if (ByteMatch(fileBytes)) { fileResult = new FileResult(fileInfo) { MatchedRule = ClassifierRule }; Mq.FileResult(fileResult); } return; case MatchLoc.FileContentAsString: string fileString = File.ReadAllText(fileInfo.FullName); TextClassifier textClassifier = new TextClassifier(ClassifierRule); TextResult textResult = textClassifier.TextMatch(fileString); if (textResult != null) { fileResult = new FileResult(fileInfo) { MatchedRule = ClassifierRule, TextResult = textResult }; Mq.FileResult(fileResult); } return; case MatchLoc.FileLength: bool lengthResult = SizeMatch(fileInfo); if (lengthResult) { fileResult = new FileResult(fileInfo) { MatchedRule = ClassifierRule }; Mq.FileResult(fileResult); } return; case MatchLoc.FileMD5: bool Md5Result = MD5Match(fileInfo); if (Md5Result) { fileResult = new FileResult(fileInfo) { MatchedRule = ClassifierRule }; Mq.FileResult(fileResult); } return; default: Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + "."); return; } } else { Mq.Trace("The following file was bigger than the MaxSizeToGrep config parameter:" + fileInfo.FullName); } } catch (UnauthorizedAccessException) { Mq.Error($"Not authorized to access file: {fileInfo.FullName}"); return; } catch (IOException e) { Mq.Error($"IO Exception on file: {fileInfo.FullName}. {e.Message}"); return; } catch (Exception e) { Mq.Error(e.ToString()); return; } }
public ShareFinder() { Mq = BlockingMq.GetMq(); TreeTaskScheduler = SnaffCon.GetTreeTaskScheduler(); TreeWalker = SnaffCon.GetTreeWalker(); }
public void Run(string[] args) { PrintBanner(); BlockingMq.MakeMq(); Mq = BlockingMq.GetMq(); SnaffCon controller = null; Options myOptions; try { myOptions = Config.Parse(args); //------------------------------------------ // set up new fangled logging //------------------------------------------ LoggingConfiguration nlogConfig = new LoggingConfiguration(); ColoredConsoleTarget logconsole = null; FileTarget logfile = null; ParseLogLevelString(myOptions.LogLevelString); // Targets where to log to: File and Console if (myOptions.LogToConsole) { logconsole = new ColoredConsoleTarget("logconsole") { DetectOutputRedirected = true, UseDefaultRowHighlightingRules = false, WordHighlightingRules = { new ConsoleWordHighlightingRule("{Green}", ConsoleOutputColor.DarkGreen, ConsoleOutputColor.White), new ConsoleWordHighlightingRule("{Yellow}", ConsoleOutputColor.DarkYellow, ConsoleOutputColor.White), new ConsoleWordHighlightingRule("{Red}", ConsoleOutputColor.DarkRed, ConsoleOutputColor.White), new ConsoleWordHighlightingRule("{Black}", ConsoleOutputColor.Black, ConsoleOutputColor.White), new ConsoleWordHighlightingRule("[Trace]", ConsoleOutputColor.DarkGray, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Degub]", ConsoleOutputColor.Gray, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Info]", ConsoleOutputColor.White, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Error]", ConsoleOutputColor.Magenta, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Fatal]", ConsoleOutputColor.Red, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[File]", ConsoleOutputColor.Green, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule("[Share]", ConsoleOutputColor.Yellow, ConsoleOutputColor.Black), new ConsoleWordHighlightingRule { CompileRegex = true, Regex = @"<.*\|.*\|.*\|.*?>", ForegroundColor = ConsoleOutputColor.Cyan, BackgroundColor = ConsoleOutputColor.Black }, new ConsoleWordHighlightingRule { CompileRegex = true, Regex = @"^\d\d\d\d-\d\d\-\d\d \d\d:\d\d:\d\d [\+-]\d\d:\d\d ", ForegroundColor = ConsoleOutputColor.DarkGray, BackgroundColor = ConsoleOutputColor.Black }, new ConsoleWordHighlightingRule { CompileRegex = true, Regex = @"\((?:[^\)]*\)){1}", ForegroundColor = ConsoleOutputColor.DarkMagenta, BackgroundColor = ConsoleOutputColor.Black } } }; nlogConfig.AddRule(LogLevel, LogLevel.Fatal, logconsole); logconsole.Layout = "${message}"; } if (myOptions.LogToFile) { logfile = new FileTarget("logfile") { FileName = myOptions.LogFilePath }; nlogConfig.AddRule(LogLevel, LogLevel.Fatal, logfile); logfile.Layout = "${message}"; } // Apply config LogManager.Configuration = nlogConfig; //------------------------------------------- if (myOptions.Snaffle && (myOptions.SnafflePath.Length > 4)) { Directory.CreateDirectory(myOptions.SnafflePath); } controller = new SnaffCon(myOptions); Task thing = Task.Factory.StartNew(() => { controller.Execute(); }); while (true) { HandleOutput(); } } catch (Exception e) { Console.WriteLine(e.ToString()); DumpQueue(); } }
public bool ClassifyFile(FileInfo fileInfo) { BlockingMq Mq = BlockingMq.GetMq(); // figure out what part we gonna look at string stringToMatch = null; switch (ClassifierRule.MatchLocation) { case MatchLoc.FileExtension: stringToMatch = fileInfo.Extension; // special handling to treat files named like 'thing.kdbx.bak' if (stringToMatch == ".bak") { // strip off .bak string subName = fileInfo.Name.Replace(".bak", ""); stringToMatch = Path.GetExtension(subName); // if this results in no file extension, put it back. if (stringToMatch == "") { stringToMatch = ".bak"; } } // this is insane that i have to do this but apparently files with no extension return // this bullshit if (stringToMatch == "") { return(false); } break; case MatchLoc.FileName: stringToMatch = fileInfo.Name; break; case MatchLoc.FilePath: stringToMatch = fileInfo.FullName; break; case MatchLoc.FileLength: if (!SizeMatch(fileInfo)) { return(false); } else { break; } default: Mq.Error("You've got a misconfigured file classifier rule named " + ClassifierRule.RuleName + "."); return(false); } TextResult textResult = null; if (!String.IsNullOrEmpty(stringToMatch)) { TextClassifier textClassifier = new TextClassifier(ClassifierRule); // check if it matches textResult = textClassifier.TextMatch(stringToMatch); if (textResult == null) { // if it doesn't we just bail now. return(false); } } FileResult fileResult; // if it matches, see what we're gonna do with it switch (ClassifierRule.MatchAction) { case MatchAction.Discard: // chuck it. return(true); case MatchAction.Snaffle: // snaffle that bad boy fileResult = new FileResult(fileInfo) { MatchedRule = ClassifierRule, TextResult = textResult }; // if the file was list-only, don't bother sending a result back to the user. if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite) { return(false); } ; Mq.FileResult(fileResult); return(false); case MatchAction.CheckForKeys: // do a special x509 dance List <string> x509MatchReason = x509Match(fileInfo); if (x509MatchReason.Count >= 0) { // if there were any matchreasons, cat them together... string matchContext = String.Join(",", x509MatchReason); // and sling the results on the queue fileResult = new FileResult(fileInfo) { MatchedRule = ClassifierRule, TextResult = new TextResult() { MatchContext = matchContext, MatchedStrings = new List <string>() { "" } } }; if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite) { return(false); } ; Mq.FileResult(fileResult); } return(false); case MatchAction.Relay: // bounce it on to the next ClassifierRule try { bool fLoggedContentSizeWarning = false; foreach (string relayTarget in ClassifierRule.RelayTargets) { ClassifierRule nextRule = MyOptions.ClassifierRules.First(thing => thing.RuleName == relayTarget); if (nextRule.EnumerationScope == EnumerationScope.ContentsEnumeration) { if (fileInfo.Length > MyOptions.MaxSizeToGrep) { if (!fLoggedContentSizeWarning) { // Just log once per relay rule, no need to fill up the log with one for each relay target Mq.Trace("The following file was bigger than the MaxSizeToGrep config parameter:" + fileInfo.FullName); fLoggedContentSizeWarning = true; } continue; } ContentClassifier nextContentClassifier = new ContentClassifier(nextRule); nextContentClassifier.ClassifyContent(fileInfo); } else if (nextRule.EnumerationScope == EnumerationScope.FileEnumeration) { FileClassifier nextFileClassifier = new FileClassifier(nextRule); nextFileClassifier.ClassifyFile(fileInfo); } else { Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + "."); } } return(false); } catch (IOException e) { Mq.Trace(e.ToString()); } catch (Exception e) { Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + "."); Mq.Trace(e.ToString()); } return(false); case MatchAction.EnterArchive: // do a special looking inside archive files dance using // https://github.com/adamhathcock/sharpcompress // TODO FUUUUUCK throw new NotImplementedException("Haven't implemented walking dir structures inside archives."); default: Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + "."); return(false); } }
private static Options ParseImpl(string[] args) { BlockingMq Mq = BlockingMq.GetMq(); Mq.Info("Parsing args..."); Options parsedConfig = new Options(); // define args ValueArgument <string> configFileArg = new ValueArgument <string>('z', "config", "Path to a .toml config file. Run with \'generate\' to puke a sample config file into the working directory."); ValueArgument <string> outFileArg = new ValueArgument <string>('o', "outfile", "Path for output file. You probably want this if you're not using -s."); ValueArgument <string> verboseArg = new ValueArgument <string>('v', "verbosity", "Controls verbosity level, options are Trace (most verbose), Debug (less verbose), Info (less verbose still, default), and Data (results only). e.g '-v debug' "); SwitchArgument helpArg = new SwitchArgument('h', "help", "Displays this help.", false); SwitchArgument stdOutArg = new SwitchArgument('s', "stdout", "Enables outputting results to stdout as soon as they're found. You probably want this if you're not using -o.", false); ValueArgument <int> interestLevel = new ValueArgument <int>('b', "interest", "Interest level to report (0-3)"); ValueArgument <string> snaffleArg = new ValueArgument <string>('m', "snaffle", "Enables and assigns an output dir for Snaffler to automatically snaffle a copy of any found files."); ValueArgument <long> snaffleSizeArg = new ValueArgument <long>('l', "snafflesize", "Maximum size of file to snaffle, in bytes. Defaults to 10MB."); //var fileHuntArg = new SwitchArgument('f', "filehuntoff", // "Disables file discovery, will only perform computer and share discovery.", false); ValueArgument <string> dirTargetArg = new ValueArgument <string>('i', "dirtarget", "Disables computer and share discovery, requires a path to a directory in which to perform file discovery."); ValueArgument <string> domainArg = new ValueArgument <string>('d', "domain", "Domain to search for computers to search for shares on to search for files in. Easy."); ValueArgument <string> domainControllerArg = new ValueArgument <string>('c', "domaincontroller", "Domain controller to query for a list of domain computers."); ValueArgument <long> maxGrepSizeArg = new ValueArgument <long>('r', "maxgrepsize", "The maximum size file (in bytes) to search inside for interesting strings. Defaults to 500k."); ValueArgument <int> grepContextArg = new ValueArgument <int>('j', "grepcontext", "How many bytes of context either side of found strings in files to show, e.g. -j 200"); SwitchArgument domainUserArg = new SwitchArgument('u', "domainusers", "Makes Snaffler grab a list of interesting-looking accounts from the domain and uses them in searches.", false); ValueArgument <int> maxThreadsArg = new ValueArgument <int>('x', "maxthreads", "How many threads to be snaffling with. Any less than 4 and you're gonna have a bad time."); SwitchArgument tsvArg = new SwitchArgument('y', "tsv", "Makes Snaffler output as tsv.", false); SwitchArgument dfsArg = new SwitchArgument('f', "dfs", "Limits Snaffler to finding file shares via DFS, for \"OPSEC\" reasons.", false); SwitchArgument findSharesOnlyArg = new SwitchArgument('a', "sharesonly", "Stops after finding shares, doesn't walk their filesystems.", false); ValueArgument <string> compTargetArg = new ValueArgument <string>('n', "comptarget", "Computer (or comma separated list) to target."); ValueArgument <string> ruleDirArg = new ValueArgument <string>('p', "rulespath", "Path to a directory full of toml-formatted rules. Snaffler will load all of these in place of the default ruleset."); ValueArgument <string> logType = new ValueArgument <string>('t', "logtype", "Type of log you would like to output. Currently supported options are plain and JSON. Defaults to plain."); // list of letters i haven't used yet: egknqw CommandLineParser.CommandLineParser parser = new CommandLineParser.CommandLineParser(); parser.Arguments.Add(configFileArg); parser.Arguments.Add(outFileArg); parser.Arguments.Add(helpArg); parser.Arguments.Add(stdOutArg); parser.Arguments.Add(snaffleArg); parser.Arguments.Add(snaffleSizeArg); parser.Arguments.Add(dirTargetArg); parser.Arguments.Add(interestLevel); parser.Arguments.Add(domainArg); parser.Arguments.Add(verboseArg); parser.Arguments.Add(domainControllerArg); parser.Arguments.Add(maxGrepSizeArg); parser.Arguments.Add(grepContextArg); parser.Arguments.Add(domainUserArg); parser.Arguments.Add(tsvArg); parser.Arguments.Add(dfsArg); parser.Arguments.Add(findSharesOnlyArg); parser.Arguments.Add(maxThreadsArg); parser.Arguments.Add(compTargetArg); parser.Arguments.Add(ruleDirArg); parser.Arguments.Add(logType); // extra check to handle builtin behaviour from cmd line arg parser if ((args.Contains("--help") || args.Contains("/?") || args.Contains("help") || args.Contains("-h") || args.Length == 0)) { parser.ShowUsage(); Environment.Exit(0); } TomlSettings settings = TomlSettings.Create(cfg => cfg .ConfigureType <LogLevel>(tc => tc.WithConversionFor <TomlString>(conv => conv .FromToml(s => (LogLevel)Enum.Parse(typeof(LogLevel), s.Value, ignoreCase: true)) .ToToml(e => e.ToString())))); try { parser.ParseCommandLine(args); if (logType.Parsed && !String.IsNullOrWhiteSpace(logType.Value)) { //Set the default to plain parsedConfig.LogType = LogType.Plain; //if they set a different type then replace it with the new type. if (logType.Value.ToLower() == "json") { parsedConfig.LogType = LogType.JSON; } else { Mq.Info("Invalid type argument passed (" + logType.Value + ") defaulting to plaintext"); } } if (ruleDirArg.Parsed && !String.IsNullOrWhiteSpace(ruleDirArg.Value)) { parsedConfig.RuleDir = ruleDirArg.Value; } // get the args into our config // output args if (outFileArg.Parsed && (!String.IsNullOrEmpty(outFileArg.Value))) { parsedConfig.LogToFile = true; parsedConfig.LogFilePath = outFileArg.Value; Mq.Degub("Logging to file at " + parsedConfig.LogFilePath); } if (dfsArg.Parsed) { parsedConfig.DfsOnly = dfsArg.Value; } if (compTargetArg.Parsed) { string[] compTargets = null; if (compTargetArg.Value.Contains(",")) { compTargets = compTargetArg.Value.Split(','); } else { compTargets = new string[] { compTargetArg.Value }; } parsedConfig.ComputerTargets = compTargets; } if (findSharesOnlyArg.Parsed) { parsedConfig.ScanFoundShares = false; } if (maxThreadsArg.Parsed) { parsedConfig.MaxThreads = maxThreadsArg.Value; } parsedConfig.ShareThreads = parsedConfig.MaxThreads / 3; parsedConfig.FileThreads = parsedConfig.MaxThreads / 3; parsedConfig.TreeThreads = parsedConfig.MaxThreads / 3; if (tsvArg.Parsed) { parsedConfig.LogTSV = true; if (parsedConfig.Separator == ' ') { parsedConfig.Separator = '\t'; } } // Set loglevel. if (verboseArg.Parsed) { parsedConfig.LogLevelString = verboseArg.Value; Mq.Degub("Requested verbosity level: " + parsedConfig.LogLevelString); } // if enabled, display findings to the console parsedConfig.LogToConsole = stdOutArg.Parsed; Mq.Degub("Enabled logging to stdout."); // args that tell us about targeting if ((domainArg.Parsed) && (!String.IsNullOrEmpty(domainArg.Value))) { parsedConfig.TargetDomain = domainArg.Value; Mq.Degub("Target domain is " + domainArg.Value); } if ((domainControllerArg.Parsed) && (!String.IsNullOrEmpty(domainControllerArg.Value))) { parsedConfig.TargetDc = domainControllerArg.Value; Mq.Degub("Target DC is " + domainControllerArg.Value); } if (domainUserArg.Parsed) { parsedConfig.DomainUserRules = true; Mq.Degub("Enabled use of domain user accounts in rules."); } if (dirTargetArg.Parsed) { parsedConfig.ShareFinderEnabled = false; parsedConfig.PathTargets.Add(dirTargetArg.Value); Mq.Degub("Disabled finding shares."); Mq.Degub("Target path is " + dirTargetArg.Value); } if (maxGrepSizeArg.Parsed) { parsedConfig.MaxSizeToGrep = maxGrepSizeArg.Value; Mq.Degub("We won't bother looking inside files if they're bigger than " + parsedConfig.MaxSizeToGrep + " bytes"); } if (snaffleSizeArg.Parsed) { parsedConfig.MaxSizeToSnaffle = snaffleSizeArg.Value; } if (interestLevel.Parsed) { parsedConfig.InterestLevel = interestLevel.Value; Mq.Degub("Requested interest level: " + parsedConfig.InterestLevel); } // how many bytes if (grepContextArg.Parsed) { parsedConfig.MatchContextBytes = grepContextArg.Value; Mq.Degub( "We'll show you " + grepContextArg.Value + " bytes of context around matches inside files."); } // if enabled, grab a copy of files that we like. if (snaffleArg.Parsed) { if (snaffleArg.Value.Length <= 0) { Mq.Error("-m or -mirror arg requires a path value."); throw new ArgumentException("Invalid argument combination."); } parsedConfig.Snaffle = true; parsedConfig.SnafflePath = snaffleArg.Value.TrimEnd('\\'); Mq.Degub("Mirroring matched files to path " + parsedConfig.SnafflePath); } if (configFileArg.Parsed) { if (configFileArg.Value.Equals("generate")) { Toml.WriteFile(parsedConfig, ".\\default.toml", settings); Console.WriteLine("Wrote config values to .\\default.toml"); parsedConfig.LogToConsole = true; Mq.Degub("Enabled logging to stdout."); Environment.Exit(0); } else { string configFile = configFileArg.Value; parsedConfig = Toml.ReadFile <Options>(configFile, settings); Mq.Info("Read config file from " + configFile); } } if (!parsedConfig.LogToConsole && !parsedConfig.LogToFile) { Mq.Error( "\nYou didn't enable output to file or to the console so you won't see any results or debugs or anything. Your l0ss."); throw new ArgumentException("Pointless argument combination."); } if (parsedConfig.ClassifierRules.Count <= 0) { if (String.IsNullOrWhiteSpace(parsedConfig.RuleDir)) { // get all the embedded toml file resources string[] resourceNames = Assembly.GetExecutingAssembly().GetManifestResourceNames(); StringBuilder sb = new StringBuilder(); foreach (string resourceName in resourceNames) { if (!resourceName.EndsWith(".toml")) { // skip this one as it's just metadata continue; } string ruleFile = ReadResource(resourceName); sb.AppendLine(ruleFile); } string bulktoml = sb.ToString(); // deserialise the toml to an actual ruleset RuleSet ruleSet = Toml.ReadString <RuleSet>(bulktoml, settings); // stick the rules in our config! parsedConfig.ClassifierRules = ruleSet.ClassifierRules; } else { string[] tomlfiles = Directory.GetFiles(parsedConfig.RuleDir, "*.toml", SearchOption.AllDirectories); StringBuilder sb = new StringBuilder(); foreach (string tomlfile in tomlfiles) { string tomlstring = File.ReadAllText(tomlfile); sb.AppendLine(tomlstring); } string bulktoml = sb.ToString(); // deserialise the toml to an actual ruleset RuleSet ruleSet = Toml.ReadString <RuleSet>(bulktoml, settings); // stick the rules in our config! parsedConfig.ClassifierRules = ruleSet.ClassifierRules; } } parsedConfig.PrepareClassifiers(); } catch (Exception e) { Mq.Error(e.ToString()); throw; } return(parsedConfig); }
private static Options ParseImpl(string[] args) { BlockingMq Mq = BlockingMq.GetMq(); Mq.Info("Parsing args..."); Options parsedConfig = new Options(); // define args ValueArgument <string> configFileArg = new ValueArgument <string>('z', "config", "Path to a .toml config file. Run with \'generate\' to puke a sample config file into the working directory."); ValueArgument <string> outFileArg = new ValueArgument <string>('o', "outfile", "Path for output file. You probably want this if you're not using -s."); ValueArgument <string> verboseArg = new ValueArgument <string>('v', "verbosity", "Controls verbosity level, options are Trace (most verbose), Debug (less verbose), Info (less verbose still, default), and Data (results only). e.g '-v debug' "); SwitchArgument helpArg = new SwitchArgument('h', "help", "Displays this help.", false); SwitchArgument stdOutArg = new SwitchArgument('s', "stdout", "Enables outputting results to stdout as soon as they're found. You probably want this if you're not using -o.", false); ValueArgument <string> snaffleArg = new ValueArgument <string>('m', "snaffle", "Enables and assigns an output dir for Snaffler to automatically snaffle a copy of any found files."); ValueArgument <long> snaffleSizeArg = new ValueArgument <long>('l', "snafflesize", "Maximum size of file to snaffle, in bytes. Defaults to 10MB."); //var fileHuntArg = new SwitchArgument('f', "filehuntoff", // "Disables file discovery, will only perform computer and share discovery.", false); ValueArgument <string> dirTargetArg = new ValueArgument <string>('i', "dirtarget", "Disables computer and share discovery, requires a path to a directory in which to perform file discovery."); ValueArgument <string> domainArg = new ValueArgument <string>('d', "domain", "Domain to search for computers to search for shares on to search for files in. Easy."); ValueArgument <string> domainControllerArg = new ValueArgument <string>('c', "domaincontroller", "Domain controller to query for a list of domain computers."); ValueArgument <long> maxGrepSizeArg = new ValueArgument <long>('r', "maxgrepsize", "The maximum size file (in bytes) to search inside for interesting strings. Defaults to 500k."); ValueArgument <int> grepContextArg = new ValueArgument <int>('j', "grepcontext", "How many bytes of context either side of found strings in files to show, e.g. -j 200"); SwitchArgument domainUserArg = new SwitchArgument('u', "domainusers", "Makes Snaffler grab a list of interesting-looking accounts from the domain and uses them in searches.", false); // list of letters i haven't used yet: abefgknpqwxy CommandLineParser.CommandLineParser parser = new CommandLineParser.CommandLineParser(); parser.Arguments.Add(configFileArg); parser.Arguments.Add(outFileArg); parser.Arguments.Add(helpArg); parser.Arguments.Add(stdOutArg); parser.Arguments.Add(snaffleArg); parser.Arguments.Add(snaffleSizeArg); parser.Arguments.Add(dirTargetArg); parser.Arguments.Add(domainArg); parser.Arguments.Add(verboseArg); parser.Arguments.Add(domainControllerArg); parser.Arguments.Add(maxGrepSizeArg); parser.Arguments.Add(grepContextArg); parser.Arguments.Add(domainUserArg); // extra check to handle builtin behaviour from cmd line arg parser if ((args.Contains("--help") || args.Contains("/?") || args.Contains("help") || args.Contains("-h") || args.Length == 0)) { parser.ShowUsage(); Environment.Exit(0); } TomlSettings settings = TomlSettings.Create(cfg => cfg .ConfigureType <LogLevel>(tc => tc.WithConversionFor <TomlString>(conv => conv .FromToml(s => (LogLevel)Enum.Parse(typeof(LogLevel), s.Value, ignoreCase: true)) .ToToml(e => e.ToString())))); try { parser.ParseCommandLine(args); if (configFileArg.Parsed) { if (!configFileArg.Value.Equals("generate")) { string configFile = configFileArg.Value; parsedConfig = Toml.ReadFile <Options>(configFile, settings); parsedConfig.PrepareClassifiers(); Mq.Info("Read config file from " + configFile); return(parsedConfig); } } if (parsedConfig.ClassifierRules.Count <= 0) { parsedConfig.BuildDefaultClassifiers(); } // get the args into our config // output args if (outFileArg.Parsed && (!String.IsNullOrEmpty(outFileArg.Value))) { parsedConfig.LogToFile = true; parsedConfig.LogFilePath = outFileArg.Value; Mq.Degub("Logging to file at " + parsedConfig.LogFilePath); } // Set loglevel. if (verboseArg.Parsed) { parsedConfig.LogLevelString = verboseArg.Value; Mq.Degub("Requested verbosity level: " + parsedConfig.LogLevelString); } // if enabled, display findings to the console parsedConfig.LogToConsole = stdOutArg.Parsed; Mq.Degub("Enabled logging to stdout."); // args that tell us about targeting if ((domainArg.Parsed) && (!String.IsNullOrEmpty(domainArg.Value))) { parsedConfig.TargetDomain = domainArg.Value; Mq.Degub("Target domain is " + domainArg.Value); } if ((domainControllerArg.Parsed) && (!String.IsNullOrEmpty(domainControllerArg.Value))) { parsedConfig.TargetDc = domainControllerArg.Value; Mq.Degub("Target DC is " + domainControllerArg.Value); } if (domainUserArg.Parsed) { parsedConfig.DomainUserRules = true; Mq.Degub("Enabled use of domain user accounts in rules."); } if (dirTargetArg.Parsed) { parsedConfig.ShareFinderEnabled = false; parsedConfig.PathTargets = new string[] { dirTargetArg.Value }; Mq.Degub("Disabled finding shares."); Mq.Degub("Target path is " + dirTargetArg.Value); } if (maxGrepSizeArg.Parsed) { parsedConfig.MaxSizeToGrep = maxGrepSizeArg.Value; Mq.Degub("We won't bother looking inside files if they're bigger than " + parsedConfig.MaxSizeToGrep + " bytes"); } if (snaffleArg.Parsed) { parsedConfig.SnafflePath = snaffleArg.Value; } if (snaffleSizeArg.Parsed) { parsedConfig.MaxSizeToSnaffle = snaffleSizeArg.Value; } // how many bytes if (grepContextArg.Parsed) { parsedConfig.MatchContextBytes = grepContextArg.Value; Mq.Degub( "We'll show you " + grepContextArg.Value + " bytes of context around matches inside files."); } // if enabled, grab a copy of files that we like. if (snaffleArg.Parsed) { if (snaffleArg.Value.Length <= 0) { Mq.Error("-m or -mirror arg requires a path value."); throw new ArgumentException("Invalid argument combination."); } parsedConfig.Snaffle = true; parsedConfig.SnafflePath = snaffleArg.Value.TrimEnd('\\'); Mq.Degub("Mirroring matched files to path " + parsedConfig.SnafflePath); } if (!parsedConfig.LogToConsole && !parsedConfig.LogToFile) { Mq.Error( "\nYou didn't enable output to file or to the console so you won't see any results or debugs or anything. Your l0ss."); throw new ArgumentException("Pointless argument combination."); } if (configFileArg.Parsed) { if (configFileArg.Value.Equals("generate")) { Toml.WriteFile(parsedConfig, ".\\default.toml", settings); Mq.Info("Wrote default config values to .\\default.toml"); Mq.Terminate(); } } parsedConfig.PrepareClassifiers(); } catch (Exception e) { Mq.Error(e.ToString()); throw; } return(parsedConfig); }
public ArchiveClassifier(FileInfo fileInfo) { Mq = BlockingMq.GetMq(); FileScanner = SnaffCon.GetFileScanner(); }
public DfsFinder() { Mq = BlockingMq.GetMq(); }