ftp.nice.ch/pub/next/developer/objc/appkit/RemoteCommand.1.s.tar.gz#/RemoteCommand1/execServer.subproj/ExecServer.m

This is ExecServer.m in view mode; [Download] [Up]

// -------------------------------------------------------------------------------------
// ExecServer
// -------------------------------------------------------------------------------------
// Permission is granted to freely redistribute this source code, and to use fragments
// of this code in your own applications if you find them to be useful.  This class,
// along with the source code, come with no warranty of any kind, and the user assumes
// all responsibility for its use.
// -------------------------------------------------------------------------------------

#import <appkit/appkit.h>
#import <libc.h>
#import <mach/mach.h>
#import <mach/cthreads.h>
#import <stdlib.h>
#import <string.h>
#import <c.h>
#import <pwd.h>
#import <sys/param.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/time.h>
#import <sys/dir.h>
#import <mach/mach_traps.h>
#import <remote/NXConnection.h>
#import <remote/NXProxy.h>
#import <machkit/NXPort.h>
#import "ExecServer.h"

// -------------------------------------------------------------------------------------
// These variables are set in the main process, then reset in the child server process
uid_t					exeUserUid;					// login user uid
uid_t					exeRootUid;					// root user uid

// -------------------------------------------------------------------------------------
// Keep track of child process level. Used for debugging purposes only.
int						exeChildLevel = 0;
#define	forkCHILD		({extern int exeChildLevel;int c=fork();if(!c)exeChildLevel++;c;})

// -------------------------------------------------------------------------------------
// local implementation of NXCopyStringBuffer()
#define	STRCOPY(X)		strcpy((char*)malloc(strlen(X) + 1), (X));

// *************************************************************************************
// *************************************************************************************
// _ExecServer_d / RunCommand declarations
// *************************************************************************************
// *************************************************************************************

// -------------------------------------------------------------------------------------
// ExecServer shared status structure
typedef struct _shareStat_s {
	int					errCode;
	char				errMsg[512];
	char				execScript[MAXPATHLEN + 1];
} shareStat_t;

// -------------------------------------------------------------------------------------
// Root_process server protocols

@protocol RunService
- (void)_pingServer;
- (BOOL)_isRunningAsRoot;
- (execHandle_t)_runCommand:(const char*)command
		user:(const char*)userName:(const char*)password
		client:(id <RemoteClient>)theClient
		kill:(BOOL)killOnError;
- (int)_uperform:(SEL)method withArg:(const char*)arg
		user:(const char*)userName:(const char*)password;
- (void)_terminateCommand:(execHandle_t)runId;
- (void)_killCommand:(execHandle_t)runId;
- (BOOL)_childIsActive:(execHandle_t)runId;
- (void)_shutDownServer;
@end

@protocol RootInternal		// sent by RunCommander to server
- (void)_commandOutput:(const char*)buf len:(int)len execId:(u_int)fakeRun;
- (void)_commandDidComplete:(u_int)fakeRun;
@end

// -------------------------------------------------------------------------------------
// RunCommand declaration

@interface RunCommand : Object <NXSenderIsInvalid>
{ @public
	int				inputDescriptor;		// input to csh pipe
	id				client;					// object to send csh output
	BOOL			killOnError;			// kill child process on 'senderIsInvalid:'
	id				server;					// _ExecServerd
	int				cmdChild;				// child pid
	int				exitErr;				// returned exit status
	shareStat_t		*shareMem;				// common memory for error information
	char			*cmdName;				// optional name
}
- (int)_pipeExec:(const char*)cmd user:(const char*)userName:(const char*)password;
- (void)_execCommand:(const char*)cmd user:(const char*)userName:(const char*)password;
- (void)_terminateCommand;
- (void)_killCommand;
@end

// -------------------------------------------------------------------------------------
// _ExecServerd definition

@interface _ExecServerd : Object <NXSenderIsInvalid, RunService, RootInternal>
{ @public
	id				childList;
	char			*serverName;
	id				server;
	int				clientCount;
}
+ setExecServerOwner:theOwner;
@end

// *************************************************************************************
// *************************************************************************************
// _ExecServerd/RunCommand implementation
// *************************************************************************************
// *************************************************************************************

// -------------------------------------------------------------------------------------
// These variables are only set within the child server process
static ExecServer	*exeServerOwner = (id)nil;	// ExecServer instance owner

// -------------------------------------------------------------------------------------
// These variables are statically set, and are never changed
static BOOL			_debugMode = NO;			// debug mode

// -------------------------------------------------------------------------------------
// static utility functions

/* wait for specified child to exit */
static int _waitForExit(int child)
{
	int			pid, omask;
	union wait	status;
	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
	while (((pid = wait(&status)) != child) && (pid != -1));
	(void)sigsetmask(omask);
	return (status.w_status & 0xFF)? -1 : (status.w_status >> 8) & 0xFF;
}

/* check password */
static BOOL _checkPass(const char *pass, const char *pw_passwd)
{
	char	*cp;
	if (!pw_passwd || !*pw_passwd) return NO;
	cp = crypt((char*)pass, (char*)pw_passwd);
	return strcmp(cp, pw_passwd)? NO : YES;
}

/* set environment variable */
static int _setenv(char **ep, char *eVal, char *fmt, ...)
{
	va_list			args;
	register char	*cp, *dp;
	va_start(args, fmt);
	vsprintf(eVal, fmt, args);
	va_end(args);
	for (;dp = *ep; ep++) {
		for (cp = eVal; (*cp == *dp) && (*cp != '=') && *cp; cp++, dp++) continue;
		if (((*cp == '=') || !*cp) && ((*dp == '=') || !*dp)) { *ep = eVal; return 0; }
	}
	return -1;
}

// -------------------------------------------------------------------------------------
// global user functions

/* wrapper for getpwnam() */
struct passwd *exeGetpwnam(const char *user)
{
	extern void		_lu_setport(port_t);
	extern port_t	_lookupd_port(int);
	_lu_setport(_lookupd_port(0));
	return user? getpwnam(user) : getpwuid(getuid());
}

/* wrapper for getpwuid() */
struct passwd *exeGetpwuid(uid_t uid)
{
	extern void		_lu_setport(port_t);
	extern port_t	_lookupd_port(int);
	_lu_setport(_lookupd_port(0));
	return getpwuid(uid);
}

/* switch to specified user */
int exeSwitchToUser(const char *user, const char *pass)
{
	struct passwd		*pw;
	uid_t				user_uid;
	gid_t				user_gid;
	char				user_name[128], user_passwd[128];
	extern uid_t		exeUserUid; // login user uid
	extern uid_t		exeRootUid; // root user uid
	extern ExecServer	*exeServerOwner; // ExecServer instance owner

	/* get user passwd record (cache values) */
	if (!(pw = exeGetpwnam(user))) return RSRV_BADUSER;
	user_uid = pw->pw_uid;
	user_gid = pw->pw_gid;
	strcpy(user_name, pw->pw_name);
	strcpy(user_passwd, pw->pw_passwd);

	/* verify password (only in server children) */
	if (exeServerOwner) {
		if (pass) {
			if (!_checkPass(pass, user_passwd)) {
				struct passwd *rpw = exeGetpwuid(exeRootUid);
				if (!rpw || !_checkPass(pass, rpw->pw_passwd)) return RSRV_BADPASSWD;
			}
		} else {
			if (user_uid != exeUserUid) return RSRV_BADPASSWD;
		}
	}

	/* switch to user */
	if (setgid(user_gid) < 0) return RSRV_BADGID;
	if ((exeRootUid==geteuid()) && initgroups(user_name,user_gid)) return RSRV_BADINIT;
	if (setuid(user_uid) < 0) return RSRV_BADUID;
	
	/* return successful */
	return 0;
	
}

// -------------------------------------------------------------------------------------
// shared memory functions

/* allocate shared memory (used by Root_process) */
static void *_share_alloc(int size)
{
	kern_return_t	ret;
	vm_address_t	_share_addr;
	ret = vm_allocate(task_self(), &_share_addr, size, 1); 
	if (ret != KERN_SUCCESS) { 
		NXLogError("[_share_alloc] vm_allocate() error %d", ret);
		_exit(1);
	} 
	ret = vm_inherit(task_self(), _share_addr, size, VM_INHERIT_SHARE); 
	if (ret != KERN_SUCCESS) { 
		NXLogError("[_share_alloc] vm_inherit() error %d", ret);
		_exit(1); 
	}
	memset((char*)_share_addr, 0, size);
	return (void*)_share_addr;
}

/* free shared memory */
static kern_return_t _share_free(void *_share_mem, int size)
{
	kern_return_t	ret;
	ret = vm_deallocate(task_self(), (vm_address_t)_share_mem, size);
	return ret;
}

// -------------------------------------------------------------------------------------
// RunCommand implementation

@implementation RunCommand

/* initialization */
- init
{
	[super init];
	inputDescriptor	= -1;
	client			= (id)nil;
	killOnError		= YES;
	server			= (id)nil;
	cmdChild		= -1;
	exitErr			= 0;
	shareMem		= (shareStat_t*)_share_alloc(sizeof(shareStat_t));
	return self;
}

/* free */
- free
{
	kern_return_t	ret;
	ret = _share_free((void*)shareMem, sizeof(shareStat_t));
	if (ret != KERN_SUCCESS) {
		NXLogError("[RunCommand] vm_deallocate() error %d", ret);
		_exit(1);
	}
	return [super free];
}

// -------------------------------------------------------------------------------------

/* open pipe to shell and execute command */
- (int)_pipeExec:(const char*)command user:(const char*)userName:(const char*)password
{
	int					inputP[2], hisOutput, myInput;
	const char			**locEnv = environ;
	extern ExecServer	*exeServerOwner; // ExecServer instance owner
	
	/* open pipe */
	pipe(inputP);
	myInput = inputP[0];
	hisOutput = inputP[1];
	
	/* fork and execute User_process shell */
	if ((cmdChild = forkCHILD) == 0) {
		char **env, *cmd = (char*)command;
		int envi;
		setpgrp(getpid(), getpid());

		/* reset pipe */
		close(myInput);
		if (hisOutput != 1) dup2(hisOutput, 1);
		if (hisOutput != 2) dup2(hisOutput, 2);
		if ((hisOutput != 1) && (hisOutput != 2)) close(hisOutput);
		
		/* set user */
		if (shareMem->errCode = exeSwitchToUser(userName, password)) {
			sprintf(shareMem->errMsg, "user=%s, pass=%s",
				(userName?userName:""), (password?password:""));
			_exit(0);
		}
		
		/* make local copy of environment table */
		for (envi = 0; locEnv[envi]; envi++);	// count entries
		env = (char**)alloca(sizeof(char*) * (envi + 3 + 1));	// on stack (extra if needed)
		memset(env, 0, sizeof(char*) * (envi + 3 + 1));
		memcpy(env, locEnv, sizeof(char*) * envi);
		
		/* set path to app */
		if (*(exeServerOwner->mainAppPath)) {
			env[envi] = (char*)alloca(strlen(exeServerOwner->mainAppPath) + 8 + 2);
			sprintf(env[envi], "CMDPATH=%s", exeServerOwner->mainAppPath);
			envi++;
		}
		
		/* set server name */
		if (*(exeServerOwner->mainAppServerName)) {
			env[envi] = (char*)alloca(strlen(exeServerOwner->mainAppServerName) + 11 + 2);
			sprintf(env[envi], "SERVERNAME=%s", exeServerOwner->mainAppServerName);
			envi++;
			if (*(exeServerOwner->mainAppHost)) {
				env[envi] = (char*)alloca(strlen(exeServerOwner->mainAppHost) + 11 + 2);
				sprintf(env[envi], "SERVERHOST=%s", exeServerOwner->mainAppHost);
				envi++;
			}
		}
		
		/* update environment if user change */
		if (userName && *userName) {
			struct passwd *upw = exeGetpwnam(userName);
			_setenv(env,(char*)alloca(strlen(upw->pw_dir)+7),"HOME=%s",upw->pw_dir);
			_setenv(env,(char*)alloca(strlen(userName)+7),"USER=%s",userName);
		}

		/* check for script file wrapper */
		memset(shareMem->execScript, 0, sizeof(shareMem->execScript));
		if (cmd && !strncmp(cmd, "#!", 2)) {
			FILE *fhnd;
			sprintf(shareMem->execScript, "/tmp/.execScript_%d", getpid());
			unlink(shareMem->execScript);
			fhnd = fopen(shareMem->execScript, "w");
			if (fhnd) {
				fputs(cmd, fhnd);
				fputs("\n", fhnd);
				fchmod(fileno(fhnd), 0755);
				fclose(fhnd);
				cmd = shareMem->execScript;
			}
		}
		
		/* execute command */
		shareMem->errCode = RSRV_SUCCESS;	// make sure it's been reset
		execle("/bin/csh", "csh", "-f", "-c", cmd, (char*)nil, env);
		shareMem->errCode = RSRV_EXEC;
		_exit(0);
		
	}
	
	/* set io */
	if (cmdChild == -1)  { close(myInput); myInput = -1; }
	close(hisOutput);
	
	return myInput;
}

/* thread to wait for command completion */
- (void*)_waitForComplete
{

	/* read data if inputDescriptor is valid */
	if (inputDescriptor >= 0) {
		int cnt;
		char buffer[1025];
		do {
			cnt = read(inputDescriptor, buffer, 1024);
			if (cnt != -1) [server _commandOutput:buffer len:cnt execId:(u_int)self];
		} while (cnt > 0);
		if ((exitErr=_waitForExit(cmdChild)) < 0) shareMem->errCode = RSRV_ABORTED;
		inputDescriptor = cmdChild = -1;	// indicate that child is now inactive
	} else {
		shareMem->errCode = RSRV_FORK;	// command never even had a chance
	}
	
	/* indicate command completion */
	[server _commandDidComplete:(u_int)self];

	return self;
	
}

/* thread router */
static void *_waitForComplete(id fakeSelf)
{
	return (void*)[(RunCommand*)fakeSelf _waitForComplete];
}

/* execute command */
- (void)_execCommand:(const char*)cmd user:(const char*)user:(const char*)pass
{
	inputDescriptor = [self _pipeExec:cmd user:user:pass];
	cthread_detach(cthread_fork((cthread_fn_t)_waitForComplete,self));
}

/* terminate command (MAY be blocked) */
- (void)_terminateCommand
{
	if (cmdChild > 0) {
		killpg(cmdChild, SIGTERM);
		kill(cmdChild, SIGTERM);
	}
}

/* kill command (CANNOT be blocked) */
- (void)_killCommand
{
	if (cmdChild > 0) {
		killpg(cmdChild, SIGKILL);
		kill(cmdChild, SIGKILL);
	}
}

// -------------------------------------------------------------------------------------
// RunCommand: sender(client) connection is invalid
- senderIsInvalid:sender
{
	NXLogError("[RunCommand] Connection to client failed ...");
	client = (id)nil;	// clear client
	if (killOnError) [self _killCommand];
	return self;
}

@end

// -------------------------------------------------------------------------------------
// _ExecServerd implementation

#define isMyCHILD(X)	((X) && ([childList indexOf:(id)(X)] != NX_NOT_IN_LIST))

@implementation _ExecServerd

/* set exeServerOwner. (this is set in the child process only) */
+ setExecServerOwner:theOwner
{
	extern ExecServer	*exeServerOwner; // ExecServer instance owner
	exeServerOwner = theOwner;
	return self;
}

/* init instance */
- init
{
	[super init];
	childList = [[[List alloc] initCount:1] empty];
	serverName = (char*)nil;
	server = (id)nil;
	clientCount = 0;
	return self;
}

// -------------------------------------------------------------------------------------
// Root_process commands
// -------------------------------------------------------------------------------------

/* remote: client initiated connection to server */
- (void)_pingServer
{

	/* count active client */
	clientCount++;
	
}

/* remote: return 'root' flag */
- (BOOL)_isRunningAsRoot
{
	return (exeGetpwnam("root")->pw_uid == geteuid())? YES : NO;
}

/* remote: exec command */
- (execHandle_t)_runCommand:(const char*)command
		user:(const char*)userName:(const char*)password
		client:(id <RemoteClient>)theClient
		kill:(BOOL)killOnError
{
	RunCommand			*runCmd;
	
	/* create/initialize command executor */
	runCmd = [[RunCommand alloc] init];
	runCmd->killOnError = killOnError;
	runCmd->server = server;
	if (theClient) {
		NXConnection *conn = [(NXProxy*)theClient connectionForProxy];
    	runCmd->client = theClient;
    	[(NXProxy*)theClient setProtocolForProxy:@protocol(RemoteClient)];
		[[conn outPort] registerForInvalidationNotification:runCmd];
	}

	/* keep track of all children */
	[childList addObject:runCmd];
	
	/* set command to execute */
	[runCmd _execCommand:command user:userName:password];
	
	/* free arguments */
	if (command) free((char*)command);
	if (userName) free((char*)userName);
	if (password) free((char*)password);
	
	return (execHandle_t)runCmd;
}

/* remote: open pipe to shell and execute method */
- (int)_uperform:(SEL)method withArg:(const char*)arg
		user:(const char*)userName:(const char*)password
{
	int					child, exitErr;
	shareStat_t			*share;
	extern ExecServer	*exeServerOwner; // ExecServer instance owner

	/* allocate storage shared with child process */
	share = (shareStat_t*)_share_alloc(sizeof(shareStat_t));
	
	/* single pass loop */
	for (;;) {
		id ms = exeServerOwner->methodDelegate;
	
		/* check for methServer response to method */
		if (!ms || ![ms respondsTo:method]) {
			exitErr = RSRV_UNDEF;
			break;
		}
	
		/* fork and perform method */
		if ((child = forkCHILD) == 0) {
			setpgrp(getpid(), getpid());
			if (share->errCode = exeSwitchToUser(userName, password)) {
				sprintf(share->errMsg, "user=%s, pass=%s", userName, password);
				_exit(0);
			}
			_exit((int)[ms perform:method with:(id)arg] & 0xFF);
		}

		/* check for fork failure */
		if (child == -1) {
			exitErr =  RSRV_FORK;
			break;
		}
	
		/* wait for child to exit */
		exitErr = _waitForExit(child);
		if (share->errCode) exitErr = share->errCode;
		break;
		
	}
	
	/* free arguments and return */
	_share_free((void*)share, sizeof(shareStat_t));
	if (arg) free((char*)arg);
	if (userName) free((char*)userName);
	if (password) free((char*)password);
	return exitErr;

}

/* terminate command */
- (void)_terminateCommand:(execHandle_t)runId
{
	RunCommand	*runCmd = (RunCommand*)runId;
	if (isMyCHILD(runCmd)) [runCmd _terminateCommand];
}

/* kill command */
- (void)_killCommand:(execHandle_t)runId
{
	RunCommand	*runCmd = (RunCommand*)runId;
	if (isMyCHILD(runCmd)) [runCmd _killCommand];
}

/* return true if child is still active */
- (BOOL)_childIsActive:(execHandle_t)runId
{
	RunCommand	*runCmd = (RunCommand*)runId;
	return (isMyCHILD(runCmd))? YES : NO;
}

/* shut down server */
- (void)_shutDownServer
{
	int					i;
	extern ExecServer	*exeServerOwner; // ExecServer instance owner
	exeServerOwner->exitWhenDone = YES;
	for (i = 0; i < [childList count]; i++) {
		RunCommand	*runId = (RunCommand*)[childList objectAt:i];
		[runId _killCommand];
	}
}

// -------------------------------------------------------------------------------------
// internal Root_process notification
// -------------------------------------------------------------------------------------

- (void)_commandOutput:(const char*)buff len:(int)len execId:(u_int)fakeRun
{
	RunCommand	*runCmd = (RunCommand*)fakeRun;
	
	/* send text output to client */
	if (runCmd->client) [runCmd->client commandOutput:buff len:len];
	
	/* free buffer */
	free((char*)buff);
	
}

- (void)_commandDidComplete:(u_int)fakeRun
{
	RunCommand			*runCmd = (RunCommand*)fakeRun;
	int					err;
	extern BOOL			_debugMode; // debug mode
	extern ExecServer	*exeServerOwner; // ExecServer instance owner
	
	/* debug message */
	if (_debugMode && *(runCmd->shareMem->errMsg))	{	// debuging purposes
		NXLogError("[_ExecServerd] (0x%X) %s",
			runCmd->shareMem->errCode, runCmd->shareMem->errMsg);
	}
		
	/* message client */
	err = runCmd->shareMem->errCode?runCmd->shareMem->errCode:runCmd->exitErr;
	if (runCmd->client) [runCmd->client commandDidCompleteWithError:err];
	
	/* unregister runCmd */
	if (runCmd->client) {
		NXPort *port = [[(NXProxy*)runCmd->client connectionForProxy] outPort];
		[port unregisterForInvalidationNotification:runCmd];
	}
	[NXConnection unregisterForInvalidationNotification:runCmd];

	/* remove script wrapper, if any */
	if (*(runCmd->shareMem->execScript)) unlink(runCmd->shareMem->execScript);
	
	/* free resources */
	[NXConnection removeObject:runCmd];
	[childList removeObject:runCmd];
	[runCmd free];
	
	/* check for exit */
	if (!clientCount && ([childList count] <= 0) && exeServerOwner->exitWhenDone) {
		NXLogError("[_ExecServerd] Server terminating...");
		_exit(0);
	}
	
}

// -------------------------------------------------------------------------------------
// external notification
// -------------------------------------------------------------------------------------

/* from connection startup notification */
- connection:(NXConnection*)conn didConnect:(NXConnection*)newConn
{
    [[newConn outPort] registerForInvalidationNotification:self];
    return newConn;
}

/* server: mainApp(client) connection went bad (client died?) */
- senderIsInvalid:sender
{

	/* decrement client count */
	clientCount--;
	
	/* terminate now if no children */
	if (!clientCount && ([childList count] <= 0)) {
		NXLogError("[_ExecServerd] Server terminating...");
		_exit(0);
	}
	
    return self;
	
}

@end

// *************************************************************************************
// *************************************************************************************
// ExecServer implementation
// *************************************************************************************
// *************************************************************************************

/* print already running error */
#define IS_RUNNING	(isRunning? _alreadyRunning(self,_cmd) : 0) 
static int _alreadyRunning(id self, SEL _cmd)
{
	NXLogError("[ExecServer] Attempt to set attribute after server has started");
	return 1;
}

@implementation ExecServer

// -------------------------------------------------------------------------------------
// ExecServer class methods
// -------------------------------------------------------------------------------------

/* return error code description */
typedef struct {
	int					err;
	char				*desc;
} _shellError_t;
static _shellError_t	_shellErrors[] = {
	{ RSRV_ABORTED,		"Command Aborted" },
	{ RSRV_BADPASSWD,	"Invalid User Password" },
	{ RSRV_BADUSER,		"Invalid User Name" },
	{ RSRV_BADGID,		"setgid() Error: Permission Denied" },
	{ RSRV_BADUID,		"setuid() Error: Permission Denied" },
	{ RSRV_BADINIT,		"initgroups() Error: Permission Denied" },
	{ RSRV_EXEC,		"Unable to Execute Command" },
	{ RSRV_RSH,			"Remote rsh exec() failed" },
	{ RSRV_FORK,		"Unable to Create New Process" },
	{ RSRV_UNKNOWN,		"Unknown Error" },
	{ RSRV_UNDEF,		"Undefined target/process" },
	{ RSRV_SUCCESS,		"Command Completed Successfully" },	// must be last
	{ 0, 				(char*)nil }
};
static char *_errorDesc(int err)
{
	_shellError_t	*e;
	for (e = _shellErrors; e->desc && (err != e->err); e++);
	return e->desc;
}
+ (char*)errorDesc:(int)err
{
	return _errorDesc(err);
}

/* cache uids */
+ (void)_cacheUid
{
	struct passwd			*pw;
	extern uid_t			exeUserUid; // login user uid
	extern uid_t			exeRootUid; // root user uid
	extern struct passwd	*exeGetpwnam(const char *user);
	
	/* get 'root' uid */
	if (!(pw = exeGetpwnam("root"))) {
		NXLogError("[ExecServer] exeGetpwnam(\"root\") failed");
		exit(1);
	}
	exeRootUid = pw->pw_uid;

	/* get login uid */
	if (!(pw = exeGetpwnam((char*)nil))) {
		NXLogError("[ExecServer] Unknown login user name");
		exit(1);
	}
	exeUserUid = pw->pw_uid;
	
}

// -------------------------------------------------------------------------------------
// ExecServer initialization
// -------------------------------------------------------------------------------------

/* ExecServer initialization */
- init
{
	char	localHostName[MAXHOSTNAMELEN + 1];
	[super init];
	isRunning = NO;
	exitWhenDone = YES;
	rootServer = (id)nil;
	methodDelegate = (id)nil;
	memset(mainAppPath, 0, sizeof(mainAppPath));
	memset(mainAppHost, 0, sizeof(mainAppHost));
	memset(mainAppServerName, 0, sizeof(mainAppServerName));
	memset(remoteHost, 0, sizeof(remoteHost));
	gethostname(localHostName, sizeof(localHostName));
	sprintf(remoteServerName, "ExecServer_%s_%d", localHostName, getpid());
	memset(serverCommandName, 0, sizeof(serverCommandName));
	return self;
}

- free
{
	// not yet implemented
	return [super free];
}

// -------------------------------------------------------------------------------------
// ExecServer attributes
// -------------------------------------------------------------------------------------

/* set exit when done flag (NOTE: MUST be called BEFORE startServer!) */
- setExitWhenDone:(BOOL)flag
{
	exitWhenDone = flag;
	return self;
}

/* set method delegate class (NOTE: MUST be called BEFORE startServer!) */
- setMethodDelegate:classId
{
	if (IS_RUNNING) return (id)nil;
	methodDelegate = (classId == [classId class])? classId : (id)nil;
	return methodDelegate;
}

/* set Main app path */
- setMainAppPath:(const char*)appPath
{
	if (IS_RUNNING) return (id)nil;
	if (appPath) strcpy(mainAppPath, appPath);
	else *mainAppPath = 0;
	return self;
}

/* set remote server name used in environment (NOTE: MUST be called BEFORE startServer!) */
- setMainAppServerName:(const char*)servName host:(const char*)hostName
{
	if (IS_RUNNING) return (id)nil;
	if (!servName || !*servName) *mainAppServerName = 0;
	else {
		strncpy(mainAppServerName, servName, sizeof(mainAppServerName));
		if (!hostName || !*hostName) *mainAppHost = 0;
		else strncpy(mainAppHost, hostName, sizeof(mainAppHost));
	}
	return self;
}

/* return main app server name */
- (const char*)mainAppServerName
{
	return mainAppServerName;
}

/* return main app host name */
- (const char*)mainAppHost
{
	return mainAppHost;
}

/* set remote host name (NOTE: MUST be called BEFORE startServer!) */
- setRemoteHost:(const char*)hostName
{
	if (IS_RUNNING) return (id)nil;
	if (!hostName) *remoteHost = 0;
	else strncpy(remoteHost, hostName, sizeof(remoteHost));
	return self;
}

/* return remote server name */
- (const char*)remoteHost
{
	return remoteHost;
}

/* set name of remote ExecServer (NOTE: MUST be called BEFORE startServer!) */
- setRemoteServerName:(const char*)serverName
{
	if (IS_RUNNING) return (id)nil;
	strncpy(remoteServerName, serverName, MAXHOSTNAMELEN);
	return self;
}

/* return remote runServer name */
- (const char*)remoteServerName
{
	return remoteServerName;
}

/* set RemoteExecServer command path/name (NOTE: MUST be called BEFORE startServer!) */
- setServerCommandName:(const char*)cmdPath
{
	if (IS_RUNNING) return (id)nil;
	if (!cmdPath) *serverCommandName = 0;
	else {
		struct stat	st;
		if (stat((char*)cmdPath,&st)) return (id)nil;
		strncpy(serverCommandName, cmdPath, sizeof(serverCommandName));
	}
	return self;
}

// -------------------------------------------------------------------------------------
// ExecServer startup

/* ExecServer mainline (does not return) */
- (void)_runServer
{
	_ExecServerd			*rootId;
	NXConnection			*connection;
	extern uid_t			exeRootUid; // root user uid
	extern struct passwd	*exeGetpwuid(uid_t uid);

	/* (re)cache uids */
	[_ExecServerd setExecServerOwner:self];
	[ExecServer _cacheUid];
		
	/* set process group */
	setpgrp(getpid(), getpid());
		
	/* init root process */
	if (exeRootUid == geteuid()) {
		int err = 0;
		struct passwd *pw;
		if (!(pw = exeGetpwuid(exeRootUid))) err = RSRV_BADUSER;
		else if (setgid(pw->pw_gid) < 0) err = RSRV_BADGID;
		else if (initgroups(pw->pw_name, pw->pw_gid)) err = RSRV_BADINIT;
		else if (setuid(exeRootUid) < 0) err = RSRV_BADUID;
		if (err) {
			NXLogError("[ExecServer] Server User 'root': %s", _errorDesc(err));
			_exit(1);
		}
	}
		
	/* start server */
//	NXLogError("[ExecServer] starting %s", remoteServerName);
	rootId = [[_ExecServerd alloc] init];
	connection = [NXConnection registerRoot:rootId withName:remoteServerName];
	rootId->serverName = STRCOPY(remoteServerName);
	rootId->server = (id)[NXConnection connectToPort:[connection inPort]];
	[(NXProxy*)rootId->server setProtocolForProxy:@protocol(RootInternal)];
	[NXPort worryAboutPortInvalidation];
	[connection registerForInvalidationNotification:rootId];
	[connection setDelegate:rootId];
	[connection run];
	_exit(0);

}

/* connect to server */
- _connectToServer:(int)tries
{
	
	/* establish connection to server, retry until successful */
	if (!rootServer) {
		int i;
		for (i = tries;;) {
			rootServer = (_ExecServerd*)[NXConnection connectToName:remoteServerName
				onHost:(*remoteHost?remoteHost:(char*)nil)];
			if (rootServer || (--i <= 0)) break;
			sleep(1);
		}
	}
	
	/* return connection */
	return (_ExecServerd*)rootServer;
	
}

/* start root server (MUST be called only ONCE at the beginning of the application) */
- startServer
{
	NXConnection	*connection;
	int				err;
	extern int		exeSwitchToUser(const char *user, const char *pass);

	/* check for already running */
	if (isRunning) return self;

	/* set timeout defaults */
	[NXConnection setDefaultTimeout:-1];

	/* (re)cache uids */
	[ExecServer _cacheUid];

	/* start up server */
	if (*remoteHost) {
	
		if (![self _connectToServer:1]) {
		
			/* start ExecServer process */
			if ((rootChild = forkCHILD) == 0) {
				char cmd[2048], *c = cmd;
				setpgrp(getpid(), getpid());
				sprintf(c, "%s -r %s", serverCommandName, remoteServerName);
				c += strlen(c);
				if (exitWhenDone) {
					sprintf(c, " -e");
					c += strlen(c);
				}
				if (*mainAppPath) {
					sprintf(c, " -c %s", mainAppPath);
					c += strlen(c);
				}
				if (*mainAppServerName) {
					sprintf(c, " -m %s", mainAppServerName);
					c += strlen(c);
					if (*mainAppHost) {
						sprintf(c, " %s", mainAppHost);
						c += strlen(c);
					}
				}
				execl("/usr/ucb/rsh", "/usr/ucb/rsh", remoteHost, "-n", cmd, NULL);
				NXLogError("[ExecServer] %s", _errorDesc(RSRV_RSH));
				exit(1);	// child process termination error
			}
	
			/* connect to server and return */
			if (![self _connectToServer:12]) {
				NXLogError("[ExecServer] Cannot connect to server '%s' on '%s'",
					remoteServerName, remoteHost);
				killpg(rootChild, SIGKILL);
				kill(rootChild, SIGKILL);
				return (id)nil;
			}
			
		} else {
		
			NXLogError("[ExecServer] Connected to existing server '%s' on '%s'",		
					remoteServerName, remoteHost);

		}
	
	} else {
	
		/* start ExecServer process */
		if ((rootChild = forkCHILD) == 0) {
			setpgrp(getpid(), getpid());
			[self _runServer];
			exit(1);	// child process termination error
		}
	
		/* connect to server and return */
		if (![self _connectToServer:7]) {
			NXLogError("[ExecServer] Cannot connect to local server '%s'", remoteServerName);
			killpg(rootChild, SIGKILL);
			kill(rootChild, SIGKILL);
			return (id)nil;
		}
	
	}
	
	/* connection successful */
	isRunning = YES;
	
	/* switch to normal login user (password not required) */
	if (err = exeSwitchToUser((char*)nil, (char*)nil)) {
		NXLogError("[ExecServer] login user: %s", _errorDesc(err));
		return (id)nil;
	}

	/* initialize server connection */
	connection = [(NXProxy*)rootServer connectionForProxy];
	[(NXProxy*)rootServer setProtocolForProxy:@protocol(RunService)];
	[connection registerForInvalidationNotification:self];
	[connection runFromAppKit];
	[(_ExecServerd*)rootServer _pingServer];
	
	/* return successful */
	return self;
	
}

// -------------------------------------------------------------------------------------
// password check
// -------------------------------------------------------------------------------------

/* check user password flag */
- (BOOL)needUserPassword:(const char*)user
{
	struct passwd			*pw;
	extern uid_t			exeUserUid; // login user uid
	extern struct passwd	*exeGetpwnam(const char *user);
	if (!(pw = exeGetpwnam(user))) return YES;	// invalid user
	if (pw->pw_uid == exeUserUid) return NO;
	return YES;
}

// -------------------------------------------------------------------------------------
// ExecServer services
// -------------------------------------------------------------------------------------

- (BOOL)isRunningAsRoot
{
	BOOL	isRoot;
	if (!rootServer) return NO;
NX_DURING
	isRoot = [(_ExecServerd*)rootServer _isRunningAsRoot];
NX_HANDLER
	isRoot = NO;
NX_ENDHANDLER
	return isRoot;
}

/* issue run command */
- (execHandle_t)runCommand:(const char*)cmd
		withUser:(const char*)userName:(const char*)password
		forClient:(id <RemoteClient>)client
		killOnError:(BOOL)killOnError
{
	execHandle_t	runId;
	if (!rootServer) return (execHandle_t)nil;
NX_DURING
	runId = [(_ExecServerd*)rootServer _runCommand:cmd user:userName:password
				client:client kill:killOnError];
NX_HANDLER
	runId = (execHandle_t)nil;
NX_ENDHANDLER
	return runId;
}

/* issue run command */
- (execHandle_t)runCommand:(const char*)cmd
		forClient:(id <RemoteClient>)client
		killOnError:(BOOL)killOnError
{
	return [self runCommand:cmd withUser:(char*)nil:(char*)nil
		forClient:client killOnError:killOnError];
}

/* terminate command */
- terminateCommand:(execHandle_t)runId
{
	id	rtn = self;
	if (!rootServer) return (id)nil;
NX_DURING    
	[(_ExecServerd*)rootServer _terminateCommand:(execHandle_t)runId];
NX_HANDLER
	rtn = (id)nil;
NX_ENDHANDLER   	
	return rtn;
}

/* kill command (unmaskable) */
- killCommand:(execHandle_t)runId
{
	id	rtn = self;
	if (!rootServer) return (id)nil;
NX_DURING    
	[(_ExecServerd*)rootServer _killCommand:(execHandle_t)runId];
NX_HANDLER
	rtn = (id)nil;
NX_ENDHANDLER   	
	return rtn;
}

/* returns true if command handle is still valid */
- (BOOL)commandIsActive:(execHandle_t)runId
{
	BOOL	rtn = NO;
	if (!rootServer) return NO;
NX_DURING    
	rtn = [(_ExecServerd*)rootServer _childIsActive:(execHandle_t)runId];
NX_HANDLER
	rtn = NO;
NX_ENDHANDLER
	return rtn;
}

/* shut down _ExecServerd */
- shutDownServer
{
	id	rtn = self;
	if (!rootServer) return self;
NX_DURING    
	[(_ExecServerd*)rootServer _shutDownServer];
NX_HANDLER
	rtn = (id)nil;
NX_ENDHANDLER
	return rtn;
}

// -------------------------------------------------------------------------------------

/* perform method from server */
- (int)perform:(SEL)method withArg:(const char*)arg
		withUser:(const char*)userName:(const char*)password;
{
	int			rtn;
	if (!rootServer) return RSRV_UNDEF;
NX_DURING
	rtn = (int)[(_ExecServerd*)rootServer _uperform:method withArg:arg
			user:userName:password];
NX_HANDLER
	rtn = RSRV_UNKNOWN;
NX_ENDHANDLER
	return rtn;
}

// -------------------------------------------------------------------------------------
// failure notification
// -------------------------------------------------------------------------------------

/* mainApp: connection to server went bad (server died?) */
- senderIsInvalid:sender
{
    rootServer = (id)nil;	// ??????
	NXLogError("[ExecServer] Server port went bad!");
	_exit(1);
    return self;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.