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

Last change on this file since 229 was 229, checked in by rchin, 16 years ago

Created DSCL framework from DSCL open source code (distributed as a part of Darwin from Apple). This is for programmatically testing and adding entries to directory services, so that I can add the user to the procmod group. The modified DSCL code is in a zip file available for people to use (had to add some methods to provide better read functionality). Also has a binary that does the adding to procmod group with escalated privileges when the user authenticates via the authorization services security framework. Updated the readme to reflect this additional step for building.

File size: 16.2 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 <DSCL/PathManager.h>
30#import <CoreFoundation/CoreFoundation.h>
31#import <ApplicationServices/ApplicationServices.h>
32#import <Security/Authorization.h>
33#import <Security/AuthorizationTags.h>
34
35NSString * const PatchBundleIdentifier = @"net.sabi.FScriptAnywhere";
36NSString * const PatchBundleName = @"F-Script Anywhere.bundle";
37
38typedef struct {
39 OSStatus err;
40 NSString * const desc;
41} errRec, errList[];
42
43// These are just educated guesses based on the errors I've seen; I haven't seen these documented anywhere!
44static errList ERRS = { // XXX change for mach_inject/SCPatch
45 { 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" },
46 { 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" },
47 { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script AnywhereÕs dock menu to install" },
48 { fnfErr, @"F-Script Anywhere was unable to locate its component to install in the application. Please try reinstalling F-Script Anywhere" },
49 { cfragDupRegistrationErr, @"another running copy of F-Script Anywhere is already installed in the application"},
50 { 0, nil }
51};
52
53NSMutableDictionary *FSA_errors;
54
55NSString * FSA_descriptionForOSStatus(OSStatus err)
56{
57 NSString *desc = nil;
58
59 if (FSA_errors != nil)
60 desc = [FSA_errors objectForKey: [NSNumber numberWithLong: err]];
61
62 if (desc != nil)
63 return desc;
64
65 return [NSString stringWithFormat: NSLocalizedString(@"an error of type %ld occurred", "Unknown error message used after 'XXX happened because'"), err];
66}
67
68FSAPatchController::FSAPatchController(FSAApp *fsaApp)
69{
70 mApp = fsaApp;
71}
72
73void FSAPatchController::PatchNotification(ProcessSerialNumber *inPSN, OSType inCreator, OSType type, CFStringRef name, UInt32 flags) {
74 pid_t pid;
75 OSStatus err = GetProcessPID(inPSN, &pid);
76
77 NSCAssert3(err == noErr, @"PatchNotification can't get PID for PSN %ld.%ld: error %ld", inPSN->highLongOfPSN, inPSN->lowLongOfPSN, err);
78
79 FSALog(@"Got patch notification for %@ (pid %d)", (NSString *)name, pid);
80 [mApp controllerIsPatchingApplicationWithProcessID: pid];
81}
82
83// XXX should remove?
84void FSAPatchController::ReceiveMessage(const AppleEvent *theAE) {
85 OSErr err = noErr;
86 Size actualSize;
87 DescType actualType;
88 OSType eventClass, eventID;
89
90 if ((err = AEGetAttributePtr(theAE, keyEventClassAttr, typeType, &actualType, &eventClass, sizeof(OSType), &actualSize)) != noErr)
91 return;
92 if (eventClass != kSCMessageClass)
93 return;
94
95 if ((err = AEGetAttributePtr(theAE, keyEventIDAttr, typeType, &actualType, &eventID, sizeof(OSType), &actualSize)) != noErr)
96 return;
97
98#if 0
99 if (eventID == kSCLoadResult) {
100 OSStatus
101 (err = AEGetParamPtr(theAE, keyError, typeInteger, &actualType, &error, sizeof(long), &actualSize)) == noErr &&
102 (err = AEGetParamPtr(theAE, keyBundleID, typeText, &actualType, bundleID, sizeof(bundleID), &actualSize)) == noErr)
103 bundleID[actualSize] = 0;
104 printf("patch bundle %s loaded with error status %d\n", bundleID, error);
105 }
106#endif
107
108
109}
110
111
112@implementation FSAApp
113
114+ (void)initialize;
115{
116 errRec *rec;
117
118 FSA_errors = [[NSMutableDictionary alloc] init];
119 for (rec = &(ERRS[0]) ; rec->err != 0 ; rec++)
120 [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->err]];
121}
122
123- (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result;
124{
125 switch (result) {
126 case NSAlertOtherReturn:
127 return true;
128 case NSAlertAlternateReturn:
129 [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FSA_FScriptURL]];
130 case NSAlertDefaultReturn:
131 default:
132 [self terminate: self];
133 return false;
134 }
135}
136
137-(tDirStatus)authorizeAndAddToProcMod:(NSString *)username
138{
139 OSStatus myStatus;
140 AuthorizationFlags myFlags = kAuthorizationFlagDefaults;
141 AuthorizationRef myAuthorizationRef;
142
143 myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
144 myFlags, &myAuthorizationRef);
145 if(myStatus != errAuthorizationSuccess)
146 return eDSAuthFailed;
147
148 AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
149 AuthorizationRights myRights = {1, &myItems};
150
151 myFlags = kAuthorizationFlagDefaults
152 | kAuthorizationFlagInteractionAllowed
153 | kAuthorizationFlagPreAuthorize
154 | kAuthorizationFlagExtendRights;
155 myStatus = AuthorizationCopyRights(myAuthorizationRef, &myRights,
156 NULL, myFlags, NULL);
157
158 if(myStatus != errAuthorizationSuccess)
159 return eDSAuthFailed;
160
161 myFlags = kAuthorizationFlagDefaults;
162
163 NSString *myToolPath = [[NSBundle mainBundle] pathForResource:@"AddToProcMod" ofType:@""];
164 char *myArguments[] = { (char *)[username UTF8String], NULL };
165 FILE *myCommunicationsPipe = NULL;
166 char myReadBuffer[128];
167 myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
168 myFlags, myArguments,
169 &myCommunicationsPipe);
170
171 int didRead = 0;
172 int lastRead;
173 while((lastRead = read(fileno(myCommunicationsPipe), myReadBuffer, sizeof(myReadBuffer) - didRead - 1)) && (lastRead > 0))
174 didRead += lastRead;
175
176 myReadBuffer[didRead - 1] = 0;
177
178 AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
179
180 return (tDirStatus)strtol(myReadBuffer, NULL, 10);
181}
182
183- (void)finishLaunching
184{
185#ifdef __i386__
186 if(![[NSUserDefaults standardUserDefaults] objectForKey:@"doPathCheck"])
187 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"doPathCheck"];
188
189 if([[NSUserDefaults standardUserDefaults] boolForKey:@"doPathCheck"]){
190top:
191 PathManager *pm = [[PathManager alloc] initWithLocalNode];
192 [pm backupStack];
193 [pm cd:@"/Groups/procmod"];
194 CFDictionaryRef sessionInfoDict = CGSessionCopyCurrentDictionary();
195 if(sessionInfoDict){
196 CFStringRef shortUserName = (CFStringRef)CFDictionaryGetValue(sessionInfoDict, kCGSessionUserNameKey);
197 if(![[[pm lastObject] readArray:@"GroupMembership"] containsObject:(NSString *)shortUserName]){
198 switch([[NSAlert alertWithMessageText:[NSString stringWithFormat:@"User %@ not in the procmod group", shortUserName]
199 defaultButton:@"Add me"
200 alternateButton:@"Disable checking"
201 otherButton:@"Ignore message"
202 informativeTextWithFormat:@"F-Script Anywhere requires that you add yourself to the procmod "
203 "group in order for it to function properly. If you like, F-Script Anywhere can automatically add you "
204 "to the procmod group."] runModal]){
205 case NSAlertDefaultReturn:
206 {
207 tDirStatus status = [self authorizeAndAddToProcMod:(NSString *)shortUserName];
208 if(status != eDSNoErr){
209 [[NSAlert alertWithMessageText:@"Error adding to procmod group"
210 defaultButton:nil
211 alternateButton:nil
212 otherButton:nil
213 informativeTextWithFormat:@"There was an error (%@) adding you to the procmod group. ", [[NSClassFromString(@"DSoStatus") sharedInstance] stringForStatus:status]] runModal];
214 }
215 }
216 [pm restoreStack];
217 [pm release];
218 goto top;
219 case NSAlertAlternateReturn:
220 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"doPathCheck"];
221 break;
222 default:
223 break;
224 }
225 }
226 }
227 [pm restoreStack];
228 [pm release];
229 }
230#endif
231 patchController = new FSAPatchController(self);
232 patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"),
233 (CFStringRef)PatchBundleName);
234
235 NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator];
236 pid_t pid;
237 ProcessSerialNumber psn;
238 OSStatus err;
239
240 while ( (pid = [[e nextObject] longValue]) != 0) {
241 err = GetProcessForPID(pid, &psn);
242
243 if (err != noErr) {
244 NSLog(@"Can't get PSN for PID %ld", pid);
245 [appList applicationQuitWithProcessID: pid];
246 } else if (patchController->IsProcessPatched(&psn)) {
247 [appList didPatchProcessID: pid];
248 }
249 }
250
251 // yes, someone could move the framework while the app is running, but they deserve what they get.
252 NSFileManager *fileMgr = [NSFileManager defaultManager];
253 NSArray *libraryDirectories =
254 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
255 NSString *libPath;
256 NSString *frameworkPath;
257 CFBundleRef frameworkBundle = NULL;
258 BOOL isDirectory;
259
260 e = [libraryDirectories objectEnumerator];
261 while ( (libPath = [e nextObject]) != nil) {
262 frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"]
263 stringByAppendingPathComponent: @"FScript.framework"];
264 if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) {
265 frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]);
266 [(id)frameworkBundle autorelease];
267 break;
268 }
269 }
270 FSALog(@"Framework bundle");
271 FSAShow(frameworkBundle);
272 [super finishLaunching];
273 if (frameworkBundle == NULL) {
274 int result = NSRunInformationalAlertPanel(
275 NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"),
276 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\nIf you believe this message is in error, click '%@'.",
277 "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
278 NSLocalizedString(@"Quit", "Quit button title"),
279 NSLocalizedString(@"Get F-Script", "'Get F-Script' button title"),
280 NSLocalizedString(@"Continue", "Continue button title"),
281 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
282 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
283 } else {
284 UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
285 FSALog(@"Framework version 0x%x", version);
286 if (version < FSA_FSCRIPT_MIN_VERSION) {
287 NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey);
288 if (frameworkVersion == nil)
289 frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version");
290 int result = NSRunInformationalAlertPanel(
291 NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"),
292 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, please visit its Web site\n%@.\n\nTo use this version of F-Script regardless of the problems you may experience, click '%@'.",
293 "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"),
294 NSLocalizedString(@"Quit", "Quit button title"),
295 NSLocalizedString(@"Get F-Script", "'Get F-Script' button title"),
296 NSLocalizedString(@"Continue", "Continue button title"),
297 FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion,
298 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
299 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
300 }
301 }
302
303 NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
304 [workspaceNotificationCenter
305 addObserver: self
306 selector: @selector(applicationDidLaunch:)
307 name: NSWorkspaceWillLaunchApplicationNotification
308 object: nil];
309 [workspaceNotificationCenter
310 addObserver: self
311 selector: @selector(applicationDidTerminate:)
312 name: NSWorkspaceDidTerminateApplicationNotification
313 object: nil];
314
315 [appListWindow makeKeyAndOrderFront: self];
316}
317
318- (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid;
319{
320 NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"),
321 nil, nil, nil, appListWindow, self, nil, nil, nil,
322 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"),
323 pid, FSA_descriptionForOSStatus(err));
324}
325
326- (void)installBundleInAppWithPID:(pid_t)pid;
327{
328 if (pid == -1) return;
329
330 ProcessSerialNumber psn;
331 OSStatus err = GetProcessForPID(pid, &psn);
332
333 if (err == noErr)
334 err = patchController->PatchProcess(&psn);
335
336 if (err != noErr)
337 [self installationError: err inAppWithPID: pid];
338 else
339 [appList didPatchProcessID: pid];
340}
341
342- (void)loaderBundleMessage:(NSNotification *)notification;
343{
344 /*
345 if (error) {
346 [self installationError: error inAppWithPID: pid];
347 } else {
348 [appList didPatchProcessID: pid];
349 } */
350}
351
352- (IBAction)installBundleInSelectedApp:(id)sender;
353{
354 [self installBundleInAppWithPID: [appList selectedProcessID]];
355}
356
357// from dock menu only!
358- (IBAction)installBundleInFrontmostApp:(id)sender;
359{
360 NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
361 [self installBundleInAppWithPID: (pid_t)[sender tag]];
362}
363
364@end
365
366@implementation FSAApp (FSAPatchControllerDelegate)
367
368- (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid;
369{
370 [appList isPatchingProcessID: pid];
371}
372
373@end
374
375@implementation FSAApp (NSWorkspaceNotifications)
376
377- (void)applicationDidLaunch:(NSNotification *)notification;
378{
379 [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
380}
381
382- (void)applicationDidTerminate:(NSNotification *)notification;
383{
384 [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
385}
386
387@end
Note: See TracBrowser for help on using the repository browser.