//
//  TeXDistAuthentication.m
//  TeXDist
//
//  Created by coder on - 2.0: Tue Mar 17 16:02:20 UTC 2009.
//  Copyright 2006 Laurens'Tribune. All rights reserved.
//

#import "TeXDistAuthentication.h"
#import "TeXDistCommon.h"
#import "TeXDistUtility.h"
#import "TeXDistributionUtils.h"
#import <Security/Security.h>


@interface TeXDistConcreteAuthentication: TeXDistAuthentication
{
@private
	NSString *				iVarHelper;
	AuthorizationRef		iVarAuthRef;
    AuthorizationRights *   iVarRightsRef;
    NSUInteger              iVarCredentialsFailureCount;
}
@property (readwrite,copy) NSString * helper;
@property (readwrite,assign) AuthorizationRef authRef;
@property (readwrite,assign) AuthorizationRights * rightsRef;
@property (readwrite,assign) NSUInteger credentialsFailureCount;
- (BOOL)executeWithPrivilegesWithArguments:(char **)arguments error:(NSError **)RORef;
@end

@interface TeXDistVaporAuthentication: TeXDistAuthentication
@end

@implementation TeXDistAuthentication
id shared_TeXDistAuthentication_instance = nil;
+ (id)sharedInstance;
{
    if (shared_TeXDistAuthentication_instance) {
        return shared_TeXDistAuthentication_instance;
    }
    NSProcessInfo * PI = [NSProcessInfo processInfo];
    for (NSString * argument in PI.arguments) {
        if ([argument isEqual:kTeXDistNoCredentialsArgument]) {
            return shared_TeXDistAuthentication_instance=[[TeXDistVaporAuthentication alloc] init];
        }
    }
	return shared_TeXDistAuthentication_instance=[[TeXDistConcreteAuthentication alloc] init];
}
- (void)invalidateCredentials;
{
    return;
}
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error;
{
    NSParameterAssert(NO);
    return NO;
}
- (BOOL)createSymbolicLinkAtPath:(NSString *)path withDestinationPath:(NSString *)destPath error:(NSError **)RORef;
{
    NSParameterAssert(NO);
    return NO;
}
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)RORef;
{
    NSParameterAssert(NO);
    return NO;
}
@end

@implementation TeXDistConcreteAuthentication
@synthesize helper = iVarHelper;
@synthesize authRef = iVarAuthRef;
@synthesize rightsRef = iVarRightsRef;
@synthesize credentialsFailureCount = iVarCredentialsFailureCount;
- (id)init;
{
	if ((self = [super init])) {
		self.helper = nil;
		self.rightsRef = NULL;
        NSBundle * B = [NSBundle bundleForClass:[TeXDistAuthentication class]];
        self.helper = [[B pathForAuxiliaryExecutable:@"TeXDistUtility"] retain];
        if (NOT [self.helper length]) {
            TeXDistLog(TeXDistLogLevelAlways,@"!!!! ERROR: This is a BIG problem: the TeXDistUtility auxiliary executable is missing!");
            [self release];
            return nil;
        }
        if (errAuthorizationSuccess!=AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &iVarAuthRef)) {
            [self release];
            return nil;
        }
 	}
	return self;
}
- (void)finalize;
{
	AuthorizationFree(self.authRef,kAuthorizationFlagDestroyRights);
	AuthorizationFreeItemSet(self.rightsRef);
	self.rightsRef = NULL;
	[super finalize];
	return;
}
- (void)dealloc;
{
    self.helper = nil;
	AuthorizationFree(self.authRef,kAuthorizationFlagDestroyRights);
	AuthorizationFreeItemSet(self.rightsRef);
	self.rightsRef = NULL;
	[super dealloc];
	return;
}
- (BOOL)obtainCredentialsAndReturnError:(NSError **)RORef;
{
#define reportSTATUS(MSG,ERR)\
if (RORef) {\
    *RORef = [NSError errorWithDomain:TeXDistDomain code:ERR userInfo:[NSDictionary dictionaryWithObject:MSG forKey:NSLocalizedDescriptionKey]];\
} else {\
    TeXDistLog(TeXDistLogLevelAlways,MSG);\
}
    if (self.credentialsFailureCount==3) {
        reportSTATUS(@"No credentials to acquire privileges.",kTeXDistTooManyCredentials);
        ++self.credentialsFailureCount;
        return NO;
    }
    if (self.credentialsFailureCount>3) {
        reportSTATUS(@"No credentials to acquire privileges.",kTeXDistNoMoreCredentials);
        return NO;
    }
	OSStatus err = errAuthorizationSuccess;
	if (self.rightsRef) {
		err = AuthorizationFreeItemSet(self.rightsRef);
		if (errAuthorizationSuccess != err) { 
            reportSTATUS(@"Failed to free authorisation rights. No credentials to acquire privileges.",err);
            ++self.credentialsFailureCount;
			return NO;
		}
		self.rightsRef = NULL;
	}
	AuthorizationItem item;
	AuthorizationRights rights;
	item.name = kAuthorizationRightExecute;
	item.value = (char *)[self.helper fileSystemRepresentation];
	item.valueLength = strlen(item.value);
	rights.count = 1;
	rights.items = &item;
	err = AuthorizationCopyRights(
                                  self.authRef,         // authorization
                                  &rights,               // rights
                                  kAuthorizationEmptyEnvironment,     // environment
                                  kAuthorizationFlagDefaults|
                                  kAuthorizationFlagExtendRights|
                                  kAuthorizationFlagInteractionAllowed,                 // flags
                                  &iVarRightsRef                    // authorizedRights
                                  );
	if(err != errAuthorizationSuccess)
	{
        reportSTATUS(@"Failed to copy authorisation rights. No credentials to acquire privileges.",err);
        ++self.credentialsFailureCount;
		return NO;
	}
	if(self.rightsRef && (self.rightsRef->count == 1))
	{
		return YES;
	}
	AuthorizationFreeItemSet(self.rightsRef);
	self.rightsRef = NULL;
    reportSTATUS(@"Failed to obtain credentials to acquire privileges.",1);
    ++self.credentialsFailureCount;
    return NO;
}
- (void)invalidateCredentials;
{
    self.credentialsFailureCount = 0;
	AuthorizationFree(iVarAuthRef,kAuthorizationFlagDestroyRights);
	AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &iVarAuthRef);
	return;
}
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)RORef;
{
    TeXDistLog(2,@"sudo rm -Rf \"%@\"",path);
	const char * arguments[4];
	arguments[0]=TeXDistUtilityCommand_rm.UTF8String;
	arguments[1]=TeXDistUtilityOption_path.UTF8String;
	arguments[2]=path.fileSystemRepresentation;
    arguments[3]=NULL;
    return [self executeWithPrivilegesWithArguments:(char **)arguments error:RORef];
}
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)RORef;
{
    TeXDistLog(2,@"sudo mkdir %@\"%@\"",(createIntermediates?@"-p ":@""),path);
	const char * arguments[5];
	arguments[0]=TeXDistUtilityCommand_mkdir.UTF8String;
	arguments[1]=TeXDistUtilityOption_path.UTF8String;
	arguments[2]=[path fileSystemRepresentation];
    if (createIntermediates) {
        arguments[3]=TeXDistUtilityOption_create_intermediates.UTF8String;
    } else {
        arguments[3]=NULL;
    }
    arguments[4]=NULL;
    return [self executeWithPrivilegesWithArguments:(char **)arguments error:RORef];
}
- (BOOL)createSymbolicLinkAtPath:(NSString *)path withDestinationPath:(NSString *)destPath error:(NSError **)RORef;
{
    TeXDistLog(2,@"sudo ln -s \"%@\" \"%@\"",destPath,path);
	const char * arguments[6];
	// 	eg sudo ln -s ../gwTeX.texdist/Contents /Library/TeX/Distributions/.DefaultTeX
    //  similar to ln -s "destPath" "path"
	arguments[0]=TeXDistUtilityCommand_ln.UTF8String;
	arguments[1]=TeXDistUtilityOption_dest_path.UTF8String;
	arguments[2]=[destPath fileSystemRepresentation];
	arguments[3]=TeXDistUtilityOption_path.UTF8String;
	arguments[4]=[path fileSystemRepresentation];
	arguments[5]=NULL;
    return [self executeWithPrivilegesWithArguments:(char **)arguments error:RORef];
}
- (BOOL)executeWithPrivilegesWithArguments:(char **)arguments error:(NSError **)RORef;
{
    NSParameterAssert(arguments);
    if (NOT [self obtainCredentialsAndReturnError:RORef]) {
        if (RORef AND NOT *RORef) {
            *RORef = [NSError errorWithDomain:TeXDistDomain code:kTeXDistNoCredentials userInfo:[NSDictionary dictionaryWithObject:kTeXDistNoAuthorisationMessage forKey:NSLocalizedDescriptionKey]];
        }
        return NO;
    }
	FILE* communicationsPipe = NULL;// just to make the task synchronous to have UI react immediately
    NSProcessInfo * PI = [NSProcessInfo processInfo];
    if ([PI respondsToSelector:@selector(disableSuddenTermination)]) {
        [PI performSelector:@selector(disableSuddenTermination)];
    }
	OSStatus err = AuthorizationExecuteWithPrivileges (self.authRef, self.rightsRef->items[0].value, 
                                                       kAuthorizationFlagDefaults, 
                                                       (char **)arguments, 
                                                       &communicationsPipe);
    if ([PI respondsToSelector:@selector(enableSuddenTermination)]) {
        [PI performSelector:@selector(enableSuddenTermination)];
    }
	TeXDistLog(2,@"TeXDist communicationsPipe:%0X", communicationsPipe);
	if (err == errAuthorizationSuccess) {
#       define BUFFER_SIZE 512
		char * buffer[BUFFER_SIZE];
		NSMutableData* tempData = [NSMutableData data];
		ssize_t read_len = 0;
		while (communicationsPipe && ((read_len = read(fileno(communicationsPipe),buffer, BUFFER_SIZE))>0) && read_len<BUFFER_SIZE) {
			[tempData appendBytes:buffer length:read_len];
		}// End of the task.
		fclose(communicationsPipe);
		communicationsPipe = NULL;
		NSString * errorString = @"";
		NSDictionary * D = [NSPropertyListSerialization propertyListFromData:tempData
                                                            mutabilityOption:NSPropertyListImmutable
                                                                      format:nil
                                                            errorDescription:&errorString];
		if (errorString) {
			TeXDistLog(2,@"TeXDist INFO executeWithPrivilegesWithArguments Error while deserializing %@",errorString);
			TeXDist_RETURN_ERROR(1,errorString);
			[errorString autorelease];
			errorString = nil;
		}
        if ((errorString = [D objectForKey:kTeXDistErrorMessageKey])) {
            if ((err = [[D objectForKey:kTeXDistErrorStatusKey] integerValue])) {
                TeXDist_RETURN_ERROR(err,errorString);
                return NO;
            }
        }
        return YES;
	}
    TeXDist_RETURN_ERROR(err,kTeXDistNoAuthorisationMessage);
	return NO;
}
@end

@implementation TeXDistVaporAuthentication
- (BOOL)createSymbolicLinkAtPath:(NSString *)path withDestinationPath:(NSString *)destPath error:(NSError **)RORef;
{
    TeXDistLog(2,@"ln -s \"%@\" \"%@\"",destPath,path);
    NSDictionary * D = TeXDist_soft_link(destPath, path);
    NSInteger err = [[D objectForKey:kTeXDistErrorStatusKey] integerValue];
    if (err) {
        TeXDist_RETURN_ERROR(err,([D objectForKey:kTeXDistErrorMessageKey]?:@"Unknown error."));
    }
    return err == 0;
}
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)RORef;
{
    TeXDistLog(2,@"mkdir %@\"%@\"",(createIntermediates?@"-p ":@""),path);
    NSDictionary * D = TeXDist_mkdir(path,createIntermediates,attributes);
    NSInteger err = [[D objectForKey:kTeXDistErrorStatusKey] integerValue];
    if (err) {
        TeXDist_RETURN_ERROR(err,([D objectForKey:kTeXDistErrorMessageKey]?:@"Unknown error."));
    }
    return err == 0;
}
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)RORef;
{
    TeXDistLog(2,@"rm -Rf \"%@\"",path);
    NSDictionary * D = TeXDist_rm(path);
    NSInteger err = [[D objectForKey:kTeXDistErrorStatusKey] integerValue];
    if (err) {
        TeXDist_RETURN_ERROR(err,([D objectForKey:kTeXDistErrorMessageKey]?:@"Unknown error."));
    }
    return err == 0;
}
@end
