//*****************************************************************************
//
//	Coordinator.m  
//		
//		NXApp delegate, central control object for GateKeeper 
// 
//			by	Felipe A. Rodriguez		
//
//	The base for this file was:
//	
//			Coordinator.m
//			by Joe Freeman, David LaVallee
//			Subprocess Example, Release 2.0
//			NeXT Computer, Inc.
//
//	This code is supplied "as is" the author makes no warranty as to its 
//	suitability for any purpose.  This code is free and may be distributed 
//	in accordance with the terms of the:
//		
//			GNU GENERAL PUBLIC LICENSE
//			Version 2, June 1991
//			copyright (C) 1989, 1991 Free Software Foundation, Inc.
// 			675 Mass Ave, Cambridge, MA 02139, USA
//
//*****************************************************************************

#import "GKdefs.h"
#import "Animator.h"
#import "Parse.h"
#import "OptionsEditor.h"
#import "options.h"
#import "IconView.h"
#import "ToolBar.h"
#import "DOserver.h"
#import "Timer.h"
#import "InactivityTimer.h"
#import "Coordinator.h"
#import "CommandScroll.h"
#import "Subprocess.h"
#import "GateDocEditor.h"
#import "HLRecord.h"
#import "HLDelegate.h"

#import <appkit/nextstd.h>
#import <appkit/Application.h>
#import <dpsclient/dpsNeXT.h>
#import <objc/NXStringTable.h>
#import <defaults/defaults.h>
#import <bsd/c.h>
#import <sys/dir.h>  /* POSIX applications #include <dirent.h> */




@interface Coordinator(Private)

		// preferences controls 
	extern char *cList[];
	extern char *rfList[];
	extern char *rsList[];

		// private functions 
- miscPrep2;

		// dial on demand server 
#import <mach/mach_error.h>
#import <servers/netname.h>
#import "nsRPC_types.h"


any_t server_loop(any_t port);

					// defined by MiG: 
boolean_t nsRPC_server(msg_header_t *in, msg_header_t *out);

				// from Request types in nsRPCUser.c 
struct message 
		{                
		msg_header_t    head;        /* standard header field */
		msg_type_t      arg1_type;   /* first arg type */
		int             arg1;        /* first arg */
		msg_type_t      arg2_type;   /* second arg type */
		int             arg2;        /* second arg */
		};

@end
@implementation Coordinator(Private)

//*****************************************************************************
//
// 		misc to do before coordinator starts a subprocess 
//
//*****************************************************************************

- miscPrep2
{
int i = 0;

	[[theAnimator startAnimTimer] setStandbyGate:NO];

	[linkMenuButton setEnabled:NO];				// disable link button
	[dial setEnabled:NO];						// disable dial button
	[theOpenButton setEnabled:NO];				// disable the open doc button
	while(i < 3)					
		{										// erase captured str's array
		if(capturedStr[i])						
			free(capturedStr[i]);
		capturedStr[i++] = NULL;				// blank, for next invocation
		}
	linkStg = 0;	// prevent stages in link process from getting called twice
									
					// setup and load our keyword's hashtable
	if(strHashTable)
		free(strHashTable);
	strHashTable = [[HashTable alloc] initKeyDesc:"*" valueDesc:"i"];
	for(i = 0; i < 2; i++)
		{
		numEntries[i] = i;
		[strHashTable insertKey:NXGetDefaultValue([NXApp appName], cList[i+1]) 
													value:&numEntries[i]];
		}
	for(i = 0; i < 4; i++)
		{
		numEntries[i + 2] = i + 2;
		[strHashTable insertKey:NXGetDefaultValue([NXApp appName], rfList[i]) 
													value:&numEntries[i + 2]];
		}
	numEntries[6] = 6;
	[strHashTable insertKey:"address" value:&numEntries[6]];
	numEntries[7] = 7;
	[strHashTable insertKey:"remote" value:&numEntries[7]];

	return self;
}

@end
@implementation Coordinator

//************************ Subprocess Delegation ****************************** //*****************************************************************************
//
// 		recieves output from subprocess
//		canonicalizes output stream into lines
//
//*****************************************************************************
 
- subprocessOutput:(char *)buffer
{
static char strbuf[132];
static int strpos = 0;

	while(*buffer)		
		{			// cr/lf delimit lines and we do not exceed strbuf size		
		if((*buffer != '\n' && *buffer != '\r') && strpos < 128)	
			{
			strbuf[strpos++] = *buffer++; 
			strbuf[strpos] = '\0';
			}
		else 
			{
			while((*buffer == '\n' || *buffer == '\r') && (strpos < 130))
				strbuf[strpos++] = *buffer++;
			strbuf[strpos] = '\0';
			[self analyzeBuffer:strbuf];
			strpos = 0; 
			}
		}
		
    return self;
}
//*****************************************************************************
//
//		- captures certain keywords as status and info markers
//		- calls gotIt, displayLinkStatus methods when link is established
//		- sends data stream to diagnostics window and comments to status
//
//*****************************************************************************
 
- analyzeBuffer:(char *)buffer
{
char *str, delim[] = {" \n\r^/"}, delim2[] = {"\n\r^"}, *argString = NULL; 
int *cntr;	

	if(argString != NULL)
		free(argString);
	argString = NXCopyStringBuffer(buffer);
	[commandView appendString:buffer];				// send buffer to diag win
	str = strtok(argString, delim);					// Parse string and test it
	while(str != NULL)								
		{											
		if(*str == '#')			// comments will be displayed in status panel
			{
			if(str = strtok(NULL, delim2))
				{
				if(strlen(str) > 3)
					[statusView setStringValue:[self localString:str]];
				}
			}
		else
			{
			if ([strHashTable isKey:str] == YES) 	// if str is in hash table
				{									// we have caught a keyword
				cntr = [strHashTable valueForKey:str];
				switch(*cntr)
					{							
					case 2: 						// redial?
					case 3: 						// Persistent connect
					case 4:							// Fail connect
					case 5:							// LCP terminate
						if((strcmp(NXGetDefaultValue([NXApp appName], 
									rsList[*cntr - 2]),"YES") == 0) // sw "ON"?	
											&& (lastCall[0] != '\0'))
							[self perform:@selector(redial:) with:self 
								afterDelay:backOffDelay cancelPrevious:YES];
						else
							[self UnLink:nil];		// if redial off, unlink
						break;
					
					case 0:  						// baud str caught
						if((str = strtok(NULL, delim)) &&
													(capturedStr[0] == NULL))
							[self connectedAt:str];
						break;
		
					case 1:  							// the link is up
						if(linkStg < 4)					// if ivar is >, ppp
							{
							[self gotIt];				// may be sending
							linkStg = 4;				// keyword twice
							}
						break;								
	
					case 6:							// local address str caught
						if((str = strtok(NULL, delim)) && (linkStg < 5))	
							{		
							capturedStr[1] = NXCopyStringBuffer(str);
							linkStg = 5;	
							}
						break;
											
					case 7:  						// display addresses
						if(linkStg < 6)
							{
							if((str = strtok(NULL, delim)) != NULL)
								{
								if((str = strtok(NULL, delim)) != NULL)
									{
									if((str = strtok(NULL, delim)) != NULL)
										{
										capturedStr[2] = 
													NXCopyStringBuffer(str);
										[self displayLinkStatus];
										}
									}
								}
							}
						break;
		
					default:	 		
						break;
					}
				}
			}
		str = strtok(NULL, delim);
		}
		
    return self;
}
//************************************************************************
//
// 		Called when subprocess has terminated
//
//************************************************************************
 
- subprocessDone
{
	[theTimer stopTimer];
	if(theGateServer)
		[theGateServer free];			
	theGateServer = nil;
	[theSubprocess free];
	theSubprocess = nil;
	if(userWantsTermination)	// end only if user chose too (not subP fail)
		[NXApp terminate:self];
	else											// if not exiting	
		[theAnimator startAnimTimer];				// cycle thru animation
	
    return self;
}
//*****************************************************************************
//
// 		return our string table (contains key->value localization pairs) 
//
//*****************************************************************************

- stringTable;
{
    return stringTable;
}
//*****************************************************************************
//
// 	localize error messages using stringTable as appropriate, displays
// 	the message in an alert panel
//
//*****************************************************************************

- showAlert:(const char *)errorString
{
	NXRunAlertPanel(0, [self localString:errorString], 
							[stringTable valueForStringKey:"OK"], NULL, NULL);

    return self;
}
//*****************************************************************************
//
// 	localize string messages using stringTable as appropriate
//
//*****************************************************************************

- (const char *)localString:(const char *)aString
{
const char *returnedString;
    
    if (returnedString = [stringTable valueForStringKey:aString])
    	return returnedString;

    return aString;
}
//*****************************************************************************
//
// 		Called when link is established
//
//*****************************************************************************
 
- gotIt
{
	[theAnimator removeTimedEntry];
							// ppp is up, play snd for me
	if(strcmp(NXGetDefaultValue([NXApp appName],"sound"), "YES") == 0) 
		[[[Sound findSoundFor: "majestic"] play: nil] free];
	[theTimer Fire:self];
	if(!onImage)
		onImage = [NXImage findImageNamed:"g4"];
	[theIconView setImage:onImage];
	[statusView setStringValue:[self localString:"pppup"]];
					// if auto hide switch is set wait 5 sec before hiding	
	if(strcmp(NXGetDefaultValue([NXApp appName], "autoHide"),"YES") == 0)	
		[NXApp perform:@selector(hide:) with:self afterDelay:5000 
															cancelPrevious:NO];				

    return self;
}
//*****************************************************************************
//
// 		registers connection speed 
//
//*****************************************************************************

- connectedAt:(const char *)speed  
{
	strncpy(Path, [stringTable valueForStringKey:"connectAt"], MAXPATHLEN);
	strncat(Path, speed, MAXPATHLEN - strlen(Path));
	capturedStr[0] = NXCopyStringBuffer(Path);
	[connectionSpeedField setStringValue:capturedStr[0]];

    return self;
}
//*****************************************************************************
//
// 		displays captured info regarding current ppp session
//
//*****************************************************************************
 
- displayLinkStatus
{
	if(capturedStr[0] != NULL)				// in case we man dialed
		{									// we will not know connect speed
		[commandView appendString:"\n#################################\n"];
		[commandView appendString:"####\n"];
		[commandView appendString:"####  "];
		[commandView appendString:capturedStr[0]];
		}
	else
		[commandView appendString:"\n#################################"];
	[commandView appendString:"\n####\n"];
	[commandView appendString:[stringTable valueForStringKey:"localIP"]];
	[commandView appendString:capturedStr[1]];
	[localIPField setStringValue:capturedStr[1]];
	[commandView appendString:"\n####\n"];
	[commandView appendString:[stringTable valueForStringKey:"remoteIP"]];
	[commandView appendString:capturedStr[2]];
	[commandView appendString:"\n####"];
	[remoteIPField setStringValue:capturedStr[2]];
	[commandView appendString:"\n#################################\n"];
	linkStg = 6;									// ppp link is up, set ivar
	[iTimer pppstats:commandView];					// run pppstats w/view

    return self;
}
//*********************** Application Object Delegation ***********************
//*****************************************************************************
//
// 			setup defaults data base cache before app is init'd
//
//*****************************************************************************

- appWillInit:sender
{
    static NXDefaultsVector myDefaults = {		// setup defaults database
        {DISPLAYD, "YES"},						// display diagnostics window
		{DISPLAYS, "YES"},						// display status window
		{"autoLaunch", "NO"},			
		{"autoHide", "YES"},					// auto hide upon link		
		{"autoRedial", "YES"},			
		{"PersistCon", "YES"},			
		{"FailRedial", "YES"},					// redial if a Fail occurs
		{"LCPterminate", "YES"},			// redial if lcp termination occurs
		{"Fail", "Exit."},			
		{"reDial", "Failed"},			
		{"Persist", "down"},					// persistent connect trigger
		{"LCPterm", "terminated."},				// lcp termination trigger
		{"DispPPP", "YES"},						// display pppstats
		{DISPLAYT, "YES"},						// display toolbar
		{"DoD", "NO"},							// dial on demand
		{USEFIFO, "YES"},						// use a FIFO for IPC w/pppd?
		{"sound", "YES"},						// play sound 
		{AITIMER, "YES"},						// icon displayed online time
		{REDBOFF, "YES"},						// backoff on failed redials?
		{"iTimeout", "Off"},					// inactivity unlink's us
		{"preTimeout", "YES"},					// inactivity unlink's us
		{"iTimeThreshold", "0"},				// inactivity threshold 
		{SELCELL, "0"},							// cell selected in hotList
		{BROWSERHT, "999.0"},					// ht of browser in hotList
		{ADDRESSHT, "999.0"},					// ht of address in hotList
		{FIRSTTIME, "YES"},						// first time thru?
		{"options", "/etc/ppp/options"},			
		{"ip-down", "/etc/ppp/ip-down"},			
		{"ip-up", "/etc/ppp/ip-up"},			
		{"resolv", "/etc/resolv.conf"},			
		{"syslog", "/etc/syslog.conf"},			
		{"messages", "/usr/adm/messages"},			
		{"rc", "/etc/rc.local"},			
		{"remote", "/etc/remote"},			
		{"locFIFO", "/usr/adm/ppp2.2.debug"},				// pref form start
		{"comLine", "/usr/local/bin/pppd -detach"},			
		{HLLIST, "/"},			
		{"BaudStr", "CARRIER"},								// pref form end
		{"LinkUp", "local"},						// Link is up trigger
		{DIALINIT, "ATZ&D0L3"},			// modem init str for manual Dial
		{"dialPrefix", "ATD"},			// modem AT command prefix for dialing
		{MODEMPORT, "cufa"},			// modem port, used in releasing locks
		{"path", "/"},							// path for .Gate documents
		{"lastNumDialed", "8675309"},				// default man dial number
		{"savedTime", "000000000000"},			// save time used this mo
		{"monthTime", "000000000000"},			// save mo,yr of this ses'n
		{"preMonthTime", "000000000000"},		// save previous mo,yr 
        {NULL}};									// make local cache of ddb
    NXRegisterDefaults([NXApp appName], myDefaults);	
 
    return self;
}
//*****************************************************************************
//
// 			standard init
//
//*****************************************************************************

- appDidInit:sender
{
time_t waitTime;			// time to delay

	time(&ltime);								// Get time and place in time_t
	if(strcmp(NXGetDefaultValue([NXApp appName], USEFIFO), "YES") == 0) 
		[self syslogdReset];					// reset the syslogd daemon

							// setup appIcon for animation
	theAnimator = [[Animator alloc] init];		// get appIcon window
	theIconView = [theAnimator iconView];
	timeCell = [theIconView getTextCell]; 	// return the iconView's textCell

    if(strcmp(NXGetDefaultValue([NXApp appName], FIRSTTIME),"YES") == 0) 
		{							// if first time this version was launched
		[self showInfo:self];	
		if(!NXWriteDefault([NXApp appName], FIRSTTIME, "NO"))
			NXRunAlertPanel(0,
					[stringTable valueForStringKey:"ddbWriteError"],
                	[stringTable valueForStringKey:"OK"],
                	NULL,
                	NULL);
		}
	[NXApp loadNibSection:"StatusWindow.nib" owner:self withNames:NO];
	if(strcmp("YES", NXGetDefaultValue([NXApp appName], DISPLAYS)) == 0)
		[statusWin makeKeyAndOrderFront:self];		// show status panel

	[NXApp loadNibSection:"DiagWindow.nib" owner:self withNames:NO];
	[diagWin setFrameUsingName:[diagWin title]];	// remem sz,loc
	[diagWin setFrameAutosaveName:[diagWin title]];
	
	if(strcmp("YES", NXGetDefaultValue([NXApp appName], DISPLAYT)) == 0)	
		[self toolBar:self];						// show toolbar if set

	if(strcmp("YES", NXGetDefaultValue([NXApp appName], AITIMER)) == 0)	
		appIconTime = YES;					// show online time in app icon
	
	if(strcmp("YES", NXGetDefaultValue([NXApp appName], REDBOFF)) == 0)	
		backOffDefault = backOffDelay = BDEF;		// redial delay backoff on

						// setup Inactivity timeout and pppstats controller
	iTimer = [[InactivityTimer allocFromZone:[self zone]] init];

    if(strcmp(NXGetDefaultValue([NXApp appName],"NXAutoLaunch"),"YES") == 0) 
		{						// if app was dock autoLaunched
		waitTime = ltime + 2;	// delay for X sec so that syslogd can sync
		while(ltime < waitTime)	// ltime ivar is set in syslogReset method 
			{time(&ltime);}
		}
	theTimer = [[Timer allocFromZone:[self zone]] init];
   			// If we weren't asked to open any documents at launch time, then 	
			// we were launched by double clicking on the application instead 
			// of a document. If so check whether we should auto-launch w/def's
    if(!NXGetDefaultValue([NXApp appName], "NXOpen") &&
		!NXGetDefaultValue([NXApp appName], "NXOpenTemp") &&
			!NXGetDefaultValue([NXApp appName], "NXServiceLaunch"))
		{
		if(strcmp(NXGetDefaultValue([NXApp appName], "autoLaunch"),"YES") == 0)	
			[self Link:self];	
		}
	[self DialOnDemand];		// prepare for dial on demand if set
					 
    return self;
}
//*****************************************************************************
//
// 		redial 
//
//*****************************************************************************

- redial:sender
{					// only allow a redial if user has not stopped linking
	if((strcmp([[[NXApp mainMenu] findCellWithTag:2] title], 
				[[NXApp delegate] localString:"Disconnect"]) == 0) && 
				[[[NXApp mainMenu] findCellWithTag:2] isEnabled] && (sender))
		{
		[self UnLink:nil];		 
		if([self runScript:"preLink"])
			{				// back off in multiples of 2
			backOffDelay *= 2;
			[self perform:@selector(resetDelay:) with:self 
						afterDelay:(backOffDelay + 1000) cancelPrevious:YES];				
			[self linkWithFile:lastCall];
			}
		}
					 
    return self;
}
//*****************************************************************************
//
// 		reset the backoff delay 
//
//*****************************************************************************

- resetDelay:sender
{							// eliminate any pending redials
	[self perform:@selector(redial:) with:nil afterDelay:0 cancelPrevious:YES];	
  	if ([sender isKindOf:[Matrix class]]) 
		{
		if((BOOL)[[sender selectedCell] intValue])
			backOffDefault = BDEF;				// redial delay backoff on
		else
			backOffDefault = 0;
		}
	backOffDelay = backOffDefault;
					 
    return self;
}
//*****************************************************************************
//
// 		This method is performed whenever a user double-clicks on an icon in
// 		the Workspace Manager representing a Gate program document.  
// 		
// 		Brings up the gate doc editor which can edit or link using the doc
//
//*****************************************************************************

- (int)app:sender openFile:(const char *)path type:(const char *)type 
{
	[theGateDocEditor editGateDoc:path];
				
	return 1;
}
//*****************************************************************************
//
// 		invoked immediately after app is hidden and unhidden respectively
//
//			the following two methods are implemented to produce
//			normal main window/menu app behavior when toolBar is open
//
//******************************************************************************
- appDidHide:sender
{
	[[NXApp mainMenu] close];		// close the main menu  
									
    return self;
}

- appDidUnhide:sender
{
	[[NXApp mainMenu] makeKeyAndOrderFront:self];		 

    return self;
}
//*****************************************************************************
//
// 		Instantantiates the subprocess object which exec's pppd
//
//*****************************************************************************

- linkWithFile:(const char *)path  
{
char commandLine[MAXPATHLEN + 1]; 

	[self miscPrep2];							// misc initialization's
	if(strcmp(NXGetDefaultValue([NXApp appName], DISPLAYD),"YES") == 0)
		[diagWin makeKeyAndOrderFront:self];		// show diagnostics win

	strncpy(commandLine, NXGetDefaultValue([NXApp appName], "comLine"), 
																   MAXPATHLEN);
	commandLine[MAXPATHLEN-1] = 0;
	if(path == NULL)
		{				
		lastCall[0] = '\0';						
		if([hotListDelegate selGateDocOptionsPath] == NULL)
			commandLine[0] = '\0';
		else									// if hotlist points to valid
			{
			strncat(commandLine, " file ", MAXPATHLEN - strlen(commandLine));
			strncat(commandLine, [hotListDelegate selGateDocOptionsPath], 
											MAXPATHLEN - strlen(commandLine));
			strncpy(BPath,[hotListDelegate selGateDocOptionsPath], MAXPATHLEN);			
			[providerField setStringValue:[self localString:"manDial"]];
			}
		}
	else
		{
		strcpy(lastCall, path);			// stores path of last invocation
		if(strstr(path, "GateKeeper.app") == NULL)
			[providerField setStringValue:[self extractName:path]];
		else
			[providerField setStringValue:[self localString:"gkDefault"]];
		[statusView setStringValue:[self localString:"connecting"]];
		if((strlen([[NXBundle mainBundle] directory]) + 45 + 
					(2 * strlen(path)) + strlen(commandLine)) < MAXPATHLEN)	
			{									// if we won't overrun buffer
			strcat(commandLine, " file ");
			strcat(commandLine, path);
			strcat(commandLine, OPTION);	// options file name wrapper
			strcat(commandLine, " connect \"");				
			strcat(commandLine, [[NXBundle mainBundle] directory]);
			strcat(commandLine, "/chat -v -f ");
			strcat(commandLine, path);
			strcat(commandLine, PPPUP);	// pppup file name wrapper
			strcat(commandLine, "\"");
			}
		else
			perror("command line is longer than MAXPATHLEN");
		strncpy(BPath, path , MAXPATHLEN);	
		strncat(BPath, OPTION, MAXPATHLEN - strlen(BPath));	// options 
		}
	if(commandLine[0] != '\0')				// if we have a valid commline 
		{
		[linkMenuButton setEnabled:YES];			// enable unlink button
		[linkMenuButton setTitle:[self localString:"Disconnect"]];
		[linkMenuButton setAction:@selector(UnLink:)];
		[statusButton setTitle:[self localString:"Disconnect"]];
		[statusButton setAction:@selector(UnLink:)];
		if([iTimer posNonInter])
			[commandView appendString:
							"Dial on demand triggered ppp session\n"];
		if([self debugFlag:BPath])
			{				// if debug sw set in options disg commandline
			[commandView appendString:commandLine];
			[commandView appendString:"\n\r"];
			}
		theSubprocess = [[Subprocess alloc] init:commandLine withDelegate:self 	
									andStdErr:YES];
		}
				
	return self;
}
//*****************************************************************************
//
// 		Use terminal to do a manual link 
//
//*****************************************************************************

- Dial:sender  
{
	if((strcmp([linkMenuButton title], [self localString:"Connect"]) == 0) && 
					[linkMenuButton isEnabled] && [self runScript:"preLink"])
		{ 
		[self miscPrep2];						// misc initialization's
		[iTimer setPosNonInter:NO];				// user interactive session
		if(!theGateServer)					// create distributed Obj server
			theGateServer = [[DOserver alloc] init];  		
		[theGateServer setDelegate:self];			
		GateConnection = [NXConnection registerRoot: theGateServer
						withName:"GateKeeperServer"];	// DO name of server
		[GateConnection runFromAppKit];	// listen for DO messages from appKit
		if(![[NXBundle mainBundle] getPath:Path forResource:"MODEM" 
																ofType:NULL])
			[self showAlert:"Error getting path for MODEM file"];
		[[Application workspace] openFile:Path withApplication:"Terminal"];	
		}

    return self;
}
//*****************************************************************************
//
// 		Called: when menu item link is pressed, when DNS triggers us.  
//		Calls app:sender
//
//*****************************************************************************

- Link:sender  
{
	if((strcmp([linkMenuButton title], [self localString:"Connect"]) == 0) && 
					[linkMenuButton isEnabled] && [self runScript:"preLink"])
		{ 
		if(!sender)									// DNS triggered session,
			[iTimer setPosNonInter:YES];			// possibly non-interactive 
		else										
			[iTimer setPosNonInter:NO];				// user interactive session
	[hotListDelegate playLink];	// selected in the HotList to launch pppd
	}			

    return self;
}
//*****************************************************************************
//
// 		Called when menu item Unlink is pressed.  
//
//*****************************************************************************

- UnLink:sender  
{
	if([self runScript:"preUnLink"])
		{ 
		if(theSubprocess && [theSubprocess respondsTo:@selector(terminate:)])
			[theSubprocess terminate:sender];
		[[theAnimator setOpenGate:YES] setStandbyGate:YES];
		[iTimer pppstatsReset];					// disable pppstats
		[timeCell setStringValue:"      "];
		[linkMenuButton setEnabled:YES];			// enable unlink button
		[linkMenuButton setTitle:[self localString:"Connect"]];
		[linkMenuButton setAction:@selector(Link:)];
		[statusButton setTitle:[self localString:"Connect"]];
		[statusButton setAction:@selector(Link:)];
		[theOpenButton setEnabled:YES];			// enable the open doc button
		[dial setEnabled:YES];					// enable dial button
		[statusView setStringValue:[self localString:"pppdn"]];
		[connectionSpeedField setStringValue:"                          "];
		[timeField setStringValue:"        "];
		[providerField setStringValue:"                       "];	
		[localIPField setStringValue:"                "];
		[remoteIPField setStringValue:"                "];
		[self namedDod];					// kill/restart named to dump cache
		}
	if(sender)
		[self resetDelay:self];		

    return self;
}
//*****************************************************************************
//
// 		run the preLink or preUnLink scripts, proceed if exit status true 
//
//*****************************************************************************

- runScript:(const char *)type  
{
id exitStatus = self;
int fd;

	strncpy(Path, [[NXBundle mainBundle] directory], MAXPATHLEN);
	strncat(Path, "/", MAXPATHLEN - strlen(Path));
	strncat(Path, type, MAXPATHLEN - strlen(Path));
	if(fd = open(Path, O_RDONLY) != -1)			//	if script exists
		{
		close(fd);
		if(system(Path) != 0)					// exec script, test exit
			exitStatus = nil;
		strcpy(Path, type);
		if(!exitStatus)
			strcat(Path," script exit status prevents requested action\n");
		else
			strcat(Path, " script executed sucessfully\n");
		[commandView appendStringUseFixedFont:Path];
		}

    return exitStatus;
}
//*****************************************************************************
//
// 		we can always edit another gate doc
//
//*****************************************************************************

- (BOOL)appAcceptsAnotherFile:sender 
{
    return YES;
}
//****************************************************************************
//
//		termination is allowed only if the user has selected it, subprocess 
//		failure should not cause GateKeeper to terminate
//
//****************************************************************************

- terminate:sender
{
	userWantsTermination = YES;

    return self;
}
//****************************************************************************
//
//		termination is imminent 
//
//****************************************************************************

- appWillTerminate:sender
{
    [theSubprocess terminate:sender];
	[theAnimator removeTimedEntry];
	[self namedReset:"/usr/etc/named"];		// replace our named with system's
	[hotListDelegate appWillTerminate];
	
    return self;
}
//*****************************************************************************
//
// 		show the info panel 
//
//*****************************************************************************

- showInfo:sender
{
    if(!infoPanel)
		[NXApp loadNibSection:"InfoPanel.nib" owner:self withNames:NO];
    [infoPanel center];
    [infoPanel makeKeyAndOrderFront:self];
	
    return self;
}
//****************************************************************************
//
//		show preferences panel   
//
//****************************************************************************

- preferences:sender
{
	if(!prefPanel) 
		[NXApp loadNibSection:"Preferences.nib" owner:self withNames:NO];
	[prefPanel makeKeyAndOrderFront:self];
		
    return self;
}
//************************************************************************
//
//		show NXHelpPanel 
//
//************************************************************************

- showHelpPanel:sender 
{
    if(!helpPanel)
		helpPanel = [NXHelpPanel new];
    [helpPanel display];
    [helpPanel makeKeyAndOrderFront:self];
	
    return self;
}
//************************************************************************
//
//		display the Status panel 
//
//************************************************************************

- showStatusPanel:sender 
{
	if(!NXWriteDefault([NXApp appName], DISPLAYS, "YES"))
		[self showAlert:"ddbWriteError"];

    return [statusWin makeKeyAndOrderFront:self];
}
//*****************************************************************************
//
// 		display the Diagnostics window 
//
//*****************************************************************************

- showDiagWin:sender
{
	if(!NXWriteDefault([NXApp appName], DISPLAYD, "YES"))
		[self showAlert:"ddbWriteError"];

	return [diagWin makeKeyAndOrderFront:self];
}
//************************************************************************
//
//		show the timer 
//
//************************************************************************

- showTimerPanel:sender 
{
    return [theTimer showTimerPanel:self];
}
//*****************************************************************************
//
// 		called by timer in order to pass us appIcon time string 
//
//		also used in determining inactivity timeout (since this is called
//		once per minute during ppp sessions).
//
//*****************************************************************************

- showMenuTimer:(char *)buffer 
{
	if(appIconTime)
		[timeCell setStringValue:buffer];
	[timeField setStringValue:buffer];
	[theIconView setImage:onImage];
	[iTimer inactivityTimeout];

	return self; 
}
//*****************************************************************************
//
// 		set inactivity timer's timeout Ivar and write its value to ddb 
//
//*****************************************************************************

- setTimeout:(int)minTillTimeout 
{
	[iTimer setTimeout:minTillTimeout];

	return self; 
}
//*****************************************************************************
//
// 		enable/disable display of online time in app icon 
//
//*****************************************************************************

- setAppIconTimer:(BOOL)onOff 
{
	appIconTime = onOff;

	return self; 
}
//*****************************************************************************
//
// 		return a pointer to the hotlist window's delegate 
//
//*****************************************************************************

- hotListDelegate 
{
	return hotListDelegate; 
}
//*****************************************************************************
//
// 		tell browser to update its data from its delegate 
//
//*****************************************************************************

- updateBrowser:sender
{
	return [hotListDelegate loadBrowser];
}
//************************************************************************
//
//		extracts the Gate doc name from its path 
//
//************************************************************************

- (const char *)extractName:(const char *)aPath
{
char *ptr;

	strcpy(Path, aPath);
	ptr = Path + strlen(Path) - 1;		
	while(*ptr != '.' && *ptr)
		ptr--;
	*ptr-- = '\0';
	while(*ptr != '/' && *ptr)
		ptr--;
	++ptr;

    return ptr;
}
//************************************************************************
//
//		returns whether a Gate doc may be opened or edited by real user
//
//************************************************************************

- readable:(const char *)nameOfFile
{
FILE *fp;

	if ((fp = fopen(nameOfFile, "r+")) == NULL)
		[self showAlert:"Unable to open Gate doc"];
	else
		{
		if(readable(fileno(fp)))
			{
			fclose(fp);
  			return self;				// we have permission
			}
		[self showAlert:"Access to Gate doc denied"];
		fclose(fp);
		}

   	return nil;						// we do not have permission
}
//*****************************************************************************
//
// 		returns the Gate doc options editor
//
//*****************************************************************************

- optionsEditor
{
	if(!theOptionsEditor)
		theOptionsEditor = [[OptionsEditor alloc] init];

    return theOptionsEditor;
}
//*****************************************************************************
//
// 		returns the state of the debug flag in an options file
//
//*****************************************************************************

- (BOOL)debugFlag:(const char *)optionFile
{
  	return [[[self optionsEditor] parseOptions:optionFile] debug];
}
//*****************************************************************************
//
// 			show the Tool Bar
//
//*****************************************************************************

- toolBar:sender
{
	if(!NXWriteDefault([NXApp appName], DISPLAYT, "YES"))
		[self showAlert:"ddbWriteError"];
	if(!toolBar) 
		[NXApp loadNibSection:"ToolBar.nib" owner:self withNames:NO];
	[toolBar makeKeyAndOrderFront:self];

    return toolBar;
}
//************************************************************************
//
// 		return the app icons view
//
//************************************************************************
 
- appIconView
{
	return 	theIconView;					// enable link button
}
//************************************************************************
//
// 		called by DO server when remote manual Dial Tool's ports are 
//		invalidated
//
//************************************************************************
 
- DOFinished
{
	return 	[self UnLink:self];					// enable link button
}
//************************************************************************
//
// 	free simply gets rid of everything we created
// 	This is how nice objects clean up.
//
//************************************************************************

- free
{
	[timeCell free];
	if(onImage)
		[onImage free];
	if(toolBar) 
		[toolBar free];
	if(iTimer) 
		[iTimer free];

    return [super free];
}
//*****************************************************************************
//
// 		Attempt to reset syslogd daemon.  Failing that, try starting it. 
//
//*****************************************************************************

- syslogdReset
{
FILE *ff; 
int pid;
					
    if((ff = fopen( "/etc/syslog.pid", "r")) != NULL)
		{
    	if( fscanf( ff, "%d", &pid) < 1)
        	perror("Unable to read pid from etc/syslog.pid\n");
		else
			{
			if( kill( pid, SIGHUP) != -1) 		// tell syslogd to reconfigure
				{
				fclose( ff);

				return self;					// report success
				}
			else			// if app was not dock autoLaunched
    			if(strcmp(NXGetDefaultValue(
								[NXApp appName],"NXAutoLaunch"),"NO") == 0) 
					perror("error reconfiguring syslogd");
			}
    	}
	else
		perror("Unable to open /etc/syslog.pid\n");
	
    return [self syslogdRun];		// failed to reconfig so restart syslogd
}
//*****************************************************************************
//
// 		Terminante and then run the syslogd daemon 
//
//*****************************************************************************

- syslogdRun
{
int pid;
FILE *ff; 
					
    if((ff = fopen( "/etc/syslog.pid", "r")) != NULL)
		{
    	if( fscanf( ff, "%d", &pid) < 1)
        	perror("Unable to read pid from etc/syslog.pid\n");
		else
			if( kill( pid, SIGTERM) == -1)
				{
    			if(strcmp(NXGetDefaultValue(	
								[NXApp appName],"NXAutoLaunch"),"NO") == 0) 
					perror("error killing syslogd");
				}
		}
	switch (pid = vfork())	// create new process 
		{							
		case -1:			// error
		
			perror("Error during vfork syslogdReset.");
			return self;
			
		case 0:  			// child -- Vfork returns 0 in the child's context

			execl("/usr/etc/syslogd", "syslogd", 0);
			perror("vfork error restarting syslogd"); 
			exit(1);

		default:			// parent --  vfork returns pid of the child  		
			
			break;	
		}		
	
    return self;
}
//*****************************************************************************
//
// 		prepare ourself to be triggered by named when dial on demand is
//		necessary 
//
//*****************************************************************************

- DialOnDemand					
{
port_t	server_port;
kern_return_t	r;

 							// if dial on demand is "ON"
	if(strcmp("YES", NXGetDefaultValue([NXApp appName], "DoD")) == 0)	
		{			
							// allocate a port for this task, ret in arg 2
		r = port_allocate(task_self(), &server_port);
		if (r != KERN_SUCCESS) 
			{
			mach_error("port_allocate failed", r);
			exit(1);
			}				// Register with the Network Name Server.
		r = netname_check_in(name_server_port, GK_DNS_SERVER, PORT_NULL, 
																server_port);
		if (r != KERN_SUCCESS) 
			{
			mach_error("netname_check_in failed", r);
			exit(1);
			}
						// create dial on demand server thread and detach it
			cthread_detach(cthread_fork((cthread_fn_t)server_loop, 
														(any_t)server_port));
		[self namedDod];	// replace named with dial on demand trigger named
		}
	
    return self;
}
//*****************************************************************************
//
// 		Attempt to kill and restart named daemon.  Resets RR's cache.
//
//*****************************************************************************

- namedReset:(const char *)buffer
{
FILE *ff; 
int pid;
 							// if dial on demand is "ON"
	if(strcmp("YES", NXGetDefaultValue([NXApp appName], "DoD")) == 0)
		{	
		if((ff = fopen( "/etc/named.pid", "r")) != NULL)
			{
			if( fscanf( ff, "%d", &pid) < 1)
				perror("Unable to read pid from etc/named.pid\n");
			else
				{
				if( kill( pid, SIGTERM) == -1) 			// kill named
					perror("error killing named");
				}
			}
		else
			perror("Unable to open /etc/named.pid\n");
		fclose( ff);
		system(buffer);				// start GateKeeper named 
		}
	
    return self;
}
//*****************************************************************************
//
// 		replace the system named with our own which will inform us when a
//		dial on demand is necessary 
//
//*****************************************************************************

- namedDod
{					
	if(!strcpy(Path, [[NXBundle mainBundle] directory]))	// app's home dir
		[self showAlert:"Error getting mainbundle path"];
	else
		{
		strcat(Path,"/named");
		[self namedReset:Path];		// kill named and replace with our own
		}
	
    return self;
}
//*****************************************************************************
//
// 		search /etc/syslog.conf to find the location of the named pipe used 
//		in recieving the output from pppd/syslog 
//
//*****************************************************************************

- fifo
{
static char del1[] = {". =:|\t\r\n"}, del2[] = {" \t\r\n"};
char *fifoName = NULL;

	if(!Parser)									
		Parser = [[Parse alloc] init];			// create parser to find port
	[[Parser setKey1:"local2"] setKey2:"debug"];		// set search pattern  
	[[Parser setDelim1:del1] setDelim2:del2];			// and delimiters

	if(fifoName = [Parser parseFile:"/etc/syslog.conf"])
		{
		if(!NXWriteDefault([NXApp appName], "locFIFO", fifoName))
			NXRunAlertPanel(0,
					[stringTable valueForStringKey:"ddbWriteError"],
                	[stringTable valueForStringKey:"OK"],
                	NULL,
                	NULL);
		free(fifoName);
		}
	else
		[self showAlert:"FIFO Path Error"];

    return self;
}
//*****************************************************************************
//
// 		checks the mail queue for mail awaiting delivery 
//
//*****************************************************************************

- (BOOL)mailInQueue 
{
int	eCntr = 0;
struct direct *dirp;
DIR *dp;
BOOL mail = NO;

    if ((dp = opendir("/usr/spool/mqueue")) != NULL)
		{
		while ((dirp = readdir(dp)) && eCntr < 3) 	// read dir and cnt entries
			{
			if(*dirp->d_name != '.')				// don't count if sys file
				eCntr++;
			}
		if(eCntr > 2)
			mail = YES;
		closedir(dp);
		}
	else
		[self showAlert:"Error opening Mail queue directory"];

	return mail;
}
//************** Diagnostics Window delegate methods **************************
//*****************************************************************************
//
// 		called whenever the user minituriazes our Diagnostics window.
//
//*****************************************************************************

- windowWillMiniaturize:sender toMiniwindow:miniwindow 
{
    return [sender setMiniwindowIcon:"miniWinIcon"];
}
//*****************************************************************************
//
// 		called whenever the user closes our Diagnostics window.
//
//*****************************************************************************

- windowWillClose:sender  
{
	if(![sender isKindOf:[Panel class]])	// simple test to see if diag
		{									// win is what is being closed
		if(!NXWriteDefault([NXApp appName], DISPLAYD, "NO"))
			[self showAlert:"ddbWriteError"];
		}

    return self;
}

@end

//*****************************************************************************
//
// 		c thread loop which listens for RPC telling us to link 
//
//*****************************************************************************

any_t server_loop(any_t port)
{
struct message msg, reply;
kern_return_t ret;
        
    while (TRUE)
		{        
				/* Receive a request from a client. */
		msg.head.msg_local_port = (port_t)port;
		msg.head.msg_size = sizeof(struct message);
		ret = msg_receive(&msg.head, MSG_OPTION_NONE, 0);
		if (ret != RCV_SUCCESS)							/* ignore errors */
			continue; 
	
					/* Feed the request into the server. */
			(void)nsRPC_server((msg_header_t *)&msg, (msg_header_t *)&reply);
	
				/* Send a reply to the client. */
		reply.head.msg_local_port = (port_t)port;
		ret = msg_send(&reply.head, MSG_OPTION_NONE, 0);
		if (ret != SEND_SUCCESS) 						/* ignore errors */
			continue; 
		}
	return 0;
}
//*****************************************************************************
//
// This function is called by nsRPC_server, which was created by MiG.
// It is NOT directly called by any client process.
//
//*****************************************************************************

kern_return_t pppUp(port_t server)
{
	[[NXApp delegate] Link:nil];	

    return KERN_SUCCESS;
}
