mirror of
https://github.com/gnustep/libs-ec.git
synced 2025-02-14 15:41:00 +00:00
1256 lines
28 KiB
Objective-C
1256 lines
28 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 "EcProcess.h"
|
|
#import "EcHost.h"
|
|
#import "NSFileHandle+Printf.h"
|
|
|
|
#import "config.h"
|
|
|
|
#if defined(HAVE_LIBREADLINE)
|
|
# include <stdlib.h>
|
|
# include <unistd.h>
|
|
# include <readline/readline.h>
|
|
# include <readline/history.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_READPASSPHRASE_H)
|
|
# include <readpassphrase.h>
|
|
#elif defined(HAVE_BSD_READPASSPHRASE_H)
|
|
# include <bsd/readpassphrase.h>
|
|
#endif
|
|
static BOOL commandIsRepeat (NSString *string)
|
|
{
|
|
switch ([string length])
|
|
{
|
|
case 1: return [string isEqualToString: @"r"];
|
|
case 2: return [string isEqualToString: @"re"];
|
|
case 3: return [string isEqualToString: @"rep"];
|
|
case 4: return [string isEqualToString: @"repe"];
|
|
case 5: return [string isEqualToString: @"repea"];
|
|
case 6: return [string isEqualToString: @"repeat"];
|
|
default: return NO;
|
|
}
|
|
}
|
|
|
|
static NSString *originalUserName = nil;
|
|
|
|
@interface EcConsole : EcProcess <RunLoopEvents, Console>
|
|
{
|
|
NSFileHandle *ichan;
|
|
NSFileHandle *ochan;
|
|
NSMutableData *data;
|
|
NSTimer *timer;
|
|
NSString *local;
|
|
NSString *host;
|
|
NSString *name;
|
|
NSString *user;
|
|
NSString *pass;
|
|
NSString *rnam;
|
|
NSRegularExpression *want;
|
|
NSRegularExpression *fail;
|
|
id server;
|
|
int pos;
|
|
BOOL interactive;
|
|
}
|
|
- (void) connectionBecameInvalid: (NSNotification*)notification;
|
|
#if defined(HAVE_LIBREADLINE)
|
|
- (void) activateReadline;
|
|
- (void) deactivateReadline;
|
|
- (void) setupConnection;
|
|
#else
|
|
- (void) didRead: (NSNotification*)notification;
|
|
#endif
|
|
- (void) didWrite: (NSNotification*)notification;
|
|
- (void) doCommand: (NSMutableArray*)words;
|
|
- (void) timedOut;
|
|
- (void) waitEnded: (NSTimer*)t;
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation EcConsole
|
|
|
|
- (BOOL) cmdIsClient
|
|
{
|
|
return NO; // Not a client of the Command server
|
|
}
|
|
|
|
- (oneway void) cmdQuit: (NSInteger)sig
|
|
{
|
|
[timer invalidate];
|
|
timer = nil;
|
|
|
|
/* Attempt to output an exit message, but our tereminal may have gone away
|
|
* so we ignore exceptions during that.
|
|
*/
|
|
NS_DURING
|
|
[ochan puts: @"\nExiting\n"];
|
|
NS_HANDLER
|
|
NS_ENDHANDLER
|
|
|
|
#if defined(HAVE_LIBREADLINE)
|
|
[self deactivateReadline];
|
|
#endif
|
|
|
|
if (server)
|
|
{
|
|
id con = [(NSDistantObject*)server connectionForProxy];
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver: self
|
|
name: NSConnectionDidDieNotification
|
|
object: con];
|
|
NS_DURING
|
|
{
|
|
[server unregister: self];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Exception unregistering from Control: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
[con invalidate];
|
|
DESTROY(server);
|
|
}
|
|
|
|
if (ichan)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver: self
|
|
name: NSFileHandleReadCompletionNotification
|
|
object: (id)ichan];
|
|
if ([ichan fileDescriptor] >= 0)
|
|
{
|
|
[ichan closeFile];
|
|
}
|
|
[ichan release];
|
|
ichan = nil;
|
|
}
|
|
if (ochan)
|
|
{
|
|
if ([ochan fileDescriptor] >= 0)
|
|
{
|
|
[ochan closeFile];
|
|
}
|
|
[ochan release];
|
|
ochan = nil;
|
|
}
|
|
[super cmdQuit: sig];
|
|
}
|
|
|
|
- (void) connectionBecameInvalid: (NSNotification*)notification
|
|
{
|
|
id conn = [notification object];
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver: self
|
|
name: NSConnectionDidDieNotification
|
|
object: conn];
|
|
if ([conn isKindOfClass: [NSConnection class]])
|
|
{
|
|
if (server && [(NSDistantObject*)server connectionForProxy] == conn)
|
|
{
|
|
DESTROY(server);
|
|
NSLog(@"Lost connection to Control server.");
|
|
[self cmdQuit: 0];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[rnam release];
|
|
[user release];
|
|
[pass release];
|
|
[host release];
|
|
[name release];
|
|
[local release];
|
|
[want release];
|
|
[fail release];
|
|
[data release];
|
|
if (timer)
|
|
{
|
|
[timer invalidate];
|
|
timer = nil;
|
|
}
|
|
[self cmdQuit: 0];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void) receivedEvent: (void *)descriptorData
|
|
type: (RunLoopEventType)type
|
|
extra: (void*)extra
|
|
forMode: (NSString*)mode
|
|
{
|
|
#if defined(HAVE_LIBREADLINE)
|
|
rl_callback_read_char();
|
|
#else
|
|
NSLog(@"ERROR: this should not get called w/o readline: %s",
|
|
__PRETTY_FUNCTION__);
|
|
#endif
|
|
}
|
|
|
|
#if defined(HAVE_LIBREADLINE)
|
|
|
|
- (char **)complete:(const char *)_text range:(NSRange)_range
|
|
{
|
|
return NULL; /* no completion yet */
|
|
}
|
|
|
|
#endif
|
|
|
|
#if !defined(HAVE_LIBREADLINE)
|
|
- (void) didRead: (NSNotification*)notification
|
|
{
|
|
NSDictionary *userInfo = [notification userInfo];
|
|
NSData *d;
|
|
|
|
d = [[userInfo objectForKey: NSFileHandleNotificationDataItem] retain];
|
|
|
|
if (d == nil || [d length] == 0)
|
|
{
|
|
if (d != nil)
|
|
{
|
|
[d release];
|
|
}
|
|
[self cmdQuit: 0];
|
|
}
|
|
else
|
|
{
|
|
char *bytes;
|
|
int len;
|
|
int eol;
|
|
int done = 0;
|
|
|
|
[data appendData: d];
|
|
[d release];
|
|
bytes = (char*)[data mutableBytes];
|
|
len = [data length];
|
|
|
|
for (eol = 0; eol < len; eol++)
|
|
{
|
|
if (bytes[eol] == '\r' || bytes[eol] == '\n')
|
|
{
|
|
char *word = bytes;
|
|
char *end = word;
|
|
NSMutableArray *a;
|
|
|
|
a = [NSMutableArray arrayWithCapacity: 1];
|
|
|
|
bytes[eol++] = '\0';
|
|
while (eol < len && isspace(bytes[eol]))
|
|
{
|
|
eol++;
|
|
}
|
|
done = eol;
|
|
|
|
while (end && *end)
|
|
{
|
|
word = end;
|
|
while (*word && isspace(*word))
|
|
{
|
|
word++;
|
|
}
|
|
end = word;
|
|
|
|
if (*word == '"' || *word == '\'')
|
|
{
|
|
char term = *word;
|
|
char *ptr;
|
|
|
|
end = ++word;
|
|
ptr = end;
|
|
|
|
while (*end)
|
|
{
|
|
if (*end == term)
|
|
{
|
|
end++;
|
|
break;
|
|
}
|
|
if (*end == '\\')
|
|
{
|
|
end++;
|
|
switch (*end)
|
|
{
|
|
case '\\': *ptr++ = '\\'; break;
|
|
case 'b': *ptr++ = '\b'; break;
|
|
case 'f': *ptr++ = '\f'; break;
|
|
case 'n': *ptr++ = '\n'; break;
|
|
case 'r': *ptr++ = '\r'; break;
|
|
case 't': *ptr++ = '\t'; break;
|
|
case 'x':
|
|
{
|
|
int val = 0;
|
|
|
|
if (isxdigit(end[1]))
|
|
{
|
|
end++;
|
|
val <<= 4;
|
|
if (islower(*end))
|
|
{
|
|
val += *end - 'a' + 10;
|
|
}
|
|
else if (isupper(*end))
|
|
{
|
|
val += *end - 'A' + 10;
|
|
}
|
|
else
|
|
{
|
|
val += *end - '0';
|
|
}
|
|
}
|
|
if (isxdigit(end[1]))
|
|
{
|
|
end++;
|
|
val <<= 4;
|
|
if (islower(*end))
|
|
{
|
|
val += *end - 'a' + 10;
|
|
}
|
|
else if (isupper(*end))
|
|
{
|
|
val += *end - 'A' + 10;
|
|
}
|
|
else
|
|
{
|
|
val += *end - '0';
|
|
}
|
|
}
|
|
*ptr++ = val;
|
|
}
|
|
break;
|
|
|
|
case '0':
|
|
{
|
|
int val = 0;
|
|
|
|
if (isdigit(end[1]))
|
|
{
|
|
end++;
|
|
val <<= 3;
|
|
val += *end - '0';
|
|
}
|
|
if (isdigit(end[1]))
|
|
{
|
|
end++;
|
|
val <<= 3;
|
|
val += *end - '0';
|
|
}
|
|
if (isdigit(end[1]))
|
|
{
|
|
end++;
|
|
val <<= 3;
|
|
val += *end - '0';
|
|
}
|
|
*ptr++ = val;
|
|
}
|
|
break;
|
|
|
|
default: *ptr++ = *end; break;
|
|
}
|
|
if (*end)
|
|
{
|
|
end++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ptr++ = *end++;
|
|
}
|
|
}
|
|
*ptr = '\0';
|
|
}
|
|
else
|
|
{
|
|
while (*end && !isspace(*end))
|
|
{
|
|
if (isupper(*end))
|
|
{
|
|
*end = tolower(*end);
|
|
}
|
|
end++;
|
|
}
|
|
}
|
|
|
|
if (*end)
|
|
{
|
|
*end++ = '\0';
|
|
}
|
|
else
|
|
{
|
|
end = 0;
|
|
}
|
|
|
|
if (word && *word)
|
|
{
|
|
[a addObject: [NSString stringWithCString: word]];
|
|
}
|
|
}
|
|
|
|
[self doCommand: a];
|
|
if (ichan == nil)
|
|
{
|
|
return; /* Quit while doing command. */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (done > 0)
|
|
{
|
|
memcpy(bytes, &bytes[done], len - done);
|
|
[data setLength: len - done];
|
|
}
|
|
|
|
if (YES == interactive)
|
|
{
|
|
[ichan readInBackgroundAndNotify]; /* Need more data. */
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
- (void) didWrite: (NSNotification*)notification
|
|
{
|
|
NSDictionary *userInfo = [notification userInfo];
|
|
NSString *e;
|
|
|
|
e = [userInfo objectForKey: GSFileHandleNotificationError];
|
|
if (e)
|
|
{
|
|
[self cmdQuit: 0];
|
|
}
|
|
}
|
|
|
|
- (void) doCommand: (NSMutableArray*)words
|
|
{
|
|
NSString *cmd;
|
|
static NSArray *pastWords = nil;
|
|
|
|
if (words == nil || [words count] == 0)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
cmd = [words objectAtIndex: 0];
|
|
}
|
|
if ([cmd isEqual: @"quit"] && [words count] == 1)
|
|
{
|
|
[self cmdQuit: 0];
|
|
return;
|
|
}
|
|
|
|
#if !defined(HAVE_LIBREADLINE)
|
|
if (user == nil)
|
|
{
|
|
if ([cmd length] > 0)
|
|
{
|
|
user = [cmd retain];
|
|
[ochan puts: @"Enter your password: "];
|
|
}
|
|
else
|
|
{
|
|
[ochan puts: @"Enter your username: "];
|
|
}
|
|
}
|
|
else if (pass == nil)
|
|
{
|
|
if ([cmd length] > 0)
|
|
{
|
|
pass = [cmd retain];
|
|
server = [NSConnection rootProxyForConnectionWithRegisteredName: name
|
|
host: host
|
|
usingNameServer: [NSSocketPortNameServer sharedInstance]];
|
|
if (server == nil)
|
|
{
|
|
if ([host isEqual: @"*"])
|
|
{
|
|
[ochan printf: @"Unable to connect to %@ on any host.\n",
|
|
name];
|
|
}
|
|
else
|
|
{
|
|
[ochan printf: @"Unable to connect to %@ on %@.\n",
|
|
name, host];
|
|
}
|
|
[self cmdQuit: 10];
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
NSString *reject;
|
|
|
|
ASSIGN(rnam,
|
|
([NSString stringWithFormat: @"%@:%@", user, local]));
|
|
|
|
[server retain];
|
|
[server setProtocolForProxy: @protocol(Control)];
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(connectionBecameInvalid:)
|
|
name: NSConnectionDidDieNotification
|
|
object: (id)[server connectionForProxy]];
|
|
[[server connectionForProxy] setDelegate: self];
|
|
|
|
reject = [server registerConsole: self
|
|
name: rnam
|
|
pass: pass];
|
|
|
|
if (reject == nil)
|
|
{
|
|
[words removeAllObjects];
|
|
[words addObject: @"list"];
|
|
[words addObject: @"consoles"];
|
|
[server command: [NSPropertyListSerialization
|
|
dataFromPropertyList: words
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0] from: rnam];
|
|
}
|
|
else
|
|
{
|
|
if ([reject hasPrefix: @"Already registered "])
|
|
{
|
|
}
|
|
[pass release];
|
|
pass = nil;
|
|
[user release];
|
|
user = nil;
|
|
[ochan puts: [NSString stringWithFormat:
|
|
@"Connection attempt rejected - %@\n", reject]];
|
|
[ochan puts: @"Enter your username: "];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[ochan puts: @"Enter your password: "];
|
|
}
|
|
}
|
|
else
|
|
#endif /* defined(HAVE_LIBREADLINE) */
|
|
if (commandIsRepeat(cmd))
|
|
{
|
|
if (pastWords == nil)
|
|
{
|
|
[ochan puts: @"No command to repeat.\n"];
|
|
}
|
|
else
|
|
{
|
|
[ochan printf: @"Repeating command `%@' -\n",
|
|
[pastWords componentsJoinedByString: @" "]];
|
|
[server command: [NSPropertyListSerialization
|
|
dataFromPropertyList: pastWords
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0] from: rnam];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSIGN(pastWords, words);
|
|
[server command: [NSPropertyListSerialization
|
|
dataFromPropertyList: words
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0] from: rnam];
|
|
}
|
|
}
|
|
|
|
- (void) doLine: (NSString *)_line
|
|
{
|
|
NSAutoreleasePool *arp;
|
|
NSMutableArray *words;
|
|
NSEnumerator *wordsEnum;
|
|
NSString *word;
|
|
|
|
if (_line == nil)
|
|
{
|
|
[self cmdQuit: 0];
|
|
}
|
|
|
|
_line = [_line stringByTrimmingSpaces]; // Remove trailing newline
|
|
if ([_line length] == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
arp = [[NSAutoreleasePool alloc] init];
|
|
|
|
/* setup word array */
|
|
|
|
words = [NSMutableArray arrayWithCapacity: 16];
|
|
wordsEnum = [[_line componentsSeparatedByString: @" "] objectEnumerator];
|
|
while ((word = [wordsEnum nextObject]) != nil)
|
|
{
|
|
word = [word stringByTrimmingSpaces];
|
|
if ([word length] > 0)
|
|
{
|
|
[words addObject: word];
|
|
}
|
|
}
|
|
|
|
/* invoke server */
|
|
|
|
[self doCommand: words];
|
|
|
|
[arp release];
|
|
}
|
|
|
|
#define add(C) { if (op - out >= len - 1) { int i = op - out; len += 128; [d setLength: len]; out = [d mutableBytes]; op = &out[i];} *op++ = C; }
|
|
|
|
- (oneway void) information: (NSString*)s
|
|
{
|
|
int ilen = [s cStringLength] + 1;
|
|
int len = (ilen + 4) * 2;
|
|
char buf[ilen];
|
|
const char *ip = buf;
|
|
NSMutableData *d;
|
|
char *out;
|
|
char *op;
|
|
char c;
|
|
|
|
d = [NSMutableData dataWithCapacity: len];
|
|
out = [d mutableBytes];
|
|
op = out;
|
|
|
|
[s getCString: buf];
|
|
buf[ilen-1] = '\0';
|
|
[d setLength: len];
|
|
if (pos)
|
|
{
|
|
add('\n');
|
|
pos = 0;
|
|
}
|
|
while (*ip)
|
|
{
|
|
switch (*ip)
|
|
{
|
|
case '\r':
|
|
case '\n':
|
|
pos = 0;
|
|
add(*ip);
|
|
break;
|
|
|
|
case '\t':
|
|
if (pos >= 72)
|
|
{
|
|
pos = 0;
|
|
add('\n');
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
add(' ');
|
|
pos++;
|
|
}
|
|
while (pos % 8);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
c = *ip;
|
|
if (c < ' ')
|
|
{
|
|
c = ' ';
|
|
}
|
|
if (c > 126)
|
|
{
|
|
c = ' ';
|
|
}
|
|
if (c == ' ')
|
|
{
|
|
int l = 0;
|
|
|
|
while (ip[l] && isspace(ip[l])) l++;
|
|
while (ip[l] && !isspace(ip[l])) l++;
|
|
l--;
|
|
if ((pos + l) >= 80 && l < 80)
|
|
{
|
|
add('\n');
|
|
pos = 0;
|
|
}
|
|
else
|
|
{
|
|
add(' ');
|
|
pos++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
add(c);
|
|
pos++;
|
|
}
|
|
break;
|
|
}
|
|
ip++;
|
|
}
|
|
[d setLength: op - out];
|
|
[ochan writeData: d];
|
|
|
|
/* If we are waiting for a regular expression ... check to see if it is
|
|
* found and then end with a success status.
|
|
*/
|
|
if (nil != want)
|
|
{
|
|
NSRange r;
|
|
|
|
r = [want rangeOfFirstMatchInString: s
|
|
options: 0
|
|
range: NSMakeRange(0, [s length])];
|
|
if (r.length > 0)
|
|
{
|
|
[self cmdQuit: 0];
|
|
return;
|
|
}
|
|
}
|
|
if (nil != fail)
|
|
{
|
|
NSRange r;
|
|
|
|
r = [fail rangeOfFirstMatchInString: s
|
|
options: 0
|
|
range: NSMakeRange(0, [s length])];
|
|
if (r.length > 0)
|
|
{
|
|
[self cmdQuit: 3]; // Matched failure message ... status 3
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define NUMARGS 1
|
|
- (id) init
|
|
{
|
|
NSDictionary *appDefaults;
|
|
id objects[NUMARGS];
|
|
id keys[NUMARGS];
|
|
|
|
keys[0] = @"Daemon";
|
|
objects[0] = @"NO"; /* Never run as daemon */
|
|
|
|
appDefaults = [NSDictionary dictionaryWithObjects: objects
|
|
forKeys: keys
|
|
count: NUMARGS];
|
|
return [self initWithDefaults: appDefaults];
|
|
}
|
|
|
|
- (id) initWithDefaults: (NSDictionary*)defs
|
|
{
|
|
/* When executing a command using sudo, we should use the original username
|
|
*/
|
|
ASSIGN(originalUserName,
|
|
[[[NSProcessInfo processInfo] environment] objectForKey: @"SUDO_USER"]);
|
|
if (nil == originalUserName)
|
|
{
|
|
ASSIGN(originalUserName, NSUserName());
|
|
}
|
|
self = [super initWithDefaults: defs];
|
|
if (self)
|
|
{
|
|
NSUserDefaults *defs = [self cmdDefaults];
|
|
NSDictionary *env = [[NSProcessInfo processInfo] environment];
|
|
NSString *s;
|
|
|
|
interactive = YES;
|
|
local = [[[NSHost currentHost] wellKnownName] retain];
|
|
name = [defs stringForKey: @"ControlName"];
|
|
if (name == nil)
|
|
{
|
|
name = @"Control";
|
|
}
|
|
if (nil != (host = [NSHost controlWellKnownName]))
|
|
{
|
|
host = [[NSHost hostWithWellKnownName: host] name];
|
|
}
|
|
if (nil == host)
|
|
{
|
|
host = @"*";
|
|
}
|
|
if ([host length] == 0)
|
|
{
|
|
host = local;
|
|
}
|
|
[host retain];
|
|
|
|
/* If the User and Pass arguments are supplied, login directly.
|
|
*/
|
|
user = [[defs stringForKey: @"User"] retain];
|
|
if (nil == user)
|
|
{
|
|
user = [[env objectForKey: @"ConsoleUser"] retain];
|
|
}
|
|
pass = [[defs stringForKey: @"Pass"] retain];
|
|
if (nil == pass)
|
|
{
|
|
pass = [[env objectForKey: @"ConsolePass"] retain];
|
|
}
|
|
|
|
ASSIGN(rnam, ([NSString stringWithFormat: @"%@:%@", user, local]));
|
|
|
|
if (user && pass)
|
|
{
|
|
BOOL mark = NO;
|
|
BOOL prompt = YES;
|
|
|
|
interactive = NO;
|
|
server = [NSConnection rootProxyForConnectionWithRegisteredName: name
|
|
host: host
|
|
usingNameServer: [NSSocketPortNameServer sharedInstance]];
|
|
if (nil == server)
|
|
{
|
|
if ([host isEqual: @"*"])
|
|
{
|
|
GSPrintf(stderr, @"Unable to connect to %@ on any host.\n",
|
|
name);
|
|
}
|
|
else
|
|
{
|
|
GSPrintf(stderr, @"Unable to connect to %@ on %@.\n",
|
|
name, host);
|
|
}
|
|
[self cmdQuit: 10];
|
|
DESTROY(self);
|
|
return nil;
|
|
}
|
|
else
|
|
{
|
|
NSString *reject;
|
|
|
|
[server retain];
|
|
[server setProtocolForProxy: @protocol(Control)];
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(connectionBecameInvalid:)
|
|
name: NSConnectionDidDieNotification
|
|
object: (id)[server connectionForProxy]];
|
|
[[server connectionForProxy] setDelegate: self];
|
|
|
|
reject = [server registerConsole: self
|
|
name: rnam
|
|
pass: pass];
|
|
|
|
if (reject != nil)
|
|
{
|
|
GSPrintf(stderr, @"Login rejected for %@ on %@.\n",
|
|
name, host);
|
|
[self cmdQuit: 11];
|
|
DESTROY(self);
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
if ([defs objectForKey: @"Mark"])
|
|
{
|
|
mark = [defs boolForKey: @"Mark"];
|
|
}
|
|
else
|
|
{
|
|
if ([env objectForKey: @"ConsoleMark"])
|
|
{
|
|
mark = [[env objectForKey: @"ConsoleMark"] boolValue];
|
|
}
|
|
}
|
|
|
|
/* Tell the Control server to mark start and end of messages
|
|
* if required.
|
|
*/
|
|
if (mark)
|
|
{
|
|
[self doCommand: [NSMutableArray arrayWithObjects:
|
|
@"silent", @"set", @"display", @"mark", nil]];
|
|
}
|
|
|
|
if ([defs objectForKey: @"Prompt"])
|
|
{
|
|
prompt = [defs boolForKey: @"Prompt"];
|
|
}
|
|
else
|
|
{
|
|
if ([env objectForKey: @"ConsolePrompt"])
|
|
{
|
|
prompt = [[env objectForKey: @"ConsolePrompt"] boolValue];
|
|
}
|
|
}
|
|
|
|
/* Tell the Control server not to prompt if necessary
|
|
*/
|
|
if (NO == prompt)
|
|
{
|
|
[self doCommand: [NSMutableArray arrayWithObjects:
|
|
@"silent", @"unset", @"display", @"prompt", nil]];
|
|
}
|
|
|
|
if (nil == (s = [defs stringForKey: @"Line"]))
|
|
{
|
|
s = [env objectForKey: @"ConsoleLine"];
|
|
}
|
|
if (nil != self && nil != s)
|
|
{
|
|
[self doLine: s];
|
|
|
|
/* Now, we may delay for 'Wait' seconds looking for a response
|
|
* containing the 'Want' pattern or the 'Fail' pattern.
|
|
* An empty string as a command line argument turns off
|
|
* pattern matching specified in the environment.
|
|
*/
|
|
if (nil == (s = [defs stringForKey: @"Want"]))
|
|
{
|
|
s = [env objectForKey: @"ConsoleWant"];
|
|
}
|
|
if ([s length] > 0)
|
|
{
|
|
want = [[NSRegularExpression alloc] initWithPattern: s
|
|
options: 0
|
|
error: 0];
|
|
}
|
|
if (nil == (s = [defs stringForKey: @"Fail"]))
|
|
{
|
|
s = [env objectForKey: @"ConsoleFail"];
|
|
}
|
|
if ([s length] > 0)
|
|
{
|
|
fail = [[NSRegularExpression alloc] initWithPattern: s
|
|
options: 0
|
|
error: 0];
|
|
}
|
|
if (nil != want || nil != fail)
|
|
{
|
|
NSTimeInterval ti;
|
|
|
|
if (nil == (s = [defs stringForKey: @"Wait"]))
|
|
{
|
|
s = [env objectForKey: @"ConsoleWait"];
|
|
if (0 == [s length])
|
|
{
|
|
s = @"5";
|
|
}
|
|
}
|
|
ti = [s floatValue];
|
|
timer = [NSTimer scheduledTimerWithTimeInterval: ti
|
|
target: self selector: @selector(waitEnded:)
|
|
userInfo: nil repeats: NO];
|
|
|
|
if ([defs objectForKey: @"Quiet"])
|
|
{
|
|
if ([defs boolForKey: @"Quiet"])
|
|
{
|
|
/* If we don't want any output, return without
|
|
* setting the output channel up.
|
|
*/
|
|
return self;
|
|
}
|
|
}
|
|
else if ([[env objectForKey: @"ConsoleQuiet"] boolValue])
|
|
{
|
|
/* If we don't want any output, return without
|
|
* setting the output channel up.
|
|
*/
|
|
return self;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* No waiting ... quit immediately.
|
|
*/
|
|
[self cmdQuit: 0];
|
|
DESTROY(self);
|
|
return nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nil != self)
|
|
{
|
|
data = [[NSMutableData alloc] init];
|
|
ichan = [[NSFileHandle fileHandleWithStandardInput] retain];
|
|
ochan = [[NSFileHandle fileHandleWithStandardOutput] retain];
|
|
#if !defined(HAVE_LIBREADLINE)
|
|
if (YES == interactive)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] addObserver: self
|
|
selector: @selector(didRead:)
|
|
name: NSFileHandleReadCompletionNotification
|
|
object: (id)ichan];
|
|
[ochan puts: @"Enter your username: "];
|
|
[ichan readInBackgroundAndNotify];
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (int) ecRun
|
|
{
|
|
NSRunLoop *loop;
|
|
NSDate *distantFuture;
|
|
|
|
#if defined(HAVE_LIBREADLINE)
|
|
if (YES == interactive)
|
|
{
|
|
[self setupConnection];
|
|
[self activateReadline];
|
|
}
|
|
#endif
|
|
|
|
distantFuture = [NSDate distantFuture];
|
|
loop = [NSRunLoop currentRunLoop];
|
|
while (YES == [loop runMode: NSDefaultRunLoopMode beforeDate: distantFuture])
|
|
{
|
|
if (0 != [self cmdSignalled])
|
|
{
|
|
[self cmdQuit: [self cmdSignalled]];
|
|
}
|
|
}
|
|
|
|
#if defined(HAVE_LIBREADLINE)
|
|
if (YES == interactive)
|
|
{
|
|
[self deactivateReadline];
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
- (void) timedOut
|
|
{
|
|
return; // Ignore EcProcess timeouts
|
|
}
|
|
|
|
- (void) waitEnded: (NSTimer*)t
|
|
{
|
|
[self cmdQuit: 2]; // Timeout waiting for regex has status 2
|
|
}
|
|
|
|
/* readline handling */
|
|
|
|
#if defined(HAVE_LIBREADLINE)
|
|
|
|
/* readline callbacks have no context, so we need this one ... */
|
|
static EcConsole *rlConsole = nil;
|
|
|
|
static void
|
|
readlineReadALine(char *_line)
|
|
{
|
|
NSString *s = nil;
|
|
|
|
if (_line)
|
|
{
|
|
s = [[NSString alloc] initWithCString: _line];
|
|
if (strlen(_line) > 0)
|
|
{
|
|
add_history(_line);
|
|
}
|
|
free(_line);
|
|
}
|
|
|
|
[rlConsole doLine: s];
|
|
DESTROY(s);
|
|
}
|
|
|
|
static char **
|
|
consoleCompleter(const char *text, int start, int end)
|
|
{
|
|
return [rlConsole complete: text range: NSMakeRange(start, end - start)];
|
|
}
|
|
|
|
- (void) activateReadline
|
|
{
|
|
rlConsole = self;
|
|
|
|
/* setup readline */
|
|
|
|
rl_readline_name = "Console";
|
|
rl_attempted_completion_function = consoleCompleter;
|
|
|
|
rl_callback_handler_install("" /* prompt */, readlineReadALine);
|
|
atexit(rl_callback_handler_remove);
|
|
|
|
/* register in runloop */
|
|
|
|
[[NSRunLoop currentRunLoop] addEvent: (void*)(uintptr_t)0
|
|
type: ET_RDESC
|
|
watcher: self
|
|
forMode: NSDefaultRunLoopMode];
|
|
}
|
|
|
|
- (void) deactivateReadline
|
|
{
|
|
[[NSRunLoop currentRunLoop] removeEvent: (void*)(uintptr_t)0
|
|
type: ET_RDESC
|
|
forMode: NSDefaultRunLoopMode
|
|
all: YES];
|
|
|
|
rl_callback_handler_remove();
|
|
rlConsole = nil;
|
|
}
|
|
|
|
- (void) setupConnection
|
|
{
|
|
while (self->server == nil)
|
|
{
|
|
NSString *u;
|
|
NSString *p;
|
|
NSString *reject;
|
|
char buf[128], *line = buf;
|
|
|
|
#if !defined(EC_LOGIN_NAME)
|
|
#define EC_LOGIN_NAME 0
|
|
#endif
|
|
#if EC_LOGIN_NAME
|
|
|
|
if (nil == (u = [[self cmdDefaults] stringForKey: @"EffectiveUser"]))
|
|
{
|
|
u = originalUserName;
|
|
}
|
|
|
|
/* read username */
|
|
if (u)
|
|
{
|
|
printf("Login (%s): ", [u UTF8String]);
|
|
}
|
|
else
|
|
{
|
|
printf("Login: ");
|
|
}
|
|
fflush(stdout);
|
|
|
|
line = fgets(buf, sizeof(buf), stdin);
|
|
if (0 == line)
|
|
{
|
|
/* user pressed ctrl-D, just exit */
|
|
exit(0);
|
|
}
|
|
line[strlen(line) - 1] = '\0';
|
|
|
|
/* If we do not have a system username or if the user entered a value
|
|
* we use the username entered (or try again).
|
|
*/
|
|
if (nil == u || line[0] != '\0')
|
|
{
|
|
u = [[NSString stringWithCString: line] stringByTrimmingSpaces];
|
|
if ([u length] == 0)
|
|
{
|
|
/* user just pressed enter, retry */
|
|
continue;
|
|
}
|
|
}
|
|
#else
|
|
u = originalUserName;
|
|
#endif
|
|
|
|
if ([u caseInsensitiveCompare: @"quit"] == NSOrderedSame)
|
|
{
|
|
[self cmdQuit: 0];
|
|
}
|
|
p = [NSString stringWithFormat: @"Password (for %@): ", u];
|
|
#ifndef HAVE_READPASSPHRASE
|
|
/* read password (glibc documentation says not to use getpass?) */
|
|
|
|
line = getpass([p UTF8String]);
|
|
#else
|
|
line = readpassphrase([p UTF8String], &buf[0], 128, RPP_ECHO_OFF);
|
|
#endif
|
|
if (NULL == line)
|
|
{
|
|
NSLog(@"Could not read password: %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
p = [[NSString stringWithCString: line] stringByTrimmingSpaces];
|
|
if ([p caseInsensitiveCompare: @"quit"] == NSOrderedSame)
|
|
{
|
|
[self cmdQuit: 0];
|
|
}
|
|
|
|
/* setup connection to server */
|
|
|
|
self->server = [[NSConnection
|
|
rootProxyForConnectionWithRegisteredName: name
|
|
host: host
|
|
usingNameServer: [NSSocketPortNameServer sharedInstance]] retain];
|
|
if (self->server == nil)
|
|
{
|
|
if ([host isEqual: @"*"])
|
|
{
|
|
[ochan printf: @"Unable to connect to %@ on any host.\n", name];
|
|
}
|
|
else
|
|
{
|
|
[ochan printf: @"Unable to connect to %@ on %@.\n", name, host];
|
|
}
|
|
|
|
[self cmdQuit: 10];
|
|
return;
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(connectionBecameInvalid:)
|
|
name: NSConnectionDidDieNotification
|
|
object: (id)[self->server connectionForProxy]];
|
|
[server setProtocolForProxy: @protocol(Control)];
|
|
[[server connectionForProxy] setDelegate: self];
|
|
|
|
/* attempt login */
|
|
|
|
self->user = [u retain];
|
|
self->pass = [p retain];
|
|
ASSIGN(self->rnam,
|
|
([NSString stringWithFormat: @"%@:%@", self->user, self->local]));
|
|
|
|
reject = [self->server registerConsole: self
|
|
name: self->rnam
|
|
pass: self->pass];
|
|
|
|
/* connection failed */
|
|
|
|
if (reject != nil)
|
|
{
|
|
[ochan puts: [NSString stringWithFormat:
|
|
@"Connection attempt rejected - %@\n", reject]];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
removeObserver: self
|
|
name: NSConnectionDidDieNotification
|
|
object: (id)[self->server connectionForProxy]];
|
|
|
|
DESTROY(self->pass);
|
|
DESTROY(self->user);
|
|
DESTROY(self->rnam);
|
|
DESTROY(self->server);
|
|
[self cmdQuit: 11];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* defined(HAVE_LIBREADLINE) */
|
|
|
|
@end
|