diff --git a/ChangeLog b/ChangeLog index 8af40a5..f164b4a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2023-03-08 Richard Frith-Macdonald + + * EcCommand.m: + * EcControl.m: + * EcProcess.h: + * EcProcess.m: + * Operators.plist: + Implement a Blocked array to list the commands that an operator should + not be allowed to perform. + 2023-02-14 Richard Frith-Macdonald * EcProcess.m: When using the 'defaults' command from the Console, diff --git a/EcCommand.m b/EcCommand.m index 4dcbc68..a659b5b 100644 --- a/EcCommand.m +++ b/EcCommand.m @@ -105,6 +105,19 @@ static int comp(NSString *s0, NSString *s1) } } +static BOOL matchCmd(NSString *word, NSString *reference, NSArray *blocked) +{ + if (comp(word, reference) < 0) + { + return NO; + } + if ([blocked containsObject: reference]) + { + return NO; + } + return YES; +} + static NSString* cmdWord(NSArray* a, unsigned int pos) { @@ -4318,6 +4331,7 @@ NSLog(@"Problem %@", localException); } else if (t == nil) { + NSArray *blocked = [self ecBlocked: f]; NSString *m = @""; NSString *wd = cmdWord(cmd, 0); @@ -4325,7 +4339,7 @@ NSLog(@"Problem %@", localException); { /* Quietly ignore. */ } - else if (comp(wd, @"alarms") >= 0) + else if (matchCmd(wd, @"alarms", blocked)) { NSMutableArray *a = [NSMutableArray array]; NSEnumerator *e = [launchInfo objectEnumerator]; @@ -4356,14 +4370,14 @@ NSLog(@"Problem %@", localException); } } } - else if (comp(wd, @"archive") >= 0) + else if (matchCmd(wd, @"archive", blocked)) { NSCalendarDate *when; m = [NSString stringWithFormat: @"\n%@\n", [self ecArchive: nil]]; when = [NSCalendarDate date]; } - else if (comp(wd, @"clear") >= 0) + else if (matchCmd(wd, @"clear", blocked)) { NSMutableArray *a = [NSMutableArray array]; NSEnumerator *e = [launchInfo objectEnumerator]; @@ -4444,7 +4458,7 @@ NSLog(@"Problem %@", localException); } } } - else if (comp(wd, @"help") >= 0) + else if (matchCmd(wd, @"help", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] == 0) @@ -4565,7 +4579,7 @@ NSLog(@"Problem %@", localException); } } } - else if (comp(wd, @"launch") >= 0) + else if (matchCmd(wd, @"launch", blocked)) { if (NO == launchEnabled) { @@ -4711,7 +4725,7 @@ NSLog(@"Problem %@", localException); m = @"I need the name of a program to launch.\n"; } } - else if (comp(wd, @"list") >= 0) + else if (matchCmd(wd, @"list", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] == 0 || comp(wd, @"clients") >= 0) @@ -4819,7 +4833,7 @@ NSLog(@"Problem %@", localException); } } } - else if (comp(wd, @"memory") >= 0) + else if (matchCmd(wd, @"memory", blocked)) { if (GSDebugAllocationActive(YES) == NO) { @@ -4842,7 +4856,7 @@ NSLog(@"Problem %@", localException); m = [NSString stringWithCString: list]; } } - else if (comp(wd, @"quit") >= 0) + else if (matchCmd(wd, @"quit", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] > 0) @@ -4958,7 +4972,7 @@ NSLog(@"Problem %@", localException); m = @"Quit what?.\n"; } } - else if (comp(wd, @"restart") >= 0) + else if (matchCmd(wd, @"restart", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] > 0) @@ -5061,7 +5075,7 @@ NSLog(@"Problem %@", localException); m = @"Restart what?.\n"; } } - else if (comp(wd, @"resume") >= 0) + else if (matchCmd(wd, @"resume", blocked)) { if (NO == launchEnabled) { @@ -5075,7 +5089,7 @@ NSLog(@"Problem %@", localException); m = @"Launching was/is not suspended.\n"; } } - else if (comp(wd, @"status") >= 0) + else if (matchCmd(wd, @"status", blocked)) { m = [self description]; if ([(wd = cmdWord(cmd, 1)) length] > 0) @@ -5123,7 +5137,7 @@ NSLog(@"Problem %@", localException); } } } - else if (comp(wd, @"suspend") >= 0) + else if (matchCmd(wd, @"suspend", blocked)) { if (NO == launchEnabled) { @@ -5135,7 +5149,7 @@ NSLog(@"Problem %@", localException); m = @"Launching is now suspended.\n"; } } - else if (comp(wd, @"tell") >= 0) + else if (matchCmd(wd, @"tell", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] > 0) @@ -7157,6 +7171,7 @@ NSLog(@"Unregister with status %d", s); { [newConfig setObject: operators forKey: @"Operators"]; } + [self ecOperators: operators]; /* Finally, replace old config with new if they differ. */ diff --git a/EcControl.m b/EcControl.m index 3f4fda6..38eaa63 100644 --- a/EcControl.m +++ b/EcControl.m @@ -98,6 +98,19 @@ static int comp(NSString *s0, NSString *s1) } } +static BOOL matchCmd(NSString *word, NSString *reference, NSArray *blocked) +{ + if (comp(word, reference) < 0) + { + return NO; + } + if ([blocked containsObject: reference]) + { + return NO; + } + return YES; +} + static NSString* cmdWord(NSArray* a, unsigned int pos) { if (a != nil && [a count] > pos) @@ -708,6 +721,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) else { NSMutableString *full; + NSArray *blocked; NSString *hname = nil; NSString *m = @""; NSString *wd = cmdWord(cmd, 0); @@ -831,6 +845,10 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) } } + /* Find the commands blocked for this user. + */ + blocked = [self ecBlocked: [console name]]; + if (connected == YES || hname != nil) { NSArray *hosts; @@ -947,7 +965,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) { /* Quietly ignore. */ } - else if (comp(wd, @"alarms") >= 0) + else if (matchCmd(wd, @"alarms", blocked)) { NSArray *a = [sink alarms]; @@ -970,11 +988,11 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) } } } - else if (comp(wd, @"archive") >= 0) + else if (matchCmd(wd, @"archive", blocked)) { m = [NSString stringWithFormat: @"\n%@\n\n", [self ecArchive: nil]]; } - else if (comp(wd, @"clear") >= 0) + else if (matchCmd(wd, @"clear", blocked)) { NSArray *a = [sink alarms]; unsigned index = 1; @@ -1036,7 +1054,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) @"external SNMP monitoring systems.\n"; } } - else if (comp(wd, @"connect") >= 0) + else if (matchCmd(wd, @"connect", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] == 0) @@ -1048,7 +1066,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) [console setConnectedServ: wd]; } } - else if (comp(wd, @"config") >= 0) + else if (matchCmd(wd, @"config", blocked)) { BOOL changed; @@ -1084,13 +1102,13 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) to: nil from: nil]; } - else if (comp(wd, @"flush") >= 0) + else if (matchCmd(wd, @"flush", blocked)) { [alerter flushSms]; [alerter flushEmail]; m = @"Flushed alert messages\n"; } - else if (comp(wd, @"help") >= 0) + else if (matchCmd(wd, @"help", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] == 0) @@ -1312,7 +1330,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) } } } - else if (comp(wd, @"host") >= 0) + else if (matchCmd(wd, @"host", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] == 0) @@ -1330,7 +1348,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) } } } - else if (comp(wd, @"list") >= 0) + else if (matchCmd(wd, @"list", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] > 0 && comp(wd, @"consoles") >= 0) @@ -1407,7 +1425,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) } } } - else if (comp(wd, @"memory") >= 0) + else if (matchCmd(wd, @"memory", blocked)) { if (GSDebugAllocationActive(YES) == NO) { @@ -1430,7 +1448,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) m = [NSString stringWithCString: list]; } } - else if (comp(wd, @"quit") >= 0) + else if (matchCmd(wd, @"quit", blocked)) { m = @"Try 'help quit' for information about shutting down.\n"; wd = cmdWord(cmd, 1); @@ -1442,7 +1460,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) exit(0); } } - else if (comp(wd, @"restart") >= 0) + else if (matchCmd(wd, @"restart", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] > 0 && comp(wd, @"self") == 0) @@ -1495,7 +1513,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) @" or 'on host restart ...\n"; } } - else if (comp(wd, @"set") >= 0) + else if (matchCmd(wd, @"set", blocked)) { m = @"ok - set confirmed.\n"; wd = cmdWord(cmd, 1); @@ -1545,11 +1563,11 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) m = @"unknown parameter to 'set'\n"; } } - else if (comp(wd, @"status") >= 0) + else if (matchCmd(wd, @"status", blocked)) { m = [self description]; } - else if (comp(wd, @"suppress") >= 0) + else if (matchCmd(wd, @"suppress", blocked)) { NSArray *a = [sink alarms]; unsigned index = 1; @@ -1590,7 +1608,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) @"external SNMP monitoring systems.\n"; } } - else if (comp(wd, @"tell") >= 0) + else if (matchCmd(wd, @"tell", blocked)) { wd = cmdWord(cmd, 1); if ([wd length] > 0) @@ -1629,7 +1647,7 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) m = @"Tell where?.\n"; } } - else if (comp(wd, @"unset") >= 0) + else if (matchCmd(wd, @"unset", blocked)) { m = @"ok - unset confirmed.\n"; wd = cmdWord(cmd, 1); @@ -3339,6 +3357,8 @@ static NSString* cmdWord(NSArray* a, unsigned int pos) } } + [self ecOperators: operators]; + if (YES == changed) { NSString *alerterDef; diff --git a/EcProcess.h b/EcProcess.h index 3d7a3c8..1f8d251 100644 --- a/EcProcess.h +++ b/EcProcess.h @@ -1019,7 +1019,7 @@ extern NSString* cmdVersion(NSString *ver); * argument will be nil (and your method should respond with a short * description of the command). */ -- (NSString*) cmdMesg: (NSArray*)msg; +- (NSString*) ecMesg: (NSArray*)msg from: (NSString*)operator; /** Attempt to establish connection to Command server etc. * Return a proxy to that server if it is available. @@ -1169,9 +1169,18 @@ extern NSString* cmdVersion(NSString *ver); */ - (BOOL) cmdMatch: (NSString*)val toKey: (NSString*)key; +/** Returns an array of commands that the named operator is not permitted + * to use. + */ +- (NSArray*) ecBlocked: (NSString*)operator; + +/** Sets the operator config. + */ +- (void) ecOperators: (NSDictionary*)operators; + /** Handle with care - this method invokes the cmdMesg... methods. */ -- (NSString*) cmdMesg: (NSArray*)msg; +- (NSString*) ecMesg: (NSArray*)msg from: (NSString*)operator; /** Returns the name by which this process is known to the Command server. */ diff --git a/EcProcess.m b/EcProcess.m index 493e1ef..98bd442 100644 --- a/EcProcess.m +++ b/EcProcess.m @@ -385,6 +385,8 @@ static Class dateClass = 0; static Class stringClass = 0; static int cmdSignalled = 0; +static NSDictionary *ecOperators = nil; // Operator configuration + static NSTimeInterval initAt = 0.0; /* Internal value for use only by quitting mechanism. @@ -988,24 +990,39 @@ setMemBase() } } +/* Returns the found command, or nil if none is found, or an empty string + * if there was a match but it was in the array of commands to be blockd. + */ static NSString* -findAction(NSString *cmd) +findAction(NSString *cmd, NSArray *blocked) { NSString *found = nil; + BOOL match = NO; cmd = [cmd lowercaseString]; [ecLock lock]; - if (nil == (found = [cmdActions member: cmd])) + if (nil == (found = [cmdActions member: cmd]) + || [blocked containsObject: found]) { NSEnumerator *enumerator; NSString *name; + if (found) + { + found = nil; + match = YES; + } enumerator = [cmdActions objectEnumerator]; while (nil != (name = [enumerator nextObject])) { if (YES == [name hasPrefix: cmd]) { - if (nil == found) + match = YES; + if ([blocked containsObject: name]) + { + continue; // This match is blocked + } + else if (nil == found) { found = name; } @@ -1017,7 +1034,18 @@ findAction(NSString *cmd) } } } - cmd = [found retain]; + if (found) + { + cmd = [found retain]; + } + else if (match) + { + cmd = @""; + } + else + { + cmd = nil; + } [ecLock unlock]; return [cmd autorelease]; } @@ -4233,7 +4261,73 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval); [ecLock unlock]; } -- (NSString*) cmdMesg: (NSArray*)msg +- (void) ecOperators: (NSDictionary*)dict +{ + if (NO == [dict isKindOfClass: [NSDictionary class]]) + { + dict = nil; + } + [ecLock lock]; + ASSIGN(ecOperators, dict); + [ecLock unlock]; +} + +- (NSArray*) ecBlocked: (NSString*)operator +{ + NSArray *blocked = nil; + NSString *name; + id obj; + + if (nil == operator) + { + name = @""; + } + else + { + NSRange r = [operator rangeOfString: @":"]; + + if (r.length > 0) + { + name = [operator substringToIndex: r.location]; + } + else + { + name = operator; + } + } + + [ecLock lock]; + obj = [ecOperators objectForKey: name]; + if ([obj isKindOfClass: [NSDictionary class]]) + { + obj = [obj objectForKey: @"Blocked"]; + } + else + { + obj = nil; + } + if (nil == obj && [operator length] > 0) + { + obj = [ecOperators objectForKey: @""]; // default + if ([obj isKindOfClass: [NSDictionary class]]) + { + obj = [obj objectForKey: @"Blocked"]; + } + else + { + obj = nil; + } + } + if ([obj isKindOfClass: [NSArray class]]) + { + blocked = (NSArray*)AUTORELEASE(RETAIN(obj)); + } + [ecLock unlock]; + + return blocked; +} + +- (NSString*) ecMesg: (NSArray*)msg from: (NSString*)operator { NSMutableString *saved; NSString *result; @@ -4245,11 +4339,15 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval); return @"no command specified\n"; } - cmd = findAction([msg objectAtIndex: 0]); + cmd = findAction([msg objectAtIndex: 0], [self ecBlocked: operator]); if (nil == cmd) { return @"unrecognised command\n"; } + else if (0 == [cmd length]) + { + return @"blocked command\n"; + } sel = NSSelectorFromString([NSString stringWithFormat: @"cmdMesg%@:", cmd]); @@ -4285,7 +4383,7 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval); options: NSPropertyListMutableContainers format: 0 error: 0]; - val = [self cmdMesg: msg]; + val = [self ecMesg: msg from: name]; if (cmdServer) { NS_DURING @@ -4858,7 +4956,7 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval); NSString *found; cmd = [msg objectAtIndex: 1]; - found = findAction(cmd); + found = findAction(cmd, nil); if ([cmd caseInsensitiveCompare: @"control"] == NSOrderedSame) { @@ -4879,6 +4977,10 @@ NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval); { [self cmdPrintf: @"Unable to find the '%@' command -\n", cmd]; } + else if ([found length] == 0) + { + [self cmdPrintf: @"The '%@' command is blocked -\n", cmd]; + } else if ([found caseInsensitiveCompare: @"help"] != NSOrderedSame) { NSMutableArray *m; @@ -6708,6 +6810,12 @@ With two parameters ('maximum' and a number),\n\ NSLog(@"Configuration change during process shutdown ... ignored."); return; // Ignore config updates while quitting } + + /* The configuration should contain information about any operators + * who are allowed to issue commands to this process. + */ + [self ecOperators: [info objectForKey: @"Operators"]]; + newConfig = [NSMutableDictionary dictionaryWithCapacity: 32]; /* * Put all values for this application in the cmdConf dictionary. @@ -6792,7 +6900,7 @@ With two parameters ('maximum' and a number),\n\ { return nil; } - return [self cmdMesg: words]; + return [self ecMesg: words from: nil]; } - (bycopy NSData*) ecTestConfigForKey: (in bycopy NSString*)key diff --git a/Operators.plist b/Operators.plist index bd1a295..234f4e6 100644 --- a/Operators.plist +++ b/Operators.plist @@ -13,8 +13,29 @@ Password = hashedPasswordOrUsernameOrEmptyString; }; + /* If Password is omitted or is empty, then any password is accepted + * to allow the user to log in. + */ + guest = { + Password = ""; + }; + + /* If a Blocked array is supplied, the operator is prevented from + * using any of the commands listed in the array. + */ + restricted = { + Password = hashedPasswordOrUsernameOrEmptyString; + Blocked = ( + launch, + quit, + restart + ); + }; + /* If the entry with no name exists, it will be used to allow login for * any username which doesn't exist in this file. + * This entry will also be used to supply a Blocked array for any user + * whose own entry does not supply one. * The format for a hashed password is that created by the mkpasswd * program and the crypt() function. The SHA512 hash (starts '$6$') * is preferred. @@ -23,10 +44,4 @@ Password = hashedPasswordOrEmptyString; }; - /* If Password is omitted or is empty, then any password is accepted - * to allow the user to log in. - */ - guest = { - Password = ""; - }; }