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

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

printf %d formatting fixes.
1.5b23

Line 
1/* Copyright (C) 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
18#import "InstallationHelper.h"
19#import <PreferencePanes/PreferencePanes.h>
20#import "GBDebug.h"
21
22static BOOL hasCheckedJava = NO, javaIsAvailable = YES, fixIsToRunJavaInstaller = NO;
23
24@interface InstallationHelper ()
25
26- (void)loadUI;
27- (void)showFixInstallation:(NSString*)reason;
28- (void)showSimpleMessage:(NSString*)reason;
29- (void)showReinstallMessage:(NSString*)reason;
30
31@end
32
33@implementation InstallationHelper
34
35@synthesize _progressText;
36
37-(id)initWithCopyrightMessage:(NSAttributedString*)copyrightMessage
38           withPreferencePane:(NSPreferencePane*)preferencePane
39{
40    if (!(self = [super init]))
41        return NULL;
42    _preferencePane = [preferencePane retain];
43    _bundle = [[preferencePane bundle] retain];
44    _copyrightMessage = [copyrightMessage retain];
45    return self;
46}
47
48- (void)dealloc
49{
50    [_preferencePane release];
51    [_bundle release];
52    [_copyrightMessage release];
53    [super dealloc];
54}
55
56- (BOOL)hasDir:(NSString*)path
57{
58    NSFileManager* fm = [NSFileManager defaultManager];
59    BOOL dir = NO;
60    return [fm fileExistsAtPath:path isDirectory:&dir] && dir;
61}
62
63- (BOOL)hasRelDir:(NSString*)relPath
64{
65    return [self hasDir:[NSString stringWithFormat:@"%@/%@", [_bundle bundlePath], relPath]];
66}
67
68- (BOOL)checkLittleSnitch
69{
70    NSString* path = @"/Library/Little Snitch/Little Snitch UIAgent.app/Contents/Info.plist";
71    if (![[NSFileManager defaultManager] fileExistsAtPath:path])
72        return YES;
73    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
74    NSString* vers = [dict objectForKey:@"CFBundleVersion"];
75    NSLog(@"Little Snitch Core System Version: '%@'", vers);
76    if (![vers isEqual:@"385"])
77        return YES;
78    NSString* msg = @"You must upgrade Little Snitch to version 2.0.5 or later\n"
79        "before you can use GlimmerBlocker.\n"
80        "\n\n"
81        "You're using LittleSnitch version 2.0.4 with 'Core System Version' 385 which often breaks internet connectivity.";
82    NSLog(@"GB: %@", msg);
83    [self showSimpleMessage:msg];
84    return NO;
85}
86
87- (void)setFixBtnSize:(NSControlSize)controlSize
88           withDeltaY:(CGFloat)dy
89{
90    CGFloat fontSize = [NSFont systemFontSizeForControlSize:controlSize];
91    NSCell *theCell = [fixBtn cell];
92    if (!theCell)
93        NSLog(@"FixBtn has no cell");
94    NSFont *theFont = [NSFont fontWithName:[[theCell font] fontName] size:fontSize];
95    [theCell setFont:theFont];
96    [theCell setControlSize:controlSize];
97    [fixBtn sizeToFit];
98    NSRect f = [fixBtn frame];
99    f.origin.x = ([fixView frame].size.width - f.size.width) / 2;
100    f.origin.y += dy;
101    [fixBtn setFrame:f];
102}
103
104- (BOOL)validate:(NSString*)glimmerBlockerVersionText
105{
106    if (!javaIsAvailable)
107        return NO;
108    //if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7 && !hasCheckedJava) {
109    if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_6 && !hasCheckedJava) {
110        javaIsAvailable = ![GBDebug executeCommandlineTool:@"/usr/libexec/java_home" withArgs:[NSArray array]];
111        hasCheckedJava = YES;
112    }
113    if (!javaIsAvailable) {
114        DebugNSLog(@"java is not available");
115        [self showFixInstallation:@"Java is not installed."];
116        fixIsToRunJavaInstaller = YES;
117        [fixBtn setTitle:@"  Launch java installer  "];
118        [fixBtn setHidden:NO];
119        [self setFixBtnSize:NSRegularControlSize withDeltaY:0];
120        [pleaseWaitTextField setStringValue:@"Please re-open this Preference Panel after you have installed java."];
121        return NO;
122    }
123    NSString* panelPath = @"/Library/PreferencePanes/GlimmerBlocker.prefPane";
124    if ([[_bundle bundlePath] hasPrefix:@"/Users/"]) {
125        NSLog(@"Invalid installation path: %@", [_bundle bundlePath]);
126        NSLog(@"Required path:             %@", panelPath);
127        [self showFixInstallation:@"GlimmerBlocker is installed for a single user,\nbut must be installed for all users."];
128        return NO;
129    }
130    if (![[_bundle bundlePath] isEqual:panelPath]) {
131        NSLog(@"Invalid installation path: %@", [_bundle bundlePath]);
132        NSLog(@"Required path:             %@", panelPath);
133        [self showFixInstallation:@"GlimmerBlocker is not installed with the proper filename/path."];
134        return NO;
135    }
136    if (![self checkLittleSnitch])
137        return NO;
138    if (![self hasRelDir:@"Contents/GlimmerBlockerProxy.app"] || ![self hasRelDir:@"Contents/GlimmerBlockerProxy.app"]) {
139        [self showFixInstallation:@"The proxy files are not extracted."];
140        return NO;
141    }
142    if (REQUIRED_VALID_CODESIGN && ![GBDebug verifyCodesign]) {
143        [self showReinstallMessage:@"The installation is not valid but has been tampered with."];
144        [fixBtn setTitle:@"Show detailed error report"];
145        [fixBtn setHidden:NO];
146        // http://devworld.apple.com/documentation/Cocoa/Conceptual/ControlCell/Articles/ManipulateCellControl.html
147        [self setFixBtnSize:NSMiniControlSize withDeltaY:-39];
148        //
149        _invalidCodeSignature = YES;
150        return NO;
151    }
152    NSFileManager* fm = [NSFileManager defaultManager];
153    if (![fm fileExistsAtPath:@"/Library/GlimmerBlocker/PanelSettings.xml"]) {
154        [self showFixInstallation:@"The settings file is missing."];
155        return NO;
156    }
157    if (![self hasDir:@"/Library/Logs/GlimmerBlocker"]) {
158        [self showFixInstallation:@"The log folder is missing."];
159        return NO;
160    }
161    if (![self hasDir:@"/Library/GlimmerBlocker/Filter subscriptions"]) {
162        [self showFixInstallation:@"The subscriptions folder is missing."];
163        return NO;
164    }
165    NSArray* args = [NSArray arrayWithObjects:@"-c", @"/usr/bin/dscl . -read /Users/_glimmerblocker > /dev/null", NULL];
166    int status = [GBDebug executeCommandlineTool:@"/bin/sh" withArgs:args];
167    if (status) {
168        NSLog(@"dscl failed listing _glimmerblocker user");
169        [self showFixInstallation:@"The GlimmerBlocker proxy is not installed."];
170        return NO;
171    }
172    if (![GBDebug fingerprintForRunningServerIsValid:_bundle]) {
173        NSLog(@"Running server has incompatible fingerprint.");
174        [self showFixInstallation:@"The GlimmerBlocker proxy needs to be\nrestarted after the upgrade."];
175        [fixBtn setTitle:@"  Restart proxy  "]; // for some unknown reason, [sizeToFit] makes it too narrow.
176        [self setFixBtnSize:NSRegularControlSize withDeltaY:0];
177        return NO;
178    }
179    NSString* path = [panelPath stringByAppendingString:@"/Contents/Info.plist"];
180    NSString* vers = @"???";
181    if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
182        NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
183        vers = [dict objectForKey:@"CFBundleVersion"];
184    }
185    if (![vers isEqual:glimmerBlockerVersionText]) {
186        NSLog(@"GB version number on disk doesn't match loaded panel:");
187        NSLog(@"      Loaded panel: %@", glimmerBlockerVersionText);
188        NSLog(@"   Version on disk: %@", vers);
189        [self showSimpleMessage:@"GlimmerBlocker has been upgraded\nsince this panel was opened."];
190        [pleaseWaitTextField setStringValue:@"Please quit and relaunch System Preferences."];
191        return NO;
192    }
193    if (NO) {
194        NSLog(@"FORCES INVALID INSTALLATION PATH.");
195        [self showFixInstallation:@"The sky is not blue today."];
196        return NO;
197    }
198    return YES;
199}
200
201- (void)loadUI
202{
203    if (fixView)
204        return;
205    NSAssert([NSBundle loadNibNamed: @"Installation" owner: self], @"Can't load nib 'Installation'");
206    NSAssert(fixView, @"fixView is null");
207    NSView* mainView = [_preferencePane mainView];
208    if (mainView) {
209        DebugNSLog(@"GB Panel has already mainView, adds InstallView as only visible subview.");
210        for (NSView* view in [mainView subviews])
211            [view setHidden:YES];
212        [mainView addSubview:fixView];
213    } else {
214        [_preferencePane setMainView:fixView];
215    }
216    NSTextStorage* ts = [footerCopyrightTextView textStorage];
217    [ts replaceCharactersInRange:NSMakeRange(0, [ts length]) withAttributedString:_copyrightMessage];
218    //
219    [fixView setFrameOrigin:NSMakePoint(0, 0)];
220    [footerCopyrightTextView setEditable:NO];
221    [footerUrlTextView setEditable:NO];
222    [pleaseWaitTextField setStringValue:@""];
223    [progressTextField setStringValue:@""];
224}
225
226- (void)showFixInstallation:(NSString*)message
227{
228    NSLog(@"showFixInstallation: %@", message);
229    [self loadUI];
230    [fixMessageTextField setStringValue:message];
231}
232
233- (void)showSimpleMessage:(NSString*)reason
234{
235    [self loadUI];
236    [fixBtn setHidden:YES];
237    [fixMessageTextField setStringValue:reason];
238    NSRect f = [fixMessageTextField frame];
239    CGFloat d = 70;
240    f.origin.y -= d;
241    f.size.height += d;
242    [fixMessageTextField setFrame:f];
243}
244
245- (void)showReinstallMessage:(NSString*)reason
246{
247    NSLog(@"showReinstallMessage: %@", reason);
248    NSString* msg = [NSString stringWithFormat:@"GlimmerBlocker needs to be reinstalled:\n"
249                     "%@\n"
250                     "\n"
251                     "Please download and install the latest version from http://glimmerblocker.org",
252                     reason];
253    [self showSimpleMessage:msg];
254}
255
256//============================================================================
257
258- (void)updateProgressText
259{
260    [progressTextField setStringValue:self._progressText];
261    [[progressTextField window] display]; // force window to update GraphicsCard pixels right now.
262}
263
264- (void)installerDone
265{
266    self._progressText = @"";
267    [self updateProgressText];
268    [progressIndicator stopAnimation:self];
269    [pleaseWaitTextField setStringValue:@"Please quit and relaunch System Preferences."];
270    [self release];
271}
272
273- (void)runInstaller
274{
275    NSString* bundlePath = [_bundle bundlePath];
276    const char* cmd = [[NSString stringWithFormat:@"%@/Contents/Resources/InstallerScript.pl", bundlePath] UTF8String];
277    const char* pid = [[NSString stringWithFormat:@"%d", getpid()] UTF8String];
278    const char* args[] = { "-syspref", pid, "-bundle", [bundlePath UTF8String], NULL };
279    FILE *msgPipe = NULL;
280    NSLog(@"Calls AuthorizationExecuteWithPrivileges");
281    OSStatus status = AuthorizationExecuteWithPrivileges(_authRef,
282                                                         cmd,
283                                                         kAuthorizationFlagDefaults,
284                                                         (char**)args,
285                                                         &msgPipe);
286    if (status == errAuthorizationSuccess) {
287        NSLog(@"AuthorizationExecuteWithPrivileges returned success");
288        char buffer[128];
289        NSMutableData* msgBuffer = [NSMutableData dataWithCapacity:80];
290        while (1) {
291            long num = read(fileno(msgPipe), buffer, sizeof(buffer));
292            if (num < 1)
293                break;
294            [msgBuffer appendBytes:buffer length:num];
295            NSInteger len = [msgBuffer length];
296            int idx = 0;
297            while (idx < len) {
298                char ch = ((const char*)[msgBuffer bytes])[idx];
299                if (ch != '\n') {
300                    idx++;
301                    continue;
302                }
303                NSString* s = [[NSString alloc] initWithBytes:[msgBuffer bytes] length:idx encoding:NSUTF8StringEncoding];
304                NSLog(@"%@", s);
305                if ([s hasPrefix:@"MSG:"]) {
306                    self._progressText = [s substringFromIndex:4];
307                    [self performSelectorOnMainThread:@selector(updateProgressText)
308                                           withObject:NULL
309                                        waitUntilDone:YES];
310                }
311                [s release];
312                [msgBuffer replaceBytesInRange:NSMakeRange(0, idx + 1) withBytes:"" length:0];
313                len -= idx + 1;
314                idx = 0;
315            }
316        }
317        NSLog(@"installer done.");
318    }
319    if (msgPipe)
320        fclose(msgPipe);
321    if (status != errAuthorizationSuccess) {
322        NSLog(@"AuthorizationExecuteWithPrivileges failed with error code %ld", (long)status);
323    } else {
324        BOOL dir = NO;
325        BOOL e = [[NSFileManager defaultManager] fileExistsAtPath:@"/Library/PreferencePanes/GlimmerBlocker.prefPane"
326                                                  isDirectory:&dir];
327        NSLog(@"Installer done: %d + %d",  e, dir);
328    }
329    [self performSelectorOnMainThread:@selector(installerDone)
330                           withObject:NULL
331                        waitUntilDone:NO];
332}
333
334- (void)prepareAuth
335{
336    AuthorizationRef authRef = NULL;
337    AuthorizationFlags authFlags = kAuthorizationFlagDefaults;
338    OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, authFlags, &authRef);
339    if (status != errAuthorizationSuccess) {
340        NSLog(@"GlimmerBlockerInstaller: AuthorizationCreate failed");
341        return;
342    }
343    NSLog(@"GlimmerBlockerInstaller: AuthorizationCreate ok");
344    AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
345    AuthorizationRights myRights = {1, &myItems};
346    authFlags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
347    status = AuthorizationCopyRights(authRef, &myRights, NULL, authFlags, NULL );
348    if (status != errAuthorizationSuccess) {
349        NSLog(@"GlimmerBlockerInstaller: AuthorizationCopyRights failed: %ld", (long)status);
350        return;
351    }
352    NSLog(@"GlimmerBlockerInstaller: AuthorizationCopyRights ok");
353    _authRef = authRef;
354}
355
356- (BOOL)testCurrentTmpDir
357{
358    FILE* tmpfileF = tmpfile(); // returns NULL if we can't create a file in $TMPDIR
359    if (tmpfileF) {
360        fclose(tmpfileF);
361        NSLog(@"  works: got tmpfile().");
362        return YES;
363    } else {
364        return NO;
365    }
366}
367
368- (BOOL)createTmpDir
369{
370    NSLog(@"Finds usable TMPDIR value:");
371    // If the $TMPDIR directory doesn't exist (or is unwriteable),
372    // AuthorizationExecuteWithPrivileges fails with errAuthorizationInternal = -60008
373    // so we must ensure it points to an existing directory which this process has write permissions to.
374    //--------------------------------------------------------------------
375    char* origTmpDirPath = getenv("TMPDIR");
376    if (origTmpDirPath) {
377        NSLog(@"   Tries tmpfile() using default $TMPDIR = '%s'.", origTmpDirPath);
378        if ([self testCurrentTmpDir])
379            return YES;
380    } else {
381        NSLog(@"   TMPDIR is unset.");
382    }
383    //--------------------------------------------------------------------
384    NSString* s = NSTemporaryDirectory();
385    if (s) {
386        NSLog(@"   Tries using TMPDIR = NSTemporaryDirectory() = '%@'", s);
387        setenv("TMPDIR", [s UTF8String], 1);
388        if ([self testCurrentTmpDir])
389            return YES;
390    } else {
391        NSLog(@"   NSTemporaryDirectory() returned NULL.");
392    }
393    //--------------------------------------------------------------------
394    NSLog(@"   Tries using TMPDIR = '/tmp'");
395    setenv("TMPDIR", "/tmp", 1);
396    if ([self testCurrentTmpDir])
397        return YES;
398    //--------------------------------------------------------------------
399    s = NSHomeDirectory();
400    if (s) {
401        NSLog(@"   Tries using TMPDIR = NSHomeDirectory() = '%@'", s);
402        setenv("TMPDIR", [s UTF8String], 1);
403        if ([self testCurrentTmpDir])
404            return YES;
405    } else {
406        NSLog(@"   NSHomeDirectory() returned NULL.");
407    }
408    //--------------------------------------------------------------------
409    NSLog(@"Can't create tmp files at all!");
410    return NO;
411}
412
413//============================================================================
414
415- (void)runProxyDebugDone
416{
417    [fixBtn setEnabled:YES];
418    [progressIndicator stopAnimation:self];
419    [pleaseWaitTextField setStringValue:@"Please email the TextEdit document to feedback@glimmerblocker.org"];
420    [self release];
421}
422
423- (void)runProxyDebug
424{
425    NSArray* args = [NSArray arrayWithObjects:@"-c", @"/Library/PreferencePanes/GlimmerBlocker.prefPane/Contents/GlimmerBlockerProxy.app/Contents/MacOS/GlimmerBlocker --make-report --textedit 2>&1 | open -f", NULL];
426    [GBDebug executeCommandlineTool:@"/bin/bash" withArgs:args];
427    [self performSelectorOnMainThread:@selector(runProxyDebugDone)
428                           withObject:NULL
429                        waitUntilDone:NO];
430}
431
432//============================================================================
433
434- (IBAction)fixBtnAction:(id)sender
435{
436   
437    if (fixIsToRunJavaInstaller) {
438        [GBDebug executeCommandlineTool:@"/usr/libexec/java_home" withArgs:[NSArray arrayWithObject:@"--request"]];
439        return;
440    }
441    if (_invalidCodeSignature) {
442        [fixBtn setEnabled:NO];
443        [pleaseWaitTextField setStringValue:@"Creates TextEdit document with all the details."];
444        [progressIndicator startAnimation:self];
445        [self performSelector:@selector(runProxyDebug)
446                   withObject:NULL
447                   afterDelay:0.5];
448        [self retain];
449        return;
450    }
451    NSLog(@"GB install action.");
452    if (![self createTmpDir]) {
453        [fixBtn setEnabled:NO];
454        [pleaseWaitTextField setStringValue:@"Can't create temporary files at all!!!  No fix possible."];
455        return;
456    }
457    [self prepareAuth];
458    if (!_authRef) {
459        NSLog(@"Could not authenticate.");
460        return;
461    }
462    [fixBtn setEnabled:NO];
463    [pleaseWaitTextField setStringValue:@"Please wait!"];
464    self._progressText = @"Please wait…";
465    [self updateProgressText];
466    [progressIndicator startAnimation:self];
467    //
468    [self performSelector:@selector(runInstaller)
469               withObject:NULL
470               afterDelay:0.5];
471    [self retain];
472}
473
474@end
Note: See TracBrowser for help on using the repository browser.