Chromium Code Reviews| 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..5099e26a451d56cd73fcf24311dca798ae7e86ea 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 <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"; |
|
Mark Mentovai
2012/08/13 13:14:42
Provided we’re just letting Chrome set up a user t
Nico
2012/08/13 14:52:57
Done.
|
| -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,16 +63,44 @@ 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 sessionInfoDict = CGSessionCopyCurrentDictionary(); |
| + if (!sessionInfoDict) |
| + return NULL; // Possibly no screen plugged in. |
| + |
| + CFNumberRef userUID = (CFNumberRef)CFDictionaryGetValue(sessionInfoDict, |
|
Mark Mentovai
2012/08/13 13:14:42
Can you use something like CFCast here?
Nico
2012/08/13 15:42:15
Doneish.
|
| + kCGSessionUserIDKey); |
| + uid_t uid; |
| + BOOL success = CFNumberGetValue(userUID, kCFNumberSInt32Type, &uid); |
| + CFRelease(sessionInfoDict); |
| + if (!success) |
| + return NULL; |
| + |
| + return getpwuid(uid); |
| +} |
| + |
| enum TicketKind { |
| kSystemTicket, kUserTicket |
| }; |
| -BOOL HasChromeTicket(TicketKind kind) { |
| +// Replaces "~~" with |homedir|. |
| +NSString* AdjustHomedir(NSString* s, const char* homedir) { |
| + NSString* homeDir = [NSString stringWithUTF8String:homedir]; |
|
Mark Mentovai
2012/08/13 13:14:42
Naming nit: home_dir
Nico
2012/08/13 14:52:57
Done.
|
| + return [s stringByReplacingOccurrencesOfString:@"~~" |
|
Mark Mentovai
2012/08/13 13:14:42
I’d be a little more comfortable if this only oper
Nico
2012/08/13 14:52:57
Done.
|
| + withString:homeDir]; |
| +} |
| + |
| +BOOL HasChromeTicket(TicketKind kind, const char* homedir) { |
| // Don't use Objective-C 2 loop syntax, in case an installer runs on 10.4. |
| NSMutableArray* keystonePaths = |
|
Mark Mentovai
2012/08/13 13:14:42
Naming nit: keystone_paths. Line 109 too.
Nico
2012/08/13 14:52:57
Done.
|
| [NSMutableArray arrayWithObject:kSystemKsadminPath]; |
| - if (kind == kUserTicket && geteuid() != 0) |
| - [keystonePaths insertObject:kUserKsadminPath atIndex:0]; |
| + if (kind == kUserTicket != 0) { |
| + [keystonePaths insertObject:AdjustHomedir(kUserKsadminPath, homedir) |
| + atIndex:0]; |
| + } |
| NSEnumerator* e = [keystonePaths objectEnumerator]; |
| id ksPath; |
| while ((ksPath = [e nextObject])) { |
| @@ -78,6 +109,8 @@ BOOL HasChromeTicket(TicketKind kind) { |
| bool ksadminRanSuccessfully = false; |
| @try { |
| + // XXX does this need to run as other user? or can admin read the user store? |
|
Mark Mentovai
2012/08/13 13:14:42
What do you mean? That you want to check the conso
Nico
2012/08/13 14:13:33
Right, I imagine this code will usually run with e
Mark Mentovai
2012/08/13 15:04:34
Nico wrote:
Nico
2012/08/13 15:42:16
Done.
|
| + // it should be able to, so I think this is fine as is? |
| task = [[NSTask alloc] init]; |
| [task setLaunchPath:ksPath]; |
| @@ -120,7 +153,7 @@ mode_t Permissions() { |
| return 0755; |
| } |
| -BOOL CreatePathToFile(NSString* path) { |
| +BOOL CreatePathToFile(NSString* path, const passwd* user) { |
| path = [path stringByDeletingLastPathComponent]; |
| // Default owner, group, permissions: |
| @@ -128,14 +161,22 @@ 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. |
| + // 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 |
|
Mark Mentovai
2012/08/13 13:14:42
You haven’t made this so that all admins can write
Nico
2012/08/13 14:52:57
For admins, the group is set to wheel and permissi
Mark Mentovai
2012/08/13 15:04:34
Nico wrote:
Nico
2012/08/13 15:42:16
Done. (?)
|
| + // user paths just the owner may. |
| NSMutableDictionary* attributes = [NSMutableDictionary |
| dictionaryWithObject:[NSNumber numberWithShort:Permissions()] |
| forKey:NSFilePosixPermissions]; |
| - if (geteuid() == 0) |
| + if (user) { |
| + [attributes setObject:[NSNumber numberWithInt:user->pw_uid] |
| + forKey:NSFileOwnerAccountID]; |
| + // XXX do this? not needed i think? |
| + //[attributes setObject:[NSNumber numberWithInt:user->pw_gid] |
| + //forKey:NSFileGroupOwnerAccountID]; |
| + } else { |
| [attributes setObject:@"wheel" forKey:NSFileGroupOwnerAccountName]; |
| + } |
| NSFileManager* manager = [NSFileManager defaultManager]; |
| return [manager createDirectoryAtPath:path |
| @@ -144,11 +185,16 @@ 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 |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) { |
| + // XXX do we still want to try to write this to a system path? probably? |
|
Mark Mentovai
2012/08/13 13:14:42
Brand file, no. The brand file should only be writ
Nico
2012/08/13 14:52:57
Done.
|
| + |
| // 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); |
| @@ -156,21 +202,17 @@ NSString* WriteData(NSData* data, NSString* system_path, NSString* user_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; |
| - } |
| + user_path = AdjustHomedir(user_path, user->pw_dir); |
| + if (CreatePathToFile(user_path, user) && |
| + [data writeToFile:user_path atomically:YES]) { |
| + chmod([user_path fileSystemRepresentation], Permissions() & ~0111); |
| + chown([user_path fileSystemRepresentation], user->pw_uid, user->pw_gid); |
| + return user_path; |
| } |
| return nil; |
| } |
| -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 +221,16 @@ NSString* WriteBrandCode(const char* brand_code) { |
| format:NSPropertyListBinaryFormat_v1_0 |
| errorDescription:nil]; |
| - return WriteData(contents, kSystemBrandPath, kUserBrandPath); |
| + return WriteData(contents, kSystemBrandPath, 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) { |
| @@ -213,6 +256,8 @@ bool isbrandchar(int c) { |
| } // namespace |
| int GoogleChromeCompatibilityCheck(unsigned* reasons) { |
| + passwd* user = GetRealUserId(); |
| + |
| unsigned local_reasons = 0; |
| NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; |
| @@ -220,15 +265,20 @@ int GoogleChromeCompatibilityCheck(unsigned* reasons) { |
| if (!IsOSXVersionSupported()) |
| local_reasons |= GCCC_ERROR_OSNOTSUPPORTED; |
| - if (HasChromeTicket(kSystemTicket)) |
| + if (HasChromeTicket(kSystemTicket, user->pw_dir)) |
| local_reasons |= GCCC_ERROR_SYSTEMLEVELALREADYPRESENT; |
| - if (geteuid() != 0 && HasChromeTicket(kUserTicket)) |
| + if (HasChromeTicket(kUserTicket, user->pw_dir)) |
| local_reasons |= GCCC_ERROR_USERLEVELALREADYPRESENT; |
| if (![[NSFileManager defaultManager] isWritableFileAtPath:@"/Applications"]) |
| local_reasons |= GCCC_ERROR_ACCESSDENIED; |
| + if (!local_reasons && [[NSFileManager defaultManager] |
| + fileExistsAtPath:@"/Applications/Google Chrome.app"]) { |
| + local_reasons |= GCCC_ERROR_ACCESSDENIED; |
|
Mark Mentovai
2012/08/13 13:14:42
Is this the right “reason” or should you add a new
Nico
2012/08/13 14:52:57
How would the new reason be called?
Or should the
Mark Mentovai
2012/08/13 15:04:34
Nico wrote:
Nico
2012/08/13 15:42:16
Done.
|
| + } |
| + |
| [pool drain]; |
| // Done. Copy/return results. |
| @@ -245,6 +295,8 @@ int InstallGoogleChrome(const char* source_path, |
| if (!GoogleChromeCompatibilityCheck(NULL)) |
| return 0; |
| + passwd* user = GetRealUserId(); |
| + |
| @autoreleasepool { |
| NSString* app_path = [NSString stringWithUTF8String:source_path]; |
| NSString* info_plist_path = |
| @@ -260,9 +312,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 +341,23 @@ 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() { |
| + //passwd* user = GetRealUserId(); |
| + |
| @autoreleasepool { |
| + // NSWorkspace launches processes as the current console owner, |
| + // even when running with euid of 0. |
| return [[NSWorkspace sharedWorkspace] launchApplication:kChromeInstallPath]; |
| } |
| } |