source: trunk/PrefsPane/Resources/InstallerScript.pl @ 1046

Revision 1032, 15.0 KB checked in by speck, 9 months ago (diff)

Make sure launchd log file is writeable by admin users etc.

  • Property svn:executable set to *
Line 
1#!/usr/bin/perl
2
3use strict;
4use Socket;
5use POSIX 'setsid';
6use XML::XPath;
7
8$| = 1; # can't use stderr as AuthorizationExecuteWithPrivileges only reads stdout.
9
10$ENV{"SHELL"} = "/bin/sh";
11$ENV{"IFS"} = "";
12$ENV{"PATH"} = "/bin:/usr/bin:/usr/sbin:/sbin";
13$ENV{"CDPATH"} = "";
14
15my $verbose = defined($ENV{VERBOSE});
16print "GlimmerBlocker installer: uid = $<, euid = $>\n";
17system("id");
18$< = 0;
19die "Not running as root" if $<;
20
21my $runningBundlePath = undef;
22my $runningFromSystemPreferences = undef;
23while (@ARGV) {
24    my $opt = shift(@ARGV);
25    if ($opt eq "-v") {
26        $verbose = 1;
27    } elsif ($opt eq "-syspref") {
28        $runningFromSystemPreferences = 1;
29        print "Running from System Preferences.\n";
30    } elsif ($opt eq "-bundle") {
31        $_ = shift(@ARGV) || die "Missing -bundle argument.";
32        die unless m{^(/.*[^/])\/*$};
33        $runningBundlePath = $1; # untaint.
34    } else {
35        warn "Unknown arg: $opt\n";
36    }
37}
38die "-bundle <path> not specified." unless defined $runningBundlePath;
39print "Current panel bundle path: $runningBundlePath\n";
40
41my $panelFilename = "GlimmerBlocker.prefPane";
42my $panelPath = "/Library/PreferencePanes/$panelFilename";
43my $authHelper = "$panelPath/Contents/MacOS/AuthHelper";
44my $settingsPath = "/Library/GlimmerBlocker";
45my $panelSettingsPath = "$settingsPath/PanelSettings.xml";
46my $javaSettingsPath = "$settingsPath/JavaSettings.xml";
47my $lastUpdateCheckPath = "$settingsPath/LastUpdateCheck";
48my $javaJarDir = "$panelPath/Contents/GlimmerBlockerProxy.app/Contents/Resources/Java";
49
50sub sys
51{
52    my (@cmd) = @_;
53    print "# execs: ", join(" ", @cmd), "\n";
54    my $x = system(@cmd);
55    return unless $x;
56    die "system(".join(" ", @cmd).") failed with exitcode ".($x / 256)."\n";
57}
58
59sub getHash
60{
61    my ($cmd) = @_;
62    my %h;
63    open(DSCL, "$cmd |") || die;
64    while (<DSCL>) {
65        chomp;
66        if (/^(\S+?):?\s+(\S+)/) {
67            $h{$1} = $2;
68        } elsif (/^(\S+)/) {
69            $h{$1} = 1;
70        } else {
71            print "Weird value from '$cmd': '$_'\n";
72        }
73    }
74    close(DSCL);
75    return \%h;
76}
77
78sub findUnusedId
79{
80    my ($hR) = @_;
81    my %id;
82    $id{$_} = 1 foreach values %$hR;
83    for (my $i = 50; 1; $i++) {
84        return $i unless $id{$i};
85    }
86}
87
88sub makeGroup
89{
90    my $hR = getHash("dscl . -list /Groups PrimaryGroupID");
91    if ($$hR{_glimmerblocker}) {
92        print "Has already _glimmerblocker group\n";
93        return;
94    }
95    my $id = findUnusedId($hR);
96    print "Creates group _glimmerblocker with PrimaryGroupID = $id\n";
97    my $g = "/Groups/_glimmerblocker";
98    sys(("dscl", ".", "-create", $g));
99    sys(("dscl", ".", "-create", $g, "PrimaryGroupID", $id));
100    sys(("dscl", ".", "-append", $g, "RecordName", "glimmerblocker"));
101    sys(("dscl", ".", "-create", $g, "RealName", "GlimmerBlocker-proxy"));
102}
103
104sub makeUser
105{
106    my $hR = getHash("dscl . -read /Groups/_glimmerblocker PrimaryGroupID");
107    my $groupId = $$hR{PrimaryGroupID} || die "Can't get PrimaryGroupID of _glimmerblocker group";
108    $hR = getHash("dscl . -list /Users UniqueID");
109    if ($$hR{_glimmerblocker}) {
110        print "Has already _glimmerblocker user\n";
111        return;
112    }
113    my $id = findUnusedId($hR);
114    print "Creates user _glimmerblocker with UniqueID = $id\n";
115    my $g = "/Users/_glimmerblocker";
116    sys(("dscl", ".", "-create", $g));
117    sys(("dscl", ".", "-create", $g, "UniqueID", $id));
118    sys(("dscl", ".", "-create", $g, "PrimaryGroupID", $groupId));
119    sys(("dscl", ".", "-append", $g, "RecordName", "glimmerblocker"));
120    sys(("dscl", ".", "-create", $g, "RealName", "GlimmerBlocker-proxy"));
121    sys(("dscl", ".", "-create", $g, "NFSHomeDirectory", "$panelPath/Contents/Resources"));
122    sys(("dscl", ".", "-create", $g, "UserShell", "/usr/bin/false"));
123}
124
125sub setPerms
126{
127    my ($path, $perm) = @_;
128    return unless -e $path;
129    if ($perm =~ /\D/) {
130        sys(("chmod", sprintf("0%o", $perm), $path));
131    } else {
132        printf "chmod 0%o  %s\n", $perm, $path if $verbose;
133        chmod($perm, $path) || die sprintf("Can't chmod 0%o %s", $perm, $path);
134    }
135}
136
137sub createFolder
138{
139    my ($path, $owner, $perm, $force) = @_;
140    if (-e $path) {
141        return unless $force;
142    } else {
143        mkdir($path, $perm) || die "Can't create dir: $path";
144        sys(("chown", $owner, $path));
145        return;
146    }
147    if ($force > 1) {
148        print STDERR "createFolder.setDeepPerms($owner, $perm, $force): $path\n";
149        system("find '$path' -type d -print0 | xargs -0 chmod ".sprintf("0%o", $perm));
150        system("find '$path' -type f -print0 | xargs -0 chmod ".sprintf("0%o", $perm & 0666));
151    } else {
152        print STDERR "createFolder.setPerms($owner, $perm, $force): $path\n";
153        setPerms($path, $perm);
154    }
155    system("find '$path' -print0 | xargs -0 chown $owner");
156}
157
158sub prefByXPath
159{
160    my ($xpath) = @_;
161    if (! -e $panelSettingsPath) {
162        print STDERR "prefByXPath: Missing settings file for: '$xpath'\n";
163        return '';
164    }
165    my $r = XML::XPath->new(filename => $panelSettingsPath);
166    my $nodes = $r->find($xpath);
167    unless ($nodes->isa('XML::XPath::NodeSet')) {
168        print STDERR "prefByXPath: xpath didn't return nodeset: '$xpath'\n";
169        return "";
170    }
171    unless ($nodes->size) {
172        print STDERR "prefByXPath: No node matches: '$xpath'\n";
173        return '';
174    }
175    my $v = ($nodes->get_nodelist)[0]->string_value;
176    print STDERR "prefByXPath: Node with value '$v' matches: '$xpath'\n";
177    return $v;
178}
179
180sub stopLaunchdDaemons
181{
182    my $any = 1;
183    foreach my $dir (qw( /Library/LaunchAgents /Library/LaunchDaemons )) {
184        opendir(DIR, $dir) or next;
185        while (defined($_ = readdir(DIR))) {
186            next unless /^(org\.glimmerblocker.*\.plist)$/;
187            print STDERR "Stops launchd daemon: $1\n";
188            my $path = "$dir/$1";
189            sys(("launchctl", "unload", $path));
190            unlink($path);
191            $any = 1;
192        }
193        closedir(DIR);
194    }
195    sleep 1 if $any;
196}
197
198sub killSystemPreferencesApplication
199{
200    if ($runningFromSystemPreferences) {
201        print "Detaches from System Preferences process: pid = $$, ppid = ", getppid(), "\n";
202        if (setsid() < 0) {
203            print "Failed to setsid() nr 1\n";
204        }
205        exit if fork();
206        if (setsid() < 0) {
207            print "Failed to setsid() nr 2\n";
208        }
209        exit if fork();
210        if (setsid() < 0) {
211            print "Failed to setsid() nr 3\n";
212        }
213        sleep 1;
214        print "Now detacted from SysPref parent process: pid = $$, ppid = ", getppid(), "\n";
215#       if (fork()) {
216#           print "Tells System Preferences to quit using AppleScript (helper pid $$).\n";
217#           system(("osascript", "-e",
218#                       "tell application \"System Preferences\"\n".
219#                       "   ignoring application responses\n".
220#                       "      quit\n".
221#                       "   end ignoring\n".
222#                       "end tell\n"));
223#           exit;
224#       }
225    }
226# The updater application should already have quitted it,
227# but make really sure it doesn't override the upgraded PanelPreferences.xml
228    unless (open(FH, "ps axo pid=,user=,comm= |")) {
229        print "Can't pipe from ps-find-syspanel.\n";
230        return;
231    }
232    my $num = 0;
233    while (<FH>) {
234        next unless m!^\s*(\d+)\s+(\S+)\s+/Applications/System Preferences.app/Contents/MacOS!;
235        print "Kills System Preferences.app with pid $1 user ($2)\n";
236#kill 1, $1;
237#sleep 1;
238#kill 9, $1;
239        $num++;
240    }
241    print "Found $num running System Preferences apps.\n";
242    close(FH);
243}
244
245###############################################################################################
246#
247print "MSG:Stops old proxy.\n";
248stopLaunchdDaemons();
249#
250print "MSG:Prepares system.\n";
251makeGroup();
252makeUser();
253#
254createFolder("/Library/Logs", "root:admin", 0775, 0);
255createFolder("/Library/Logs/GlimmerBlocker", "_glimmerblocker:_glimmerblocker", 0755, 2);
256my $log = "/Library/Logs/GlimmerBlocker/GlimmerBlocker.log";
257unless (-e $log) {
258    open(FH, ">$log") && close(FH);
259}
260setPerms($log, 0644);
261system(("chown", "_glimmerblocker:_glimmerblocker", $log));
262system(("chmod", "666", "/Library/Logs/GlimmerBlocker/Launchd.log"));
263#
264# Can't store preferences in /Library/Preferences because it has drwxrwxr-x
265# so all admin users can change all files at will.
266createFolder($settingsPath, "_glimmerblocker:_glimmerblocker", 0755, 1);
267createFolder("$settingsPath/Filter subscriptions", "_glimmerblocker:_glimmerblocker", 0755, 2);
268createFolder($lastUpdateCheckPath, "_glimmerblocker:admin", 0777, 2);
269setPerms($javaSettingsPath, 0600);
270setPerms($panelSettingsPath, 0644);
271#
272unless ($runningBundlePath eq $panelPath) {
273    print "\n";
274    printf "Payload size: %.1f MB.\n", (-s $runningBundlePath) / 1024.0 / 1024.0;
275    print "MSG:Relocates Panel bundle.\n";
276    sys(("rsync", "--archive", "--delete-after", "$runningBundlePath/", $panelPath));
277}
278foreach my $payloadName (qw( GlimmerBlockerProxy GlimmerBlockerUpdater )) {
279    my $tar = "$panelPath/$payloadName.tar.gz";
280    next unless -e $tar;
281    print "MSG:Installs $payloadName\n";
282    sys(("tar", "zxf", $tar, "-C", "$panelPath/Contents"));
283    sys(("rm", $tar));
284}
285print "MSG:Sets permissions.\n";
286system(("chown", "-R", "_glimmerblocker:_glimmerblocker", $panelPath));
287system(("chown", "root:admin", "$panelPath/Contents/MacOS/AuthHelper"));
288system(("chmod", "6555", "$panelPath/Contents/MacOS/AuthHelper"));
289unlink("/Library/GlimmerBlocker/LastUpdateCheck.txt"); # obsolete.
290
291###############################################################################################
292
293sub needsUpgradeLittleSnitch
294{
295    my $path = "/Library/Little Snitch/Little Snitch UIAgent.app/Contents/Info.plist";
296    return 0 unless -s $path;
297    open(FH, $path) || return 0;
298    $_ = join("", <FH>);
299    close(FH);
300    return m!<key>CFBundleVersion</key>\s*<string>385</string>! ? 1 : 0;
301}   
302
303sub openPreferencesPanel
304{
305    print "Opens System Preferences.\n";
306    system(("open", $panelPath));
307}
308
309sub getPlistPath
310{
311    my ($opt) = @_;
312    my $r = `$authHelper $opt`;
313    unless ($r =~ m{^((/[-.\w]+)+)$}) {
314        die "Could not decode AuthHelper $opt output = '$r'";
315    }
316    return $1;
317}
318
319sub savePlist
320{
321    my ($pathOpt, $contentsOpt) = @_;
322    my $plistPath = getPlistPath($pathOpt);
323    my $plistContents = `$authHelper $contentsOpt`;
324    my $plistDir = $plistPath;
325    $plistDir =~ s{/[^/]+$}{};
326    unless (-e $plistDir) {
327        sys("mkdir -p $plistDir");
328        sys("chmod 755 $plistDir");
329        system("chown root:wheel $plistDir"); # ok to fail in 10.6
330    }
331    sys("chmod u+w $plistDir");
332    open(FH, ">$plistPath") || die "Can't create $plistPath for writing.";
333    print FH $plistContents;
334    close(FH);
335    sys("chown root:wheel $plistPath");
336    sys("chmod 644 $plistPath");
337    sys("launchctl load $plistPath");
338}
339
340sub startServer
341{
342    print "\n";
343    print "Launches java server.\n";
344    print "MSG:Starts proxy server.\n";
345    savePlist("--plist-path", "--plist-contents");
346}
347
348sub runJavaProgram
349{
350    my ($simpleClassPath, $mainClass, $desc) = @_;
351    my $cp = "";
352    opendir(DIR, $javaJarDir) || die "Can't readdir ($!): $javaJarDir";
353    while (defined($_ = readdir(DIR))) {
354        next unless /^(\w+(-\w+)*\.jar)$/;
355        my $fn = $1;
356        next if $simpleClassPath && $fn ne "glimmerblocker-server.jar";
357        $cp .= ":" if length($cp);
358        my $path = "$javaJarDir/$fn";
359        sys(("file", $path));
360        sys(("md5", $path));
361        $cp .= $path;
362    }
363    closedir(DIR);
364    print "\n";
365    print "Default 'java':\n";
366    system(("java", "-version"));
367    print "Running $desc.\n";
368    foreach my $v (("1.6", "1.5", "1.7", "1.8", "1.9", "")) {
369        my $java = length($v);
370        if (-x "/usr/libexec/java_home" && -e "/System/Library/Java/JavaVirtualMachines") {
371            ;# java_home in 10.6.0 doesn't support the -exec parameter.
372            $java = "/usr/libexec/java_home";
373            $java .= " -v'$v+'" if length $v;
374            $java .= " -exec java";
375        } else {
376            $java = length($v)
377                ? "/System/Library/Frameworks/JavaVM.framework/Versions/$v/Home/bin/java"
378                : "/usr/bin/java";
379            next unless -e $java;
380        }
381        my $cmd = "time sudo -u _glimmerblocker $java";
382        $cmd .= " -XX:+UseCompressedOops" unless $v eq "1.5";
383        $cmd .= " -Djava.awt.headless=true";
384        $cmd .= " -Dfile.encoding=UTF-8";
385        $cmd .= " -cp '$cp'";
386        $cmd .= " $mainClass";
387        print "Runs: $cmd\n";
388        print "\n";
389        my $x = system($cmd);
390        unless ($x) {
391            print "Done running $desc.\n";
392            return;
393        }
394        print "Failed ($x) executing: $cmd\n";
395    }
396    print "CAN'T FIND JAVA INSTALLATION.\n";
397    exit(1);
398}
399
400sub upgradePanelPreferences
401{
402    runJavaProgram(0, "org.glimmerblocker.proxy.PanelPreferencesUpgrader", "PanelPreferencesUpgrader");
403}
404
405sub savePanelPrefsXml
406{
407    my ($xml) = @_;
408    open(FH, ">$panelSettingsPath") || die "Can't create $panelSettingsPath for writing: $!";
409    print FH $xml;
410    close(FH);
411    sys("chown _glimmerblocker:_glimmerblocker $panelSettingsPath");
412    sys("chmod 644 $panelSettingsPath");
413}
414
415sub wantsServerToRun
416{
417    return 1 if -e getPlistPath("--plist-path");
418    return prefByXPath('prefs/proxy-server/@enabled');
419}
420
421sub doneInstalling
422{
423    unless ($runningBundlePath eq $panelPath) {
424        print "\n";
425        print "MSG:Zaps wrongly located bundle.\n";
426        sys(("rm", "-rf", $runningBundlePath));
427    }
428    if (prefByXPath('prefs/updater/@check-in-background')) {
429        my $beta = prefByXPath('prefs/updater/@use-beta-versions');
430        my $content = $beta ? '--updater-plist-content --beta' : '--updater-plist-content';
431        savePlist("--updater-plist-path", $content);
432    }
433    sleep 2; # give Java a bit of time to get running.
434    openPreferencesPanel(); # if needsUpgradeLittleSnitch() || $runningFromSystemPreferences;
435    print "\n";
436    print "Installer done, exits.\n";
437    exit(0);
438}
439
440killSystemPreferencesApplication();
441sys("ls -ld / /Library /Library/PreferencePanes $panelPath $javaJarDir");
442print "#\n";
443print "# OBS: If the following command fails,\n";
444print "       you probably have a permission problem with one of the above directories.\n";
445print "#\n";
446sys("sudo -u _glimmerblocker sh -c 'id;pwd;cd $javaJarDir;pwd;ls -l'");
447
448runJavaProgram(1, "org.glimmerblocker.proxy.TestJava", "SimpleJavaTest");
449
450if (-e $panelSettingsPath) {
451    print "\n";
452    my $run = wantsServerToRun();
453    print "", ($run ? "Wants" : "Does not want"), " server to run.\n";
454    print "MSG:Updates settings.\n";
455    upgradePanelPreferences();
456    openPreferencesPanel() if needsUpgradeLittleSnitch() || $runningFromSystemPreferences;
457    print "postflight done of upgrade, has panel-prefs, so just quit\n";
458    if ($run) {
459        sleep 2;
460        startServer();
461    }
462    doneInstalling();
463}
464
465print "\n";
466print "Missing panel prefs -> creates initial file\n";
467print "MSG:Creates initial settings.\n";
468# initial installation. Create prefs file and launch java server.
469open(FH, "/dev/random") || die "Can't open /dev/random for reading";
470my $adminKey;
471read(FH, $adminKey, 20);
472$adminKey = unpack("H*", $adminKey);
473my $port = 8228;
474my $xml = <<"XML";
475<?xml version="1.0" encoding="UTF-8"?>
476<prefs version="0">
477    <filters>
478    </filters>
479    <proxy-server listen-port="$port" info-admin-key="$adminKey" enabled="1"></proxy-server>
480    <suspects show-details="1" enabled="1"/>
481    <history enabled="1"/>
482    <updater check-in-background="1"/>
483</prefs>
484XML
485savePanelPrefsXml($xml);
486upgradePanelPreferences();
487sys("sync");
488#
489startServer();
490#
491print "\n";
492print "Synchronizes network proxy settings.\n";
493print "MSG:Updates 'Network' proxy settings.\n";
494sys("$panelPath/Contents/MacOS/NetworkSettingsTool $port");
495#
496# wait for java server to start before we're really done
497# so the user don't get the "proxy not responding" error if he's quick to launch Safari.
498print "\n";
499print "MSG:Waits for proxy to become ready.\n";
500my $iaddr = inet_aton("127.0.0.1") || die "Failed creating iaddr";
501my $paddr = sockaddr_in($port, $iaddr) || die "Failed creating paddr";
502my $proto = getprotobyname('tcp') || die "Failed getting tcp proto";
503foreach (1 .. 20) {
504    socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "Failed creating socket";
505    my $ok = connect(SOCK, $paddr);
506    close(SOCK);
507    if ($ok) {
508        print "Java proxy is now responding at localhost:$port\n";
509        last;
510    }
511    print "Waits for java proxy to respond at localhost:$port\n";
512    sleep 1;
513}
514doneInstalling();
515# EOF
Note: See TracBrowser for help on using the repository browser.