diff --git a/Source/santactl/Commands/SNTCommandRule.m b/Source/santactl/Commands/SNTCommandRule.m index 91c67a730..fd323ce36 100644 --- a/Source/santactl/Commands/SNTCommandRule.m +++ b/Source/santactl/Commands/SNTCommandRule.m @@ -45,58 +45,65 @@ + (NSString *)shortHelpText { } + (NSString *)longHelpText { - return (@"Usage: santactl rule [options]\n" - @" One of:\n" - @" --allow: add to allow\n" - @" --block: add to block\n" - @" --silent-block: add to silent block\n" - @" --compiler: allow and mark as a compiler\n" - @" --remove: remove existing rule\n" - @" --check: check for an existing rule\n" - @" --import {path}: import rules from a JSON file\n" - @" --export {path}: export rules to a JSON file\n" - @"\n" - @" One of:\n" - @" --path {path}: path of binary/bundle to add/remove.\n" - @" Will add the hash of the file currently at that path.\n" - @" Does not work with --check. Use the fileinfo verb to check.\n" - @" the rule state of a file.\n" - @" --identifier {sha256|teamID|signingID}: identifier to add/remove/check\n" - @" --sha256 {sha256}: hash to add/remove/check [deprecated]\n" - @"\n" - @" Optionally:\n" - @" --teamid: add or check a team ID rule instead of binary\n" - @" --signingid: add or check a signing ID rule instead of binary (see notes)\n" - @" --certificate: add or check a certificate sha256 rule instead of binary\n" + return ( + @"Usage: santactl rule [options]\n" + @" One of:\n" + @" --allow: add to allow\n" + @" --block: add to block\n" + @" --silent-block: add to silent block\n" + @" --compiler: allow and mark as a compiler\n" + @" --remove: remove existing rule\n" + @" --check: check for an existing rule\n" + @" --import {path}: import rules from a JSON file\n" + @" --export {path}: export rules to a JSON file\n" + @"\n" + @" One of:\n" + @" --path {path}: path of binary/bundle to add/remove.\n" + @" Will add the hash of the file currently at that path.\n" + @" Does not work with --check. Use the fileinfo verb to check.\n" + @" the rule state of a file.\n" + @" --identifier {sha256|teamID|signingID}: identifier to add/remove/check\n" + @" --sha256 {sha256}: hash to add/remove/check [deprecated]\n" + @"\n" + @" Optionally:\n" + @" --teamid: add or check a team ID rule instead of binary\n" + @" --signingid: add or check a signing ID rule instead of binary (see notes)\n" + @" --certificate: add or check a certificate sha256 rule instead of binary\n" #ifdef DEBUG - @" --force: allow manual changes even when SyncBaseUrl is set\n" + @" --force: allow manual changes even when SyncBaseUrl is set\n" #endif - @" --message {message}: custom message\n" - @"\n" - @" Notes:\n" - @" The format of `identifier` when adding/checking a `signingid` rule is:\n" - @"\n" - @" `TeamID:SigningID`\n" - @"\n" - @" Because signing IDs are controlled by the binary author, this ensures\n" - @" that the signing ID is properly scoped to a developer. For the special\n" - @" case of platform binaries, `TeamID` should be replaced with the string\n" - @" \"platform\" (e.g. `platform:SigningID`). This allows for rules\n" - @" targeting Apple-signed binaries that do not have a team ID.\n" - @"\n" - @" Importing / Exporting Rules:\n" - @" If santa is not configured to use a sync server one can export\n" - @" & import its non-static rules to and from JSON files using the \n" - @" --export/--import flags. These files have the following form:\n" - @"\n" - @" {\"rules\": [{rule-dictionaries}]}\n" - @" e.g. {\"rules\": [\n" - @" {\"policy\": \"BLOCKLIST\",\n" - @" \"identifier\": " - @"\"84de9c61777ca36b13228e2446d53e966096e78db7a72c632b5c185b2ffe68a6\"\n" - @" \"custom_url\" : \"\",\n" - @" \"custom_msg\": \"/bin/ls block for demo\"}\n" - @" ]}\n"); + @" --message {message}: custom message\n" + @" --clean: when importing rules via JSON clear all non-transitive rules before importing\n" + @" --clean-all: when importing rules via JSON clear all rules before importing\n" + @"\n" + @" Notes:\n" + @" The format of `identifier` when adding/checking a `signingid` rule is:\n" + @"\n" + @" `TeamID:SigningID`\n" + @"\n" + @" Because signing IDs are controlled by the binary author, this ensures\n" + @" that the signing ID is properly scoped to a developer. For the special\n" + @" case of platform binaries, `TeamID` should be replaced with the string\n" + @" \"platform\" (e.g. `platform:SigningID`). This allows for rules\n" + @" targeting Apple-signed binaries that do not have a team ID.\n" + @"\n" + @" Importing / Exporting Rules:\n" + @" If santa is not configured to use a sync server one can export\n" + @" & import its non-static rules to and from JSON files using the \n" + @" --export/--import flags. These files have the following form:\n" + @"\n" + @" {\"rules\": [{rule-dictionaries}]}\n" + @" e.g. {\"rules\": [\n" + @" {\"policy\": \"BLOCKLIST\",\n" + @" \"identifier\": " + @"\"84de9c61777ca36b13228e2446d53e966096e78db7a72c632b5c185b2ffe68a6\"\n" + @" \"custom_url\" : \"\",\n" + @" \"custom_msg\": \"/bin/ls block for demo\"}\n" + @" ]}\n" + @"\n" + @" By default rules are not cleared when importing. To clear the\n" + @" database you must use either --clean or --clean-all\n" + @"\n"); } - (void)runWithArguments:(NSArray *)arguments { @@ -120,6 +127,7 @@ - (void)runWithArguments:(NSArray *)arguments { NSString *path; NSString *jsonFilePath; BOOL check = NO; + SNTRuleCleanup cleanupType = SNTRuleCleanupNone; BOOL importRules = NO; BOOL exportRules = NO; @@ -181,6 +189,10 @@ - (void)runWithArguments:(NSArray *)arguments { [self printErrorUsageAndExit:@"--import requires an argument"]; } jsonFilePath = arguments[i]; + } else if ([arg caseInsensitiveCompare:@"--clean"] == NSOrderedSame) { + cleanupType = SNTRuleCleanupNonTransitive; + } else if ([arg caseInsensitiveCompare:@"--clean-all"] == NSOrderedSame) { + cleanupType = SNTRuleCleanupAll; } else if ([arg caseInsensitiveCompare:@"--export"] == NSOrderedSame) { if (importRules) { [self printErrorUsageAndExit:@"--import and --export are mutually exclusive"]; @@ -199,12 +211,27 @@ - (void)runWithArguments:(NSArray *)arguments { } } + if (!importRules && cleanupType != SNTRuleCleanupNone) { + switch (cleanupType) { + case SNTRuleCleanupNonTransitive: + [self printErrorUsageAndExit:@"--clean can only be used with --import"]; + break; + case SNTRuleCleanupAll: + [self printErrorUsageAndExit:@"--clean-all can only be used with --import"]; + break; + default: + // This is a programming error. + LOGE(@"Unexpected SNTRuleCleanupType %ld", cleanupType); + exit(EXIT_FAILURE); + } + } + if (jsonFilePath.length > 0) { if (importRules) { if (newRule.identifier != nil || path != nil || check) { [self printErrorUsageAndExit:@"--import can only be used by itself"]; } - [self importJSONFile:jsonFilePath]; + [self importJSONFile:jsonFilePath with:cleanupType]; } else if (exportRules) { if (newRule.identifier != nil || path != nil || check) { [self printErrorUsageAndExit:@"--export can only be used by itself"]; @@ -385,7 +412,7 @@ - (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)da exit(0); } -- (void)importJSONFile:(NSString *)jsonFilePath { +- (void)importJSONFile:(NSString *)jsonFilePath with:(SNTRuleCleanup)cleanupType { // If the file exists parse it and then add the rules one at a time. NSError *error; NSData *data = [NSData dataWithContentsOfFile:jsonFilePath options:0 error:&error]; @@ -422,7 +449,7 @@ - (void)importJSONFile:(NSString *)jsonFilePath { [[self.daemonConn remoteObjectProxy] databaseRuleAddRules:parsedRules - ruleCleanup:SNTRuleCleanupNone + ruleCleanup:cleanupType reply:^(NSError *error) { if (error) { printf("Failed to modify rules: %s",