// /////////////////////////////////////////////////////////////////////////////
// exitCleanly
//
// Exits the program with the correct status and releases the autorelease pool.
void exitCleanly(int code, NSAutoreleasePool *pool)
{
[pool release];
exit(code);
}
// /////////////////////////////////////////////////////////////////////////////
//
// /////////////////////////////////////////////////////////////////////////////
// createDirectoriesToPath
//
// Creates all required directories up to the given path (but not including the
// last path component). Directories are created with the same ownership as the
// containing directory.
void createDirectoriesToPath(NSString *path)
{
NSString *test = [path stringByDeletingLastPathComponent];
if ([test isEqualToString:@"/"])
{
// Reached root!
return;
}
NSFileManager *fm = [NSFileManager defaultManager];
BOOL isDir;
if ([fm fileExistsAtPath:test isDirectory:&isDir] && isDir)
{
// Directory exists
return;
}
// Create directories up to this point
createDirectoriesToPath(test);
// Get the attributes of the containing directory
NSDictionary *att = [fm fileAttributesAtPath:[test stringByDeletingLastPathComponent] traverseLink:NO];
if (![fm createDirectoryAtPath:test attributes:att])
{
NSLog(@"RDPPFramework SecureCopy createDirectoriesToPath Failed to create directory %@",test);
}
}
// /////////////////////////////////////////////////////////////////////////////
//
// /////////////////////////////////////////////////////////////////////////////
// main
//
// main function for the program. Does more or less everything.
int main (int argc, const char * argv[])
{
// This could be written in straight C but I like NSFileManager etc too much!
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
MyAuthorizedCommand myCommand;
int bytesRead;
BOOL authenticatedMode = NO;
AuthorizationRef auth;
AuthorizationExternalForm extAuth;
OSStatus status;
// Unlike AuthSample we don't have to worry about running on 10.0 or 10.1. We will use _NSGetExecutablePath and not provide a fallback for OSs without it.
unsigned long path_to_self_size = MAXPATHLEN;
char *path_to_self = NULL;
if (! (path_to_self = malloc(path_to_self_size)))
{
NSLog(@"RDPPFramework SecureCopy main unable to malloc path_to_self");
exitCleanly(-1,pool);
}
if (_NSGetExecutablePath(path_to_self, &path_to_self_size)==-1)
{
// Fix the size.
if (! (path_to_self = realloc(path_to_self, path_to_self_size + 1)))
{
NSLog(@"RDPPFramework SecureCopy main unable to realloc path_to_self");
exitCleanly(-1,pool);
}
if (_NSGetExecutablePath(path_to_self, &path_to_self_size) != 0)
{
NSLog(@"RDPPFramework SecureCopy main unable to get path to self");
exitCleanly(-1,pool);
}
}
if (argc == 2 && !strcmp(argv[1], "--self-repair"))
{
NSLog(@"RDPPFramework SecureCopy main Self-repair. Starting");
// We have started ourself in self-repair mode. This means that this executable is not owned by root and/or the setuid bit is not set. We need to recover that...
struct stat st;
int fd_tool;
/* Recover the passed in AuthorizationRef. */
status = AuthorizationCopyPrivilegedReference(&auth, kAuthorizationFlagDefaults);
if (status)
{
NSLog(@"RDPPFramework SecureCopy main Self-Repair. Did not recover AuthorizationRef. Return code was %d", status);
exitCleanly(-1,pool);
}
/* Open tool exclusively, so noone can change it while we bless it */
fd_tool = open(path_to_self, O_NONBLOCK|O_RDONLY|O_EXLOCK, 0);
if (fd_tool == -1)
{
NSLog(@"RDPPFramework SecureCopy main Self-Repair. Exclusive open while repairing tool failed: %d.",errno);
exitCleanly(-1,pool);
}
if (fstat(fd_tool, &st))
{
NSLog(@"RDPPFramework SecureCopy main Self-Repair. fstat failed");
exitCleanly(-1,pool);
}
if (st.st_uid != 0)
{
fchown(fd_tool, 0, st.st_gid);
}
/* Disable group and world writability and make setuid root. */
fchmod(fd_tool, (st.st_mode & (~(S_IWGRP|S_IWOTH))) | S_ISUID);
close(fd_tool);
NSLog(@"RDPPFramework SecureCopy main Self-repair. Complete");
authenticatedMode = YES;
}
else
{
/* Read the Authorization "byte blob" from our input pipe. */
if (read(0, &extAuth, sizeof(extAuth)) != sizeof(extAuth))
{
NSLog(@"RDPPFramework SecureCopy main Error reading authorization blob from input pipe.");
exitCleanly(-1,pool);
}
/* Restore the externalized Authorization back to an AuthorizationRef */
if (AuthorizationCreateFromExternalForm(&extAuth, &auth))
{
authenticatedMode = NO;
}
else
{
authenticatedMode = YES;
}
}
/* If we are not running as root we need to self-repair. */
if (authenticatedMode && geteuid() != 0)
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Not running as root! Starting self-repair mode.");
int pid;
FILE *commPipe = NULL;
char *arguments[] = { "--self-repair", NULL };
char buffer[1024];
int bytesRead;
/* Set our own stdin and stdout to be the communication channel with ourself. */
status = AuthorizationExecuteWithPrivileges(auth, path_to_self, kAuthorizationFlagDefaults, arguments, &commPipe);
if (status)
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Failed to execute ourselves (%@) with privileges. Return code was %d", [NSString stringWithCString:path_to_self],status);
exitCleanly(-1,pool);
}
/* Read from stdin and write to commPipe. */
for (;;)
{
bytesRead = read(0, buffer, 1024);
if (bytesRead < 1) break;
fwrite(buffer, 1, bytesRead, commPipe);
}
/* Flush any remaining output. */
fflush(commPipe);
/* Close the communication pipe to let the child know we are done. */
fclose(commPipe);
/* Wait for the child of AuthorizationExecuteWithPrivileges to exit. */
int wait_status;
pid = wait(&wait_status);
if (pid == -1 || ! WIFEXITED(wait_status))
{
exitCleanly(-1,pool);
}
/* Exit with the same exit code as the child spawned by AuthorizationExecuteWithPrivileges() */
exitCleanly(WEXITSTATUS(status),pool);
}
/* No need for it anymore */
if (path_to_self)
{
free(path_to_self);
}
/* Read a 'MyAuthorizedCommand' object from stdin. This contains the source and destination... */
bytesRead = read(0, &myCommand, sizeof(MyAuthorizedCommand));
/* Make sure that we received a full 'MyAuthorizedCommand' object */
if (bytesRead == sizeof(MyAuthorizedCommand))
{
NSString *source = [NSString stringWithCString:myCommand.source];
NSString *destination = [NSString stringWithCString:myCommand.destination];
NSFileManager *manager = [NSFileManager defaultManager];
if (![manager fileExistsAtPath:source])
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Source path does not exist");
exitCleanly(-1,pool);
}
createDirectoriesToPath(destination);
// Users can be sneeky! Check that the destination dir is owned by the current user if we are no running in authenticated mode
if (authenticatedMode==NO)
{
int uid = getuid();
NSDictionary *attr = [manager fileAttributesAtPath:[destination stringByDeletingLastPathComponent]
traverseLink:NO];
int dirOwner = [[attr objectForKey:NSFileOwnerAccountID] intValue];
if (uid!=dirOwner)
{
// You must be authenticated to copy into a dir you do not own (overly simplistic but secure)
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Not authenticated and uid!=dirOwner. Not allowed.");
exitCleanly(-1,pool);
}
}
// Finally actually copy the file!
BOOL worked = [manager copyPath:source toPath:destination handler:nil];
if (worked==NO)
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. NSFileManager failed to copy the file.");
exitCleanly(-1,pool);
}
}
else
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Did not read a valid command structure");
exitCleanly(-1,pool);
}
exitCleanly(0,pool);
// You can never reach here, but anyway
return 0;
}