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

Last change on this file since 463 was 409, checked in by Nicholas Riley, 17 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
RevLine 
[153]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"
[229]29#import <CoreFoundation/CoreFoundation.h>
30#import <ApplicationServices/ApplicationServices.h>
31#import <Security/AuthorizationTags.h>
[231]32#include <mach/mach.h>
[153]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" },
[342]46 { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script Anywhere's dock menu to install" },
[153]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;
[217]96
97#if 0
[153]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 }
[217]105#endif
[153]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
[231]122-(void)installFScriptAtPath:(NSString *)installPath
[230]123{
124 OSStatus myStatus;
125
126 if(installPath){
[231]127 if([installPath hasSuffix:@"FScript.framework"]){
128 installPath = [installPath stringByDeletingLastPathComponent];
129 NSLog(@"new ip %@", installPath);
130 }
[230]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
[231]140 installPath = [@"~/Library/Frameworks" stringByExpandingTildeInPath];
[230]141 break;
142 case NSAlertAlternateReturn: // All users
[231]143 installPath = @"/Library/Frameworks";
[230]144 break;
145 default:
146 break;
147 }
148 }
149
150 NSString *fromPath = [[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"];
[231]151 if(![[NSFileManager defaultManager] fileExistsAtPath:installPath]){
[230]152 if ([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){
[231]153 [[NSFileManager defaultManager] createDirectoryAtPath:installPath
[230]154 attributes:nil];
155 } else {
156 [self createAuthorization];
157 NSString *myToolPath = @"/bin/mkdir";
[231]158 char *myArguments[] = { (char *)[installPath UTF8String], NULL };
[230]159 myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
160 myFlags, myArguments,
161 NULL);
162 }
163 }
[231]164 if([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){
165 [[NSFileManager defaultManager] copyPath:fromPath toPath:[installPath stringByAppendingString:@"/FScript.framework"] handler:nil];
[230]166 } else {
167 [self createAuthorization];
168 NSString *myToolPath = @"/bin/cp";
[231]169 char *myArguments[] = { "-R", "-f", (char *)[fromPath UTF8String], (char *)[installPath UTF8String], NULL };
[230]170 myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
171 myFlags, myArguments,
172 NULL);
173 }
174}
175
[231]176- (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result withFrameworkPath:(NSString *)frameworkPath
[153]177{
178 switch (result) {
179 case NSAlertOtherReturn:
[231]180 [self terminate: self];
181 return false;
182 case NSAlertAlternateReturn:
[153]183 return true;
[231]184 case NSAlertDefaultReturn: /* Install from resources */
185 [self installFScriptAtPath:frameworkPath];
[230]186 return true;
[153]187 default:
188 [self terminate: self];
189 return false;
190 }
191}
192
[230]193-(BOOL)createAuthorization
[229]194{
[230]195 if(myAuthorizationRef)
196 return YES;
197
[229]198 OSStatus myStatus;
[230]199 myFlags = kAuthorizationFlagDefaults;
[229]200
201 myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
202 myFlags, &myAuthorizationRef);
203 if(myStatus != errAuthorizationSuccess)
[230]204 return NO;
[229]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)
[230]217 return NO;
[229]218
219 myFlags = kAuthorizationFlagDefaults;
[230]220 return YES;
221}
222
[153]223- (void)finishLaunching
224{
[231]225 mach_port_t taskOfOurProcess = mach_task_self();
226 mach_port_t machPortForProcess;
[342]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];
[409]229 if (task_for_pid(taskOfOurProcess, ourPid, &machPortForProcess) == KERN_SUCCESS) { // launchd should always be pid 1
[342]230 mach_port_deallocate(taskOfOurProcess, machPortForProcess);
[231]231 } else {
[342]232 int result = NSRunInformationalAlertPanel(
[409]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"
[342]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"),
[409]239 NSLocalizedString(@"Add certificate", "Add certificate button title"),
[342]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 }
[229]253 }
[153]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;
[231]279 NSString *frameworkPath = NULL;
[153]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"),
[230]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 '%@'.",
[153]300 "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
[230]301 NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
[153]302 NSLocalizedString(@"Continue", "Continue button title"),
[231]303 NSLocalizedString(@"Quit", "Quit button title"),
[153]304 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
[231]305 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:nil]) return;
[153]306 } else {
[231]307 CFBundleRef includedBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"]]);
308 [(id)includedBundle autorelease];
309 UInt32 includedVersion = CFBundleGetVersionNumber(includedBundle);
[153]310 UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
311 FSALog(@"Framework version 0x%x", version);
[231]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 }
[153]342 }
[231]343 [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInt:version] forKey:@"LastCheckedVersion"];
[153]344 }
345
[230]346 if(myAuthorizationRef){
347 AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
348 myAuthorizationRef = nil;
349 }
350
[153]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);
[217]383
[153]384 if (err != noErr)
385 [self installationError: err inAppWithPID: pid];
[222]386 else
387 [appList didPatchProcessID: pid];
[153]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.