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

Last change on this file since 391 was 342, checked in by rchin, 17 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.