<NME> TCTcpcryptController.m
<BEF> #import "TCTcpcryptController.h"
#include <sys/stat.h>
#include <string.h>

@interface TCTcpcryptController ()
- (BOOL)daemonIsRunning;
- (NSString *)daemonStatus;
- (void)refreshDaemonStatus;
- (void)setSuidRoot;
@end

@implementation TCTcpcryptController

////////////////////////////////////////////////////////////////////////////////////////
#pragma mark NSObject

- (id)init {
	if (self = [super init]) {
		_launchPath = [[[NSBundle mainBundle] pathForResource:@"launch_tcpcryptd" ofType:@""] retain];
	}
	return self;
}

- (void)dealloc {
	if ([self daemonIsRunning]) {
		[self stopDaemon:nil];
	}
	[_launchPath release];
	[super dealloc];
}

#pragma mark NSWindowDelegate

- (void)windowDidBecomeMain:(NSNotification *)notification {
	[self refreshDaemonStatus];
}

#pragma mark -

- (void)setSuidRoot {
	char *cLaunchPath = (char *)[_launchPath cStringUsingEncoding:NSUTF8StringEncoding];
	
	if (chmod(cLaunchPath, 04755) != 0) {
		NSLog(@"chmod failed, assume it's because it belongs to root");
		return;
	}
	
	const char *launchPath = "/usr/sbin/chown";
	char *args[] = {"root", cLaunchPath, NULL};
	
	AuthorizationRef authRef;
	OSStatus status;
	status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
								 kAuthorizationFlagDefaults, &authRef);
	status = AuthorizationExecuteWithPrivileges(authRef, launchPath, kAuthorizationFlagDefaults,
												args, NULL);
	NSLog(@"chmod $? = %d", status);
	if (status) {
		NSLog(@"chmod $? = %d, %s", status, strerror(status));
	}
	
}

- (IBAction)startDaemon:(id)sender {
	NSLog(@"starting tcpcryptd...");
	NSAssert(![self daemonIsRunning], @"tcpcryptd already started");
	
	[self setSuidRoot];
	_daemon = [[NSTask alloc] init];
	_daemon.launchPath = _launchPath;
		
	_pipe = [NSPipe pipe];
	//[_daemon setStandardOutput:_pipe];
	//NSFileHandle *daemonOut = [_pipe fileHandleForReading];
	[_daemon setStandardInput:[NSPipe pipe]];
	
	[_daemon launch];
	
	//NSData *data;
    //data = [daemonOut readDataToEndOfFile];
	
    //NSString *string;
    //string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
    //NSLog (@"woop!  got\n%@", string);

	NSLog(@"started tcpcryptd");
	NSAssert([self daemonIsRunning], @"failed to start tcpcryptd");
	[self refreshDaemonStatus];
}

- (IBAction)stopDaemon:(id)sender {
	NSLog(@"stopping tcpcryptd...");
	[_daemon interrupt];
	NSLog(@"sent SIGINT");
	[_daemon waitUntilExit];
	NSLog(@"stopped tcpcryptd");
	NSAssert(![self daemonIsRunning], @"failed to stop tcpcryptd");
	[_daemon release];
											   arguments:[NSArray arrayWithObject:@"stop"]];
	
	[stopper waitUntilExit];
	NSLog(@"stopped tcpcryptd");
	NSAssert(![self daemonIsRunning], @"failed to stop tcpcryptd");
	if (_daemon) {
		[_daemon release];
		_daemon = nil;
	}
	[self refreshDaemonStatus];
}

- (BOOL)daemonIsRunning {
- (void)refreshDaemonStatus {
	[_startButton setHidden:[self daemonIsRunning]];
	[_stopButton setHidden:![self daemonIsRunning]];
	[_statusLabel setStringValue:[self daemonStatus]];
}

@end
	[_statusLabel setStringValue:[self daemonStatus]];
}

- (IBAction)openTestPage:(id)sender {
	[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://tcpcrypt.org/fame.php"]];
}

@end

<MSG> setting file perms and ui interaction

<DFF> @@ -1,12 +1,13 @@
 #import "TCTcpcryptController.h"
 #include <sys/stat.h>
+#include <sys/types.h>
 #include <string.h>
 
 @interface TCTcpcryptController ()
 - (BOOL)daemonIsRunning;
 - (NSString *)daemonStatus;
 - (void)refreshDaemonStatus;
-- (void)setSuidRoot;
+- (void)checkPermissions;
 @end
 
 @implementation TCTcpcryptController
@@ -16,7 +17,8 @@
 
 - (id)init {
 	if (self = [super init]) {
-		_launchPath = [[[NSBundle mainBundle] pathForResource:@"launch_tcpcryptd" ofType:@""] retain];
+		_wrapperPath = [[[NSBundle mainBundle] pathForResource:@"tcpcryptd_wrapper" ofType:@""] retain];
+		_tcpcryptdPath = [[[NSBundle mainBundle] pathForResource:@"tcpcryptd" ofType:@""] retain];
 	}
 	return self;
 }
@@ -25,74 +27,76 @@
 	if ([self daemonIsRunning]) {
 		[self stopDaemon:nil];
 	}
-	[_launchPath release];
+	[_wrapperPath release];
+	[_tcpcryptdPath release];
 	[super dealloc];
 }
 
-#pragma mark NSWindowDelegate
-
-- (void)windowDidBecomeMain:(NSNotification *)notification {
-	[self refreshDaemonStatus];
-}
-
 #pragma mark -
 
-- (void)setSuidRoot {
-	char *cLaunchPath = (char *)[_launchPath cStringUsingEncoding:NSUTF8StringEncoding];
+- (void)fixPermissionsForFile:(NSString *)path_ setUIDRoot:(BOOL)setUIDRoot {
+	int fd, ret;
+	mode_t mode;
+	struct stat st;
+	const char *path = [path_ cStringUsingEncoding:NSUTF8StringEncoding];
+	AuthorizationRef authRef;
+	OSStatus status;
 	
-	if (chmod(cLaunchPath, 04755) != 0) {
-		NSLog(@"chmod failed, assume it's because it belongs to root");
-		return;
-	}
+	fd = open(path, O_NOFOLLOW);
+	NSAssert(fd != -1, @"open(%s)", path);
+
+	ret = fstat(fd, &st);
+	NSAssert(ret != -1, @"fstat(%s)", path);
 	
-	const char *launchPath = "/usr/sbin/chown";
-	char *args[] = {"root", cLaunchPath, NULL};
+	// chmod
+	mode = 0755 | S_IFREG;
+	if (setUIDRoot) mode = mode | S_ISUID;
+	if (st.st_mode != mode) {
+		NSLog(@"%@ is 0%o, will set to 0%o", path_, st.st_mode, mode);
+		if (st.st_uid == 0) {
+			NSLog(@"lost perms but kept root ownership of %@", path_);
+		}
+		ret = fchmod(fd, mode);
+		NSAssert(ret != -1, @"fchmod()");
+	}
 	
-	AuthorizationRef authRef;
-	OSStatus status;
-	status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
-								 kAuthorizationFlagDefaults, &authRef);
-	status = AuthorizationExecuteWithPrivileges(authRef, launchPath, kAuthorizationFlagDefaults,
-												args, NULL);
-	NSLog(@"chmod $? = %d", status);
-	if (status) {
-		NSLog(@"chmod $? = %d, %s", status, strerror(status));
+	// chown root
+	if (st.st_uid != 0) {
+		const char *args[] = {"root", path, NULL};
+		status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
+									 kAuthorizationFlagDefaults, &authRef);
+		status = AuthorizationExecuteWithPrivileges(authRef, "/usr/sbin/chown", kAuthorizationFlagDefaults,
+													  (char *const *)args, NULL);
+		NSAssert(status != -1, @"chown");
 	}
 	
+	close(fd);
+}
+
+- (void)checkPermissions {
+	[self fixPermissionsForFile:_wrapperPath setUIDRoot:YES];
+	[self fixPermissionsForFile:_tcpcryptdPath setUIDRoot:NO];
 }
 
 - (IBAction)startDaemon:(id)sender {
 	NSLog(@"starting tcpcryptd...");
 	NSAssert(![self daemonIsRunning], @"tcpcryptd already started");
 	
-	[self setSuidRoot];
-	_daemon = [[NSTask alloc] init];
-	_daemon.launchPath = _launchPath;
-		
-	_pipe = [NSPipe pipe];
-	//[_daemon setStandardOutput:_pipe];
-	//NSFileHandle *daemonOut = [_pipe fileHandleForReading];
-	[_daemon setStandardInput:[NSPipe pipe]];
-	
-	[_daemon launch];
-	
-	//NSData *data;
-    //data = [daemonOut readDataToEndOfFile];
-	
-    //NSString *string;
-    //string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
-    //NSLog (@"woop!  got\n%@", string);
-
-	NSLog(@"started tcpcryptd");
+	[self checkPermissions];
+	_daemon = [[NSTask launchedTaskWithLaunchPath:_wrapperPath 
+									   arguments:[NSArray arrayWithObject:@"start"]] retain];
+	NSLog(@"started tcpcryptd, pid %u", [_daemon processIdentifier]);
 	NSAssert([self daemonIsRunning], @"failed to start tcpcryptd");
 	[self refreshDaemonStatus];
 }
 
 - (IBAction)stopDaemon:(id)sender {
 	NSLog(@"stopping tcpcryptd...");
-	[_daemon interrupt];
-	NSLog(@"sent SIGINT");
-	[_daemon waitUntilExit];
+	
+	NSTask *stopper = [NSTask launchedTaskWithLaunchPath:_wrapperPath 
+											   arguments:[NSArray arrayWithObject:@"stop"]];
+	
+	[stopper waitUntilExit];
 	NSLog(@"stopped tcpcryptd");
 	NSAssert(![self daemonIsRunning], @"failed to stop tcpcryptd");
 	[_daemon release];
@@ -112,7 +116,12 @@
 - (void)refreshDaemonStatus {
 	[_startButton setHidden:[self daemonIsRunning]];
 	[_stopButton setHidden:![self daemonIsRunning]];
+	[_testButton setHidden:![self daemonIsRunning]];
 	[_statusLabel setStringValue:[self daemonStatus]];
 }
 
+- (IBAction)openTestPage:(id)sender {
+	[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://tcpcrypt.org/test.php"]];
+}
+
 @end

setting file perms and ui interaction