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]; |
} |
} |