source: trunk/PrefsPane/src/Prefs.m @ 1046

Revision 1046, 12.8 KB checked in by speck, 3 days ago (diff)

printf %d formatting fixes.
1.5b23

Line 
1/* Copyright (C) 2008-2009 Peter Speck
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#import <Security/AuthorizationTags.h>
18#import <SecurityInterface/SFAuthorizationView.h>
19#import <openssl/md5.h>
20#import "Prefs.h"
21#import "GBDebug.h"
22#import "XmlDoc.h"
23#import "Xml.h"
24#import "AlertHelper.h"
25
26static NSString* authHelperPath = @"/Library/PreferencePanes/GlimmerBlocker.prefPane/Contents/MacOS/AuthHelper";
27
28@interface PrefsHelper : NSObject {
29
30@private
31    NSFileManager *_fm;
32    NSString* _dirPath;
33    NSString* _filePath;
34    AlertHelper* _alertHelper;
35}
36- (id)initWithAlertHelper:(AlertHelper*)alertHelper;
37- (void)dealloc;
38
39- (NSString*)filePath;
40- (AlertHelper*)alertHelper;
41
42- (XmlDoc*)load;
43
44@end
45
46@implementation PrefsHelper
47
48- (id)initWithAlertHelper:(AlertHelper*)alertHelper
49{
50    if (!(self = [super init]))
51        return NULL;
52    _alertHelper = [alertHelper retain];
53    _fm = [[NSFileManager defaultManager] retain];
54    _dirPath = @"/Library/GlimmerBlocker";
55    _filePath = [[NSString stringWithFormat:@"%@/PanelSettings.xml", _dirPath] retain];
56    return self;
57}
58
59- (void)dealloc
60{
61    [_alertHelper release];
62    [_fm release];
63    [_dirPath release];
64    [_filePath release];
65    [super dealloc];
66}
67
68- (NSString*)filePath
69{
70    return _filePath;
71}
72
73- (AlertHelper*)alertHelper
74{
75    return _alertHelper;
76}
77
78- (XmlDoc*)load
79{
80    if (![_fm fileExistsAtPath:_filePath]) {
81        NSLog(@"GlimmerBlocker preferences file missing: %@", _filePath);
82        return NULL;
83    }
84    NSURL* url = [NSURL fileURLWithPath:_filePath];
85    NSData* data = [NSData dataWithContentsOfURL:url];
86    if (!data)
87        return NULL;
88    XmlDoc* doc = [[[XmlDoc alloc] initWithURL:url withData:data] autorelease];
89    if ([doc doc] && [doc rootE])
90        return doc;
91    NSLog(@"Failed loading data from %@", _filePath);
92    return NULL;
93}
94
95@end
96
97//==============================================================================================================
98@interface Prefs ()
99
100- (id)initWithPrefsHelper:(PrefsHelper*)helper
101          withRootElement:(NSXMLElement*)rootE
102  withSFAuthorizationView:(SFAuthorizationView*)authView;
103- (BOOL)checkAutoHelperPermissions;
104
105@end
106
107@implementation Prefs
108
109+ (Prefs*)prefsWithAlertHelper:(AlertHelper*)alertHelper
110       withSFAuthorizationView:(SFAuthorizationView*)authView
111{
112    NSAssert(alertHelper, @"alertHelper");
113    NSAssert(authView, @"authView");
114    PrefsHelper* helper = [[[PrefsHelper alloc] initWithAlertHelper:alertHelper] autorelease];
115    if (!helper)
116        return NULL;
117    XmlDoc* doc = [helper load];
118    NSXMLElement* rootE = doc ? [doc rootE] : [Xml createElementWithName:@"prefs"];
119    return [[[Prefs alloc] initWithPrefsHelper:helper
120                               withRootElement:rootE
121                       withSFAuthorizationView:authView] autorelease];
122}
123
124- (id)initWithPrefsHelper:(PrefsHelper*)helper
125          withRootElement:(NSXMLElement*)rootE
126  withSFAuthorizationView:(SFAuthorizationView*)authView
127{
128    if (!(self = [super initWithRootElement:rootE]))
129        return NULL;
130    _helper = [helper retain];
131    _authView = [authView retain];
132    return self;
133}
134
135- (void)dealloc
136{
137    [_helper release];
138    [_authView release];
139    [super dealloc];
140}
141
142- (void)forcedReload
143{
144    XmlDoc* doc = [_helper load];
145    if (!doc) {
146        NSLog(@"GB prefs can't reload xml panel prefs.");
147        return;
148    }
149    NSXMLElement* rootE = [doc rootE];
150    if (!rootE) {
151        NSLog(@"GB prefs can't reload xml panel prefs: got doc without root element");
152        return;
153    }
154    [self reloadWithRootElement:rootE];
155    DebugNSLog(@"Prefs reloaded.");
156}
157
158/*
159- (id)retain
160{
161    NSLog(@"#### Prefs retain: %d", [self retainCount]);
162    [GBDebug debugDumpStackframe];
163    return [super retain];
164}
165
166- (void)release
167{
168    NSLog(@"#### Prefs release: %d", [self retainCount]);
169    [GBDebug debugDumpStackframe];
170    [super release];
171}
172 */
173
174#pragma mark -------------------------------- Globals
175- (AlertHelper*)alertHelper
176{
177    return [_helper alertHelper];
178}
179
180#pragma mark -------------------------------- Filters
181
182- (NSXMLElement*)filtersE
183{
184    return [Xml singletonElementForXPath:@"filters" forParent:[self rootE]];
185}
186
187- (NSXMLElement*)rulePreviewE
188{
189    return [Xml singletonElementForXPath:@"rule-preview" forParent:[self rootE]];
190}
191
192- (BOOL)zapRulePreviewElement
193{
194    NSArray* a = [[self rootE] elementsForName:@"rule-preview"];
195    if (![a count])
196        return NO;
197    for (NSXMLElement* e in a)
198        [e detach];
199    [self markAsDirty];
200    return YES;
201}
202
203#pragma mark -------------------------------- Save
204
205#ifdef DEBUG_DISABLED
206//#ifdef DEBUG
207#define DirtyPrefsNSLog(fmt, ...) NSLog(fmt, ## __VA_ARGS__)
208#else
209#define DirtyPrefsNSLog(fmt, ...) do { } while (0)
210#endif
211
212- (BOOL)save
213{
214    if (![self hasGlimmerAuth]) {
215        NSLog(@"Prefs.save without auth. Ignored.");
216        return NO;
217    }
218    if (_dirty)
219        DirtyPrefsNSLog(@"Saving dirty prefs, _numberOfRunningHelpers = %d", _numberOfRunningHelpers);
220    else
221        DirtyPrefsNSLog(@"Saves non-dirty prefs");
222    NSData* fileContents = [self printToData];
223    NSData* md5 = [NSData dataWithBytes:MD5([fileContents bytes], [fileContents length], NULL) length:MD5_DIGEST_LENGTH];
224    NSArray* inputData = [NSArray arrayWithObjects:fileContents, md5, NULL];
225    NSArray* args = [NSArray arrayWithObjects:@"--save-prefs", NULL];
226    int status = [self runAuthHelperWithArgs:(NSArray*)args withInputData:inputData withAuth:YES];
227    if (!status) {
228        _dirty = NO;
229        DirtyPrefsNSLog(@"Save ok, _numberOfRunningHelpers = %d", _numberOfRunningHelpers);
230        return YES;
231    }
232    DirtyPrefsNSLog(@"Save failed, _numberOfRunningHelpers = %d", _numberOfRunningHelpers);
233    if (!_hasWarnedInstallProblem) {
234        AlertHelper* ah = [self alertHelper];
235        [ah prepareCriticalAlertWithTitle:@"Failed saving preferences"
236                      withInformativeText:[_helper filePath]];
237        [ah showPreparedAlert];
238    }
239    return NO;
240}
241
242- (void)saveIfDirty:(id)sender
243{
244    if (_numberOfRunningHelpers) // dunny why I get these nested calls.
245        return;
246    DirtyPrefsNSLog(@"saveIfDirty: %d, _numberOfRunningHelpers = %d", _dirty, _numberOfRunningHelpers);
247    //[GBDebug debugDumpStackframe];
248    if (_dirty) {
249        [self save];
250        DirtyPrefsNSLog(@"  dirty: %d, _numberOfRunningHelpers = %d", _dirty, _numberOfRunningHelpers);
251    }
252}
253
254- (void)markAsDirty
255{
256    if (_dirty)
257        return;
258    DirtyPrefsNSLog(@"Prefs now dirty, _numberOfRunningHelpers = %d", _numberOfRunningHelpers);
259    _dirty = YES;
260    [self performSelectorOnMainThread:@selector(saveIfDirty:) withObject:self waitUntilDone:NO];
261}
262
263
264#pragma mark -------------------------------- XPath scalars
265
266- (void)setString:(NSString*)value forXPath:(NSString*)xpath
267{
268    NSString* old = [self stringForXPath:xpath];
269    if (![old length] && ![value length])
270        return;
271    if (old && value && [old isEqual:value])
272        return;
273    DebugNSLog(@"Changes global pref (%@): '%@' -> '%@'", xpath, old, value);
274    [super setString:value forXPath:xpath];
275}
276
277#pragma mark -------------------------------- Auth
278
279- (AuthorizationRef)authRef
280{
281    if (!_hasGlimmerAuth)
282        return NULL;
283    SFAuthorization* auth = [_authView authorization];
284    if (!auth) {
285        DebugNSLog(@"Has no SFAuthorization");
286        return NULL;
287    }
288    return [auth authorizationRef];
289}
290
291- (void)notifyUpdatedAuthorizationState
292{
293    _hasGlimmerAuth = YES; // so [self authRef] doesn't short-circuit to NULL
294    AuthorizationRef authRef = [self authRef];
295    AuthorizationItem myItems = {glimmerAuthString, 0, NULL, 0};
296    AuthorizationRights myRights = {1, &myItems};
297    AuthorizationRights *authorizedRights = NULL;
298    OSStatus stat = AuthorizationCopyRights(authRef, &myRights, NULL,
299                                            kAuthorizationFlagDefaults,
300                                            &authorizedRights);
301    if (authorizedRights)
302        AuthorizationFreeItemSet(authorizedRights);
303    DebugNSLog(@"AuthorizationCopyRights returned %d", stat);
304    _hasGlimmerAuth = (stat == errAuthorizationSuccess);
305    [_authInExternalForm release];
306    _authInExternalForm = NULL;
307    if (!_hasGlimmerAuth)
308        return;
309    AuthorizationExternalForm extAuth;
310    stat = AuthorizationMakeExternalForm(authRef, &extAuth);
311    if (stat == errAuthorizationSuccess) {
312        _authInExternalForm = [[NSData dataWithBytes:&extAuth length:sizeof(extAuth)] retain];
313    } else {
314        NSLog(@"AuthorizationMakeExternalForm failed (%ld) after AuthorizationCopyRights returned success", (long)stat);
315        _hasGlimmerAuth = NO;
316    }
317    if (_hasGlimmerAuth) {
318        _hasWarnedInstallProblem = NO;
319        [self checkAutoHelperPermissions];
320    }
321}
322
323- (BOOL)hasGlimmerAuth
324{
325    return _hasGlimmerAuth;
326}
327
328- (NSData*)authExternalForm
329{
330    return _authInExternalForm;
331}
332
333- (BOOL)has:(int)value withMask:(int)mask withKey:(NSString*)key inDict:(NSDictionary*)dict
334{
335    NSObject* obj = [dict objectForKey:key];
336    if (!obj || ![obj isKindOfClass:[NSNumber class]])
337        return NO;
338    NSNumber* n = (NSNumber*)obj;
339    BOOL ok = ([n intValue] & mask) == value;
340    if (!ok)
341        DebugNSLog(@"Missing file attr: %@ = %d, expected %d with mask %d", key, [n intValue], value, mask);
342    return ok;
343}
344
345- (BOOL)checkAutoHelperPermissions
346{
347    NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:authHelperPath error:NULL];
348    if (![self has:06555 withMask:06555 withKey:NSFilePosixPermissions inDict:attrs]
349        || ![self has:0 withMask:-1 withKey:NSFileOwnerAccountID inDict:attrs]) {
350        if ([_authView authorizationState] == SFAuthorizationViewUnlockedState)
351            NSLog(@"GB Deauthorizes due to invalid AuthHelper permissions/owner.");
352        [_authView deauthorize:self];
353        if (_hasWarnedInstallProblem)
354            return NO;
355        if (![[_helper alertHelper] canDisplayAlert])
356            return NO;
357        _hasWarnedInstallProblem = YES;
358        AlertHelper* ah = [self alertHelper];
359        [ah prepareCriticalAlertWithTitle:@"GlimmerBlocker needs to be reinstalled"
360                      withInformativeText:@"Reason: The authentication tool does not have the proper permission assigned."];
361        [ah showPreparedAlert];
362        return NO;
363    }
364    _hasWarnedInstallProblem = NO;
365    return YES;
366}
367
368- (void)writeData:(NSData*)data toFileHandle:(NSFileHandle*)handle
369{
370    long long len = [data length];
371    NSAssert(sizeof(len) == 8, @"longlong != 64");
372    NSMutableData* tmp = [NSMutableData dataWithCapacity:sizeof(len)];
373    [tmp appendBytes:&len length:sizeof(len)];
374    [handle writeData:tmp];
375    [handle writeData:data];
376}
377
378- (int)runAuthHelperWithArgs:(NSArray*)args
379               withInputData:(NSArray*)inputData // array of NSData
380                    withAuth:(BOOL)withAuth
381{
382    return [self runAuthHelperWithArgs:args
383                         withInputData:inputData
384                        withOutputData:NULL
385                              withAuth:withAuth];
386}
387
388- (int)runAuthHelperWithArgs:(NSArray*)args
389               withInputData:(NSArray*)inputData
390              withOutputData:(NSMutableData*)outputData
391                    withAuth:(BOOL)withAuth
392{
393    NSAssert([args count], @"Must have --command");
394    if (withAuth && ![self hasGlimmerAuth]) {
395        NSLog(@"Can't run helper tool: required auth, but doesn't have it: %@", [args objectAtIndex:0]);
396        return -1;
397    }
398    /*
399     For some reason [writeHandle closeFile] creates a NSUncaughtSystemExceptionException
400     (it jumps to 0xffffffff)
401     if the tool exits with an error before reading the input.
402    */
403    if (![self checkAutoHelperPermissions])
404        return -1;
405    [self retain];
406    _numberOfRunningHelpers++;
407    NSData* authData = withAuth ? [self authExternalForm] : NULL;
408    NSAssert(!withAuth || authData, @"Auth");
409    [args retain];
410    NSTask* task = [[NSTask alloc] init];
411    int status = 0;
412    NSPipe *writePipe = NULL;
413    NSFileHandle *writeHandle = NULL;
414    NSPipe *readPipe = NULL;
415    NSFileHandle *readHandle = NULL;
416    @try {
417        [task setLaunchPath:authHelperPath];
418        [task setArguments:args];
419        [task setCurrentDirectoryPath:@"/tmp"];
420        if (outputData) {
421            readPipe = [[NSPipe pipe] retain];
422            readHandle = [[readPipe fileHandleForReading] retain];
423            [task setStandardOutput:readPipe];
424        }
425        if (withAuth || inputData) {
426            writePipe = [[NSPipe pipe] retain];
427            writeHandle = [[writePipe fileHandleForWriting] retain];
428            [task setStandardInput:writePipe];
429            [task launch];
430            if (withAuth)
431                [self writeData:authData toFileHandle:writeHandle];
432            if (inputData) {
433                for (NSData* data in inputData)
434                    [self writeData:data toFileHandle:writeHandle];
435            }
436            [writeHandle closeFile];
437        } else {
438            [task launch];
439        }
440        while (outputData) {
441            NSData* data = [readHandle availableData];
442            if (!data || ![data length])
443                break;
444            [outputData appendData:data];
445        }
446        [task waitUntilExit];
447        status = [task terminationStatus];
448        //DebugNSLog(@"task retains: %d", [task retainCount]);
449    }
450    @catch (NSException* ex) {
451        status = -1;
452        NSLog(@"AuthHelper failed with exception: %@", [ex name]);
453        [GBDebug debugDumpStackframe];
454    }
455    if ([task isRunning])
456        [task terminate];
457    [task release];
458    [writeHandle release];
459    [writePipe release];
460    [readHandle release];
461    [readPipe release];
462    [args release];
463    _numberOfRunningHelpers--;
464    if (status)
465        DebugNSLog(@"Status from auto-helper %@: %d", [args objectAtIndex:0], status);
466    [self release];
467    return status;
468}
469
470@end
Note: See TracBrowser for help on using the repository browser.