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

Last change on this file since 409 was 409, checked in by Nicholas Riley, 12 years ago

Info.plists, VERSION.xcconfig: Using Dave Dribin's build configuration
trick to propagate ${CURRENT_MARKETING_VERSION}. Updated for 2.0d2.

InfoPlist?.strings: Remove unnecessary repeated localization; update
copyright date.

Read Me: Updated for 2.0d2.

FSAnywhere.[hm]: F-Script 2.0a2 is the minimum version. Remove
FSA_VERSION (it's propagated from CURRENT_MARKETING_VERSION during
build) and turn on FSA_DEBUG.

F-Script Anywhere.xcodeproj: Updated for Xcode 3, Leopard only, etc.
Development build is still broken on inject and Deployment app build
is currently -O0 for debugging.

FSAApp.mm: Grammar/formatting cleanups. Some annoying race conditions
on startup still exist.

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    if (task_for_pid(taskOfOurProcess, ourPid, &machPortForProcess) == KERN_SUCCESS) { // launchd should always be pid 1
230        mach_port_deallocate(taskOfOurProcess, machPortForProcess);
231    } else {
232        int result = NSRunInformationalAlertPanel(
233                                                  NSLocalizedString(@"Certificate not trusted", "Certificate not trusted alert title"),
234                                                  NSLocalizedString(@"Due to new security features in Leopard, F-Script Anywhere requires you to to trust the signature on the current application.\n\n"
235                                                                    "You have several options:\n\n"
236                                                                    "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\n"
237                                                                    "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\n"
238                                                                    @"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"),
239                                                  NSLocalizedString(@"Add certificate", "Add certificate button title"),
240                                                  NSLocalizedString(@"Quit", "Quit button title"),
241                                                  NULL);
242        switch (result) {
243            case NSAlertDefaultReturn:
244                NSString *certPath = [[NSBundle mainBundle] pathForResource:@"Certificate" ofType:@"cer"];
245                [[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:certPath]];
246                break;
247            case NSAlertAlternateReturn:
248                [self terminate: self];
249                break;
250            default:
251                break;
252        }       
253    }
254    patchController = new FSAPatchController(self);
255    patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"),
256                              (CFStringRef)PatchBundleName);
257   
258    NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator];
259    pid_t pid;
260    ProcessSerialNumber psn;
261    OSStatus err;
262   
263    while ( (pid = [[e nextObject] longValue]) != 0) {
264        err = GetProcessForPID(pid, &psn);
265       
266        if (err != noErr) {
267            NSLog(@"Can't get PSN for PID %ld", pid);
268            [appList applicationQuitWithProcessID: pid];
269        } else if (patchController->IsProcessPatched(&psn)) {
270            [appList didPatchProcessID: pid];
271        }
272    }
273
274    // yes, someone could move the framework while the app is running, but they deserve what they get.
275    NSFileManager *fileMgr = [NSFileManager defaultManager];
276    NSArray *libraryDirectories =
277        NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
278    NSString *libPath;
279    NSString *frameworkPath = NULL;
280    CFBundleRef frameworkBundle = NULL;
281    BOOL isDirectory;
282
283    e = [libraryDirectories objectEnumerator];
284    while ( (libPath = [e nextObject]) != nil) {
285        frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"]
286            stringByAppendingPathComponent: @"FScript.framework"];
287        if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) {
288            frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]);
289            [(id)frameworkBundle autorelease];
290            break;
291        }
292    }
293    FSALog(@"Framework bundle");
294    FSAShow(frameworkBundle);
295    [super finishLaunching];
296    if (frameworkBundle == NULL) {
297        int result = NSRunInformationalAlertPanel(
298            NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"),
299            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 '%@'.",
300                              "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
301            NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
302            NSLocalizedString(@"Continue", "Continue button title"),
303            NSLocalizedString(@"Quit", "Quit button title"),
304            FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
305        if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:nil]) return;
306    } else {
307        CFBundleRef includedBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"]]);
308        [(id)includedBundle autorelease];
309        UInt32 includedVersion = CFBundleGetVersionNumber(includedBundle);
310        UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
311        FSALog(@"Framework version 0x%x", version);
312        if(version < includedVersion){
313            if (version < FSA_FSCRIPT_MIN_VERSION){ // must force upgrade
314                NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey);
315                if (frameworkVersion == nil)
316                    frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version");
317                int result = NSRunInformationalAlertPanel(
318                                                          NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"),
319                                                          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 '%@'.",
320                                                                            "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"),
321                                                          NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
322                                                          NSLocalizedString(@"Continue", "Continue button title"),
323                                                          NSLocalizedString(@"Quit", "Quit button title"),
324                                                          FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion,
325                                                          FSA_FScriptURL, NSLocalizedString(@"Install F-Script", ""));
326                if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:frameworkPath]) return;               
327            } else {
328                NSNumber *lastChecked = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastCheckedVersion"];
329                if((version < includedVersion) && (!lastChecked || ([lastChecked unsignedIntValue] != version))){
330                    FSALog(@"Included framework in resources is newer than installed version.");
331                    int result = NSRunInformationalAlertPanel(
332                                                              NSLocalizedString(@"Newer Version of F-Script Framework Available", "Framework newer found alert title"),
333                                                              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.",
334                                                                                "Framework newer found alert message, 'Continue' button parameter"),
335                                                              NSLocalizedString(@"Upgrade F-Script Framework", "Upgrade button title"),
336                                                              NSLocalizedString(@"Continue", "Continue button title"),
337                                                              nil,
338                                                              NSLocalizedString(@"Upgrade", ""), NSLocalizedString(@"Continue", ""));
339                    if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:frameworkPath]) return;               
340                }
341            }
342        }
343        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInt:version] forKey:@"LastCheckedVersion"];
344    }
345
346    if(myAuthorizationRef){
347        AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
348        myAuthorizationRef = nil;
349    }
350   
351    NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
352    [workspaceNotificationCenter
353        addObserver: self
354           selector: @selector(applicationDidLaunch:)
355               name: NSWorkspaceWillLaunchApplicationNotification
356             object: nil];
357    [workspaceNotificationCenter
358        addObserver: self
359           selector: @selector(applicationDidTerminate:)
360               name: NSWorkspaceDidTerminateApplicationNotification
361             object: nil];
362   
363    [appListWindow makeKeyAndOrderFront: self];
364}
365
366- (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid;
367{
368    NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"),
369                      nil, nil, nil, appListWindow, self, nil, nil, nil,
370                      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"),
371                      pid, FSA_descriptionForOSStatus(err));
372}
373
374- (void)installBundleInAppWithPID:(pid_t)pid;
375{
376    if (pid == -1) return;
377       
378    ProcessSerialNumber psn;
379    OSStatus err = GetProcessForPID(pid, &psn);
380   
381    if (err == noErr)
382        err = patchController->PatchProcess(&psn);
383
384    if (err != noErr)
385        [self installationError: err inAppWithPID: pid];
386    else
387        [appList didPatchProcessID: pid];
388}
389
390- (void)loaderBundleMessage:(NSNotification *)notification;
391{
392    /*
393    if (error) {
394        [self installationError: error inAppWithPID: pid];
395    } else {
396        [appList didPatchProcessID: pid];
397    } */
398}
399
400- (IBAction)installBundleInSelectedApp:(id)sender;
401{
402    [self installBundleInAppWithPID: [appList selectedProcessID]];
403}
404
405// from dock menu only!
406- (IBAction)installBundleInFrontmostApp:(id)sender;
407{
408    NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
409    [self installBundleInAppWithPID: (pid_t)[sender tag]];
410}
411
412@end
413
414@implementation FSAApp (FSAPatchControllerDelegate)
415
416- (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid;
417{
418    [appList isPatchingProcessID: pid];
419}
420
421@end
422
423@implementation FSAApp (NSWorkspaceNotifications)
424
425- (void)applicationDidLaunch:(NSNotification *)notification;
426{
427    [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
428}
429
430- (void)applicationDidTerminate:(NSNotification *)notification;
431{
432    [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
433}
434
435@end
Note: See TracBrowser for help on using the repository browser.