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.