From 3ebdb1bd7ae38bf0fb205dfaa2f5fde3d67ea141 Mon Sep 17 00:00:00 2001 From: nealsid Date: Wed, 1 Apr 2009 03:18:49 +0000 Subject: Open sourcing the Breakpad framework from Google. A=many, many people R=nealsid, jeremy moskovich(from Chromium project) git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@322 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/client/mac/sender/crash_report_sender.m | 762 ++++++++++++++++++++++++++++ 1 file changed, 762 insertions(+) create mode 100644 src/client/mac/sender/crash_report_sender.m (limited to 'src/client/mac/sender/crash_report_sender.m') diff --git a/src/client/mac/sender/crash_report_sender.m b/src/client/mac/sender/crash_report_sender.m new file mode 100644 index 00000000..5d1b1e51 --- /dev/null +++ b/src/client/mac/sender/crash_report_sender.m @@ -0,0 +1,762 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +#import +#import + +#import +#import + +#import "common/mac/HTTPMultipartUpload.h" + +#import "crash_report_sender.h" +#import "common/mac/GTMLogger.h" + +#define kLastSubmission @"LastSubmission" +const int kMinidumpFileLengthLimit = 800000; + +#define kApplePrefsSyncExcludeAllKey @"com.apple.PreferenceSync.ExcludeAllSyncKeys" + +@interface Reporter(PrivateMethods) ++ (uid_t)consoleUID; + +- (id)initWithConfigurationFD:(int)fd; + +- (NSString *)readString; +- (NSData *)readData:(ssize_t)length; + +- (BOOL)readConfigurationData; +- (BOOL)readMinidumpData; +- (BOOL)readLogFileData; + +- (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport; +- (BOOL)shouldSubmitReport; + +// Run an alert window with the given timeout. Returns NSAlertButtonDefault if +// the timeout is exceeded. A timeout of 0 queues the message immediately in the +// modal run loop. +- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout; + +- (NSString*)clientID; +@end + +@implementation Reporter +//============================================================================= ++ (uid_t)consoleUID { + SCDynamicStoreRef store = + SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Reporter"), NULL, NULL); + uid_t uid = -2; // Default to "nobody" + if (store) { + CFStringRef user = SCDynamicStoreCopyConsoleUser(store, &uid, NULL); + + if (user) + CFRelease(user); + else + uid = -2; + + CFRelease(store); + } + + return uid; +} + +//============================================================================= +- (id)initWithConfigurationFD:(int)fd { + if ((self = [super init])) { + configFile_ = fd; + } + + // Because the reporter is embedded in the framework (and many copies + // of the framework may exist) its not completely certain that the OS + // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our + // Info.plist. To make sure, also set the key directly if needed. + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) { + [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey]; + } + + return self; +} + +//============================================================================= +- (NSString *)readString { + NSMutableString *str = [NSMutableString stringWithCapacity:32]; + char ch[2] = { 0 }; + + while (read(configFile_, &ch[0], 1) == 1) { + if (ch[0] == '\n') { + // Break if this is the first newline after reading some other string + // data. + if ([str length]) + break; + } else { + [str appendString:[NSString stringWithUTF8String:ch]]; + } + } + + return str; +} + +//============================================================================= +- (NSData *)readData:(ssize_t)length { + NSMutableData *data = [NSMutableData dataWithLength:length]; + char *bytes = (char *)[data bytes]; + + if (read(configFile_, bytes, length) != length) + return nil; + + return data; +} + +//============================================================================= +- (BOOL)readConfigurationData { + parameters_ = [[NSMutableDictionary alloc] init]; + + while (1) { + NSString *key = [self readString]; + + if (![key length]) + break; + + // Read the data. Try to convert to a UTF-8 string, or just save + // the data + NSString *lenStr = [self readString]; + ssize_t len = [lenStr intValue]; + NSData *data = [self readData:len]; + id value = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + + [parameters_ setObject:value ? value : data forKey:key]; + [value release]; + } + + // generate a unique client ID based on this host's MAC address + // then add a key/value pair for it + NSString *clientID = [self clientID]; + [parameters_ setObject:clientID forKey:@"guid"]; + + close(configFile_); + configFile_ = -1; + + return YES; +} + +// Per user per machine +- (NSString *)clientID { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey]; + if (crashClientID) { + return crashClientID; + } + + // Otherwise, if we have no client id, generate one! + srandom([[NSDate date] timeIntervalSince1970]); + long clientId1 = random(); + long clientId2 = random(); + long clientId3 = random(); + crashClientID = [NSString stringWithFormat:@"%x%x%x", + clientId1, clientId2, clientId3]; + + [ud setObject:crashClientID forKey:kClientIdPreferenceKey]; + [ud synchronize]; + return crashClientID; +} + +//============================================================================= +- (BOOL)readLogFileData { + unsigned int logFileCounter = 0; + + NSString *logPath; + int logFileTailSize = [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] + intValue]; + + NSMutableArray *logFilenames; // An array of NSString, one per log file + logFilenames = [[NSMutableArray alloc] init]; + + char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX"; + char *tmpDir = mkdtemp(tmpDirTemplate); + + // Construct key names for the keys we expect to contain log file paths + for(logFileCounter = 0;; logFileCounter++) { + NSString *logFileKey = [NSString stringWithFormat:@"%@%d", + @BREAKPAD_LOGFILE_KEY_PREFIX, + logFileCounter]; + + logPath = [parameters_ objectForKey:logFileKey]; + + // They should all be consecutive, so if we don't find one, assume + // we're done + + if (!logPath) { + break; + } + + NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath]; + + if (entireLogFile == nil) { + continue; + } + + NSRange fileRange; + + // Truncate the log file, only if necessary + + if ([entireLogFile length] <= logFileTailSize) { + fileRange = NSMakeRange(0, [entireLogFile length]); + } else { + fileRange = NSMakeRange([entireLogFile length] - logFileTailSize, + logFileTailSize); + } + + char tmpFilenameTemplate[100]; + + // Generate a template based on the log filename + sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir, + [[logPath lastPathComponent] fileSystemRepresentation]); + + char *tmpFile = mktemp(tmpFilenameTemplate); + + NSData *logSubdata = [entireLogFile subdataWithRange:fileRange]; + NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile]; + [logSubdata writeToFile:tmpFileString atomically:NO]; + + [logFilenames addObject:[tmpFileString lastPathComponent]]; + [entireLogFile release]; + } + + if ([logFilenames count] == 0) { + [logFilenames release]; + logFileData_ = nil; + return NO; + } + + // now, bzip all files into one + NSTask *tarTask = [[NSTask alloc] init]; + + [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]]; + [tarTask setLaunchPath:@"/usr/bin/tar"]; + + NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf", + @"log.tar.bz2",nil]; + [bzipArgs addObjectsFromArray:logFilenames]; + + [logFilenames release]; + + [tarTask setArguments:bzipArgs]; + [tarTask launch]; + [tarTask waitUntilExit]; + [tarTask release]; + + NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir]; + logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile]; + if (logFileData_ == nil) { + GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile); + return NO; + } + return YES; + +} + +//============================================================================= +- (BOOL)readMinidumpData { + NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey]; + NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey]; + + if (![minidumpID length]) + return NO; + + NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID]; + path = [path stringByAppendingPathExtension:@"dmp"]; + + // check the size of the minidump and limit it to a reasonable size + // before attempting to load into memory and upload + const char *fileName = [path fileSystemRepresentation]; + struct stat fileStatus; + + BOOL success = YES; + + if (!stat(fileName, &fileStatus)) { + if (fileStatus.st_size > kMinidumpFileLengthLimit) { + fprintf(stderr, "Breakpad Reporter: minidump file too large " \ + "to upload : %d\n", (int)fileStatus.st_size); + success = NO; + } + } else { + fprintf(stderr, "Breakpad Reporter: unable to determine minidump " \ + "file length\n"); + success = NO; + } + + if (success) { + minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path]; + success = ([minidumpContents_ length] ? YES : NO); + } + + if (!success) { + // something wrong with the minidump file -- delete it + unlink(fileName); + } + + return success; +} + +//============================================================================= +- (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport { + // Send without confirmation + if ([[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM] isEqualToString:@"YES"]) { + GTMLoggerDebug(@"Skipping confirmation and sending report"); + return YES; + } + + // Determine if we should create a text box for user feedback + BOOL shouldRequestComments = + [[parameters_ objectForKey:@BREAKPAD_REQUEST_COMMENTS] + isEqual:@"YES"]; + + NSString *display = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; + + if (![display length]) + display = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; + + NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR]; + + if (![vendor length]) + vendor = @"Vendor not specified"; + + NSBundle *bundle = [NSBundle mainBundle]; + [self setHeaderMessage:[NSString stringWithFormat: + NSLocalizedStringFromTableInBundle(@"headerFmt", nil, + bundle, + @""), vendor, display]]; + NSString *defaultButtonTitle = nil; + NSString *otherButtonTitle = nil; + NSTimeInterval timeout = 60.0; // timeout value for the user notification + + // Get the localized alert strings + // If we're going to submit a report, prompt the user if this is okay. + // Otherwise, just let them know that the app crashed. + + if (shouldSubmitReport) { + NSString *msgFormat = NSLocalizedStringFromTableInBundle(@"msg", + nil, + bundle, @""); + + [self setReportMessage:[NSString stringWithFormat:msgFormat, vendor]]; + + defaultButtonTitle = NSLocalizedStringFromTableInBundle(@"sendReportButton", + nil, bundle, @""); + otherButtonTitle = NSLocalizedStringFromTableInBundle(@"cancelButton", nil, + bundle, @""); + + // Nominally use the report interval + timeout = [[parameters_ objectForKey:@BREAKPAD_REPORT_INTERVAL] + floatValue]; + } else { + [self setReportMessage:NSLocalizedStringFromTableInBundle(@"noSendMsg", nil, + bundle, @"")]; + defaultButtonTitle = NSLocalizedStringFromTableInBundle(@"noSendButton", + nil, bundle, @""); + timeout = 60.0; + } + // show the notification for at least one minute + if (timeout < 60.0) { + timeout = 60.0; + } + + // Initialize Cocoa, needed to display the alert + NSApplicationLoad(); + + int buttonPressed = NSAlertAlternateReturn; + + if (shouldRequestComments) { + BOOL didLoadNib = [NSBundle loadNibNamed:@"Breakpad" owner:self]; + if (didLoadNib) { + // Append the request for comments to the |reportMessage| string. + NSString *commentsMessage = + NSLocalizedStringFromTableInBundle(@"commentsMsg", nil, bundle, @""); + [self setReportMessage:[NSString stringWithFormat:@"%@\n\n%@", + [self reportMessage], + commentsMessage]]; + + // Add the request for email address. + [self setEmailMessage: + NSLocalizedStringFromTableInBundle(@"emailMsg", nil, bundle, @"")]; + + // Run the alert + buttonPressed = [self runModalWindow:alertWindow withTimeout:timeout]; + + // Extract info from the user into the parameters_ dictionary + if ([self commentsValue]) { + [parameters_ setObject:[self commentsValue] + forKey:@BREAKPAD_COMMENTS]; + } + if ([self emailValue]) { + [parameters_ setObject:[self emailValue] + forKey:@BREAKPAD_EMAIL]; + } + } + } else { + // Create an alert panel to tell the user something happened + NSPanel* alert = NSGetAlertPanel([self headerMessage], + [self reportMessage], + defaultButtonTitle, + otherButtonTitle, nil); + + // Pop the alert with an automatic timeout, and wait for the response + buttonPressed = [self runModalWindow:alert withTimeout:timeout]; + + // Release the panel memory + NSReleaseAlertPanel(alert); + } + return buttonPressed == NSAlertDefaultReturn; +} + +- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout { + // Queue a |stopModal| message to be performed in |timeout| seconds. + [NSApp performSelector:@selector(stopModal) + withObject:nil + afterDelay:timeout]; + + // Run the window modally and wait for either a |stopModal| message or a + // button click. + [NSApp activateIgnoringOtherApps:YES]; + int returnMethod = [NSApp runModalForWindow:window]; + + // Cancel the pending |stopModal| message. + if (returnMethod != NSRunStoppedResponse) { + [NSObject cancelPreviousPerformRequestsWithTarget:NSApp + selector:@selector(stopModal) + object:nil]; + } + return returnMethod; +} + +- (IBAction)sendReport:(id)sender { + [alertWindow orderOut:self]; + // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow| + // matches the AppKit function NSRunAlertPanel() + [NSApp stopModalWithCode:NSAlertDefaultReturn]; +} + +// UI Button Actions +//============================================================================= +- (IBAction)cancel:(id)sender { + [alertWindow orderOut:self]; + // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow| + // matches the AppKit function NSRunAlertPanel() + [NSApp stopModalWithCode:NSAlertAlternateReturn]; +} + +- (IBAction)showPrivacyPolicy:(id)sender { + // Get the localized privacy policy URL and open it in the default browser. + NSURL* privacyPolicyURL = + [NSURL URLWithString:NSLocalizedStringFromTableInBundle( + @"privacyPolicyURL", nil, [NSBundle mainBundle], @"")]; + [[NSWorkspace sharedWorkspace] openURL:privacyPolicyURL]; +} + +// Text Field Delegate Methods +//============================================================================= +- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView + doCommandBySelector:(SEL)commandSelector { + BOOL result = NO; + // If the user has entered text, don't end editing on "return" + if (commandSelector == @selector(insertNewline:) + && [[textView string] length] > 0) { + [textView insertNewlineIgnoringFieldEditor:self]; + result = YES; + } + return result; +} + +// Accessors +//============================================================================= +- (NSString *)headerMessage { + return [[headerMessage_ retain] autorelease]; +} + +- (void)setHeaderMessage:(NSString *)value { + if (headerMessage_ != value) { + [headerMessage_ autorelease]; + headerMessage_ = [value copy]; + } +} + +- (NSString *)reportMessage { + return [[reportMessage_ retain] autorelease]; +} + +- (void)setReportMessage:(NSString *)value { + if (reportMessage_ != value) { + [reportMessage_ autorelease]; + reportMessage_ = [value copy]; + } +} + +- (NSString *)commentsValue { + return [[commentsValue_ retain] autorelease]; +} + +- (void)setCommentsValue:(NSString *)value { + if (commentsValue_ != value) { + [commentsValue_ autorelease]; + commentsValue_ = [value copy]; + } +} + +- (NSString *)emailMessage { + return [[emailMessage_ retain] autorelease]; +} + +- (void)setEmailMessage:(NSString *)value { + if (emailMessage_ != value) { + [emailMessage_ autorelease]; + emailMessage_ = [value copy]; + } +} + +- (NSString *)emailValue { + return [[emailValue_ retain] autorelease]; +} + +- (void)setEmailValue:(NSString *)value { + if (emailValue_ != value) { + [emailValue_ autorelease]; + emailValue_ = [value copy]; + } +} + +//============================================================================= +- (BOOL)shouldSubmitReport { + float interval = [[parameters_ objectForKey:@BREAKPAD_REPORT_INTERVAL] + floatValue]; + NSString *program = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + NSMutableDictionary *programDict = + [NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]]; + NSNumber *lastTimeNum = [programDict objectForKey:kLastSubmission]; + NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0; + NSTimeInterval now = CFAbsoluteTimeGetCurrent(); + NSTimeInterval spanSeconds = (now - lastTime); + + [programDict setObject:[NSNumber numberWithFloat:now] forKey:kLastSubmission]; + [ud setObject:programDict forKey:program]; + [ud synchronize]; + + // If we've specified an interval and we're within that time, don't ask the + // user if we should report + GTMLoggerDebug(@"Reporter Interval: %f", interval); + if (interval > spanSeconds) { + GTMLoggerDebug(@"Within throttling interval, not sending report"); + return NO; + } + return YES; +} + +//============================================================================= +- (void)report { + NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]]; + HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url]; + NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary]; + + // Set the known parameters. This should be kept up to date with the + // parameters defined in the Breakpad.h list of parameters. The intent + // is so that if there's a parameter that's not in this list, we consider + // it to be a "user-defined" parameter and we'll upload it to the server. + NSSet *knownParameters = + [NSSet setWithObjects:@kReporterMinidumpDirectoryKey, + @kReporterMinidumpIDKey, @BREAKPAD_PRODUCT_DISPLAY, + @BREAKPAD_PRODUCT, @BREAKPAD_VERSION, @BREAKPAD_URL, + @BREAKPAD_REPORT_INTERVAL, @BREAKPAD_SKIP_CONFIRM, + @BREAKPAD_SEND_AND_EXIT, @BREAKPAD_REPORTER_EXE_LOCATION, + @BREAKPAD_INSPECTOR_LOCATION, @BREAKPAD_LOGFILES, + @BREAKPAD_LOGFILE_UPLOAD_SIZE, @BREAKPAD_EMAIL, + @BREAKPAD_REQUEST_COMMENTS, @BREAKPAD_COMMENTS, + @BREAKPAD_VENDOR, nil]; + + // Add parameters + [uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_PRODUCT] + forKey:@"prod"]; + [uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_VERSION] + forKey:@"ver"]; + + if ([parameters_ objectForKey:@BREAKPAD_EMAIL]) { + [uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_EMAIL] forKey:@"email"]; + } + + NSString* comments = [parameters_ objectForKey:@BREAKPAD_COMMENTS]; + if (comments != nil) { + [uploadParameters setObject:comments forKey:@"comments"]; + } + // Add any user parameters + NSArray *parameterKeys = [parameters_ allKeys]; + int keyCount = [parameterKeys count]; + for (int i = 0; i < keyCount; ++i) { + NSString *key = [parameterKeys objectAtIndex:i]; + if (![knownParameters containsObject:key] && + ![key hasPrefix:@BREAKPAD_LOGFILE_KEY_PREFIX]) + [uploadParameters setObject:[parameters_ objectForKey:key] forKey:key]; + } + [upload setParameters:uploadParameters]; + // Add minidump file + if (minidumpContents_) { + [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"]; + + // Send it + NSError *error = nil; + NSData *data = [upload send:&error]; + NSString *result = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + const char *reportID = "ERR"; + + if (error) + fprintf(stderr, "Breakpad Reporter: Send Error: %s\n", + [[error description] UTF8String]); + else + reportID = [result UTF8String]; + + // rename the minidump file according to the id returned from the server + NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey]; + NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey]; + + NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp", + minidumpDir, minidumpID]; + NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp", + minidumpDir, reportID]; + + const char *src = [srcString fileSystemRepresentation]; + const char *dest = [destString fileSystemRepresentation]; + + if (rename(src, dest) == 0) { + fprintf(stderr, "Breakpad Reporter: Renamed %s to %s after successful " \ + "upload\n",src, dest); + } + else { + // can't rename - don't worry - it's not important for users + fprintf(stderr, "Breakpad Reporter: successful upload report ID = %s\n", + reportID ); + } + [result release]; + } + + if (logFileData_) { + HTTPMultipartUpload *logUpload = [[HTTPMultipartUpload alloc] initWithURL:url]; + + [uploadParameters setObject:@"log" forKey:@"type"]; + [logUpload setParameters:uploadParameters]; + [logUpload addFileContents:logFileData_ name:@"log"]; + + NSError *error = nil; + NSData *data = [logUpload send:&error]; + NSString *result = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + [result release]; + [logUpload release]; + } + + [upload release]; +} + +//============================================================================= +- (void)dealloc { + [parameters_ release]; + [minidumpContents_ release]; + [logFileData_ release]; + [super dealloc]; +} +@end + +//============================================================================= +int main(int argc, const char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + // The expectation is that there will be one argument which is the path + // to the configuration file + if (argc != 2) { + exit(1); + } + + // Open the file before (potentially) switching to console user + int configFile = open(argv[1], O_RDONLY, 0600); + + // we want to avoid a build-up of old config files even if they + // have been incorrectly written by the framework + unlink(argv[1]); + + if (configFile == -1) { + exit(1); + } + + Reporter *reporter = [[Reporter alloc] initWithConfigurationFD:configFile]; + + // Gather the configuration data + if (![reporter readConfigurationData]) { + exit(1); + } + + // Read the minidump into memory before we (potentially) switch from the + // root user + [reporter readMinidumpData]; + + [reporter readLogFileData]; + + // only submit a report if we have not recently crashed in the past + BOOL shouldSubmitReport = [reporter shouldSubmitReport]; + BOOL okayToSend = NO; + + // ask user if we should send + if (shouldSubmitReport) { + okayToSend = [reporter askUserPermissionToSend:shouldSubmitReport]; + } + + // If we're running as root, switch over to nobody + if (getuid() == 0 || geteuid() == 0) { + struct passwd *pw = getpwnam("nobody"); + + // If we can't get a non-root uid, don't send the report + if (!pw) + exit(0); + + if (setgid(pw->pw_gid) == -1) + exit(0); + + if (setuid(pw->pw_uid) == -1) + exit(0); + } + + if (okayToSend && shouldSubmitReport) { + [reporter report]; + } + + // Cleanup + [reporter release]; + [pool release]; + + return 0; +} -- cgit v1.2.1