/*
    SambaManger. A graphical frontend to configure the NetInfo enhanced samba.
    Copyright (C) 1998  Robert Frank

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
		
		Robert Frank, frank@ifi.unibas.ch
*/

#import "Controller.h"
#import "Users.h"
#import "Printers.h"
#import "Hosts.h"
#import "Services.h"
#import "DomainLogin.h"
#import "NetInfoKeys.h"
#import	"NIDirectory.h"
#import	"NIProperty.h"
#import <libc.h>
#import <stdio.h>
#import <string.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <pwd.h>

#define INETD_CONF	"/etc/inetd.conf"
#define INETD_BACK	"/etc/inetd.conf~"

@implementation Controller

// ****************************************************
// Local methods:

- (BOOL)menuActive:menuCell
{
id		theClass = [[[NXApp mainWindow] delegate] class];
BOOL	hs = (theClass == [Hosts class]) || (theClass == [Services class]);
BOOL	up = (theClass == [Users class]) || (theClass == [Printers class]); 
BOOL	shouldBeEnabled = hs || up;

		if (shouldBeEnabled) {
			if (menuCell == menuDelete)
				shouldBeEnabled = [[[NXApp mainWindow] delegate] isOld:self] && hs;
			else if (menuCell == menuSaveToDomain)
				shouldBeEnabled = hs;
			else if (menuCell == menuRevert)
				shouldBeEnabled = [[[NXApp mainWindow] delegate] isOld:self];
			else
				shouldBeEnabled = (menuCell == menuClose) || [[NXApp mainWindow] isDocEdited];
			
			if ([menuCell isEnabled] != shouldBeEnabled) {
				[menuCell setEnabled:shouldBeEnabled];
				return YES;
			}
		} else if ([menuCell isEnabled] != shouldBeEnabled) {
			[menuCell setEnabled:shouldBeEnabled];
			return YES;
		}
		
		return NO;

}

// The method called by the NIDirectory open method. It should check if the requested
// object is already open.
- (BOOL)niDirOk:(const char *)domain path:(const char *)directory
{
int	windows, i;
id	obj;

			// Check all opened windows of this class. 
			// (Can't use the window list; miniwindows don't have a delegate!)
			// If we already have an open window, show it.
			windows = [serviceList count];
			for (i = 0; i < windows; i++) {
				obj = [serviceList objectAt:i];
				if ([obj isSame:directory inDomain:domain]) {
					[obj show:self];
					return NO;
				}
			}
			return YES;
}

// Check if the given directory with its property value pairs exists.
// If p2 is NULL, v2 is assumed to be the second value of p1.
// Return self if the directory exists or was saved successfully.
- checkNetInfoIn:(const char *)domainPath forDir:(const char *)dirName
									 prop1:(const char *)p1 val1:(const char *)v1
									 prop2:(const char *)p2 val2:(const char *)v2
									 prop3:(const char *)p3 val3:(const char *)v3
{
id					retVal = self;
NIDirectory	*NIDir = [[NIDirectory alloc] init:self dom:domainPath root:dirName dir:v1 errors:NO];
NIProperty	*nameProp = [NIDir addProperty:p1];

		if (![[NIDir property:p1] values]) {
			if (p2) {
				[nameProp updateValue:v1 at:0];
				[[NIDir addProperty:p2] updateValue:v2 at:0];
				[[NIDir addProperty:p3] updateValue:v3 at:0];
			} else {
				[[nameProp updateValue:v1 at:0] updateValue:v2 at:1];
				[[NIDir addProperty:p3] updateValue:v3 at:0];
			}
			retVal = [NIDir save];
		}
		[NIDir close];
		return retVal;
}

- setNMBD:(const char *)line
{
unsigned char	token[1024];
int						d, r, i = 0, p = 0;

		while ((i < 6) && ((r = sscanf(line, "%s%n", token, &p)) != EOF)) {
			i++;
			line+= p;
		}

		// Here we should have the fully qualified file name
		if (r != EOF) {
			[fieldnmbProgram setStringValue:token];
			r = sscanf(line, "%s%n", token, &p); // Skip the daemon's name
			line+= p;
			r = sscanf(line, "%s%n", token, &p);
			while (r != EOF) { // Options
				line+= p;
				if (*token == '-')
					switch (token[1]) {
						case 'd' : // Debug level
							while (*line && (*line <= ' ')) line++;
							r = sscanf(line, "%d%n", &d, &p);
							if ((d >= 10) || (*line == 'A'))
								d = 10;
							[buttonNMBLogging setTitle:[[matrixNMBLogging findCellWithTag:d] title]];
							[matrixNMBLogging selectCellWithTag:d];
							line+= p;
							break;
						case 'n' : // Log method
							r = sscanf(line, "%s%n", token, &p);
							[fieldnmbNetBiosName setStringValue:token];
							line+= p;
							break;
						case 'l' : // Log file
							r = sscanf(line, "%s%n", token, &p);
							[fieldnmbLogFile setStringValue:token];
							line+= p;
							break;
						default  : ;
					}
				r = sscanf(line, "%s%n", token, &p);
			}
		}
		
		return self;
}

- setSMBD:(const char *)line
{
unsigned char	token[1024], *t;
int						d, r, i = 0, p = 0;

		while ((i < 6) && ((r = sscanf(line, "%s%n", token, &p)) != EOF)) {
			i++;
			line+= p;
		}

		// Here we should have the fully qualified file name
		if (r != EOF) {
			[fieldsmbProgram setStringValue:token];
			r = sscanf(line, "%s%n", token, &p); // Skip the daemon's name
			line+= p;
			r = sscanf(line, "%s%n", token, &p);
			while (r != EOF) { // Options
				line+= p;
				if (*token == '-')
					switch (token[1]) {
						case 'a' : // Log method
							[buttonSMBLogMethod setTitle:[[matrixSMBLogMethod findCellWithTag:1] title]];
							[matrixSMBLogMethod selectCellWithTag:1];
							break;
						case 'd' : // Debug level
							while (*line && (*line <= ' ')) line++;
							r = sscanf(line, "%d%n", &d, &p);
							if ((d >= 10) || (*line == 'A'))
								d = 10;
							[buttonSMBLogging setTitle:[[matrixSMBLogging findCellWithTag:d] title]];
							[matrixSMBLogging selectCellWithTag:d];
							line+= p;
							break;
						case 'l' : // Log file
							r = sscanf(line, "%s%n", token, &p);
							[fieldsmbLogFile setStringValue:token];
							line+= p;
							break;
						case 'O' : // Socket options
							r = sscanf(line, "%s%n", token, &p);
							while ((r != EOF) && (*token != '-')) {
								line+= p;
								if (*token == '"')
									t = token+1;
								else
									t = token;
								if (!strncmp(t, "SO_KEEPALIVE", strlen("SO_KEEPALIVE")))
									[checkKeepAlive setIntValue:1];
								else if (!strncmp(t, "SO_REUSEADDR", strlen("SO_REUSEADDR")))
									[checkReuse setIntValue:1];
								else if (!strncmp(t, "SO_BROADCAST", strlen("SO_BROADCAST")))
									[checkBroadcast setIntValue:1];
								else if (!strncmp(t, "TCP_NODELAY", strlen("TCP_NODELAY")))
									[checkNoDelay setIntValue:1];
								else if (!strncmp(t, "SO_SNDBUF", strlen("SO_SNDBUF")))
									[fieldSendBufSize setIntValue:atoi(t+strlen("SO_SNDBUF")+1)];
								else if (!strncmp(t, "SO_RCVBUF", strlen("SO_RCVBUF")))
									[fieldRecBufSize setIntValue:atoi(t+strlen("SO_RCVBUF")+1)];
								else if (!strncmp(t, "SO_SNDLOWAT", strlen("SO_SNDLOWAT")))
									[fieldSendLowWat setIntValue:atoi(t+strlen("SO_SNDLOWAT")+1)];
								else if (!strncmp(t, "SO_RCVLOWAT", strlen("SO_RCVLOWAT")))
									[fieldRecLowWat setIntValue:atoi(t+strlen("SO_RCVLOWAT")+1)];
								r = sscanf(line, "%s%n", token, &p);
							}
							break;
						default  : ;
					}
				r = sscanf(line, "%s%n", token, &p);
			}
		}
		
		return self;
}

- clearStartup
{
		// Clear all fields and reset popups.
		[fieldnmbProgram setStringValue:""];
		[fieldnmbNetBiosName setStringValue:""];
		[buttonNMBLogging setTitle:[[matrixNMBLogging findCellWithTag:0] title]];
		[matrixNMBLogging selectCellWithTag:0];
		[fieldnmbLogFile setStringValue:""];
		[fieldsmbProgram setStringValue:""];
		[checkKeepAlive setIntValue:0];
		[checkReuse setIntValue:0];
		[checkBroadcast setIntValue:0];
		[checkNoDelay setIntValue:0];
		[fieldSendBufSize setStringValue:""];
		[fieldRecBufSize setStringValue:""];
		[fieldSendLowWat setStringValue:""];
		[fieldRecLowWat setStringValue:""];
		[buttonSMBLogging setTitle:[[matrixSMBLogging findCellWithTag:0] title]];
		[matrixSMBLogging selectCellWithTag:0];
		[buttonSMBLogMethod setTitle:[[matrixSMBLogMethod findCellWithTag:0] title]];
		[matrixSMBLogMethod selectCellWithTag:0];
		[fieldsmbLogFile setStringValue:""];

		// Set the buttons.
		[buttonStop setEnabled:NO];
		[buttonStart setEnabled:NO];
		[buttonRemove setEnabled:NO];
		[buttonRevert setEnabled:NO];
		[buttonSave setEnabled:NO];

		[windowStartup setDocEdited:NO];

		return self;
}

- (BOOL)alertChoice:(const char *)msg buttons:(BOOL)YesNo
{
int	choice =	NXRunAlertPanel([strings valueForStringKey:"Alert:Alert"],
															[strings valueForStringKey:msg],
															[strings valueForStringKey:YesNo?"Button:Yes":"Button:OK"],
															YesNo?[strings valueForStringKey:"Button:No"]:NULL, 
															NULL, INETD_CONF, strerror(errno));
		return (choice == NX_ALERTDEFAULT);
}

- (BOOL)smbdLine:(char *)buffer
{
BOOL				keep      = [checkKeepAlive state],
						reuse     = [checkReuse state],
						broadcast = [checkBroadcast state],
						noDelay   = [checkNoDelay state],
						logMode   = [[matrixSMBLogMethod selectedCell] tag];
int					d     = [[matrixSMBLogging selectedCell] tag],
						snd   = [fieldSendBufSize intValue],
						rcv   = [fieldRecBufSize intValue],
						sndlo = [fieldSendLowWat intValue],
						rcvlo = [fieldRecLowWat intValue],
						statRet;
const char	*smbd = [fieldsmbProgram stringValue],
						*log  = [fieldsmbLogFile stringValue];
struct stat	status;
FILE				*pipe;
char				pbuffer[4096];

		if (!*smbd || (d && !*log)) {
			[self alertChoice:*log?"Message:No smb daemon":"Message:No smbd log file" buttons:NO];
			return NO;
		}
		
		// Try to execute the daemon. If it doesn't exist, return NO.
		// If it doesn't return NetInfo ..., warn about using SambaManager.app.
		status.st_mode = 0;
		statRet = stat(smbd, &status);
		if ((statRet != 0) || !(status.st_mode&S_IEXEC)) {
			[self alertChoice:"Message:Cannot find smb daemon" buttons:NO];
			return NO;
		}
		
		(void)strcpy(pbuffer, smbd);
		(void)strcat(pbuffer, " -V");
		pipe = popen(pbuffer, "r");
		fscanf(pipe, "%s", pbuffer);
		pclose(pipe);
		if (strcmp(pbuffer, "NetInfo"))
			[self alertChoice:"Message:SMB daemon not NetInfo enhanced" buttons:NO];
		
		(void)strcpy(buffer, "netbios-ssn  stream  tcp  nowait  root  ");
		(void)strcat(buffer, smbd);
		if (logMode)
			(void)strcat(buffer, " -a");
		if (d < 10)
			sprintf(buffer+strlen(buffer), " smbd -d %d", d);
		else
			(void)strcat(buffer," smbd -d All");
		if (d)
			sprintf(buffer+strlen(buffer), " -l %s", log);
		if (keep | reuse | broadcast | noDelay | snd | rcv | sndlo | rcvlo) {
			sprintf(buffer+strlen(buffer), " -O \"%s%s%s%s",
							keep?"SO_KEEPALIVE ":"", reuse?"SO_REUSEADDR ":"",
							broadcast?"SO_BROADCAST ":"", noDelay?"TCP_NODELAY ":"");
			if (snd)
				sprintf(buffer+strlen(buffer), "SO_SNDBUF=%d ", snd);
			if (rcv)
				sprintf(buffer+strlen(buffer), "SO_RCVBUF=%d ", rcv);
			if (sndlo)
				sprintf(buffer+strlen(buffer), "SO_SNDLOWAT=%d ", sndlo);
			if (rcvlo)
				sprintf(buffer+strlen(buffer), "SO_RCVLOWAT=%d ", rcvlo);
			sprintf(buffer+strlen(buffer)-1, "\"");
		}
		(void)strcat(buffer, " -f /tmp/smbd.pid\n");
		return YES;
}

- (BOOL)nmbdLine:(char *)buffer
{
int					d       = [[matrixNMBLogging selectedCell] tag],
						statRet;
const char	*nmbd   = [fieldnmbProgram stringValue],
						*log    = [fieldnmbLogFile stringValue],
						*nbName = [fieldnmbNetBiosName stringValue];
struct stat	status;
FILE				*pipe;
char				pbuffer[4096];

		if (!*nmbd || (d && !*log)) {
			[self alertChoice:*log?"Message:No nmb daemon":"Message:No nmbd log file" buttons:NO];
			return NO;
		}
		
		// Try to execute the daemon. If it doesn't exist, return NO.
		// If it doesn't return NetInfo ..., warn about using SambaManager.app.
		status.st_mode = 0;
		statRet = stat(nmbd, &status);
		if ((statRet != 0) || !(status.st_mode&S_IEXEC)) {
			[self alertChoice:"Message:Cannot find nmb daemon" buttons:NO];
			return NO;
		}
		
		(void)strcpy(pbuffer, nmbd);
		(void)strcat(pbuffer, " -V");
		pipe = popen(pbuffer, "r");
		fscanf(pipe, "%s", pbuffer);
		pclose(pipe);
		if (strcmp(pbuffer, "NetInfo"))
			[self alertChoice:"Message:NMB daemon not NetInfo enhanced" buttons:NO];
		
		(void)strcpy(buffer, "netbios-ns   dgram   udp  wait    root  ");
		(void)strcat(buffer, nmbd);
		sprintf(buffer+strlen(buffer), " nmbd -d %d", d);
		if (d)
			sprintf(buffer+strlen(buffer), " -l %s", log);
		if (*nbName)
			sprintf(buffer+strlen(buffer), " -n %s", nbName);
		(void)strcat(buffer, " -f /tmp/nmbd.pid\n");
		return YES;
}

- scanEtcInetdConf:(int)mode
// Mode is: 0 for reading, 1 for writing, 2 for stopping, 3 for starting, 4 for removing
{
NXStream			*rStream = NXMapFile(INETD_CONF, NX_READONLY),
							*wStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
unsigned char	line[1024], c;
char					buf[MAXPATHLEN + 1];
int						i, both = 0, o, have = 0, smbd, nmbd;
BOOL					retVal = YES;
FILE					*pipe;
struct passwd	*pwEnt;
char					salt[3];

		[windowStartup disableFlushWindow];

		if (!mode)
			[self clearStartup];

		if (!rStream) {
			NXCloseMemory(wStream, NX_FREEBUFFER);
			[windowStartup reenableFlushWindow];
			[windowStartup display];
			return nil;
		}

		c = NXGetc(rStream);
		while (!NXAtEOS(rStream) && (both < 2)) {
			while (!NXAtEOS(rStream) && (c <= ' ')) {
				NXPutc(wStream, c);
				c = NXGetc(rStream);
			}
			i = 0;
			while (!NXAtEOS(rStream) && (i < 1024) && (c != '\n')) {
				line[i++] = c;
				c = NXGetc(rStream);
			}
			line[i] = '\0';
			
			o = ((*line == '#') && (*(line+1) == '!'))?2:0;
			if ((*line == '#') && !o) {
				line[i++] = '\n';
				NXWrite(wStream, line, i);
				*line = '\0';
			}
			
			if (*(line+o)) {
				smbd = !strncmp(line+o, SMNIP_NBSSN, strlen(SMNIP_NBSSN));
				nmbd = !strncmp(line+o, SMNIP_NBNS, strlen(SMNIP_NBNS));
				if (smbd || nmbd) {
					[buttonRemove setEnabled:!uid || !euid];
					[buttonStart setEnabled:o && (!uid || !euid)];
					[buttonStop setEnabled:!o && (!uid || !euid)];
					switch (mode) {
						case 0:
							if (smbd)
								[self setSMBD:line+o];
							else
								[self setNMBD:line+o];
							both++;
							break;
						case 1: // write
							have|= smbd?1:2;
							// We'll continue and back out before saving.
							if (smbd)
								retVal&= [self smbdLine:line];
							else
								retVal&= [self nmbdLine:line];
							NXWrite(wStream, line, strlen(line));
							break;
						case 2: // stop
							NXWrite(wStream, "#!", 2);
						case 3: // start
							NXWrite(wStream, line+o, i-o);
							NXPutc(wStream, '\n');
							break;
						case 4: // remove
						default: ;
					}
				} else {
					NXWrite(wStream, line, i);
					NXPutc(wStream, '\n');
				}
			}
			c = NXGetc(rStream);
		}
		
		if (mode && retVal) {
			if ((mode == 1) && !(have&1)) {
				retVal&= [self smbdLine:line];
				NXWrite(wStream, "#!", 2);
				NXWrite(wStream, line, strlen(line));
			}
			if (retVal && (mode == 1) && !(have&2)) {
				retVal&= [self nmbdLine:line];
				NXWrite(wStream, "#!", 2);
				NXWrite(wStream, line, strlen(line));
			}
			// If we are root (uid = 0) save. If we have set uid, validate the root password.
			if (retVal && (uid && !euid) && !rootOk) {
				pwEnt = getpwuid(0);
				strncpy(salt, pwEnt->pw_passwd, 2);
				salt[2] = '\0';
				[fieldPassword selectText:self];
				i = [NXApp runModalFor:panelAuthenicate];
				[panelAuthenicate close];
				switch(i) {
					case NX_RUNSTOPPED:
						rootOk = retVal = !strcmp(crypt((char *)[fieldPassword stringValue], salt), pwEnt->pw_passwd);
						break;
					default: 
						retVal = NO;
				}
			}

			if (retVal) {
				if (retVal = (rename(INETD_CONF, INETD_BACK) == 0)) {
					if (retVal = !NXSaveToFile(wStream, INETD_CONF))
						switch (mode) {
							case 1: // save
								[windowStartup setDocEdited:NO];
								[buttonRevert setEnabled:NO];
								[buttonSave setEnabled:NO];
								[buttonStart setEnabled:!have && (!uid || !euid)];
								[buttonRemove setEnabled:have && (!uid || !euid)];
								break;
							case 2: // stop
								[buttonStop setEnabled:NO];
								[buttonStart setEnabled:!uid || !euid];
								break;
							case 3: // start
								[buttonStart setEnabled:NO];
								[buttonStop setEnabled:!uid || !euid];
								break;
							case 4: // clear
								[self clearStartup];
						}
					else 
						NXRunAlertPanel([strings valueForStringKey:"Alert:Alert"],
														[strings valueForStringKey:"Message:Could not save configuraton"],
														[strings valueForStringKey:"Button:OK"],
														NULL, NULL, INETD_CONF);
				} else 
					NXRunAlertPanel([strings valueForStringKey:"Alert:Alert"],
													[strings valueForStringKey:"Message:Could not rename"],
													[strings valueForStringKey:"Button:OK"],
													NULL, NULL, INETD_CONF, strerror(errno));
			}
		}
		NXCloseMemory(wStream, NX_FREEBUFFER);
		NXCloseMemory(rStream, NX_FREEBUFFER);

		// If no daemons set, search for them!
		if (!*[fieldsmbProgram stringValue]) {
			if (![[NXBundle mainBundle] getPath:buf forResource:"smbd" ofType:NULL])
				if (![NXBundle getPath:buf forResource:"smbd" ofType:NULL
											 inDirectory:"/LocalLibrary/SambaManager" withVersion:0])
					if (![NXBundle getPath:buf forResource:"smbd" ofType:NULL
												 inDirectory:"/LocalLibrary/SambaManager/samba" withVersion:0])
						if (![NXBundle getPath:buf forResource:"smbd" ofType:NULL
													 inDirectory:"/users/local/samba/bin" withVersion:0])
							*buf = '\0';
			if (*buf) {
				[fieldsmbProgram setStringValue:buf];
				[windowStartup setDocEdited:YES];
				[buttonSave setEnabled:!uid || !euid];
			}
		}
		if (!*[fieldnmbProgram stringValue]) {
			if (![[NXBundle mainBundle] getPath:buf forResource:"nmbd" ofType:NULL])
				if (![NXBundle getPath:buf forResource:"nmbd" ofType:NULL
											 inDirectory:"/LocalLibrary/SambaManager" withVersion:0])
					if (![NXBundle getPath:buf forResource:"nmbd" ofType:NULL
												 inDirectory:"/LocalLibrary/SambaManager/samba" withVersion:0])
						if (![NXBundle getPath:buf forResource:"nmbd" ofType:NULL
													 inDirectory:"/users/local/samba/bin" withVersion:0])
							*buf = '\0';
			if (*buf) {
				[fieldnmbProgram setStringValue:buf];
				[windowStartup setDocEdited:YES];
				[buttonSave setEnabled:!uid || !euid];
			}
		}
		
		[windowStartup reenableFlushWindow];
		[windowStartup display];

		// Send a hangup to the inetd process.
		if (retVal && (mode > 1)) {
			have = (pipe = popen("/bin/ps ax | /bin/grep inetd | /bin/grep -v grep", "r")) != NULL;
			i = -1;
			if (have) {
				fscanf(pipe, "%d", &i);
				pclose(pipe);
				have = kill(i, SIGHUP) == 0;
			}
			// Warn that inetd wasn't restarted if no pipe or kill fails.
			if (!have)
				if (i > 0)
					NXRunAlertPanel([strings valueForStringKey:"Alert:Alert"],
													[strings valueForStringKey:"Message:Could not signal inetd pid"],
													[strings valueForStringKey:"Button:OK"],
													NULL, NULL, i);
				else
					[self alertChoice:"Message:Could not signal inetd" buttons:NO];
			// If disable, try to stop the daemons. Remove any pid files.
			if ((mode == 2) || (mode == 4)) {
				if ((pipe = fopen("/tmp/nmbd.pid", "r")) != NULL) {
					if (fscanf(pipe, "%d", &i) == 1)
						kill(i, SIGKILL);
					fclose(pipe);
				}
				unlink("/tmp/nmbd.pid");
				if ((pipe = fopen("/tmp/smbd.pid", "r")) != NULL) {
					if (fscanf(pipe, "%d", &i) == 1)
						kill(i, SIGKILL);
					fclose(pipe);
				}
				unlink("/tmp/smbd.pid");
			}
		}
		
		return self;
}

- markEdited:sender
{
		[windowStartup setDocEdited:YES];
		[buttonSave setEnabled:!uid || !euid];
		[buttonRevert setEnabled:YES];
		return self;
}

- openFilePath:(BOOL)dirsOnly field:outlet
{
const char	*docFileType[1] = {NULL};
char				buffer[4096], hostName[128];
id					openPanel;

		openPanel = [[OpenPanel new] allowMultipleFiles:NO];
		[openPanel chooseDirectories:dirsOnly];
		[openPanel setTitle:[strings valueForStringKey:dirsOnly?"Title:Select Directory":"Title:Select File"]]; 
	
		switch([openPanel runModalForTypes:docFileType]) {
			case NX_OKTAG:
				if (dirsOnly) {
					strcpy(buffer, [openPanel filename]);
					strcat(buffer, "/");
					if (gethostname(hostName, 128))
						strcpy(hostName, "log");
					strcat(buffer, hostName);
					[outlet setStringValue:buffer];
				} else
					[outlet setStringValue:[openPanel filename]];
				[self markEdited:self];
			default: ;
		}
		return self;
}

// Check and possibly set the NetInfo values for the samba daemons.
- setNetInfo
{
	if ([self checkNetInfoIn:"." forDir:SMNI_SERVICES
						prop1:SMNIP_NAME val1:SMNIP_NBSSN
						prop2:SMNIP_PORT val2:"139"
						prop3:SMNIP_PROTOCOL val3:"tcp"])
		if ([self checkNetInfoIn:"." forDir:SMNI_SERVICES
							prop1:SMNIP_NAME val1:SMNIP_NBNS
							prop2:SMNIP_PORT val2:"137"
							prop3:SMNIP_PROTOCOL val3:"udp"])
			if ([self checkNetInfoIn:"." forDir:SMNIP_PROTOCOLS
								prop1:SMNIP_NAME val1:"tcp"
								prop2:NULL val2:"TCP"
								prop3:SMNIP_NUMBER val3:"6"])
				if ([self checkNetInfoIn:"." forDir:SMNIP_PROTOCOLS
									prop1:SMNIP_NAME val1:"udp"
									prop2:NULL val2:"UDP"
									prop3:SMNIP_NUMBER val3:"17"])
					return self;

	return nil;
}

// ****************************************************
// Other methods:

- removeFromServiceList:sender
// Rmove the sender from the list of opened services.
{
		[serviceList removeObject:sender];
		return self;
}

// ****************************************************
// Actions from the menus, window, and panel:

- cancelPassword:sender
{
		[NXApp abortModal];
		return self;
}

- okayPassword:sender
{
		[NXApp stopModal];
		return self;
}

- info:sender
{
    return self;
}

- preferences:sender
{
    return self;
}

- showStartup:sender
{
		[fieldnmbProgram selectText:self];
		[windowStartup makeKeyAndOrderFront:self];
		return self;
}

- saveStartup:sender
{
		if ([self scanEtcInetdConf:1])
    	[self setNetInfo];
			
		return self;
}

- revertStartup:sender
{
		[self scanEtcInetdConf:0];
    return self;
}

- openUser:sender
{
		return [serviceList addObject:[Users open:self at:&offset]];
}

- openPrinter:sender
{
		return [serviceList addObject:[Printers open:self at:&offset]];
}

- newHost:sender
{
		return [serviceList addObject:[Hosts new:self at:&offset]];
}

- newService:sender
{
		return [serviceList addObject:[Services new:self at:&offset]];
}

- openHost:sender
{
		return [serviceList addObject:[Hosts open:self at:&offset]];
}

- openService:sender
{
		return [serviceList addObject:[Services open:self at:&offset]];
}

- saveToDomain:sender
{
		if ([NXApp mainWindow] != nil)
			[[[NXApp mainWindow] delegate] saveToDomain:self];
		return self;
}

- delete:sender
{
		if ([NXApp mainWindow] != nil)
			[[[NXApp mainWindow] delegate] delete:self];
    return self;
}

- close:sender
{
id	obj;

		if ([NXApp mainWindow] != nil) {
			obj = [[NXApp mainWindow] delegate];
			if ([obj close:self])
				[obj free];
		}
		return self;
}

// Remove a samba daemon from this system
- remove:sender
{
		[self scanEtcInetdConf:4];
		return self;
}

- revert:sender
{
		if ([NXApp mainWindow] != nil)
			[[[NXApp mainWindow] delegate] revert:self];
    return self;
}

- save:sender
{
		if ([NXApp mainWindow] != nil)
			[[[NXApp mainWindow] delegate] save:self];
    return self;
}

- start:sender
{
		if ([windowStartup isDocEdited])
			if ([self alertChoice:"Message:Save before enabling?" buttons:YES])
				if (![self saveStartup:self])
					return self;
		[self scanEtcInetdConf:3];
		return self;
}

- stop:sender
{
		[self scanEtcInetdConf:2];
		return self;
}

- setNameDaemon:sender
{
		return [self openFilePath:NO field:fieldnmbProgram];
}

- setNameLogging:sender
{
		return [self openFilePath:YES field:fieldnmbLogFile];
}

- setSambaDaemon:sender
{
		return [self openFilePath:NO field:fieldsmbProgram];
}

- setSambaLogging:sender
{
		return [self openFilePath:YES field:fieldsmbLogFile];
}

- testParameters:sender
{
char	buf[4096];
FILE	*pipe;

		if (![[NXBundle mainBundle] getPath:buf forResource:"testparm" ofType:NULL])
				if (![NXBundle getPath:buf forResource:"testparm" ofType:NULL
											 inDirectory:"/LocalLibrary/SambaManager" withVersion:0])
					if (![NXBundle getPath:buf forResource:"testparm" ofType:NULL
												 inDirectory:"/LocalLibrary/SambaManager/samba" withVersion:0])
						if (![NXBundle getPath:buf forResource:"testparm" ofType:NULL
													 inDirectory:"/users/local/samba/bin" withVersion:0])
							*buf = '\0';

		[textParameters setEditable:YES];
		[textParameters selectText:self];
		if (*buf) {
			[textParameters replaceSel:""];
			(void)strcat(buf,"\n\n");
			pipe = popen(buf, "r");
			while (fgets(buf, 4096, pipe)) {
				[textParameters setSel:[textParameters textLength] :[textParameters textLength]];
				[textParameters replaceSel:buf];
			}
			pclose(pipe);
		} else {
			[textParameters replaceSel:"Could not find the testparm program.\nLooked for it in:\n  the bundle\n  /LocalLibrary/SambaManager\n  /LocalLibrary/SambaManager/samba\n  /users/local/samba/bin"];
		}
		[textParameters setEditable:NO];

		[windowParameters makeKeyAndOrderFront:self];
		return self;
}

// ****************************************************
// Delegates

-appDidInit:sender
{
id	menu;
id	popup;

		// Allocate and initialize the lists of opened windows.
		serviceList = [[List alloc] initCount:1];
		offset = 0.0;
		uid = getuid();
		euid = geteuid();
		rootOk = NO;

		// Set up the popups for the logging level
		popup = [buttonSMBLogging target];
		[popup setTarget:self];
		[popup setAction:@selector(markEdited:)];
		matrixSMBLogging = [popup itemList];
		popup = [buttonSMBLogMethod target];
		[popup setTarget:self];
		[popup setAction:@selector(markEdited:)];
		matrixSMBLogMethod = [popup itemList];
		popup = [buttonNMBLogging target];
		[popup setTarget:self];
		[popup setAction:@selector(markEdited:)];
		matrixNMBLogging = [popup itemList];

		// Scan the INETD.CONF file.
		[self scanEtcInetdConf:0];
		
		// Set up menu enabling/disabling methods.
		menu = [menuCellSamba target];
		[menuDelete setUpdateAction:@selector(menuActive:) forMenu:menu];
		[menuSave setUpdateAction:@selector(menuActive:) forMenu:menu];
		[menuSaveToDomain setUpdateAction:@selector(menuActive:) forMenu:menu];
		[menuRevert setUpdateAction:@selector(menuActive:) forMenu:menu];
		[menuClose setUpdateAction:@selector(menuActive:) forMenu:menu];

		// Set up automatic menu enabling/disabling.
		[NXApp setAutoupdate:YES];

		return self;
}

-appWillTerminate:sender
{
int	i, elements = [serviceList count];

		if ([windowStartup isDocEdited] && (!uid || !euid)) {
			i = NXRunAlertPanel([strings valueForStringKey:"Alert:Alert"],
													[strings valueForStringKey:"Message:Save changes to daemon startup?"],
													[strings valueForStringKey:"Button:Save"],
													[strings valueForStringKey:"Button:Don't Save"],
													[strings valueForStringKey:"Button:Cancel"]);
			switch (i) {
				case NX_ALERTDEFAULT:
					if (![self setNetInfo])
						return nil;
				case NX_ALERTALTERNATE:
					break;
				default:
					return nil;
			}
		}
		
		for (i = 0; i < elements; i++)
			if (![[serviceList objectAt:i] close:self])
				return nil;

		return self;
}

- textDidGetKeys:sender isEmpty:(BOOL)flag
{
		return [self markEdited:self];
}

- textDidEnd:textObject endChar:(unsigned short)whyEnd
{
		if (([textObject superview] == fieldPassword) && (whyEnd == NX_RETURN))
			[NXApp stopModal];

		return self;
}

@end
