/* * GNUnet Setup (Cocoa UI) * Copyright (C) 2009 Heikki Lindholm * * 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. */ // launch advanced configurator: //NSTask *task = [NSTask ......]; //> pid_t pid = [task processIdentifier]; //> ProcessSerialNumber psn; //> if (noErr == GetProcessForPID(pid, &psn)) { //> SetFrontProcess(&psn); //> } #include #include #include #include #include #include #include #import "GNSetup.h" static char * input_string(FILE *f) { char *buf; int len; int n; n = fread(&len, sizeof(len), 1, f); if (n < 1) return NULL; if (len <= 0) return NULL; buf = malloc(len); n = fread(buf, 1, len, f); if (n < len) { free(buf); return NULL; } return buf; } static void output_string(FILE *f, char *s) { int len; if (s == NULL) { len = 0; fwrite(&len, sizeof(len), 1, f); return; } len = strlen(s)+1; fwrite(&len, sizeof(len), 1, f); fwrite(s, 1, len, f); } @implementation GNSetup - (id)init { if (self = [super init]) { errorContext = GNUNET_GE_create_context_stderr(GNUNET_NO, GNUNET_GE_WARNING | GNUNET_GE_ERROR | GNUNET_GE_FATAL | GNUNET_GE_USER | GNUNET_GE_ADMIN | GNUNET_GE_DEVELOPER | GNUNET_GE_IMMEDIATE | GNUNET_GE_BULK); GNUNET_GE_setDefaultContext(errorContext); GNUNET_os_init(errorContext); config = GNUNET_GC_create(); if (config == NULL) { GNUNET_GE_free_context(errorContext); [self release]; return nil; } gnsContext = NULL; gnsOptionsArray = nil; cocoaSetupPlugin = GNUNET_plugin_load(errorContext, "libgnunet", "setup_cocoa"); if (!cocoaSetupPlugin) { GNUNET_GE_free_context(errorContext); GNUNET_GC_free(config); [self release]; return nil; } GNUNETSetupView_class = objc_getClass("GNUNETSetupView"); if (!GNUNETSetupView_class) { GNUNET_GE_free_context(errorContext); GNUNET_GC_free(config); GNUNET_plugin_unload(cocoaSetupPlugin); [self release]; return nil; } windowTitle = nil; setupView = nil; actionBeforeAlert = GNSetupUserActionNone; shouldQuit = NO; configFilename = nil; [NSApp setDelegate:self]; } return self; } - (void)awakeFromNib { NSString *s; // handle command line args isDaemonConfig = NO; s = [[NSUserDefaults standardUserDefaults] stringForKey:@"StartupConfigType"]; if (s != nil && [s caseInsensitiveCompare:@"Daemon"] == NSOrderedSame) { isDaemonConfig = YES; } s = [[NSUserDefaults standardUserDefaults] stringForKey:@"DefaultDaemonConfig"]; if (s != nil) defaultDaemonConfigFilename = strdup([s UTF8String]); else defaultDaemonConfigFilename = NULL; s = [[NSUserDefaults standardUserDefaults] stringForKey:@"DefaultClientConfig"]; if (s != nil) defaultClientConfigFilename = strdup([s UTF8String]); else defaultClientConfigFilename = NULL; [self loadAndDisplayConfigOfType:isDaemonConfig]; } - (int)loadConfigOfType:(BOOL)isDaemon fromFile:(const char *)filename { char *dirname; char *specname; BOOL didParseConfig; isPreauthorized = NO; // authorize if needed if (isDaemon && access(filename, F_OK | R_OK | W_OK) == -1) { if ([self preauthorize] == NO) { if (configFilename == nil) actionBeforeAlert = GNSetupUserActionQuit; else actionBeforeAlert = GNSetupUserActionNone; [self displayErrorLoadingAlertWithInfo:@"Authorization failed."]; return -1; } isPreauthorized = YES; } if (setupView != nil) { [setupView removeFromSuperviewWithoutNeedingDisplay]; setupView = nil; } if (gnsContext != NULL) GNUNET_GNS_free_specification(gnsContext); if (config != NULL) GNUNET_GC_free(config); config = GNUNET_GC_create(); if (config == NULL) { if (configFilename == nil) actionBeforeAlert = GNSetupUserActionQuit; else actionBeforeAlert = GNSetupUserActionNone; [self displayErrorLoadingAlertWithInfo:@"Cannot create configuration."]; return -1; } // load spec dirname = GNUNET_get_installation_path(GNUNET_IPK_DATADIR); specname = GNUNET_malloc(strlen (dirname) + strlen ("config-daemon.scm") + 1); strcpy (specname, dirname); if (isDaemon) strcat (specname, "config-daemon.scm"); else strcat (specname, "config-client.scm"); gnsContext = GNUNET_GNS_load_specification(errorContext, config, specname); if (gnsContext == NULL) { if (configFilename == nil) actionBeforeAlert = GNSetupUserActionQuit; else actionBeforeAlert = GNSetupUserActionNone; [self displayErrorLoadingAlertWithInfo:@"Cannot load configuration specification."]; return -1; } GNUNET_free(specname); GNUNET_free(dirname); if (gnsOptionsArray != nil) [gnsOptionsArray release]; gnsOptionsArray = [NSMutableArray new]; [self buildOptionList:gnsOptionsArray from:GNUNET_GNS_get_tree_root(gnsContext)]; // load config configFilename = GNUNET_expand_file_name(errorContext, filename); isDaemonConfig = isDaemon; windowTitle = [[NSString alloc] initWithCString:configFilename encoding:NSUTF8StringEncoding]; didParseConfig = NO; if (access(configFilename, F_OK | R_OK | W_OK) == 0) { GNUNET_GC_parse_configuration (config, configFilename); didParseConfig = YES; } else if (isDaemonConfig) { int n; n = [self privilegedLoadConfig]; if (n > 0 || access(configFilename, F_OK) == 0) didParseConfig = YES; } if (didParseConfig == NO) { NSString *s; s = [windowTitle stringByAppendingString:@" (new)"]; [windowTitle release]; windowTitle = s; } [self getDefaultsFrom:GNUNET_GNS_get_tree_root(gnsContext)]; return 0; } - (BOOL)preauthorize { AuthorizationFlags authFlags = kAuthorizationFlagDefaults; OSStatus status; status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, authFlags, &authRef); if (status != errAuthorizationSuccess) return NO; AuthorizationItem authItems = { kAuthorizationRightExecute, 0, NULL, 0}; AuthorizationRights authRights = {1, &authItems}; authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; status = AuthorizationCopyRights(authRef, &authRights, NULL, authFlags, NULL); if (status != errAuthorizationSuccess) { AuthorizationFree(authRef, kAuthorizationFlagDefaults); return NO; } return YES; } - (int)privilegedLoadConfig { OSStatus status; AuthorizationFlags authFlags = kAuthorizationFlagDefaults; NSString *toolPath; char **toolArgs; FILE *ioPipe = NULL; NSBundle *toolBundle; int ret, i; toolBundle = [NSBundle bundleWithIdentifier:@"org.gnunet.GNUnet"]; toolPath = [[toolBundle resourcePath] stringByAppendingPathComponent:@"gnunet-macosx-tool"]; if (toolPath == nil) return -1; toolArgs = malloc((2 + [gnsOptionsArray count] + 1)*sizeof(char *)); /*readConfig + ... + filename + NULL */ if (toolArgs == NULL) return -1; NSEnumerator *e = [gnsOptionsArray objectEnumerator]; id o; toolArgs[0] = "readConfig"; toolArgs[1] = configFilename; i = 2; while (o = [e nextObject]) { toolArgs[i] = strdup([(NSString *)o UTF8String]); if (toolArgs[i] == NULL) { free(toolArgs); return -1; } i++; } toolArgs[i] = NULL; authFlags = kAuthorizationFlagDefaults; status = AuthorizationExecuteWithPrivileges(authRef, [toolPath UTF8String], authFlags, toolArgs, &ioPipe); ret = -1; if (status == errAuthorizationSuccess) { ret = 0; do { char *section, *option, *value; section = input_string(ioPipe); option = input_string(ioPipe); value = input_string(ioPipe); if (section && option) { ret++; if (GNUNET_GC_set_configuration_value_string( config, errorContext, section, option, value) != 0) NSLog(@"error setting (%s:%s) [%s]\n", section, option, value); } if (section) free(section); if (option) free(option); if (value) free(value); } while (!feof(ioPipe)); fclose(ioPipe); } for (i = 0; i < [gnsOptionsArray count]; i++) free(toolArgs[2+i]); free(toolArgs); return ret; } - (int)saveConfig { BOOL didSave; didSave = NO; if (isPreauthorized) { if ([self privilegedSaveConfig] == 0) didSave = YES; } else { if (GNUNET_GC_write_configuration (config, configFilename) == 0) didSave = YES; } return didSave == YES ? 0 : 1; } - (int)privilegedSaveConfig { OSStatus status; AuthorizationFlags authFlags = kAuthorizationFlagDefaults; NSString *toolPath; char *toolArgs[] = { "writeConfig", configFilename, NULL }; FILE *ioPipe = NULL; NSBundle *toolBundle; int ret; toolBundle = [NSBundle bundleWithIdentifier:@"org.gnunet.GNUnet"]; toolPath = [[toolBundle resourcePath] stringByAppendingPathComponent:@"gnunet-macosx-tool"]; if (toolPath == nil) return -1; authFlags = kAuthorizationFlagDefaults; status = AuthorizationExecuteWithPrivileges(authRef, [toolPath UTF8String], authFlags, toolArgs, &ioPipe); ret = -1; if (status == errAuthorizationSuccess) { NSEnumerator *e = [gnsOptionsArray objectEnumerator]; id o; while (o = [e nextObject]) { char *section, *option, *value; section = strdup([(NSString *)o UTF8String]); option = section; while (*option != '\0' && *option != ':') option++; if (option == '\0') { free(section); continue; } option[0] = '\0'; option++; ret = GNUNET_GC_get_configuration_value_string(config, section, option, NULL, &value); if (ret != GNUNET_SYSERR) { output_string(ioPipe, section); output_string(ioPipe, option); output_string(ioPipe, value); GNUNET_free(value); } free(section); } output_string(ioPipe, NULL); do { char *value; value = input_string(ioPipe); if (value != NULL && strcmp(value, "OK") == 0) ret = 0; } while (!feof(ioPipe)); fclose(ioPipe); } return ret; } - (BOOL)initSetupView { if (setupView != nil) { [setupView removeFromSuperviewWithoutNeedingDisplay]; } setupView = [[GNUNETSetupView_class alloc] initWithConfig:config setupContext:gnsContext errorContext:errorContext maxWidth:768]; [[window contentView] addSubview:setupView]; [self updateWindowFrame]; [window setTitle:windowTitle]; [window center]; return YES; } - (void)updateWindowFrame { NSSize size; NSPoint origin; NSRect frame; size = [setupView frame].size; size.width += 2*16.0; size.height += 2*16.0; [[window contentView] setFrameSize:size]; origin.x = 16.0; origin.y = 16.0; [setupView setFrameOrigin:origin]; frame = [window frame]; frame.size = [window frameRectForContentRect:[[window contentView] frame]].size; frame.origin.y -= ((frame.size.height) - [window frame].size.height); [window setFrame:frame display:YES]; } - (NSString *)getFilenameFromOpenPanel { NSOpenPanel *openPanel; NSArray *filetypes = [NSArray arrayWithObjects:@"conf", nil]; int result; openPanel = [NSOpenPanel openPanel]; [openPanel setCanChooseFiles:YES]; [openPanel setCanChooseDirectories:NO]; [openPanel setAllowsMultipleSelection:NO]; result = [openPanel runModalForDirectory:nil file:nil types:filetypes]; if (result == NSOKButton) return [openPanel filename]; else return nil; } - (IBAction)selectedDaemonConfig:(id)sender { if (isDaemonConfig == YES) return; if (GNUNET_GC_test_dirty(config)) { actionBeforeAlert = GNSetupUserActionDaemonConfig; [self displaySaveAlert]; return; } [self loadAndDisplayConfigOfType:YES]; } - (IBAction)selectedClientConfig:(id)sender { if (isDaemonConfig == NO) return; if (GNUNET_GC_test_dirty(config)) { actionBeforeAlert = GNSetupUserActionClientConfig; [self displaySaveAlert]; return; } [self loadAndDisplayConfigOfType:NO]; } - (IBAction)saveChangesAction:(id)sender { if ([self saveConfig] != 0) [self displayErrorSavingAlert]; } - (void)loadAndDisplayConfigOfType:(BOOL)isDaemon { int ret; ret = [self loadConfigOfType:isDaemon fromFile:(isDaemon ? (defaultDaemonConfigFilename != NULL ? defaultDaemonConfigFilename : GNUNET_DEFAULT_DAEMON_CONFIG_FILE) : (defaultClientConfigFilename != NULL ? defaultClientConfigFilename : GNUNET_DEFAULT_CLIENT_CONFIG_FILE))]; if (ret != 0) return; [daemonConfigMenuItem setState:isDaemonConfig ? NSOnState : NSOffState]; [clientConfigMenuItem setState:isDaemonConfig ? NSOffState : NSOnState]; [self initSetupView]; } - (void)getDefaultsFrom:(struct GNUNET_GNS_TreeNode *)pos { struct GNUNET_GNS_TreeNode *child; char *val; int i; if (pos == NULL) return; i = 0; while ((child = pos->children[i]) != NULL) { switch (child->type & GNUNET_GNS_KIND_MASK) { case GNUNET_GNS_KIND_NODE: [self getDefaultsFrom:child]; break; case GNUNET_GNS_KIND_LEAF: if ((child->section == NULL) || (child->option == NULL)) break; if (GNUNET_NO == GNUNET_GC_have_configuration_value (config, child->section, child->option)) { val = GNUNET_GNS_get_default_value_as_string(child->type, &child->value); if (val != NULL) { GNUNET_GC_set_configuration_value_string (config, errorContext, child->section, child->option, val); GNUNET_free (val); } } break; default: NSLog(@"unknown GNS tree node\n"); break; } i++; } } - (void)buildOptionList:(NSMutableArray *)optionsArray from:(struct GNUNET_GNS_TreeNode *)pos { struct GNUNET_GNS_TreeNode *child; char *s; int i; if (pos == NULL) return; i = 0; while ((child = pos->children[i]) != NULL) { switch (child->type & GNUNET_GNS_KIND_MASK) { case GNUNET_GNS_KIND_NODE: [self buildOptionList:optionsArray from:child]; break; case GNUNET_GNS_KIND_LEAF: if ((child->section == NULL) || (child->option == NULL)) break; s = GNUNET_malloc(strlen(child->section) + 1 + strlen(child->option)+1); strcpy(s, child->section); strcat(s, ":"); strcat(s, child->option); [optionsArray addObject:[[NSString alloc] initWithUTF8String:s]]; GNUNET_free(s); break; default: NSLog(@"unknown GNS tree node\n"); break; } i++; } } - (void)displayErrorLoadingAlertWithInfo:(NSString *)info { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Error loading configuration!"]; if (info != nil) [alert setInformativeText:info]; [alert addButtonWithTitle:@"OK"]; [alert setAlertStyle:NSWarningAlertStyle]; [alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(errorAlertDidEnd: returnCode: contextInfo:) contextInfo:nil]; } - (void)displaySaveAlert { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Configuration changed. Save?"]; [alert addButtonWithTitle:@"Yes"]; [alert addButtonWithTitle:@"Cancel"]; [alert addButtonWithTitle:@"No"]; [alert setAlertStyle:NSWarningAlertStyle]; [alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(saveAlertDidEnd: returnCode: contextInfo:) contextInfo:nil]; } - (void)displayErrorSavingAlert { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Error saving configuration!"]; [alert addButtonWithTitle:@"OK"]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(errorAlertDidEnd: returnCode: contextInfo:) contextInfo:nil]; } - (void)saveAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo { [[alert window] orderOut:self]; if (returnCode == NSAlertFirstButtonReturn) { // Yes if ([self saveConfig] == 0) [self continueInterruptedAction]; else { actionBeforeAlert = GNSetupUserActionNone; [self displayErrorSavingAlert]; } } else if (returnCode == NSAlertThirdButtonReturn) { // No [self continueInterruptedAction]; } else // Cancel actionBeforeAlert = GNSetupUserActionNone; } - (void)errorAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo { [self continueInterruptedAction]; } - (void)continueInterruptedAction { GNSetupUserAction action; action = actionBeforeAlert; actionBeforeAlert = GNSetupUserActionNone; switch (action) { case GNSetupUserActionDaemonConfig: [self loadAndDisplayConfigOfType:YES]; break; case GNSetupUserActionClientConfig: [self loadAndDisplayConfigOfType:NO]; break; case GNSetupUserActionQuit: shouldQuit = YES; [NSApp terminate:self]; break; case GNSetupUserActionNone: break; default: NSLog(@"tried to continue unknown user action\n"); break; } } - (BOOL)isReadyToTerminate { if ([window firstResponder] != nil && [window makeFirstResponder:nil] == NO) return NO; if (GNUNET_GC_test_dirty(config) && shouldQuit == NO) { actionBeforeAlert = GNSetupUserActionQuit; [self displaySaveAlert]; return NO; } return YES; } - (BOOL)windowShouldClose:(id)sender { if ([self isReadyToTerminate] == NO) return NO; if (setupView != nil) { [setupView removeFromSuperviewWithoutNeedingDisplay]; } return YES; } - (void)windowWillClose:(NSNotification *)notification { [NSApp terminate:self]; } - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender { return [self isReadyToTerminate]; } - (void)applicationWillTerminate:(NSNotification *)notification { if (defaultDaemonConfigFilename) free(defaultDaemonConfigFilename); if (defaultClientConfigFilename) free(defaultClientConfigFilename); GNUNET_plugin_unload(cocoaSetupPlugin); if (gnsOptionsArray != nil) [gnsOptionsArray release]; if (gnsContext) GNUNET_GNS_free_specification(gnsContext); GNUNET_GC_free(config); GNUNET_GE_free_context(errorContext); } @end