1/*
2 * JavaAppLauncher: a simple Java application launcher for Mac OS X.
3 * main.mm
4 *
5 * Paul J. Lucas [paul@lightcrafts.com]
6 *
7 * This code is based on:
8 * http://developer.apple.com/samplecode/simpleJavaLauncher/simpleJavaLauncher.html
9 */
10
11/**
12 * Undefine to cause this application to crash immediately to test the Crash
13 * Reporter application.
14 */
15/* #define TEST_CRASH_REPORTER */
16
17// standard
18#import <Cocoa/Cocoa.h>
19#import <CoreFoundation/CoreFoundation.h>
20#import <cstdlib>
21#import <cstring>
22#import <dirent.h>
23#import <fcntl.h>                       /* for open(2) */
24#import <fstream>
25#import <iostream>
26#import <mach-o/arch.h>                 /* for NXGetLocalArchInfo(3) */
27#import <signal.h>
28#import <sys/types.h>
29#import <unistd.h>
30
31// local
32#import "JavaParamBlock.h"
33#import "SetJVMVersion.h"
34#import "LC_CocoaUtils.h"
35#import "md5.h"
36#import "UI.h"
37
38using namespace std;
39using namespace LightCrafts;
40
41/**
42 * The path relative to the application bundle of where the Crash Reporter
43 * application is.
44 */
45#define CRASH_REPORTER_PATH "/Contents/Resources/CrashReporter.app"
46
47/**
48 * The full path to where the current Mac OS X version information is.
49 */
50#define SYSTEM_VERSION_PLIST \
51    "/System/Library/CoreServices/SystemVersion.plist"
52
53//
54// Obfuscate function names in object code.
55//
56#define checkCPUType    cC
57#define checkJar        cJ
58#define checkJars       cJs
59#define checkOSXVersion cO
60#define rot47           r
61
62cpu_type_t lc_cpuType;
63
64////////// Local functions ////////////////////////////////////////////////////
65
66/**
67 * This is a do-nothing call-back function needed to ensure the CFRunLoop
68 * doesn't exit right away.  This call-back is called when the source has
69 * fired.
70 */
71extern "C" void doNothing( void* ) {
72    // do nothing
73}
74
75/**
76 * Catch a signal and launch our custom Crash Reporter.
77 */
78extern "C" void catchSignal( int sigID ) {
79    NSString *const bundlePath = [[NSBundle mainBundle] bundlePath];
80    NSString *const crashReporterPath =
81        [bundlePath stringByAppendingPathComponent:@CRASH_REPORTER_PATH];
82
83    [[NSWorkspace sharedWorkspace]
84        openFile:bundlePath withApplication:crashReporterPath
85    ];
86    ::exit( -1 );
87}
88
89/**
90 * Check the CPU type.
91 */
92static void checkCPUType() {
93    NXArchInfo const *const arch = NXGetLocalArchInfo();
94    if ( !arch ) {
95        //
96        // This should never return null (how can the info not be available?),
97        // but the manual page arch(3) says it's possible.  So err on the side
98        // of allowing the application to run and hope for the best.
99        //
100        return;
101    }
102    lc_cpuType = arch->cputype;
103    switch ( arch->cputype ) {
104        case CPU_TYPE_I386:
105            cout << "CPU = i386" << endl;
106            return;
107        case CPU_TYPE_POWERPC:
108            if ( arch->cpusubtype >= CPU_SUBTYPE_POWERPC_7400 ) {
109                cout << "CPU = ppc" << endl;
110                return;
111            }
112    }
113    LC_die( @"Newer CPU required", @"CPU requirements" );
114}
115
116/**
117 * Check a jar to see if its hash still matches what it did at the time it was
118 * built.
119 */
120static void checkJar( char const *jarPath, char const *correctMD5 ) {
121    typedef unsigned char UCHAR;
122    typedef UCHAR const UCHARC;
123
124    int const MD5_BUF_SIZE = 16;        // MD5 encodes to 128 bits or 16 bytes
125
126    //
127    // The "salt" is the secret key.  First, it's broken into two big pieces
128    // because it's better cryptographically speaking to put a salt both before
129    // and after the actual data to be encrypted by MD5 since, even if you know
130    // what the salt is, you don't know where the "split" between the two
131    // pieces is.  (A null separates the 2 pieces.)
132    //
133    // Second, it's broken into 4-character chunks so the output of the Unix
134    // "strings" command on the library shows at most the chunks and not the
135    // entire salt as a single string.
136    //
137    char const *const SALT[] = {
138        "Quid", "quid", " lat", "ine ", "dict", "um", 0,
139        " sit", " alt", "um v", "idit", "ur", 0
140    };
141
142    //
143    // This SEASON macro is used to add salt to the MD5 data.
144    // Season ... salt: get it?  :-)
145    //
146#   define SEASON(STR) MD5Update( &md5_ctx, (UCHARC*)(STR), ::strlen( STR ) )
147
148    MD5Context md5_ctx;
149    MD5Init( &md5_ctx );
150
151    char const *const *salt;
152    for ( salt = SALT; *salt; ++salt )
153        SEASON( *salt );
154
155    ifstream ifs( jarPath );
156    if ( !ifs )
157        LC_die( @"Corrupted", @"Missing Jar" );
158    char buf[ 8192 ];
159    while ( !ifs.eof() ) {
160        ifs.read( buf, sizeof buf );
161        MD5Update( &md5_ctx, (UCHARC*)buf, ifs.gcount() );
162    }
163
164    for ( ++salt; *salt; ++salt )
165        SEASON( *salt );
166
167    UCHAR md5_buf[ MD5_BUF_SIZE ];
168    MD5Final( md5_buf, &md5_ctx );
169
170    char md5_char_buf[ MD5_BUF_SIZE * 2 + 1 ];
171    char *p = md5_char_buf;
172    for ( int i = 0; i < MD5_BUF_SIZE; ++i, p += 2 )
173        sprintf( p, "%02x", md5_buf[i] );
174    *p = '\0';
175
176    if ( ::strcmp( md5_char_buf, correctMD5 ) != 0 )
177        LC_die( @"Corrupted", @"Bad Jar" );
178}
179
180/**
181 * ROT-47 cipher.
182 */
183static char* rot47( char *s, char const *s47 ) {
184    char *const s0 = s;
185    do {
186        char c = *s47;
187        if ( c >= '!' && c <= '~' )
188            c = (c - '!' + 47) % 94 + '!';
189        *s++ = c;
190    } while ( *s47++ );
191    return s0;
192}
193
194/**
195 * Ensure the jars haven't been altered since build-time.
196 */
197static void checkJars() {
198    auto_obj<NSAutoreleasePool> pool;
199    NSString const *const javaPath =
200        [[[NSBundle mainBundle] bundlePath]
201            stringByAppendingPathComponent:@"/Contents/Resources/Java"];
202
203    char const *const jar_md5[] = {
204#       include "jar_md5_include"
205        0
206    };
207    for ( char const *const *s = jar_md5; *s; s += 2 ) {
208        char jar_buf[ 50 ];
209        NSString const *const jarPath =
210            [javaPath stringByAppendingPathComponent:
211                [NSString stringWithUTF8String:rot47( jar_buf, s[0] )]];
212        checkJar( [jarPath UTF8String], s[1] );
213    }
214}
215
216/**
217 * Check the Mac OS X version.
218 */
219static void checkOSXVersion() {
220    auto_obj<NSAutoreleasePool> pool;
221    NSDictionary *const versionDict =
222        [NSDictionary dictionaryWithContentsOfFile:@SYSTEM_VERSION_PLIST];
223    if ( !versionDict )
224        return;
225    NSString *const osVersion = [versionDict objectForKey:@"ProductVersion"];
226    if ( !osVersion )
227        return;
228    cout << "This is Mac OS X " << [osVersion UTF8String] << endl;
229    int major, minor = 0, point = 0;
230    ::sscanf( [osVersion UTF8String], "%d.%d.%d", &major, &minor, &point );
231    switch ( minor ) {
232        case 0:     // Cheetah
233        case 1:     // Puma
234        case 2:     // Jaguar
235        case 3:     // Panther
236            break;
237        case 4:     // Tiger
238            if ( point >= 3 )
239                return;
240            break;
241        default:    // Assume any future Mac OS X is OK.
242            return;
243    }
244    LC_die( @"Newer Mac OS X required", @"OS requirements" );
245}
246
247/**
248 * Redirect standard output and standard error to a log file.  The name of the
249 * log file is <CFBundleName>.log.
250 */
251static void redirectOutput() {
252    auto_obj<NSAutoreleasePool> pool;
253
254    NSString *const bundleName =
255        [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
256    if ( !bundleName )
257        LC_die( @"Corrupted", @"Missing CFBundleName" );
258
259    NSString const *const logFile =
260        [[[[NSString stringWithString:@"~/Library/Logs"]
261            stringByAppendingPathComponent:bundleName]
262            stringByAppendingString:@".log"]
263            stringByExpandingTildeInPath];
264
265    int const logFD =
266        ::open( [logFile UTF8String], O_WRONLY | O_CREAT | O_TRUNC, 0644 );
267    if ( logFD == -1 ) {
268        //
269        // If for whatever reason we couldn't open the log file, just return
270        // and sacrifice logging rather than prevent the application from
271        // running.
272        //
273        return;
274    }
275    ::close( STDOUT_FILENO );
276    ::dup( logFD );                         // stdout -> log
277    ::close( logFD );
278    ::dup2( STDOUT_FILENO, STDERR_FILENO ); // stderr -> log
279}
280
281////////// main ///////////////////////////////////////////////////////////////
282
283/**
284 * Fire up the app.
285 */
286int main( int argc, char const **argv ) {
287    //
288    // Set up to catch nasty signals.
289    //
290    ::signal( SIGILL , &catchSignal );
291    ::signal( SIGTRAP, &catchSignal );
292    ::signal( SIGEMT , &catchSignal );
293    ::signal( SIGFPE , &catchSignal );
294    ::signal( SIGBUS , &catchSignal );
295    ::signal( SIGSEGV, &catchSignal );
296    ::signal( SIGSYS , &catchSignal );
297    ::signal( SIGPIPE, SIG_IGN );
298
299#ifdef  TEST_CRASH_REPORTER
300    //
301    // We want to test the Crash Reporter application, so do something to make
302    // this application crash.  Writing to memory location 0 works.
303    //
304    int *p = 0;
305    *p = 0;
306#endif
307
308    redirectOutput();
309
310#ifdef DEBUG
311    ::setenv( "AEDebugSends", "1", 1 );
312    ::setenv( "AEDebugReceives", "1", 1 );
313#endif
314
315    checkCPUType();
316    checkOSXVersion();
317    checkJars();
318
319    return NSApplicationMain( argc, argv );
320}
321/* vim:set et sw=4 ts=4: */
322