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.