// // FSAApp.m // F-Script Anywhere // // Created by Nicholas Riley on Fri Feb 01 2002. // Copyright (c) 2002 Nicholas Riley. All rights reserved. // /* F-Script Anywhere 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. F-Script Anywhere 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 F-Script Anywhere; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #import "FSAApp.h" #import "FSAnywhere.h" #import #import #import #include NSString * const PatchBundleIdentifier = @"net.sabi.FScriptAnywhere"; NSString * const PatchBundleName = @"F-Script Anywhere.bundle"; typedef struct { OSStatus err; NSString * const desc; } errRec, errList[]; // These are just educated guesses based on the errors I've seen; I haven't seen these documented anywhere! static errList ERRS = { // XXX change for mach_inject/SCPatch { 5, @"F-Script Anywhere must be installed in a Cocoa application running as the current user.\n\nYou may be attempting to install in a setuid application, which is not supported" }, { 11, @"F-Script Anywhere cannot be installed in itself.\n\nIf you wish to install F-Script Anywhere in itself, make a copy of the F-Script Anywhere application and install one copy into the other" }, { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script Anywhere's dock menu to install" }, { fnfErr, @"F-Script Anywhere was unable to locate its component to install in the application. Please try reinstalling F-Script Anywhere" }, { cfragDupRegistrationErr, @"another running copy of F-Script Anywhere is already installed in the application"}, { 0, nil } }; NSMutableDictionary *FSA_errors; NSString * FSA_descriptionForOSStatus(OSStatus err) { NSString *desc = nil; if (FSA_errors != nil) desc = [FSA_errors objectForKey: [NSNumber numberWithLong: err]]; if (desc != nil) return desc; return [NSString stringWithFormat: NSLocalizedString(@"an error of type %ld occurred", "Unknown error message used after 'XXX happened because'"), err]; } FSAPatchController::FSAPatchController(FSAApp *fsaApp) { mApp = fsaApp; } void FSAPatchController::PatchNotification(ProcessSerialNumber *inPSN, OSType inCreator, OSType type, CFStringRef name, UInt32 flags) { pid_t pid; OSStatus err = GetProcessPID(inPSN, &pid); NSCAssert3(err == noErr, @"PatchNotification can't get PID for PSN %ld.%ld: error %ld", inPSN->highLongOfPSN, inPSN->lowLongOfPSN, err); FSALog(@"Got patch notification for %@ (pid %d)", (NSString *)name, pid); [mApp controllerIsPatchingApplicationWithProcessID: pid]; } // XXX should remove? void FSAPatchController::ReceiveMessage(const AppleEvent *theAE) { OSErr err = noErr; Size actualSize; DescType actualType; OSType eventClass, eventID; if ((err = AEGetAttributePtr(theAE, keyEventClassAttr, typeType, &actualType, &eventClass, sizeof(OSType), &actualSize)) != noErr) return; if (eventClass != kSCMessageClass) return; if ((err = AEGetAttributePtr(theAE, keyEventIDAttr, typeType, &actualType, &eventID, sizeof(OSType), &actualSize)) != noErr) return; #if 0 if (eventID == kSCLoadResult) { OSStatus (err = AEGetParamPtr(theAE, keyError, typeInteger, &actualType, &error, sizeof(long), &actualSize)) == noErr && (err = AEGetParamPtr(theAE, keyBundleID, typeText, &actualType, bundleID, sizeof(bundleID), &actualSize)) == noErr) bundleID[actualSize] = 0; printf("patch bundle %s loaded with error status %d\n", bundleID, error); } #endif } @implementation FSAApp + (void)initialize; { errRec *rec; FSA_errors = [[NSMutableDictionary alloc] init]; for (rec = &(ERRS[0]) ; rec->err != 0 ; rec++) [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->err]]; } -(void)installFScriptAtPath:(NSString *)installPath { OSStatus myStatus; if(installPath){ if([installPath hasSuffix:@"FScript.framework"]){ installPath = [installPath stringByDeletingLastPathComponent]; NSLog(@"new ip %@", installPath); } } if(!installPath){ switch([[NSAlert alertWithMessageText:@"Install F-Script" defaultButton:@"Current User" alternateButton:@"All Users" otherButton:nil informativeTextWithFormat:@"No previous version of F-Script found. You can choose to install for just your current user (~/Library/Frameworks) or for all users (/Library/Frameworks)."] runModal]){ case NSAlertDefaultReturn: // Current user installPath = [@"~/Library/Frameworks" stringByExpandingTildeInPath]; break; case NSAlertAlternateReturn: // All users installPath = @"/Library/Frameworks"; break; default: break; } } NSString *fromPath = [[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"]; if(![[NSFileManager defaultManager] fileExistsAtPath:installPath]){ if ([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){ [[NSFileManager defaultManager] createDirectoryAtPath:installPath attributes:nil]; } else { [self createAuthorization]; NSString *myToolPath = @"/bin/mkdir"; char *myArguments[] = { (char *)[installPath UTF8String], NULL }; myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String], myFlags, myArguments, NULL); } } if([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){ [[NSFileManager defaultManager] copyPath:fromPath toPath:[installPath stringByAppendingString:@"/FScript.framework"] handler:nil]; } else { [self createAuthorization]; NSString *myToolPath = @"/bin/cp"; char *myArguments[] = { "-R", "-f", (char *)[fromPath UTF8String], (char *)[installPath UTF8String], NULL }; myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String], myFlags, myArguments, NULL); } } - (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result withFrameworkPath:(NSString *)frameworkPath { switch (result) { case NSAlertOtherReturn: [self terminate: self]; return false; case NSAlertAlternateReturn: return true; case NSAlertDefaultReturn: /* Install from resources */ [self installFScriptAtPath:frameworkPath]; return true; default: [self terminate: self]; return false; } } -(BOOL)createAuthorization { if(myAuthorizationRef) return YES; OSStatus myStatus; myFlags = kAuthorizationFlagDefaults; myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, myFlags, &myAuthorizationRef); if(myStatus != errAuthorizationSuccess) return NO; AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0}; AuthorizationRights myRights = {1, &myItems}; myFlags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; myStatus = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags, NULL); if(myStatus != errAuthorizationSuccess) return NO; myFlags = kAuthorizationFlagDefaults; return YES; } - (void)finishLaunching { mach_port_t taskOfOurProcess = mach_task_self(); mach_port_t machPortForProcess; /* under new rules for task_for_pid, only processes with proper permissions can call task_for_pid successfullly */ int ourPid = [[NSProcessInfo processInfo] processIdentifier]; NSLog(@"our pid %d", ourPid); if(task_for_pid(taskOfOurProcess, ourPid, &machPortForProcess) == KERN_SUCCESS){ // launchd should always be pid 1 mach_port_deallocate(taskOfOurProcess, machPortForProcess); } else { int result = NSRunInformationalAlertPanel( NSLocalizedString(@"Certificate not trusted", "Framework not found alert title"), NSLocalizedString(@"Due to new security features in Leopard, F-Script Anywhere requires you to to trust the signature on the current application. " "You have several options:\n" "1. You can add the signing certificate automatically to your keychain, in which case you should click \"OK\" in the next dialog box to add the certificate to your keychain, and then \"Always Trust.\"\n" "2. You can quit F-Script Anywhere, create a signing authority on your local machine, trust it, and then sign the application binary yourself.\n" @"Note that if you add the certificate properly and you still get an F-Script Anywhere error when injecting, you may need to restart your computer to clear the proper keychain caches.", @"no certificate warning message"), NSLocalizedString(@"Add certificate", "'add certificate button title"), NSLocalizedString(@"Quit", "Quit button title"), NULL); switch (result) { case NSAlertDefaultReturn: NSString *certPath = [[NSBundle mainBundle] pathForResource:@"Certificate" ofType:@"cer"]; [[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:certPath]]; break; case NSAlertAlternateReturn: [self terminate: self]; break; default: break; } } patchController = new FSAPatchController(self); patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"), (CFStringRef)PatchBundleName); NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator]; pid_t pid; ProcessSerialNumber psn; OSStatus err; while ( (pid = [[e nextObject] longValue]) != 0) { err = GetProcessForPID(pid, &psn); if (err != noErr) { NSLog(@"Can't get PSN for PID %ld", pid); [appList applicationQuitWithProcessID: pid]; } else if (patchController->IsProcessPatched(&psn)) { [appList didPatchProcessID: pid]; } } // yes, someone could move the framework while the app is running, but they deserve what they get. NSFileManager *fileMgr = [NSFileManager defaultManager]; NSArray *libraryDirectories = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE); NSString *libPath; NSString *frameworkPath = NULL; CFBundleRef frameworkBundle = NULL; BOOL isDirectory; e = [libraryDirectories objectEnumerator]; while ( (libPath = [e nextObject]) != nil) { frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"] stringByAppendingPathComponent: @"FScript.framework"]; if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) { frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]); [(id)frameworkBundle autorelease]; break; } } FSALog(@"Framework bundle"); FSAShow(frameworkBundle); [super finishLaunching]; if (frameworkBundle == NULL) { int result = NSRunInformationalAlertPanel( NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"), NSLocalizedString(@"F-Script Anywhere requires the F-Script framework be installed in a Frameworks directory, such as ~/Library/Frameworks or /Library/Frameworks.\n\nTo download F-Script, please visit its Web site\n%@.\n\nThis package also includes a copy of F-Script for your convenience that can be installed easily by clicking below. Note that there may be a more recent version of F-Script available from the F-Script web site.\n\nIf you believe this message is in error, click '%@'.", "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"), NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"), NSLocalizedString(@"Continue", "Continue button title"), NSLocalizedString(@"Quit", "Quit button title"), FSA_FScriptURL, NSLocalizedString(@"Continue", "")); if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:nil]) return; } else { CFBundleRef includedBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"]]); [(id)includedBundle autorelease]; UInt32 includedVersion = CFBundleGetVersionNumber(includedBundle); UInt32 version = CFBundleGetVersionNumber(frameworkBundle); FSALog(@"Framework version 0x%x", version); if(version < includedVersion){ if (version < FSA_FSCRIPT_MIN_VERSION){ // must force upgrade NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey); if (frameworkVersion == nil) frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version"); int result = NSRunInformationalAlertPanel( NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"), NSLocalizedString(@"F-Script Anywhere %s requires version %@ or later of the F-Script framework be installed in a Frameworks directory, such as ~/Library/Frameworks or /Library/Frameworks. The installed version is %@.\n\nTo upgrade F-Script to the latest version, please visit its Web site\n%@.\n\nThis package also includes a copy of F-Script for your convenience that can be installed easily by clicking below. Note that there may be a more recent version of F-Script available from the F-Script web site.\n\nTo use this version of F-Script regardless of the problems you may experience, click '%@'.", "Framework too old alert message, FSA version parameter, minimum F-Script version parameter, current F-Script version parameter, F-Script Web site URL parameter, 'Continue' button parameter"), NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"), NSLocalizedString(@"Continue", "Continue button title"), NSLocalizedString(@"Quit", "Quit button title"), FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion, FSA_FScriptURL, NSLocalizedString(@"Install F-Script", "")); if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:frameworkPath]) return; } else { NSNumber *lastChecked = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastCheckedVersion"]; if((version < includedVersion) && (!lastChecked || ([lastChecked unsignedIntValue] != version))){ FSALog(@"Included framework in resources is newer than installed version."); int result = NSRunInformationalAlertPanel( NSLocalizedString(@"Newer Version of F-Script Framework Available", "Framework newer found alert title"), NSLocalizedString(@"F-Script Anywhere is bundled with a newer version of the F-Script framework. If you wish, F-Script Anywhere can upgrade your framework version.", "Framework newer found alert message, 'Continue' button parameter"), NSLocalizedString(@"Upgrade F-Script Framework", "Upgrade button title"), NSLocalizedString(@"Continue", "Continue button title"), nil, NSLocalizedString(@"Upgrade", ""), NSLocalizedString(@"Continue", "")); if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:frameworkPath]) return; } } } [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInt:version] forKey:@"LastCheckedVersion"]; } if(myAuthorizationRef){ AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults); myAuthorizationRef = nil; } NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; [workspaceNotificationCenter addObserver: self selector: @selector(applicationDidLaunch:) name: NSWorkspaceWillLaunchApplicationNotification object: nil]; [workspaceNotificationCenter addObserver: self selector: @selector(applicationDidTerminate:) name: NSWorkspaceDidTerminateApplicationNotification object: nil]; [appListWindow makeKeyAndOrderFront: self]; } - (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid; { NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"), nil, nil, nil, appListWindow, self, nil, nil, nil, NSLocalizedString(@"F-Script Anywhere was unable to install itself in the selected application (process ID %d), because %@.\n\nThe application may have crashed; restart it if needed.", "Unable to install alert message, PID parameter, 'XXX happened because' explanation parameter"), pid, FSA_descriptionForOSStatus(err)); } - (void)installBundleInAppWithPID:(pid_t)pid; { if (pid == -1) return; ProcessSerialNumber psn; OSStatus err = GetProcessForPID(pid, &psn); if (err == noErr) err = patchController->PatchProcess(&psn); if (err != noErr) [self installationError: err inAppWithPID: pid]; else [appList didPatchProcessID: pid]; } - (void)loaderBundleMessage:(NSNotification *)notification; { /* if (error) { [self installationError: error inAppWithPID: pid]; } else { [appList didPatchProcessID: pid]; } */ } - (IBAction)installBundleInSelectedApp:(id)sender; { [self installBundleInAppWithPID: [appList selectedProcessID]]; } // from dock menu only! - (IBAction)installBundleInFrontmostApp:(id)sender; { NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender); [self installBundleInAppWithPID: (pid_t)[sender tag]]; } @end @implementation FSAApp (FSAPatchControllerDelegate) - (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid; { [appList isPatchingProcessID: pid]; } @end @implementation FSAApp (NSWorkspaceNotifications) - (void)applicationDidLaunch:(NSNotification *)notification; { [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]]; } - (void)applicationDidTerminate:(NSNotification *)notification; { [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]]; } @end