| Index: chrome/installer/gcapi_mac/gcapi.mm
|
| diff --git a/chrome/installer/gcapi_mac/gcapi.mm b/chrome/installer/gcapi_mac/gcapi.mm
|
| index ab561e7977c9c67ca94a980c710e23da0971b09a..7c9396124df768778a1539c84eead6ec8618f69e 100644
|
| --- a/chrome/installer/gcapi_mac/gcapi.mm
|
| +++ b/chrome/installer/gcapi_mac/gcapi.mm
|
| @@ -5,30 +5,33 @@
|
| #include "chrome/installer/gcapi_mac/gcapi.h"
|
|
|
| #import <Cocoa/Cocoa.h>
|
| +#include <grp.h>
|
| +#include <pwd.h>
|
| #include <sys/stat.h>
|
| #include <sys/types.h>
|
| #include <sys/utsname.h>
|
|
|
| namespace {
|
|
|
| +// The "~~" prefixes are replaced with the home directory of the
|
| +// console owner (i.e. not the home directory of the euid).
|
| NSString* const kChromeInstallPath = @"/Applications/Google Chrome.app";
|
|
|
| NSString* const kBrandKey = @"KSBrandID";
|
| -NSString* const kSystemBrandPath = @"/Library/Google/Google Chrome Brand.plist";
|
| -NSString* const kUserBrandPath = @"~/Library/Google/Google Chrome Brand.plist";
|
| +NSString* const kUserBrandPath = @"~~/Library/Google/Google Chrome Brand.plist";
|
|
|
| NSString* const kSystemKsadminPath =
|
| @"/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
|
| "Contents/MacOS/ksadmin";
|
|
|
| NSString* const kUserKsadminPath =
|
| - @"~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
|
| + @"~~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
|
| "Contents/MacOS/ksadmin";
|
|
|
| NSString* const kSystemMasterPrefsPath =
|
| @"/Library/Google/Google Chrome Master Preferences";
|
| NSString* const kUserMasterPrefsPath =
|
| - @"~/Library/Application Support/Google/Google Chrome Master Preferences";
|
| + @"~~/Library/Application Support/Google/Google Chrome Master Preferences";
|
|
|
| NSString* const kChannelKey = @"KSChannelID";
|
| NSString* const kVersionKey = @"KSVersion";
|
| @@ -60,26 +63,58 @@ bool IsOSXVersionSupported() {
|
| return mac_os_x_minor_version >= 6 && mac_os_x_minor_version <= 8;
|
| }
|
|
|
| +// Returns the pid/gid of the logged-in user, even if getuid() claims that the
|
| +// current user is root.
|
| +// Returns NULL on error.
|
| +passwd* GetRealUserId() {
|
| + CFDictionaryRef session_info_dict = CGSessionCopyCurrentDictionary();
|
| + [NSMakeCollectable(session_info_dict) autorelease];
|
| + if (!session_info_dict)
|
| + return NULL; // Possibly no screen plugged in.
|
| +
|
| + CFNumberRef ns_uid = (CFNumberRef)CFDictionaryGetValue(session_info_dict,
|
| + kCGSessionUserIDKey);
|
| + if (CFGetTypeID(ns_uid) != CFNumberGetTypeID())
|
| + return NULL;
|
| +
|
| + uid_t uid;
|
| + BOOL success = CFNumberGetValue(ns_uid, kCFNumberSInt32Type, &uid);
|
| + if (!success)
|
| + return NULL;
|
| +
|
| + return getpwuid(uid);
|
| +}
|
| +
|
| enum TicketKind {
|
| kSystemTicket, kUserTicket
|
| };
|
|
|
| -BOOL HasChromeTicket(TicketKind kind) {
|
| +// Replaces "~~" with |home_dir|.
|
| +NSString* AdjustHomedir(NSString* s, const char* home_dir) {
|
| + if (![s hasPrefix:@"~~"])
|
| + return s;
|
| + NSString* ns_home_dir = [NSString stringWithUTF8String:home_dir];
|
| + return [ns_home_dir stringByAppendingString:[s substringFromIndex:2]];
|
| +}
|
| +
|
| +BOOL HasChromeTicket(TicketKind kind, const passwd* user) {
|
| // Don't use Objective-C 2 loop syntax, in case an installer runs on 10.4.
|
| - NSMutableArray* keystonePaths =
|
| + NSMutableArray* keystone_paths =
|
| [NSMutableArray arrayWithObject:kSystemKsadminPath];
|
| - if (kind == kUserTicket && geteuid() != 0)
|
| - [keystonePaths insertObject:kUserKsadminPath atIndex:0];
|
| - NSEnumerator* e = [keystonePaths objectEnumerator];
|
| - id ksPath;
|
| - while ((ksPath = [e nextObject])) {
|
| + if (kind == kUserTicket) {
|
| + [keystone_paths insertObject:AdjustHomedir(kUserKsadminPath, user->pw_dir)
|
| + atIndex:0];
|
| + }
|
| + NSEnumerator* e = [keystone_paths objectEnumerator];
|
| + id ks_path;
|
| + while ((ks_path = [e nextObject])) {
|
| NSTask* task = nil;
|
| NSString* string = nil;
|
| - bool ksadminRanSuccessfully = false;
|
| + bool ksadmin_ran_successfully = false;
|
|
|
| @try {
|
| task = [[NSTask alloc] init];
|
| - [task setLaunchPath:ksPath];
|
| + [task setLaunchPath:ks_path];
|
|
|
| NSArray* arguments = @[
|
| kind == kUserTicket ? @"--user-store" : @"--system-store",
|
| @@ -87,6 +122,12 @@ BOOL HasChromeTicket(TicketKind kind) {
|
| @"--productid",
|
| @"com.google.Chrome",
|
| ];
|
| + if (geteuid() == 0 && kind == kUserTicket) {
|
| + NSString* run_as = [NSString stringWithUTF8String:user->pw_name];
|
| + [task setLaunchPath:@"/usr/bin/sudo"];
|
| + arguments = [@[@"-u", run_as, ks_path]
|
| + arrayByAddingObjectsFromArray:arguments];
|
| + }
|
| [task setArguments:arguments];
|
|
|
| NSPipe* pipe = [NSPipe pipe];
|
| @@ -99,28 +140,27 @@ BOOL HasChromeTicket(TicketKind kind) {
|
| NSData* data = [file readDataToEndOfFile];
|
| [task waitUntilExit];
|
|
|
| - ksadminRanSuccessfully = [task terminationStatus] == 0;
|
| + ksadmin_ran_successfully = [task terminationStatus] == 0;
|
| string = [[[NSString alloc] initWithData:data
|
| encoding:NSUTF8StringEncoding] autorelease];
|
| }
|
| @catch (id exception) {
|
| - // Most likely, ksPath didn't exist.
|
| + // Most likely, ks_path didn't exist.
|
| }
|
| [task release];
|
|
|
| - if (ksadminRanSuccessfully && [string length] > 0)
|
| + if (ksadmin_ran_successfully && [string length] > 0)
|
| return YES;
|
| }
|
|
|
| return NO;
|
| }
|
|
|
| -// Returns the file permission mask for files created by gcapi.
|
| -mode_t Permissions() {
|
| - return 0755;
|
| -}
|
| +// File permission mask for files created by gcapi.
|
| +const mode_t kUserPermissions = 0755;
|
| +const mode_t kAdminPermissions = 0775;
|
|
|
| -BOOL CreatePathToFile(NSString* path) {
|
| +BOOL CreatePathToFile(NSString* path, const passwd* user) {
|
| path = [path stringByDeletingLastPathComponent];
|
|
|
| // Default owner, group, permissions:
|
| @@ -128,14 +168,21 @@ BOOL CreatePathToFile(NSString* path) {
|
| // more information, see umask.
|
| // * The owner ID is set to the effective user ID of the process.
|
| // * The group ID is set to that of the parent directory.
|
| - // The default group ID is fine. Owner ID is fine too, since user directory
|
| - // paths won't be created if euid is 0. Do set permissions explicitly; for
|
| - // admin paths all admins can write, for user paths just the owner may.
|
| - NSMutableDictionary* attributes = [NSMutableDictionary
|
| - dictionaryWithObject:[NSNumber numberWithShort:Permissions()]
|
| - forKey:NSFilePosixPermissions];
|
| - if (geteuid() == 0)
|
| - [attributes setObject:@"wheel" forKey:NSFileGroupOwnerAccountName];
|
| + // The default group ID is fine. Owner ID is fine if creating a system path,
|
| + // but when creating a user path explicitly set the owner in case euid is 0.
|
| + // Do set permissions explicitly; for admin paths all admins can write, for
|
| + // user paths just the owner may.
|
| + NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
|
| + if (user) {
|
| + [attributes setObject:[NSNumber numberWithShort:kUserPermissions]
|
| + forKey:NSFilePosixPermissions];
|
| + [attributes setObject:[NSNumber numberWithInt:user->pw_uid]
|
| + forKey:NSFileOwnerAccountID];
|
| + } else {
|
| + [attributes setObject:[NSNumber numberWithShort:kAdminPermissions]
|
| + forKey:NSFilePosixPermissions];
|
| + [attributes setObject:@"admin" forKey:NSFileGroupOwnerAccountName];
|
| + }
|
|
|
| NSFileManager* manager = [NSFileManager defaultManager];
|
| return [manager createDirectoryAtPath:path
|
| @@ -144,33 +191,42 @@ BOOL CreatePathToFile(NSString* path) {
|
| error:nil];
|
| }
|
|
|
| -// Tries to write |data| at |system_path| or if that fails and geteuid() is not
|
| -// 0 at |user_path|. Returns the path where it wrote, or nil on failure.
|
| -NSString* WriteData(NSData* data, NSString* system_path, NSString* user_path) {
|
| +// Tries to write |data| at |user_path|.
|
| +// Returns the path where it wrote, or nil on failure.
|
| +NSString* WriteUserData(NSData* data,
|
| + NSString* user_path,
|
| + const passwd* user) {
|
| + user_path = AdjustHomedir(user_path, user->pw_dir);
|
| + if (CreatePathToFile(user_path, user) &&
|
| + [data writeToFile:user_path atomically:YES]) {
|
| + chmod([user_path fileSystemRepresentation], kUserPermissions & ~0111);
|
| + chown([user_path fileSystemRepresentation], user->pw_uid, user->pw_gid);
|
| + return user_path;
|
| + }
|
| + return nil;
|
| +}
|
| +
|
| +// Tries to write |data| at |system_path| or if that fails at |user_path|.
|
| +// Returns the path where it wrote, or nil on failure.
|
| +NSString* WriteData(NSData* data,
|
| + NSString* system_path,
|
| + NSString* user_path,
|
| + const passwd* user) {
|
| // Try system first.
|
| - if (CreatePathToFile(system_path) &&
|
| + if (CreatePathToFile(system_path, NULL) &&
|
| [data writeToFile:system_path atomically:YES]) {
|
| - // files are created with group of parent dir (good), owner of euid (good).
|
| - chmod([system_path fileSystemRepresentation], Permissions() & ~0111);
|
| + chmod([system_path fileSystemRepresentation], kAdminPermissions & ~0111);
|
| + // Make sure the file is owned by group admin.
|
| + if (group* group = getgrnam("admin"))
|
| + chown([system_path fileSystemRepresentation], 0, group->gr_gid);
|
| return system_path;
|
| }
|
|
|
| // Failed, try user.
|
| - // -stringByExpandingTildeInPath returns root's home directory if this is run
|
| - // setuid root, and in that case the kSystemBrandPath path above should have
|
| - // worked anyway. So only try user if geteuid() isn't root.
|
| - if (geteuid() != 0) {
|
| - user_path = [user_path stringByExpandingTildeInPath];
|
| - if (CreatePathToFile(user_path) &&
|
| - [data writeToFile:user_path atomically:YES]) {
|
| - chmod([user_path fileSystemRepresentation], Permissions() & ~0111);
|
| - return user_path;
|
| - }
|
| - }
|
| - return nil;
|
| + return WriteUserData(data, user_path, user);
|
| }
|
|
|
| -NSString* WriteBrandCode(const char* brand_code) {
|
| +NSString* WriteBrandCode(const char* brand_code, const passwd* user) {
|
| NSDictionary* brand_dict = @{
|
| kBrandKey: [NSString stringWithUTF8String:brand_code],
|
| };
|
| @@ -179,15 +235,16 @@ NSString* WriteBrandCode(const char* brand_code) {
|
| format:NSPropertyListBinaryFormat_v1_0
|
| errorDescription:nil];
|
|
|
| - return WriteData(contents, kSystemBrandPath, kUserBrandPath);
|
| + return WriteUserData(contents, kUserBrandPath, user);
|
| }
|
|
|
| BOOL WriteMasterPrefs(const char* master_prefs_contents,
|
| - size_t master_prefs_contents_size) {
|
| + size_t master_prefs_contents_size,
|
| + const passwd* user) {
|
| NSData* contents = [NSData dataWithBytes:master_prefs_contents
|
| length:master_prefs_contents_size];
|
| - return
|
| - WriteData(contents, kSystemMasterPrefsPath, kUserMasterPrefsPath) != nil;
|
| + return WriteData(
|
| + contents, kSystemMasterPrefsPath, kUserMasterPrefsPath, user) != nil;
|
| }
|
|
|
| NSString* PathToFramework(NSString* app_path, NSDictionary* info_plist) {
|
| @@ -214,27 +271,26 @@ bool isbrandchar(int c) {
|
|
|
| int GoogleChromeCompatibilityCheck(unsigned* reasons) {
|
| unsigned local_reasons = 0;
|
| + @autoreleasepool {
|
| + passwd* user = GetRealUserId();
|
|
|
| - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
| -
|
| - if (!IsOSXVersionSupported())
|
| - local_reasons |= GCCC_ERROR_OSNOTSUPPORTED;
|
| -
|
| - if (HasChromeTicket(kSystemTicket))
|
| - local_reasons |= GCCC_ERROR_SYSTEMLEVELALREADYPRESENT;
|
| + if (!IsOSXVersionSupported())
|
| + local_reasons |= GCCC_ERROR_OSNOTSUPPORTED;
|
|
|
| - if (geteuid() != 0 && HasChromeTicket(kUserTicket))
|
| - local_reasons |= GCCC_ERROR_USERLEVELALREADYPRESENT;
|
| + if (HasChromeTicket(kSystemTicket, NULL))
|
| + local_reasons |= GCCC_ERROR_ALREADYPRESENT;
|
|
|
| - if (![[NSFileManager defaultManager] isWritableFileAtPath:@"/Applications"])
|
| - local_reasons |= GCCC_ERROR_ACCESSDENIED;
|
| + if (HasChromeTicket(kUserTicket, user))
|
| + local_reasons |= GCCC_ERROR_ALREADYPRESENT;
|
|
|
| - [pool drain];
|
| + if (![[NSFileManager defaultManager] isWritableFileAtPath:@"/Applications"])
|
| + local_reasons |= GCCC_ERROR_ACCESSDENIED;
|
|
|
| - // Done. Copy/return results.
|
| + if ([[NSFileManager defaultManager] fileExistsAtPath:kChromeInstallPath])
|
| + local_reasons |= GCCC_ERROR_ALREADYPRESENT;
|
| + }
|
| if (reasons != NULL)
|
| *reasons = local_reasons;
|
| -
|
| return local_reasons == 0;
|
| }
|
|
|
| @@ -246,6 +302,8 @@ int InstallGoogleChrome(const char* source_path,
|
| return 0;
|
|
|
| @autoreleasepool {
|
| + passwd* user = GetRealUserId();
|
| +
|
| NSString* app_path = [NSString stringWithUTF8String:source_path];
|
| NSString* info_plist_path =
|
| [app_path stringByAppendingPathComponent:@"Contents/Info.plist"];
|
| @@ -260,9 +318,13 @@ int InstallGoogleChrome(const char* source_path,
|
| }
|
|
|
| @try {
|
| + // install.sh tries to make the installed app admin-writable, but
|
| + // only when it's not run as root.
|
| + NSString* run_as = [NSString stringWithUTF8String:user->pw_name];
|
| NSTask* task = [[[NSTask alloc] init] autorelease];
|
| - [task setLaunchPath:install_script];
|
| - [task setArguments:@[app_path, kChromeInstallPath]];
|
| + [task setLaunchPath:@"/usr/bin/sudo"];
|
| + [task setArguments:
|
| + @[@"-u", run_as, install_script, app_path, kChromeInstallPath]];
|
| [task launch];
|
| [task waitUntilExit];
|
| if ([task terminationStatus] != 0) {
|
| @@ -285,20 +347,21 @@ int InstallGoogleChrome(const char* source_path,
|
|
|
| NSString* brand_path = nil;
|
| if (valid_brand_code)
|
| - brand_path = WriteBrandCode(brand_code);
|
| + brand_path = WriteBrandCode(brand_code, user);
|
|
|
| // Write master prefs.
|
| if (master_prefs_contents)
|
| - WriteMasterPrefs(master_prefs_contents, master_prefs_contents_size);
|
| + WriteMasterPrefs(master_prefs_contents, master_prefs_contents_size, user);
|
|
|
| - // TODO Set default browser if requested. Will be tricky when running as
|
| - // root.
|
| + // TODO Set default browser if requested.
|
| }
|
| return 1;
|
| }
|
|
|
| int LaunchGoogleChrome() {
|
| @autoreleasepool {
|
| + // NSWorkspace launches processes as the current console owner,
|
| + // even when running with euid of 0.
|
| return [[NSWorkspace sharedWorkspace] launchApplication:kChromeInstallPath];
|
| }
|
| }
|
|
|