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