source: trunk/Cocoa/F-Script Anywhere/Source/FSAApp.mm @ 342

Last change on this file since 342 was 342, checked in by rchin, 13 years ago

Leopard compatibility changes:

  • Removed some icon caching code that was causing crashes (not sure we really needed those optimizations anyway -- doesn't appear to affect performance)
  • Added code to automatically try to add certificate to keychain, for new code signing behavior (replaces previous procmod nonesense).
  • Note that the enclosed public certificate is mine, and so it will need to be signed by me. In the case that someone else wants to distribute this binary, please replace Certficiate.cer with your own public certificate, and then make sure o code sign the binary after it is built.
File size: 21.0 KB
Line 
1//
2//  FSAApp.m
3//  F-Script Anywhere
4//
5//  Created by Nicholas Riley on Fri Feb 01 2002.
6//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9/*
10
11 F-Script Anywhere is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 F-Script Anywhere is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with F-Script Anywhere; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
25*/
26
27#import "FSAApp.h"
28#import "FSAnywhere.h"
29#import <CoreFoundation/CoreFoundation.h>
30#import <ApplicationServices/ApplicationServices.h>
31#import <Security/AuthorizationTags.h>
32#include <mach/mach.h>
33
34NSString * const PatchBundleIdentifier = @"net.sabi.FScriptAnywhere";
35NSString * const PatchBundleName = @"F-Script Anywhere.bundle";
36
37typedef struct {
38    OSStatus err;
39    NSString * const desc;
40} errRec, errList[];
41
42// These are just educated guesses based on the errors I've seen; I haven't seen these documented anywhere!
43static errList ERRS = { // XXX change for mach_inject/SCPatch
44    { 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" },
45    { 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" },
46    { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script Anywhere's dock menu to install" },
47    { fnfErr, @"F-Script Anywhere was unable to locate its component to install in the application.  Please try reinstalling F-Script Anywhere" },
48    { cfragDupRegistrationErr, @"another running copy of F-Script Anywhere is already installed in the application"},
49    { 0, nil }
50};
51
52NSMutableDictionary *FSA_errors;
53
54NSString * FSA_descriptionForOSStatus(OSStatus err)
55{
56    NSString *desc = nil;
57   
58    if (FSA_errors != nil)
59        desc = [FSA_errors objectForKey: [NSNumber numberWithLong: err]];
60   
61    if (desc != nil)
62        return desc;
63   
64    return [NSString stringWithFormat: NSLocalizedString(@"an error of type %ld occurred", "Unknown error message used after 'XXX happened because'"), err];
65}
66
67FSAPatchController::FSAPatchController(FSAApp *fsaApp)
68{
69    mApp = fsaApp;
70}
71
72void FSAPatchController::PatchNotification(ProcessSerialNumber *inPSN, OSType inCreator, OSType type, CFStringRef name, UInt32 flags) {
73    pid_t pid;
74    OSStatus err = GetProcessPID(inPSN, &pid);
75   
76    NSCAssert3(err == noErr, @"PatchNotification can't get PID for PSN %ld.%ld: error %ld", inPSN->highLongOfPSN, inPSN->lowLongOfPSN, err);
77   
78    FSALog(@"Got patch notification for %@ (pid %d)", (NSString *)name, pid);
79    [mApp controllerIsPatchingApplicationWithProcessID: pid];
80}
81
82// XXX should remove?
83void FSAPatchController::ReceiveMessage(const AppleEvent *theAE) {
84    OSErr err = noErr;
85    Size actualSize;
86    DescType actualType;
87    OSType eventClass, eventID;
88   
89    if ((err = AEGetAttributePtr(theAE, keyEventClassAttr, typeType, &actualType, &eventClass, sizeof(OSType), &actualSize)) != noErr)
90        return;
91    if (eventClass != kSCMessageClass)
92        return;
93
94    if ((err = AEGetAttributePtr(theAE, keyEventIDAttr, typeType, &actualType, &eventID, sizeof(OSType), &actualSize)) != noErr)
95        return;
96 
97#if 0
98    if (eventID == kSCLoadResult) {
99        OSStatus
100        (err = AEGetParamPtr(theAE, keyError, typeInteger, &actualType, &error, sizeof(long), &actualSize)) == noErr &&
101        (err = AEGetParamPtr(theAE, keyBundleID, typeText, &actualType, bundleID, sizeof(bundleID), &actualSize)) == noErr)
102        bundleID[actualSize] = 0;
103        printf("patch bundle %s loaded with error status %d\n", bundleID, error);
104    }
105#endif
106   
107
108}
109
110
111@implementation FSAApp
112
113+ (void)initialize;
114{
115    errRec *rec;
116
117    FSA_errors = [[NSMutableDictionary alloc] init];
118    for (rec = &(ERRS[0]) ; rec->err != 0 ; rec++)
119        [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->err]];
120}
121
122-(void)installFScriptAtPath:(NSString *)installPath
123{
124    OSStatus myStatus;
125   
126    if(installPath){
127        if([installPath hasSuffix:@"FScript.framework"]){
128            installPath = [installPath stringByDeletingLastPathComponent];
129            NSLog(@"new ip %@", installPath);
130        }
131    }
132   
133    if(!installPath){
134        switch([[NSAlert alertWithMessageText:@"Install F-Script"
135                                defaultButton:@"Current User"
136                              alternateButton:@"All Users"
137                                  otherButton:nil
138                    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]){
139            case NSAlertDefaultReturn:  // Current user
140                installPath = [@"~/Library/Frameworks" stringByExpandingTildeInPath];
141                break;
142            case NSAlertAlternateReturn: // All users
143                installPath = @"/Library/Frameworks";
144                break;
145            default:
146                break;
147        }       
148    }
149   
150    NSString *fromPath = [[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"];
151    if(![[NSFileManager defaultManager] fileExistsAtPath:installPath]){
152        if ([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){
153            [[NSFileManager defaultManager] createDirectoryAtPath:installPath
154                                                       attributes:nil];
155        } else {
156            [self createAuthorization];
157            NSString *myToolPath = @"/bin/mkdir";
158            char *myArguments[] = { (char *)[installPath UTF8String], NULL };
159            myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
160                                                          myFlags, myArguments,
161                                                          NULL);
162        }
163    }
164    if([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){
165        [[NSFileManager defaultManager] copyPath:fromPath toPath:[installPath stringByAppendingString:@"/FScript.framework"] handler:nil];
166    } else {
167        [self createAuthorization];
168        NSString *myToolPath = @"/bin/cp";
169        char *myArguments[] = { "-R", "-f", (char *)[fromPath UTF8String], (char *)[installPath UTF8String], NULL };
170        myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
171                                                      myFlags, myArguments,
172                                                      NULL);
173    }   
174}
175
176- (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result withFrameworkPath:(NSString *)frameworkPath
177{
178    switch (result) {
179        case NSAlertOtherReturn:
180            [self terminate: self];
181            return false;
182        case NSAlertAlternateReturn:
183            return true;
184        case NSAlertDefaultReturn:  /* Install from resources */
185            [self installFScriptAtPath:frameworkPath];
186            return true;
187        default:
188            [self terminate: self];
189            return false;
190    }
191}
192
193-(BOOL)createAuthorization
194{
195    if(myAuthorizationRef)
196        return YES;
197   
198    OSStatus myStatus;
199    myFlags = kAuthorizationFlagDefaults;
200   
201    myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
202                                   myFlags, &myAuthorizationRef);
203    if(myStatus != errAuthorizationSuccess)
204        return NO;
205   
206    AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
207    AuthorizationRights myRights = {1, &myItems};
208   
209    myFlags = kAuthorizationFlagDefaults
210        | kAuthorizationFlagInteractionAllowed
211        | kAuthorizationFlagPreAuthorize
212        | kAuthorizationFlagExtendRights;
213    myStatus = AuthorizationCopyRights(myAuthorizationRef, &myRights,
214                                       NULL, myFlags, NULL);
215   
216    if(myStatus != errAuthorizationSuccess)
217        return NO;
218   
219    myFlags = kAuthorizationFlagDefaults;
220    return YES;
221}
222
223- (void)finishLaunching
224{
225    mach_port_t     taskOfOurProcess = mach_task_self();
226    mach_port_t     machPortForProcess;
227    /* under new rules for task_for_pid, only processes with proper permissions can call task_for_pid successfullly */
228    int ourPid = [[NSProcessInfo processInfo] processIdentifier];
229    NSLog(@"our pid %d", ourPid);
230    if(task_for_pid(taskOfOurProcess, ourPid, &machPortForProcess) == KERN_SUCCESS){ // launchd should always be pid 1
231        mach_port_deallocate(taskOfOurProcess, machPortForProcess);
232    } else {
233        int result = NSRunInformationalAlertPanel(
234                                                  NSLocalizedString(@"Certificate not trusted", "Framework not found alert title"),
235                                                  NSLocalizedString(@"Due to new security features in Leopard, F-Script Anywhere requires you to to trust the signature on the current application. "
236                                                                    "You have several options:\n"
237                                                                    "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"
238                                                                    "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"
239                                                                    @"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"),
240                                                  NSLocalizedString(@"Add certificate", "'add certificate button title"),
241                                                  NSLocalizedString(@"Quit", "Quit button title"),
242                                                  NULL);
243        switch (result) {
244            case NSAlertDefaultReturn:
245                NSString *certPath = [[NSBundle mainBundle] pathForResource:@"Certificate" ofType:@"cer"];
246                [[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:certPath]];
247                break;
248            case NSAlertAlternateReturn:
249                [self terminate: self];
250                break;
251            default:
252                break;
253        }       
254    }
255    patchController = new FSAPatchController(self);
256    patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"),
257                              (CFStringRef)PatchBundleName);
258   
259    NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator];
260    pid_t pid;
261    ProcessSerialNumber psn;
262    OSStatus err;
263   
264    while ( (pid = [[e nextObject] longValue]) != 0) {
265        err = GetProcessForPID(pid, &psn);
266       
267        if (err != noErr) {
268            NSLog(@"Can't get PSN for PID %ld", pid);
269            [appList applicationQuitWithProcessID: pid];
270        } else if (patchController->IsProcessPatched(&psn)) {
271            [appList didPatchProcessID: pid];
272        }
273    }
274
275    // yes, someone could move the framework while the app is running, but they deserve what they get.
276    NSFileManager *fileMgr = [NSFileManager defaultManager];
277    NSArray *libraryDirectories =
278        NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
279    NSString *libPath;
280    NSString *frameworkPath = NULL;
281    CFBundleRef frameworkBundle = NULL;
282    BOOL isDirectory;
283
284    e = [libraryDirectories objectEnumerator];
285    while ( (libPath = [e nextObject]) != nil) {
286        frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"]
287            stringByAppendingPathComponent: @"FScript.framework"];
288        if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) {
289            frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]);
290            [(id)frameworkBundle autorelease];
291            break;
292        }
293    }
294    FSALog(@"Framework bundle");
295    FSAShow(frameworkBundle);
296    [super finishLaunching];
297    if (frameworkBundle == NULL) {
298        int result = NSRunInformationalAlertPanel(
299            NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"),
300            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 '%@'.",
301                              "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
302            NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
303            NSLocalizedString(@"Continue", "Continue button title"),
304            NSLocalizedString(@"Quit", "Quit button title"),
305            FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
306        if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:nil]) return;
307    } else {
308        CFBundleRef includedBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"]]);
309        [(id)includedBundle autorelease];
310        UInt32 includedVersion = CFBundleGetVersionNumber(includedBundle);
311        UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
312        FSALog(@"Framework version 0x%x", version);
313        if(version < includedVersion){
314            if (version < FSA_FSCRIPT_MIN_VERSION){ // must force upgrade
315                NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey);
316                if (frameworkVersion == nil)
317                    frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version");
318                int result = NSRunInformationalAlertPanel(
319                                                          NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"),
320                                                          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 '%@'.",
321                                                                            "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"),
322                                                          NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
323                                                          NSLocalizedString(@"Continue", "Continue button title"),
324                                                          NSLocalizedString(@"Quit", "Quit button title"),
325                                                          FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion,
326                                                          FSA_FScriptURL, NSLocalizedString(@"Install F-Script", ""));
327                if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:frameworkPath]) return;               
328            } else {
329                NSNumber *lastChecked = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastCheckedVersion"];
330                if((version < includedVersion) && (!lastChecked || ([lastChecked unsignedIntValue] != version))){
331                    FSALog(@"Included framework in resources is newer than installed version.");
332                    int result = NSRunInformationalAlertPanel(
333                                                              NSLocalizedString(@"Newer Version of F-Script Framework Available", "Framework newer found alert title"),
334                                                              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.",
335                                                                                "Framework newer found alert message, 'Continue' button parameter"),
336                                                              NSLocalizedString(@"Upgrade F-Script Framework", "Upgrade button title"),
337                                                              NSLocalizedString(@"Continue", "Continue button title"),
338                                                              nil,
339                                                              NSLocalizedString(@"Upgrade", ""), NSLocalizedString(@"Continue", ""));
340                    if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:frameworkPath]) return;               
341                }
342            }
343        }
344        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInt:version] forKey:@"LastCheckedVersion"];
345    }
346
347    if(myAuthorizationRef){
348        AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
349        myAuthorizationRef = nil;
350    }
351   
352    NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
353    [workspaceNotificationCenter
354        addObserver: self
355           selector: @selector(applicationDidLaunch:)
356               name: NSWorkspaceWillLaunchApplicationNotification
357             object: nil];
358    [workspaceNotificationCenter
359        addObserver: self
360           selector: @selector(applicationDidTerminate:)
361               name: NSWorkspaceDidTerminateApplicationNotification
362             object: nil];
363   
364    [appListWindow makeKeyAndOrderFront: self];
365}
366
367- (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid;
368{
369    NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"),
370                      nil, nil, nil, appListWindow, self, nil, nil, nil,
371                      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"),
372                      pid, FSA_descriptionForOSStatus(err));
373}
374
375- (void)installBundleInAppWithPID:(pid_t)pid;
376{
377    if (pid == -1) return;
378       
379    ProcessSerialNumber psn;
380    OSStatus err = GetProcessForPID(pid, &psn);
381   
382    if (err == noErr)
383        err = patchController->PatchProcess(&psn);
384
385    if (err != noErr)
386        [self installationError: err inAppWithPID: pid];
387    else
388        [appList didPatchProcessID: pid];
389}
390
391- (void)loaderBundleMessage:(NSNotification *)notification;
392{
393    /*
394    if (error) {
395        [self installationError: error inAppWithPID: pid];
396    } else {
397        [appList didPatchProcessID: pid];
398    } */
399}
400
401- (IBAction)installBundleInSelectedApp:(id)sender;
402{
403    [self installBundleInAppWithPID: [appList selectedProcessID]];
404}
405
406// from dock menu only!
407- (IBAction)installBundleInFrontmostApp:(id)sender;
408{
409    NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
410    [self installBundleInAppWithPID: (pid_t)[sender tag]];
411}
412
413@end
414
415@implementation FSAApp (FSAPatchControllerDelegate)
416
417- (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid;
418{
419    [appList isPatchingProcessID: pid];
420}
421
422@end
423
424@implementation FSAApp (NSWorkspaceNotifications)
425
426- (void)applicationDidLaunch:(NSNotification *)notification;
427{
428    [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
429}
430
431- (void)applicationDidTerminate:(NSNotification *)notification;
432{
433    [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
434}
435
436@end
Note: See TracBrowser for help on using the repository browser.