libs-ec/EcControl.m
Richard Frith-MacDonald f185875dc8 Twaek for use of host well known names
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/devmodules/dev-libs/ec@35635 72102866-910b-0410-8b05-ffd578937521
2012-10-05 14:40:19 +00:00

2580 lines
58 KiB
Objective-C

/** Enterprise Control Configuration and Logging
Copyright (C) 2012 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
Date: Febrary 2010
Originally developed from 1996 to 2012 by Brainstorm, and donated to
the FSF.
This file is part of the GNUstep project.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111 USA.
*/
#import <Foundation/Foundation.h>
#import "EcAlarm.h"
#import "EcAlarmSinkSNMP.h"
#import "EcAlerter.h"
#import "EcClientI.h"
#import "EcHost.h"
#import "EcProcess.h"
#import "EcUserDefaults.h"
#import "NSFileHandle+Printf.h"
/*
* Catagory so that NSHost objects can be safely used as dictionary keys.
*/
@implementation NSHost (ControlExtension)
- (id) copyWithZone: (NSZone)z
{
return RETAIN(self);
}
@end
static EcAlarmSinkSNMP *sink = nil;
static NSTimeInterval pingDelay = 240.0;
static int comp_len = 0;
static int comp(NSString *s0, NSString *s1)
{
if ([s0 length] > [s1 length])
{
comp_len = -1;
return -1;
}
if ([s1 compare: s0
options: NSCaseInsensitiveSearch|NSLiteralSearch
range: NSMakeRange(0, [s0 length])] == NSOrderedSame)
{
comp_len = [s0 length];
if (comp_len == [s1 length])
{
return 0;
}
else
{
return 1;
}
}
else
{
comp_len = -1;
return -1;
}
}
static NSString* cmdWord(NSArray* a, unsigned int pos)
{
if (a != nil && [a count] > pos)
{
return [a objectAtIndex: pos];
}
else
{
return @"";
}
}
@interface CommandInfo : EcClientI
{
NSArray *servers;
}
- (NSString*) serverByAbbreviation: (NSString*)s;
- (NSArray*) servers;
- (void) setServers: (NSArray*)s;
@end
@implementation CommandInfo
- (void) dealloc
{
DESTROY(servers);
[super dealloc];
}
- (NSString*) serverByAbbreviation: (NSString*)s
{
NSString *svr;
int i;
int best_pos = -1;
int best_len = 0;
/*
* Special case - a numeric value is used as an index into the array.
*/
if (isdigit(*[s cString]))
{
i = [s intValue];
if (i >= 0 && i < [servers count])
{
svr = [servers objectAtIndex: i];
return svr;
}
}
for (i = 0; i < [servers count]; i++)
{
svr = [servers objectAtIndex: i];
if (comp(s, svr) == 0)
{
return svr;
}
if (comp_len > best_len)
{
best_len = comp_len;
best_pos = i;
}
}
if (best_pos >= 0)
{
svr = [servers objectAtIndex: best_pos];
return svr;
}
return nil;
}
- (NSArray*) servers
{
return servers;
}
- (void) setServers: (NSArray*)s
{
ASSIGN(servers, s);
}
@end
@interface ConsoleInfo : EcClientI
{
NSString *cserv;
NSString *chost;
NSString *pass;
BOOL general;
BOOL warnings;
BOOL errors;
BOOL alerts;
}
- (id) initFor: (id)o
name: (NSString*)n
with: (id<CmdClient>)s
pass: (NSString*)p;
- (NSString*) chost;
- (NSString*) cserv;
- (BOOL) getAlerts;
- (BOOL) getErrors;
- (BOOL) getGeneral;
- (BOOL) getWarnings;
- (NSString*) pass;
- (NSString*) promptAfter: (NSString*)msg;
- (void) setAlerts: (BOOL)flag;
- (void) setConnectedHost: (NSString*)c;
- (void) setConnectedServ: (NSString*)c;
- (void) setErrors: (BOOL)flag;
- (void) setGeneral: (BOOL)flag;
- (void) setWarnings: (BOOL)flag;
@end
@implementation ConsoleInfo
- (NSString*) chost
{
return chost;
}
- (NSString*) cserv
{
return cserv;
}
- (void) dealloc
{
DESTROY(chost);
DESTROY(cserv);
DESTROY(pass);
[super dealloc];
}
- (BOOL) getAlerts
{
return alerts;
}
- (BOOL) getErrors
{
return errors;
}
- (BOOL) getGeneral
{
return general;
}
- (BOOL) getWarnings
{
return warnings;
}
- (id) initFor: (id)o
name: (NSString*)n
with: (id<CmdClient>)s
pass: (NSString*)p
{
self = [super initFor: o name: n with: s];
if (self != nil)
{
pass = [p copy];
chost = nil;
cserv = nil;
general = NO;
warnings = NO;
alerts = YES;
errors = YES;
}
return self;
}
- (NSString*) pass
{
return pass;
}
- (NSString*) promptAfter: (NSString*)msg
{
if (chost && cserv)
{
return [NSString stringWithFormat: @"%@\n%@:%@> ", msg, chost, cserv];
}
else if (chost)
{
return [NSString stringWithFormat: @"%@\n%@:> ", msg, chost];
}
else if (cserv)
{
return [NSString stringWithFormat: @"%@\n:%@> ", msg, cserv];
}
else
{
return [NSString stringWithFormat: @"%@\nControl> ", msg];
}
}
- (void) setAlerts: (BOOL)flag
{
alerts = flag;
}
- (void) setConnectedHost: (NSString*)c
{
ASSIGNCOPY(chost, c);
}
- (void) setConnectedServ: (NSString*)c
{
ASSIGNCOPY(cserv, c);
}
- (void) setErrors: (BOOL)flag
{
errors = flag;
}
- (void) setGeneral: (BOOL)flag
{
general = flag;
}
- (void) setWarnings: (BOOL)flag
{
warnings = flag;
}
@end
@interface EcControl : EcProcess <Control>
{
NSFileManager *mgr;
NSMutableArray *commands;
NSMutableArray *consoles;
NSString *logname;
NSDictionary *config;
NSMutableDictionary *operators;
NSMutableDictionary *fileBodies;
NSMutableDictionary *fileDates;
NSTimer *timer;
unsigned commandPingPosition;
unsigned consolePingPosition;
NSString *configFailed;
NSString *configIncludeFailed;
EcAlerter *alerter;
}
- (NSFileHandle*) openLog: (NSString*)lname;
- (void) cmdGnip: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data;
- (void) cmdPing: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data;
- (void) cmdQuit: (int)status;
- (void) command: (NSData*)dat
from: (NSString*)f;
- (BOOL) connection: (NSConnection*)ancestor
shouldMakeNewConnection: (NSConnection*)newConn;
- (id) connectionBecameInvalid: (NSNotification*)notification;
- (EcClientI*) findIn: (NSArray*)a
byAbbreviation: (NSString*)s;
- (EcClientI*) findIn: (NSArray*)a
byName: (NSString*)s;
- (EcClientI*) findIn: (NSArray*)a
byObject: (id)s;
- (void) information: (NSString*)inf
type: (EcLogType)t
to: (NSString*)to
from: (NSString*)from;
- (NSData*) registerCommand: (id<Command>)c
name: (NSString*)n;
- (NSString*) registerConsole: (id<Console>)c
name: (NSString*)n
pass: (NSString*)p;
- (void) reply: (NSString*) msg to: (NSString*)n from: (NSString*)c;
- (void) servers: (NSData*)d
on: (id<Command>)s;
- (void) timedOut: (NSTimer*)t;
- (void) unregister: (id)obj;
- (BOOL) update;
- (void) updateConfig: (NSData*)dummy;
- (id) getInclude: (id)s;
- (id) recursiveInclude: (id)o;
- (id) tryInclude: (id)s multi: (BOOL*)flag;
@end
@implementation EcControl
- (oneway void) alarm: (in bycopy EcAlarm*)alarm
{
EcAlarmSeverity severity;
/* First, send the alarm to the alarm sink.
*/
[sink alarm: alarm];
/* Now, for critical and major alarms, generate a corresponding old style
* alert.
*/
severity = [alarm perceivedSeverity];
if (EcAlarmSeverityCritical == severity
|| EcAlarmSeverityMajor == severity
|| EcAlarmSeverityMinor == severity)
{
NSString *additional;
NSString *component;
NSString *connector;
NSString *instance;
NSString *message;
NSString *repair;
NSString *spacing1;
NSString *spacing2;
instance = [alarm moInstance];
if ([instance length] == 0)
{
instance = @"";
connector = @"";
}
else
{
connector = @"-";
}
component = [alarm moComponent];
if (0 == [component length])
{
component = @"";
}
else
{
component = [NSString stringWithFormat: @"(%@)", component];
}
additional = [alarm additionalText];
if ([additional length] == 0)
{
additional = @"";
spacing1 = @"";
}
else
{
spacing1 = @": ";
}
repair = [alarm proposedRepairAction];
if ([repair length] == 0)
{
repair = @"";
spacing2 = @"";
}
else
{
spacing2 = @", ";
}
severity = [alarm perceivedSeverity];
if (EcAlarmSeverityCritical == severity)
{
message = [NSString stringWithFormat: cmdLogFormat(LT_ALERT,
@"%@%@%@%@%@ - '%@%@%@%@' on %@"),
[alarm specificProblem], spacing1, additional, spacing2, repair,
[alarm moProcess], connector, instance, component, [alarm moHost]];
[self information: message type: LT_ALERT to: nil from: nil];
}
else if (EcAlarmSeverityMajor == severity)
{
message = [NSString stringWithFormat: cmdLogFormat(LT_ERROR,
@"%@%@%@%@%@ - '%@%@%@%@' on %@"),
[alarm specificProblem], spacing1, additional, spacing2, repair,
[alarm moProcess], connector, instance, component, [alarm moHost]];
[self information: message type: LT_ERROR to: nil from: nil];
}
}
}
- (NSString*) description
{
return [NSString stringWithFormat: @"Control server\n%@\n%@", alerter, sink];
}
- (oneway void) domanage: (NSString*)name
{
[sink domanage: name];
}
- (NSFileHandle*) openLog: (NSString*)lname
{
NSFileHandle *lf;
if ([mgr isWritableFileAtPath: lname] == NO)
{
if ([mgr createFileAtPath: lname
contents: nil
attributes: nil] == NO)
{
NSLog(@"Log file '%@' is not writable and can't be created", lname);
return nil;
}
}
lf = [NSFileHandle fileHandleForUpdatingAtPath: lname];
if (lf == nil)
{
NSLog(@"Unable to log to %@", lname);
return nil;
}
[lf seekToEndOfFile];
return lf;
}
- (void) cmdGnip: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data
{
EcClientI *r;
/*
* See if we have a fitting client - and update records.
*/
r = [self findIn: commands byObject: (id)from];
if (r == nil)
{
r = [self findIn: consoles byObject: (id)from];
}
if (r != nil)
{
[r gnip: num];
}
}
- (BOOL) cmdIsClient
{
return NO; // Control is not a client of Command
}
- (void) cmdPing: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data
{
[from cmdGnip: self sequence: num extra: nil];
}
- (void) cmdQuit: (int)status
{
[sink shutdown];
exit (status);
}
- (void) command: (NSData*)dat
from: (NSString*)f
{
NSMutableArray *cmd;
ConsoleInfo *console;
cmd = [NSPropertyListSerialization
propertyListWithData: dat
options: NSPropertyListMutableContainers
format: 0
error: 0];
console = (ConsoleInfo*)[self findIn: consoles byName: f];
if (console == nil)
{
NSString *m;
m = [NSString stringWithFormat: cmdLogFormat(LT_ERROR,
@"command from unregistered console (from %@)"), f];
[self information: m
type: LT_ERROR
to: nil
from: nil];
}
else if (cmd == nil || [cmd count] == 0)
{
NSString *m;
m = [NSString stringWithFormat: cmdLogFormat(LT_AUDIT,
@"no command entered (from %@ %@)"), f, [console name]];
[self information: m
type: LT_AUDIT
to: [console name]
from: nil];
}
else
{
NSMutableString *full;
NSString *hname = nil;
NSString *m = @"";
NSString *wd = cmdWord(cmd, 0);
unsigned count = [cmd count];
unsigned i;
BOOL connected = NO;
/*
* Log this command!
*/
full = [[NSMutableString alloc] initWithCapacity: 1024];
for (i = 0; i < count; i++)
{
[full appendFormat: @" %@", [cmd objectAtIndex: i]];
}
[[self cmdLogFile: logname]
printf: @"%@ %@> %@\n", [NSDate date], [console name], full];
RELEASE(full);
if ([console chost] != nil || [console cserv] != nil)
{
connected = YES;
}
/*
* If we have a 'control' command - act as if the console was not
* connected to a host or server.
*/
if (comp(wd, @"control") == 0)
{
[cmd removeObjectAtIndex: 0];
wd = cmdWord(cmd, 0);
connected = NO;
}
if (comp(wd, @"password") == 0)
{
NSRange r = [[console name] rangeOfString: @":"];
NSString *s = [[console name] substringToIndex: r.location];
NSMutableDictionary *d;
NSString *p;
NSString *path;
if ([cmd count] != 3)
{
[self information: @"parameters are old password and new one.\n"
type: LT_AUDIT
to: [console name]
from: nil];
return;
}
if (operators == nil)
{
operators = [NSMutableDictionary dictionaryWithCapacity: 1];
RETAIN(operators);
[operators setObject: @"" forKey: s];
}
d = AUTORELEASE([[operators objectForKey: s] mutableCopy]);
p = [d objectForKey: @"Password"];
if ([p length] == 0 || [p isEqual: [cmd objectAtIndex: 1]])
{
[d setObject: [cmd objectAtIndex: 2] forKey: @"Password"];
[operators setObject: d forKey: s];
p = [operators description];
path = [[self cmdDataDirectory] stringByAppendingPathComponent:
@"Operators.plist"];
[p writeToFile: path atomically: YES];
[self information: @"new password set.\n"
type: LT_AUDIT
to: [console name]
from: nil];
return;
}
else
{
[self information: @"old password is incorrect.\n"
type: LT_AUDIT
to: [console name]
from: nil];
return;
}
}
/*
* If we are not connected, but have an 'on ....' at the start of
* the command line, then we send to the specified host as if we
* were connected to it.
*/
if (connected)
{
hname = [console chost];
}
else if (comp(wd, @"on") == 0)
{
[cmd removeObjectAtIndex: 0];
wd = cmdWord(cmd, 0);
hname = AUTORELEASE(RETAIN(wd));
if ([hname length] > 0)
{
CommandInfo *c;
c = (CommandInfo*)[self findIn: commands byAbbreviation: hname];
if (c)
{
hname = [c name];
[cmd removeObjectAtIndex: 0];
}
else
{
[self information: @"Attempt to work on unknown host"
type: LT_AUDIT
to: [console name]
from: nil];
return;
}
wd = cmdWord(cmd, 0);
}
else
{
hname = nil;
}
}
if (connected == YES || hname != nil)
{
NSArray *hosts;
/*
* Make an array of hosts to work with - the connected host
* or all hosts. If the connected host has gone away - disconnect.
*/
if (hname == nil)
{
hosts = [NSArray arrayWithArray: commands];
}
else
{
CommandInfo *c;
c = (CommandInfo*)[self findIn: commands byName: hname];
if (c)
{
hosts = [NSArray arrayWithObject: c];
}
else
{
if (connected)
{
[console setConnectedHost: nil];
}
[self information: @"Host has gone away"
type: LT_ERROR
to: [console name]
from: nil];
return;
}
}
if ([wd length] == 0)
{
/* Quietly ignore. */
}
else
{
BOOL foundServer = NO;
int i;
m = nil; /* Let connected host generate messages. */
/*
* Perform operation on connected host (or all hosts if
* not connected to a host).
*/
for (i = 0; i < [hosts count]; i++)
{
CommandInfo *c = [hosts objectAtIndex: i];
if ([commands indexOfObjectIdenticalTo: c] != NSNotFound)
{
if ([console cserv] == nil)
{
foundServer = YES;
NS_DURING
{
NSData *dat = [NSPropertyListSerialization
dataFromPropertyList: cmd
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
[[c obj] command: dat
to: nil
from: [console name]];
}
NS_HANDLER
{
NSLog(@"Caught: %@", localException);
}
NS_ENDHANDLER
}
else
{
NSString *to;
to = [c serverByAbbreviation: [console cserv]];
if (to)
{
foundServer = YES;
NS_DURING
{
NSData *dat;
dat = [NSPropertyListSerialization
dataFromPropertyList: cmd
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
[[c obj] command: dat
to: to
from: [console name]];
}
NS_HANDLER
{
NSLog(@"Caught: %@", localException);
}
NS_ENDHANDLER
}
}
}
}
if (foundServer == NO)
{
[console setConnectedServ: nil];
[self information: @"Server has gone away"
type: LT_AUDIT
to: [console name]
from: nil];
}
}
}
else if ([wd length] == 0)
{
/* Quietly ignore. */
}
else if (comp(wd, @"alarms") >= 0)
{
NSArray *a = [sink alarms];
if (0 == [a count])
{
m = @"No alarms currently active.\n";
}
else
{
int i;
a = [a sortedArrayUsingSelector: @selector(compare:)];
m = @"Current alarms -\n";
for (i = 0; i < [a count]; i++)
{
EcAlarm *alarm = [a objectAtIndex: i];
m = [m stringByAppendingString: [alarm description]];
m = [m stringByAppendingString: @"\n"];
}
}
}
else if (comp(wd, @"archive") >= 0)
{
NSCalendarDate *when = [NSCalendarDate date];
NSString *sub;
int yy, mm, dd;
yy = [when yearOfCommonEra];
mm = [when monthOfYear];
dd = [when dayOfMonth];
sub = [NSString stringWithFormat: @"%04d-%02d-%02d", yy, mm, dd];
m = [NSString stringWithFormat: @"\n%@\n\n", [self cmdArchive: sub]];
}
else if (comp(wd, @"clear") >= 0)
{
NSArray *a = [sink alarms];
wd = cmdWord(cmd, 1);
if ([wd length] > 0 && [wd intValue] > 0)
{
EcAlarm *alarm = nil;
int n = [wd intValue];
int i = [a count];
while (i-- > 0)
{
alarm = [a objectAtIndex: i];
if ([alarm notificationID] == n)
{
break;
}
alarm = nil;
}
if (nil == alarm)
{
m = @"No alarm found with that notificationID\n";
}
else
{
m = [NSString stringWithFormat: @"Clearing %@\n", alarm];
alarm = [alarm clear];
[sink alarm: alarm];
}
}
else
{
m = @"The 'clear' command requires an alarm notificationID\n";
}
}
else if (comp(wd, @"connect") >= 0)
{
wd = cmdWord(cmd, 1);
if ([wd length] == 0)
{
[console setConnectedServ: nil];
}
else
{
[console setConnectedServ: wd];
}
}
else if (comp(wd, @"config") >= 0)
{
BOOL changed;
changed = [self update];
if (configFailed != nil)
{
m = configFailed;
}
else
{
if (configIncludeFailed != nil)
{
m = [NSString stringWithFormat:
@"Configuration file re-read and loaded, but %@",
configIncludeFailed];
}
else if (YES == changed)
{
m = @"Configuration file re-read ... updates handled.\n";
}
else
{
m = @"Configuration file re-read ... UNCHANGED.\n";
}
}
}
else if (comp(wd, @"flush") >= 0)
{
[alerter flushSms];
[alerter flushEmail];
m = @"Flushed alert messages\n";
}
else if (comp(wd, @"help") >= 0)
{
wd = cmdWord(cmd, 1);
if ([wd length] == 0)
{
m = @"Commands are -\n"
@"Help\tAlarms\tArchive\tClear\tConfig\tConnect\t"
@"Flush\tHost\tList\tMemory\tOn\t"
@"Password\tRepeat\tQuit\tSet\tStatus\tTell\tUnset\n\n"
@"Type 'help' followed by a command word for details.\n"
@"A command line consists of a sequence of words, "
@"the first of which is the command to be executed. "
@"A word can be a simple sequence of non-space characters, "
@"or it can be a 'quoted string'. "
@"Simple words are converted to lower case before "
@"matching them against commands and their parameters. "
@"Text in a 'quoted string' is NOT converted to lower case "
@"but a '\\' character is treated in a special manner -\n"
@" \\b is replaced by a backspace\n"
@" \\f is replaced by a formfeed\n"
@" \\n is replaced by a linefeed\n"
@" \\r is replaced by a carriage-return\n"
@" \\t is replaced by a tab\n"
@" \\0 followed by up to 3 octal digits is replaced"
@" by the octal value\n"
@" \\x followed by up to 2 hex digits is replaced"
@" by the hex value\n"
@" \\ followed by any other character is replaced by"
@" the second character.\n"
@" This permits use of quotes and backslashes inside"
@" a quoted string.\n";
}
else
{
if (comp(wd, @"Alarms") >= 0)
{
m = @"Alarms\nLists the currently active alarms ordered "
@"by their notificationIDs.\n";
}
else if (comp(wd, @"Archive") >= 0)
{
m = @"Archive\nArchives the log file. The archived log "
@"file is stored in a subdirectory whose name is of "
@"the form YYYY-MM-DD being the date at "
@"which the archive was created.\n";
}
else if (comp(wd, @"Clear") >= 0)
{
m = @"Clear\nInstructs the Control server to clear an "
@"alarm (identified by numeric notificationID).\n";
}
else if (comp(wd, @"Config") >= 0)
{
m = @"Config\nInstructs the Control server to re-load "
@"it's configuration information and pass it on to "
@"all connected Command servers.\n";
}
else if (comp(wd, @"Connect") >= 0)
{
m = @"Connect name\nInstructs the Control server that "
@"commands from your console should go to servers "
@"whose names match the value you give.\n"
@"To remove this association, you will need to type "
@"the command 'control connect'\n'";
}
else if (comp(wd, @"Flush") >= 0)
{
m = @"Flush\nInstructs the Control server that messages "
@"about errors and alerts which are currently saved "
@"in the batching process should be sent out.\n";
}
else if (comp(wd, @"Host") >= 0)
{
m = @"Host name\nInstructs the Control server that "
@"commands from your console should go to the host "
@"whose names match the value you give.\n"
@"To remove this association, you will need to type "
@"the command 'control host'\n'";
}
else if (comp(wd, @"List") >= 0)
{
m = @"List\nLists all the connected commands.\n"
@"List consoles\nLists all the connected consoles.\n";
}
else if (comp(wd, @"Memory") >= 0)
{
m = @"Memory\nDisplays recent memory allocation stats.\n"
@"Memory all\nDisplays all memory allocation stats.\n";
}
else if (comp(wd, @"On") >= 0)
{
m = @"On host ...\nSends a command to the named host.\n";
}
else if (comp(wd, @"Password") >= 0)
{
m = @"Password <oldpass> <newpass>\nSets your password.\n";
}
else if (comp(wd, @"Quit") >= 0)
{
m = @"Quit 'self'\n"
@"Shuts down the Control server process - don't do "
@"this lightly - all other servers are coordinated "
@"by the Control server\n"
@"\nUse 'on <host> quit <name>' to shut down "
@"individual servers on the specified host.\n"
@"\nUse 'on <host> quit all' to shut down all the "
@"servers on the specified host.\n"
@"\nUse 'on <host> quit self' to shut down the "
@"Command server on the specified host - be careful "
@"using this - the other servers on a host are all "
@"coordinated (and log through) the Command server.\n";
}
else if (comp(wd, @"Repeat") >= 0)
{
m = @"Repeat\nRepeat the last command.\n";
}
else if (comp(wd, @"Set") >= 0)
{
m = @"Set\n"
@"Changes settings -\n"
@" set display warnings\n"
@" displays warning messages from the server to "
@"which you are connected.\n"
@" set display general\n"
@" display warning messages (if selected) "
@"from ALL servers, not just connected ones.\n"
@" set display errors\n"
@" displays error messages.\n"
@" set display alerts\n"
@" displays alert messages.\n"
@"\n";
}
else if (comp(wd, @"Status") >= 0)
{
m = @"Status\nInstructs the Control server to report its "
@"current status ... mostly the buffered alert and "
@"error messages waiting to be sent out.\n";
}
else if (comp(wd, @"Tell") >= 0)
{
m = @"Tell 'name' 'command'\n"
@"Sends the command to the named client.\n";
}
else if (comp(wd, @"UnSet") >= 0)
{
m = @"UnSet\n"
@"Changes settings -\n"
@" unset display general\n"
@" unset display warnings\n"
@" unset display errors\n"
@" unset display alerts\n"
@"\n";
}
}
}
else if (comp(wd, @"host") >= 0)
{
wd = cmdWord(cmd, 1);
if ([wd length] == 0)
{
[console setConnectedHost: nil];
}
else
{
CommandInfo *c;
c = (CommandInfo*)[self findIn: commands byAbbreviation: wd];
if (c)
{
[console setConnectedHost: [c name]];
}
}
}
else if (comp(wd, @"list") >= 0)
{
wd = cmdWord(cmd, 1);
if ([wd length] > 0 && comp(wd, @"consoles") >= 0)
{
if ([consoles count] == 1)
{
m = @"No other consoles currently connected.\n";
}
else
{
int i;
m = @"Current console processes -\n";
for (i = 0; i < [consoles count]; i++)
{
ConsoleInfo *c;
c = (ConsoleInfo*)[consoles objectAtIndex: i];
if (c == console)
{
m = [m stringByAppendingFormat:
@"%2d. your console\n", i];
}
else
{
m = [m stringByAppendingFormat:
@"%2d. %s\n", i, [[c name] cString]];
}
}
}
}
else
{
if ([commands count] == 0)
{
if (nil == alerter)
{
m = @"No database/alerter available.\n";
}
else
{
m = @"No hosts currently connected.\n";
}
}
else
{
int i;
m = @"Current server hosts -\n";
for (i = 0; i < [commands count]; i++)
{
CommandInfo* c;
c = (CommandInfo*)[commands objectAtIndex: i];
m = [m stringByAppendingFormat:
@"%2d. %-32.32s\n", i, [[c name] cString]];
if ([c servers] == nil || [[c servers] count] == 0)
{
m = [m stringByAppendingString:
@" no servers connected\n"];
}
else
{
NSArray *svrs = [c servers];
int j;
for (j = 0; j < [svrs count]; j++)
{
m = [m stringByAppendingFormat:
@" %2d. %@\n", j,
[svrs objectAtIndex: j]];
}
}
}
}
}
}
else if (comp(wd, @"memory") >= 0)
{
if (GSDebugAllocationActive(YES) == NO)
{
m = @"Memory statistics were not being gathered.\n"
@"Statistics Will start from NOW.\n";
}
else
{
const char* list;
wd = cmdWord(cmd, 1);
if ([wd length] > 0 && comp(wd, @"all") >= 0)
{
list = GSDebugAllocationList(NO);
}
else
{
list = GSDebugAllocationList(YES);
}
m = [NSString stringWithCString: list];
}
}
else if (comp(wd, @"quit") >= 0)
{
m = @"Try 'help quit' for information about shutting down.\n";
wd = cmdWord(cmd, 1);
if ([wd length] > 0 && comp(wd, @"self") == 0)
{
[alerter flushSms];
[alerter flushEmail];
DESTROY(alerter);
exit(0);
}
}
else if (comp(wd, @"set") >= 0)
{
m = @"ok - set confirmed.\n";
wd = cmdWord(cmd, 1);
if ([wd length] == 0)
{
m = @"no parameter to 'set'\n";
}
else if (comp(wd, @"display") >= 0)
{
wd = cmdWord(cmd, 2);
if ([wd length] == 0)
{
m = [NSString stringWithFormat: @"display settings -\n"
@"general: %d warnings: %d errors: %d alerts: %d\n",
[console getGeneral], [console getWarnings],
[console getErrors], [console getAlerts]];
}
else if (comp(wd, @"alerts") >= 0)
{
[console setAlerts: YES];
}
else if (comp(wd, @"errors") >= 0)
{
[console setErrors: YES];
}
else if (comp(wd, @"general") >= 0)
{
[console setGeneral: YES];
}
else if (comp(wd, @"warnings") >= 0)
{
[console setWarnings: YES];
}
else
{
m = @"unknown parameter to 'set display'\n";
}
}
else
{
m = @"unknown parameter to 'set'\n";
}
}
else if (comp(wd, @"status") >= 0)
{
m = [self description];
}
else if (comp(wd, @"tell") >= 0)
{
wd = cmdWord(cmd, 1);
if ([wd length] > 0)
{
NSString *dest = AUTORELEASE(RETAIN(wd));
int i;
NSArray *a = [[NSArray alloc] initWithArray: commands];
[cmd removeObjectAtIndex: 0];
[cmd removeObjectAtIndex: 0];
if (comp(dest, @"all") == 0)
{
dest = nil;
}
for (i = 0; i < [a count]; i++)
{
CommandInfo* c = (CommandInfo*)[a objectAtIndex: i];
if ([commands indexOfObjectIdenticalTo: c] != NSNotFound)
{
NSString *to;
if (dest)
{
to = [c serverByAbbreviation: dest];
if (to != nil)
{
NS_DURING
{
NSData *dat;
dat = [NSPropertyListSerialization
dataFromPropertyList: cmd
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
[[c obj] command: dat
to: to
from: [console name]];
}
NS_HANDLER
{
NSLog(@"Caught: %@", localException);
}
NS_ENDHANDLER
}
}
else
{
int j;
for (j = 0; j < [[c servers] count]; j++)
{
to = [[c servers] objectAtIndex: j];
NS_DURING
{
NSData *dat;
dat = [NSPropertyListSerialization
dataFromPropertyList: cmd
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
[[c obj] command: dat
to: to
from: [console name]];
}
NS_HANDLER
{
NSLog(@"Caught: %@",
localException);
}
NS_ENDHANDLER
}
}
}
}
m = nil;
}
else
{
m = @"Tell where?.\n";
}
}
else if (comp(wd, @"unset") >= 0)
{
m = @"ok - unset confirmed.\n";
wd = cmdWord(cmd, 1);
if ([wd length] == 0)
{
m = @"no parameter to 'set'\n";
}
else if (comp(wd, @"display") >= 0)
{
wd = cmdWord(cmd, 2);
if ([wd length] == 0)
{
m = [NSString stringWithFormat: @"display settings -\n"
@"general: %d warnings: %d errors: %d alerts: %d\n",
[console getGeneral], [console getWarnings],
[console getErrors], [console getAlerts]];
}
else if (comp(wd, @"alerts") >= 0)
{
[console setAlerts: NO];
}
else if (comp(wd, @"errors") >= 0)
{
[console setErrors: NO];
}
else if (comp(wd, @"general") >= 0)
{
[console setGeneral: NO];
}
else if (comp(wd, @"warnings") >= 0)
{
[console setWarnings: NO];
}
else
{
m = @"unknown parameter to 'unset display'\n";
}
}
else
{
m = @"unknown parameter to 'unset'\n";
}
}
else
{
m = [NSString stringWithFormat: @"Unknown command - '%@'\n", wd];
}
if (m != nil)
{
[self information: m
type: LT_AUDIT
to: [console name]
from: nil];
}
}
}
- (BOOL) connection: (NSConnection*)ancestor
shouldMakeNewConnection: (NSConnection*)newConn
{
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(connectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: (id)newConn];
[newConn setDelegate: self];
return YES;
}
- (id) connectionBecameInvalid: (NSNotification*)notification
{
id conn = [notification object];
[[NSNotificationCenter defaultCenter] removeObserver: self
name: NSConnectionDidDieNotification
object: conn];
if ([conn isKindOfClass: [NSConnection class]])
{
NSMutableArray *a = [NSMutableArray arrayWithCapacity: 2];
NSMutableString *m;
unsigned i;
/*
* Now remove any consoles that have disconnected.
*/
m = [NSMutableString stringWithCapacity: 100];
i = [consoles count];
while (i-- > 0)
{
ConsoleInfo *o = (ConsoleInfo*)[consoles objectAtIndex: i];
if ([[o obj] connectionForProxy] == conn)
{
[a addObject: o];
[m appendFormat: cmdLogFormat(LT_AUDIT,
@"removed (lost) console '%@'"), [o name]];
[consoles removeObjectAtIndex: i];
if (i <= consolePingPosition && consolePingPosition > 0)
{
consolePingPosition--;
}
}
}
if ([m length] > 0)
{
[self information: m
type: LT_AUDIT
to: nil
from: nil];
[m setString: @""];
}
/*
* Now remove the commands from the active list.
*/
i = [commands count];
while (i-- > 0)
{
CommandInfo* o = (CommandInfo*)[commands objectAtIndex: i];
if ([[o obj] connectionForProxy] == conn)
{
EcAlarm *e;
NSString *s;
[a addObject: o];
s = EcMakeManagedObject([o name], @"Command", nil);
e = [EcAlarm alarmForManagedObject: s
at: nil
withEventType: EcAlarmEventTypeProcessingError
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
specificProblem: @"Host command/control availability"
perceivedSeverity: EcAlarmSeverityCritical
proposedRepairAction: @"Check network/host/process"
additionalText: @"remove (lost) host"];
[self alarm: e];
[commands removeObjectAtIndex: i];
if (i <= commandPingPosition && commandPingPosition > 0)
{
commandPingPosition--;
}
}
}
[a removeAllObjects];
}
else
{
[self error: "non-Connection sent invalidation"];
}
return self;
}
- (void) dealloc
{
DESTROY(alerter);
[self cmdLogEnd: logname];
if (timer != nil)
{
[timer invalidate];
}
DESTROY(mgr);
DESTROY(fileBodies);
DESTROY(fileDates);
DESTROY(operators);
DESTROY(config);
DESTROY(commands);
DESTROY(consoles);
DESTROY(configFailed);
DESTROY(configIncludeFailed);
[super dealloc];
}
- (EcClientI*) findIn: (NSArray*)a
byAbbreviation: (NSString*)s
{
EcClientI *o;
int i;
int best_pos = -1;
int best_len = 0;
/*
* Special case - a numeric value is used as an index into the array.
*/
if (isdigit(*[s cString]))
{
i = [s intValue];
if (i >= 0 && i < [a count])
{
return (EcClientI*)[a objectAtIndex: i];
}
}
for (i = 0; i < [a count]; i++)
{
o = (EcClientI*)[a objectAtIndex: i];
if (comp(s, [o name]) == 0)
{
return o;
}
if (comp_len > best_len)
{
best_len = comp_len;
best_pos = i;
}
}
if (best_pos >= 0)
{
return (EcClientI*)[a objectAtIndex: best_pos];
}
return nil;
}
- (EcClientI*) findIn: (NSArray*)a
byName: (NSString*)s
{
EcClientI *o;
int i;
for (i = 0; i < [a count]; i++)
{
o = (EcClientI*)[a objectAtIndex: i];
if (comp([o name], s) == 0)
{
return o;
}
}
return nil;
}
- (EcClientI*) findIn: (NSArray*)a
byObject: (id)s
{
EcClientI *o;
int i;
for (i = 0; i < [a count]; i++)
{
o = (EcClientI*)[a objectAtIndex: i];
if ([o obj] == s)
{
return o;
}
}
return nil;
}
- (void) information: (NSString*)inf
type: (EcLogType)t
to: (NSString*)to
from: (NSString*)from
{
/*
* Send anything but debug or accounting info to consoles.
*/
if (t != LT_DEBUG)
{
NSArray *a;
unsigned i;
/*
* Work with a copy of the consoles array in case one goes away
* or is added while we are doing this!
*/
a = [NSArray arrayWithArray: consoles];
for (i = 0; i < [a count]; i++)
{
ConsoleInfo *c = [a objectAtIndex: i];
NSString *name = [c name];
/*
* If this console has gone away - skip it.
*/
if ([consoles indexOfObjectIdenticalTo: c] == NSNotFound)
{
continue;
}
/*
* If 'to' is nil - send to all consoles
*/
if (to != nil && [to isEqual: name] == NO)
{
continue;
}
/*
* If this is a warning message and the console is not interested
* in warning then we skip this console.
*/
if (t == LT_WARNING)
{
if ([c getWarnings] == NO)
{
continue;
}
else if ([c getGeneral] == NO)
{
if (to != nil && [to isEqual: name] == NO)
{
continue;
}
}
}
else if (t == LT_ERROR && [c getErrors] == NO)
{
continue;
}
else if (t == LT_ALERT && [c getAlerts] == NO)
{
continue;
}
NS_DURING
{
/*
* Finally - we send the message to the console along with
* a prompt.
*/
[[c obj] information: [c promptAfter: inf]];
}
NS_HANDLER
{
NSLog(@"Caught: %@", localException);
}
NS_ENDHANDLER
}
}
/*
* Log, alerts, and accounting get written to the log file too.
*/
if (t == LT_AUDIT || t == LT_ALERT)
{
[[self cmdLogFile: logname] puts: inf];
}
/*
* Errors and alerts (severe errors) get passed to a handler.
*/
if (t == LT_ERROR || t == LT_ALERT)
{
if (alerter != nil)
{
[alerter handleInfo: inf];
}
}
}
- (id) initWithDefaults: (NSDictionary*)defs
{
self = [super initWithDefaults: defs];
if (self != nil)
{
commands = [[NSMutableArray alloc] initWithCapacity: 10];
consoles = [[NSMutableArray alloc] initWithCapacity: 2];
logname = [[self cmdName] stringByAppendingPathExtension: @"log"];
RETAIN(logname);
mgr = [NSFileManager defaultManager];
RETAIN(mgr);
if ([self cmdLogFile: logname] == nil)
{
exit(0);
}
[self update];
if (configFailed != nil)
{
[self cmdQuit: 1];
return nil;
}
if (operators == nil)
{
[self cmdQuit: 1];
return nil;
}
fileBodies = [[NSMutableDictionary alloc] initWithCapacity: 8];
fileDates = [[NSMutableDictionary alloc] initWithCapacity: 8];
timer = [NSTimer scheduledTimerWithTimeInterval: 15.0
target: self
selector: @selector(timedOut:)
userInfo: nil
repeats: YES];
[self timedOut: nil];
}
return self;
}
- (int) ecRun
{
int result;
/* Start the SNMP alarm sink before entering run loop.
*/
sink = [[EcAlarmSinkSNMP alarmSinkSNMP] retain];
result = [super ecRun];
[sink shutdown];
DESTROY(sink);
return result;
}
- (NSData*) registerCommand: (id<Command>)c
name: (NSString*)n
{
NSMutableDictionary *dict;
CommandInfo *obj;
CommandInfo *old;
NSHost *h;
NSString *m;
EcAlarm *a;
id o;
if (nil == alerter)
{
[self update]; // Regenerate info from Control.plist
if (nil == alerter)
{
return nil; // Not fully configured yet
}
}
[(NSDistantObject*)c setProtocolForProxy: @protocol(Command)];
dict = [NSMutableDictionary dictionaryWithCapacity: 3];
h = [NSHost hostWithName: n];
if (h == nil)
{
m = [NSString stringWithFormat:
@"Rejected new host with bad name '%@' at %@\n", n, [NSDate date]];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
[dict setObject: @"unable to handle given hostname."
forKey: @"rejected"];
return [NSPropertyListSerialization
dataFromPropertyList: dict
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
}
n = [h wellKnownName];
old = (CommandInfo*)[self findIn: commands byObject: c];
if (old != nil && [[old name] isEqual: n])
{
obj = old;
m = [NSString stringWithFormat:
@"Re-registered new host with name '%@' at %@\n",
n, [NSDate date]];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
}
else
{
/*
* Create a new reference for this client.
*/
obj = [[CommandInfo alloc] initFor: c
name: n
with: self];
old = (CommandInfo*)[self findIn: commands byName: n];
if (old != nil)
{
NS_DURING
{
[[old obj] cmdPing: self sequence: 0 extra: nil];
}
NS_HANDLER
{
NSLog(@"Ping %@ - Caught: %@", n, localException);
}
NS_ENDHANDLER
}
if ((old = (CommandInfo*)[self findIn: commands byName: n]) != nil)
{
RELEASE(obj);
m = [NSString stringWithFormat:
@"Rejected new host with name '%@' at %@\n",
n, [NSDate date]];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
[dict setObject: @"client with that name already registered."
forKey: @"rejected"];
return [NSPropertyListSerialization
dataFromPropertyList: dict
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
}
else
{
[commands addObject: obj];
RELEASE(obj);
[commands sortUsingSelector: @selector(compare:)];
[self domanage: EcMakeManagedObject(n, @"Command", nil)];
m = [NSString stringWithFormat:
@"Registered new host with name '%@' at %@\n",
n, [NSDate date]];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
}
}
/* Inform SNMP monitoring of new Command server.
*/
a = [EcAlarm alarmForManagedObject: EcMakeManagedObject(n, @"Command", nil)
at: nil
withEventType: EcAlarmEventTypeProcessingError
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
specificProblem: @"Host command/control availability"
perceivedSeverity: EcAlarmSeverityCleared
proposedRepairAction: nil
additionalText: nil];
[self alarm: a];
/*
* Return configuration information - general stuff, plus
* host specific stuff.
*/
o = [config objectForKey: @"*"];
if (o != nil)
{
[dict setObject: o forKey: @"*"];
}
o = [config objectForKey: h];
if (o != nil)
{
[dict setObject: o forKey: n];
}
if (operators != nil)
{
[dict setObject: operators forKey: @"Operators"];
}
return [NSPropertyListSerialization
dataFromPropertyList: dict
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
}
- (NSString*) registerConsole: (id<Console>)c
name: (NSString*)n
pass: (NSString*)p
{
id obj;
NSString *m;
[(NSDistantObject*)c setProtocolForProxy: @protocol(Console)];
obj = [self findIn: consoles byName: n];
if (obj != nil)
{
m = [NSString stringWithFormat:
@"%@ rejected console with info '%@' (already registered by name)\n",
[NSDate date], n];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
return @"Already registered by that name";
}
obj = [self findIn: consoles byObject: c];
if (obj != nil)
{
m = [NSString stringWithFormat:
@"%@ rejected console with info '%@' (already registered)\n",
[NSDate date], n];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
return @"Already registered"; /* Already registered. */
}
if (operators != nil)
{
NSRange r = [n rangeOfString: @":"];
NSString *s = [n substringToIndex: r.location];
NSDictionary *info = [operators objectForKey: s];
NSString *passwd = [info objectForKey: @"Password"];
if (info == nil)
{
m = [NSString stringWithFormat:
@"%@ rejected console with info '%@' (unknown operator)\n",
[NSDate date], n];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
return @"Unknown user name";
}
else if (passwd && [passwd length] && [passwd isEqual: p] == NO)
{
m = [NSString stringWithFormat:
@"%@ rejected console with info '%@' (bad password)\n",
[NSDate date], n];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
return @"Bad username/password combination";
}
}
obj = [[ConsoleInfo alloc] initFor: c name: n with: self pass: p];
[consoles addObject: obj];
[consoles sortUsingSelector: @selector(compare:)];
RELEASE(obj);
m = [NSString stringWithFormat: @"%@ registered new console with info '%@'\n",
[NSDate date], n];
[self information: m
type: LT_AUDIT
to: nil
from: nil];
return nil;
}
- (void) reply: (NSString*) msg to: (NSString*)n from: (NSString*)c
{
[self information: msg type: LT_AUDIT to: n from: c];
}
- (void) requestConfigFor: (id<CmdConfig>)c
{
return;
}
- (void) servers: (NSData*)d
on: (id<Command>)s
{
NSArray *a;
CommandInfo *o;
a = [NSPropertyListSerialization propertyListWithData: d
options: NSPropertyListMutableContainers
format: 0
error: 0];
o = (CommandInfo*)[self findIn: commands byObject: s];
if (o != nil)
{
[o setServers: a];
}
}
- (void) timedOut: (NSTimer*)t
{
static BOOL inTimeout = NO;
NSDate *now = [t fireDate];
if (now == nil)
{
now = [NSDate date];
}
[[self cmdLogFile: logname] synchronizeFile];
if (inTimeout == NO)
{
unsigned count;
inTimeout = YES;
count = [commands count];
while (count-- > 0)
{
EcClientI *r = [commands objectAtIndex: count];
NSDate *d = [r lastUnanswered];
if (d != nil && [d timeIntervalSinceDate: now] < -pingDelay)
{
EcAlarm *a;
NSString *s;
NSString *t;
s = EcMakeManagedObject([r name], @"Command", nil);
t = [NSString stringWithFormat:
@"failed to respond for over %d seconds", (int)pingDelay];
a = [EcAlarm alarmForManagedObject: s
at: nil
withEventType: EcAlarmEventTypeProcessingError
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
specificProblem: @"Host command/control availability"
perceivedSeverity: EcAlarmSeverityCritical
proposedRepairAction: @"Check network/host/process"
additionalText: t];
[self alarm: a];
[[[r obj] connectionForProxy] invalidate];
[commands removeObjectIdenticalTo: r];
}
}
count = [consoles count];
while (count-- > 0)
{
EcClientI *r = [consoles objectAtIndex: count];
NSDate *d = [r lastUnanswered];
if (d != nil && [d timeIntervalSinceDate: now] < -pingDelay)
{
NSString *m;
m = [NSString stringWithFormat: cmdLogFormat(LT_WARNING,
@"Operator '%@' failed to respond for over %d seconds"),
[r name], (int)pingDelay];
[[[r obj] connectionForProxy] invalidate];
[consoles removeObjectIdenticalTo: r];
[self information: m type: LT_WARNING to: nil from: nil];
}
}
/*
* We ping each client in turn. If there are fewer than 4 clients,
* we skip timeouts so that clients get pinged no more frequently
* than one per 4 timeouts.
*/
count = [commands count];
if (commandPingPosition >= 4 && commandPingPosition >= count)
{
commandPingPosition = 0;
}
if (commandPingPosition < count)
{
[[commands objectAtIndex: commandPingPosition++] ping];
}
count = [consoles count];
if (consolePingPosition >= 4 && consolePingPosition >= count)
{
consolePingPosition = 0;
}
if (consolePingPosition < count)
{
[[consoles objectAtIndex: consolePingPosition++] ping];
}
/*
* Write heartbeat file to let external programs know we are alive.
*/
[[NSString stringWithFormat: @"Heartbeat: %@\n", now]
writeToFile: [NSString stringWithFormat: @"/tmp/%@.alive",
[self cmdName]]
atomically: YES];
}
inTimeout = NO;
}
- (id) recursiveInclude: (id)o
{
id tmp;
if ([o isKindOfClass: [NSArray class]] == YES)
{
NSMutableArray *n;
unsigned i;
unsigned c = [o count];
n = [[NSMutableArray alloc] initWithCapacity: c];
for (i = 0; i < c; i++)
{
id tmp = [o objectAtIndex: i];
if ([tmp isKindOfClass: [NSString class]] == YES)
{
BOOL multi = NO;
tmp = [self tryInclude: tmp multi: &multi];
if (multi == YES)
{
[n addObjectsFromArray: tmp];
}
else if (tmp != nil)
{
[n addObject: tmp];
}
}
else
{
tmp = [self recursiveInclude: tmp];
if (tmp != nil)
{
[n addObject: tmp];
}
}
}
return AUTORELEASE(n);
}
else if ([o isKindOfClass: [NSDictionary class]] == YES)
{
NSMutableDictionary *n;
NSEnumerator *e = [o keyEnumerator];
NSString *k;
unsigned c = [o count];
n = [[NSMutableDictionary alloc] initWithCapacity: c];
while ((k = [e nextObject]) != nil)
{
id tmp = [self tryInclude: k multi: 0];
if ([tmp isKindOfClass: [NSDictionary class]] == YES)
{
[n addEntriesFromDictionary: tmp];
}
else
{
tmp = [self recursiveInclude:
[(NSDictionary *)o objectForKey: k]];
if (tmp != nil)
{
[n setObject: tmp forKey: k];
}
}
}
return AUTORELEASE(n);
}
else
{
tmp = [self tryInclude: o multi: 0];
return tmp;
}
}
- (id) getInclude: (id)s
{
NSString *base = [self cmdDataDirectory];
NSString *file;
id info;
NSRange r;
r = [s rangeOfCharacterFromSet: [NSCharacterSet whitespaceCharacterSet]];
if (r.length == 0)
{
return s; // Not an include.
}
file = [s substringFromIndex: NSMaxRange(r)];
if ([file isAbsolutePath] == NO)
{
file = [base stringByAppendingPathComponent: file];
}
if ([mgr isReadableFileAtPath: file] == NO)
{
NSString *e;
e = [NSString stringWithFormat: @"Unable to read file for '%@'\n", s];
ASSIGN(configIncludeFailed, e);
[[self cmdLogFile: logname] printf: @"%@", configIncludeFailed];
return nil;
}
info = [NSString stringWithContentsOfFile: file];
if (info == nil)
{
NSString *e;
e = [NSString stringWithFormat: @"Unable to load string for '%@'\n", s];
ASSIGN(configIncludeFailed, e);
[[self cmdLogFile: logname] printf: @"%@", configIncludeFailed];
return nil;
}
NS_DURING
{
info = [info propertyList];
}
NS_HANDLER
{
NSString *e;
e = [NSString stringWithFormat: @"Unable to parse for '%@' - %@\n",
s, localException];
ASSIGN(configIncludeFailed, e);
[[self cmdLogFile: logname] printf: @"%@", configIncludeFailed];
info = nil;
}
NS_ENDHANDLER
s = info;
return s;
}
- (id) tryInclude: (id)s multi: (BOOL*)flag
{
if (flag != 0)
{
*flag = NO;
}
if ([s isKindOfClass: [NSString class]] == YES)
{
if ([s hasPrefix: @"#include "] == YES)
{
s = [self getInclude: s];
}
else if ([s hasPrefix: @"#includeKeys "] == YES)
{
s = [self getInclude: s];
if ([s isKindOfClass: [NSDictionary class]] == YES)
{
s = [s allKeys];
}
if (flag != 0 && [s isKindOfClass: [NSArray class]] == YES)
{
*flag = YES;
}
}
else if ([s hasPrefix: @"#includeValues "] == YES)
{
s = [self getInclude: s];
if ([s isKindOfClass: [NSDictionary class]] == YES)
{
s = [s allValues];
}
if (flag != 0 && [s isKindOfClass: [NSArray class]] == YES)
{
*flag = YES;
}
}
}
return s;
}
- (oneway void) unmanage: (NSString*)name
{
[sink unmanage: name];
}
- (void) unregister: (id)obj
{
NSString *m;
EcClientI *o = [self findIn: commands byObject: obj];
if (o == nil)
{
o = [self findIn: consoles byObject: obj];
if (o == nil)
{
m = [NSString stringWithFormat:
@"%@ unregister by unknown host/console\n", [NSDate date]];
}
else
{
m = [NSString stringWithFormat:
@"%@ removed (unregistered) console - '%@'\n",
[NSDate date], [o name]];
[o setUnregistered: YES];
[consoles removeObjectIdenticalTo: o];
}
}
else
{
[self unmanage: EcMakeManagedObject([o name], @"Command", nil)];
m = [NSString stringWithFormat:
@"%@ removed (unregistered) host - '%@'\n", [NSDate date], [o name]];
[o setUnregistered: YES];
[commands removeObjectIdenticalTo: o];
}
[self information: m
type: LT_AUDIT
to: nil
from: nil];
}
- (BOOL) update
{
NSMutableDictionary *dict;
NSDictionary *conf;
NSDictionary *d;
NSArray *a;
NSHost *host;
NSString *base;
NSString *path;
NSString *str;
unsigned count;
unsigned i;
BOOL changed = NO;
host = [NSHost currentHost];
str = [NSHost controlWellKnownName];
if (nil != str)
{
if (NO == [str isEqual: [host wellKnownName]])
{
NSLog(@"Well known name of Control host (%@) does not match"
@" that of current host (%@)", str, [host wellKnownName]);
[self cmdQuit: 1];
return NO;
}
}
base = [self cmdDataDirectory];
path = [base stringByAppendingPathComponent: @"AlertConfig.plist"];
if ([mgr isReadableFileAtPath: path] == YES
&& (d = [NSDictionary dictionaryWithContentsOfFile: path]) != nil)
{
d = [NSDictionary dictionaryWithObjectsAndKeys:
d, @"Alerter", nil];
[[self cmdDefaults] setConfiguration: d];
if (nil == alerter)
{
alerter = [EcAlerter new];
}
}
else
{
DESTROY(alerter);
}
path = [base stringByAppendingPathComponent: @"Operators.plist"];
d = [NSDictionary dictionaryWithContentsOfFile: path];
if (d == nil)
{
[[self cmdLogFile: logname]
printf: @"Failed to load %@\n", path];
}
else
{
if (operators == nil || [operators isEqual: d] == NO)
{
changed = YES;
RELEASE(operators);
operators = [d mutableCopy];
}
}
DESTROY(configIncludeFailed);
path = [base stringByAppendingPathComponent: @"Control.plist"];
str = [NSString stringWithContentsOfFile: path];
if (str == nil)
{
NSString *e;
e = [NSString stringWithFormat: @"Failed to load %@\n", path];
ASSIGN(configFailed, e);
[[self cmdLogFile: logname] printf: @"%@", configFailed];
return NO;
}
NS_DURING
{
conf = [str propertyList];
if ([conf isKindOfClass: [NSDictionary class]] == NO)
{
[NSException raise: NSGenericException
format: @"Contents of file not a dictionary"];
}
}
NS_HANDLER
{
NSString *e;
e = [NSString stringWithFormat: @"Failed to load %@ - %@\n",
path, [localException reason]];
ASSIGN(configFailed, e);
[[self cmdLogFile: logname] printf: @"%@", configFailed];
return NO;
}
NS_ENDHANDLER
if (nil != conf)
{
NSMutableDictionary *root;
NSEnumerator *rootEnum;
id hostKey;
if ([conf isKindOfClass: [NSDictionary class]] == NO)
{
NSString *e;
e = [NSString stringWithFormat:
@"%@ top-level is not a dictionary.\n", path];
ASSIGN(configFailed, e);
[[self cmdLogFile: logname] printf: @"%n", configFailed];
return NO;
}
/*
* Build version with mutable dictionaries at the hosts and
* applications/classes levels of the configuration.
*/
conf = [self recursiveInclude: conf];
[conf writeToFile: @"/tmp/control.log" atomically: YES];
root = [NSMutableDictionary dictionaryWithCapacity: [conf count]];
rootEnum = [conf keyEnumerator];
while ((hostKey = [rootEnum nextObject]) != nil)
{
NSDictionary *rootObj;
NSMutableDictionary *host;
NSEnumerator *hostEnum;
NSString *appKey;
rootObj = [conf objectForKey: hostKey];
if ([rootObj isKindOfClass: [NSDictionary class]] == NO)
{
NSString *e;
e = [NSString stringWithFormat:
@"%@ host-level is not a dictionary for '%@'.\n",
path, hostKey];
ASSIGN(configFailed, e);
[[self cmdLogFile: logname] printf: @"%@", configFailed];
return NO;
}
if ([hostKey isEqual: @"*"] == NO)
{
id o = hostKey;
if ([o isEqual: @""]
|| [o caseInsensitiveCompare: @"localhost"] == NSOrderedSame)
{
hostKey = [NSHost currentHost];
}
else
{
hostKey = [NSHost hostWithName: o];
}
if (hostKey == nil)
{
NSString *e;
e = [NSString stringWithFormat:
@"%@ host '%@' unknown.\n", path, o];
ASSIGN(configFailed, e);
[[self cmdLogFile: logname] printf: @"%@", configFailed];
return NO;
}
}
host = [NSMutableDictionary dictionaryWithCapacity:
[rootObj count]];
hostEnum = [rootObj keyEnumerator];
while ((appKey = [hostEnum nextObject]) != nil)
{
NSDictionary *hostObj;
NSMutableDictionary *app;
hostObj = [rootObj objectForKey: appKey];
if ([hostObj isKindOfClass: [NSDictionary class]] == NO)
{
NSString *e;
e = [NSString stringWithFormat:
@"%@ app-level is not a dictionary for '%@'\n",
path, appKey];
ASSIGN(configFailed, e);
[[self cmdLogFile: logname] printf: @"%@", configFailed];
return NO;
}
app = [self recursiveInclude: hostObj];
[host setObject: app forKey: appKey];
}
[root setObject: host forKey: hostKey];
}
if (config == nil || [config isEqual: root] == NO)
{
changed = YES;
ASSIGN(config, root);
}
}
if (YES == changed)
{
dict = [NSMutableDictionary dictionaryWithCapacity: 3];
/*
* Now per-host config dictionaries consisting of general and
* host-specific dictionaries.
*/
a = [NSArray arrayWithArray: commands];
count = [a count];
for (i = 0; i < count; i++)
{
CommandInfo *c = [a objectAtIndex: i];
if ([commands indexOfObjectIdenticalTo: c] != NSNotFound)
{
id o;
NSHost *h;
[dict removeAllObjects];
o = [config objectForKey: @"*"];
if (o != nil)
{
[dict setObject: o forKey: @"*"];
}
h = [NSHost hostWithName: [c name]];
o = [config objectForKey: h];
if (o != nil)
{
[dict setObject: o forKey: [c name]];
}
if (operators != nil)
{
[dict setObject: operators forKey: @"Operators"];
}
NS_DURING
{
NSData *dat;
dat = [NSPropertyListSerialization
dataFromPropertyList: dict
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
[[c obj] updateConfig: dat];
}
NS_HANDLER
{
[[self cmdLogFile: logname]
printf: @"Updating config for '%@':%@",
[c name], localException];
}
NS_ENDHANDLER
}
}
}
DESTROY(configFailed);
return changed;
}
- (void) updateConfig: (NSData*)dummy
{
[self update];
}
@end