/*
 * KerberosAgentMachIPCShim.m
 *
 * $Header$
 *
 * Copyright 2004 Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 * require a specific license from the United States Government.
 * It is the responsibility of any person or organization contemplating
 * export to obtain such a license before exporting.
 * 
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */

#import "AuthenticationController.h"
#import "ChangePasswordController.h"
#import "PrompterController.h"
#import "ErrorAlert.h"
#import "KerberosAgentIPCServer.h"
#import "KerberosLoginPrivate.h"


// ---------------------------------------------------------------------------

static boolean_t CallingApplicationIsKerberosApp (NSString *inApplicationPath)
{
    NSBundle *bundle = [NSBundle bundleWithPath: inApplicationPath];
    if (bundle != NULL) {
        NSString *identifier = [bundle bundleIdentifier];
        if (identifier != NULL) {
            return ([identifier compare: @"edu.mit.Kerberos.KerberosApp"] == NSOrderedSame);
        }
    }
    
    return false;
}

// ---------------------------------------------------------------------------

static NSImage *CallingApplicationIcon (NSString *inApplicationPath)
{
    if (CallingApplicationIsKerberosApp (inApplicationPath)) {
        return NULL;
    } else {
        return [[NSWorkspace sharedWorkspace] iconForFile: inApplicationPath];
    }
}

// ---------------------------------------------------------------------------

static NSString *CallingApplicationName (task_t inTask, NSString *inApplicationPath)
{
    KLStatus err = klNoErr;
    pid_t taskPID;
    ProcessSerialNumber taskPSN;
    CFStringRef taskNameStringRef = NULL;
    NSString *taskName = NULL;
    
    if (CallingApplicationIsKerberosApp (inApplicationPath)) {
        return NULL;
    }
    
    if (!err) {
        err = pid_for_task (inTask, &taskPID);
    }
    
    if (!err) {
        err = GetProcessForPID (taskPID, &taskPSN);
    }
    
    if (!err) {
        err = CopyProcessName (&taskPSN, &taskNameStringRef);
    }
    
    if (!err) {
        taskName = [NSString stringWithString: (NSString *) taskNameStringRef];
    }
    
    if (taskNameStringRef != NULL) { CFRelease (taskNameStringRef); }
    
    return taskName;
}

// ---------------------------------------------------------------------------

static boolean_t CallingApplicationIsFrontProcess (task_t inTask)
{
    KLStatus err = klNoErr;
    Boolean taskIsFrontProcess;
    pid_t taskPID;
    ProcessSerialNumber taskPSN, frontPSN;
    
    if (!err) {
        err = pid_for_task (inTask, &taskPID);
    }
    
    if (err == klNoErr) {
        err = GetProcessForPID (taskPID, &taskPSN);
    }
    
    if (err == klNoErr) {
        err = GetFrontProcess (&frontPSN);
    }
    
    if (err == klNoErr) {
        err = SameProcess (&taskPSN, &frontPSN, &taskIsFrontProcess);
    }
    
    return (err == klNoErr) ? !taskIsFrontProcess : false;
}

#pragma mark -

// ---------------------------------------------------------------------------

kern_return_t KAIPCGetServerPID (mach_port_t  inServerPort,
                                 KLIPCPid    *outServerPID)
{
    return pid_for_task (mach_task_self (), outServerPID);
}

// ---------------------------------------------------------------------------

kern_return_t KAIPCAcquireNewInitialTickets (mach_port_t        inServerPort,
                                             task_t             inApplicationTask,
                                             KLIPCInString      inApplicationPath, mach_msg_type_number_t inApplicationPathCnt,
                                             KLIPCBoolean       inAllowHomeDirectoryAccess,
                                             KLIPCInPrincipal   inPrincipal,       mach_msg_type_number_t inPrincipalCnt,
                                             KLIPCFlags         inFlags,
                                             KLIPCTime          inLifetime,
                                             KLIPCTime          inRenewableLifetime,
                                             KLIPCTime          inStartTime,
                                             KLIPCBoolean       inForwardable,
                                             KLIPCBoolean       inProxiable,
                                             KLIPCBoolean       inAddressless,
                                             KLIPCInString      inServiceName,     mach_msg_type_number_t inServiceNameCnt,
                                             KLIPCOutPrincipal *outPrincipal,      mach_msg_type_number_t *outPrincipalCnt,
                                             KLIPCOutString    *outCacheName,      mach_msg_type_number_t *outCacheNameCnt,
                                             KLIPCStatus       *outResult)
{
    kern_return_t   err = KERN_SUCCESS;
    KLStatus        result = klNoErr;
    
    AuthenticationController *controller = NULL;
    Principal *principal = NULL;
    NSString *serviceNameString = NULL;
    NSString *applicationPath = NULL;
    
    const char*     newPrincipalString = NULL;
    const char*     newCacheNameString = NULL;
    char*           newPrincipal = NULL;
    char*           newCacheName = NULL;
    size_t          newPrincipalLength = 0;
    size_t          newCacheNameLength = 0;
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        result = __KLSetHomeDirectoryAccess (inAllowHomeDirectoryAccess);
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        controller = [[AuthenticationController alloc] init];
        if (controller == NULL) { result = klFatalDialogErr; }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        if (inApplicationPathCnt > 0) {
            applicationPath = [[NSString alloc] initWithUTF8String: inApplicationPath];
            if (applicationPath == NULL) { result = klMemFullErr; }
        }
    }
    
    if (inPrincipalCnt > 0) {
        NSString *principalString = NULL;
        
        if ((result == klNoErr) && (err == KERN_SUCCESS)) {
            principalString = [NSString stringWithUTF8String: inPrincipal];
            if (principalString == NULL) { result = klMemFullErr; }
        }
        
        if ((result == klNoErr) && (err == KERN_SUCCESS)) {
            principal = [[Principal alloc] initWithString: principalString klVersion: kerberosVersion_V5];
            if (principal == NULL) { result = klMemFullErr; }
        }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        if (inServiceNameCnt > 0) {
            serviceNameString = [[NSString alloc] initWithUTF8String: inServiceName];
            if (serviceNameString == NULL) { result = klMemFullErr; }
        }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        boolean_t isAutoPopup = CallingApplicationIsFrontProcess (inApplicationTask);
        
        [controller setDoesMinimize: isAutoPopup];
        [controller setCallerNameString: CallingApplicationName (inApplicationTask, applicationPath)];
        [controller setCallerIcon: CallingApplicationIcon (applicationPath)];
        [controller setCallerProvidedPrincipal: principal];
        [controller setServiceName: serviceNameString];
        [controller setStartTime: inStartTime];
        if (inFlags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE)     { [controller setLifetime:    inLifetime]; }
        if (inFlags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE)  { [controller setForwardable: inForwardable]; }
        if (inFlags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE)    { [controller setProxiable:   inProxiable]; }
        if (inFlags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST) { [controller setAddressless: inAddressless]; }
        if (inFlags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE) { 
            [controller setRenewable: (inRenewableLifetime > 0)]; 
            [controller setRenewableLifetime: inRenewableLifetime];
        }
        
        if (!isAutoPopup) { 
            [NSApp activateIgnoringOtherApps: YES]; 
        }
        result = [controller runWindow];
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        newPrincipalString = [[[controller acquiredPrincipal] stringForKLVersion: kerberosVersion_V5] UTF8String];
        newCacheNameString = [[controller acquiredCacheName] UTF8String];
        newPrincipalLength = (newPrincipalString != NULL) ? strlen (newPrincipalString) + 1 : 0;
        newCacheNameLength = (newCacheNameString != NULL) ? strlen (newCacheNameString) + 1 : 0;
    }    
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        if (newPrincipalString != NULL) {
            err = vm_allocate (mach_task_self (), (vm_address_t *) &newPrincipal, newPrincipalLength, true);
        }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        if (newCacheNameString != NULL) {
            err = vm_allocate (mach_task_self (), (vm_address_t *) &newCacheName, newCacheNameLength, true);
        }
    }
    
    if (err == KERN_SUCCESS) {
        if (result == klNoErr) {
            memmove (newPrincipal, newPrincipalString, newPrincipalLength);
            memmove (newCacheName, newCacheNameString, newCacheNameLength);
            *outPrincipal = newPrincipal;
            *outCacheName = newCacheName;
            *outPrincipalCnt = newPrincipalLength;
            *outCacheNameCnt = newCacheNameLength;
            newPrincipal = NULL;
            newCacheName = NULL;
        }
        *outResult = result;
    }
    
    // newPrincipalString and newCacheNameString are autoreleased
    
    if (newPrincipal      != NULL) { vm_deallocate (mach_task_self (), (vm_address_t) newPrincipal, newPrincipalLength); }
    if (newCacheName      != NULL) { vm_deallocate (mach_task_self (), (vm_address_t) newCacheName, newCacheNameLength); }
    if (serviceNameString != NULL) { [serviceNameString release]; }
    if (applicationPath   != NULL) { [applicationPath release]; }
    if (principal         != NULL) { [principal release]; }
    if (controller        != NULL) { [controller release]; }
    
    mach_server_quit_self ();
    return err;
}

// ---------------------------------------------------------------------------

kern_return_t KAIPCChangePassword (mach_port_t       inServerPort,
                                   task_t            inApplicationTask,
                                   KLIPCInString     inApplicationPath, mach_msg_type_number_t inApplicationPathCnt,
                                   KLIPCBoolean      inAllowHomeDirectoryAccess,
                                   KLIPCInPrincipal  inPrincipal,       mach_msg_type_number_t inPrincipalCnt,
                                   KLIPCStatus      *outResult)
{
    kern_return_t   err = KERN_SUCCESS;
    KLStatus        result = klNoErr;

    ChangePasswordController *controller = NULL;
    Principal                *principal = NULL;
    NSString                 *applicationPath = NULL;

    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        result = __KLSetHomeDirectoryAccess (inAllowHomeDirectoryAccess);
    }

    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        if (inApplicationPathCnt > 0) {
            applicationPath = [[NSString alloc] initWithUTF8String: inApplicationPath];
            if (applicationPath == NULL) { result = klMemFullErr; }
        }
    }
    
    if (inPrincipalCnt > 0) {
        NSString *principalString = NULL;
        
        if ((result == klNoErr) && (err == KERN_SUCCESS)) {
            principalString = [NSString stringWithUTF8String: inPrincipal];
            if (principalString == NULL) { result = klMemFullErr; }
        }
        
        if ((result == klNoErr) && (err == KERN_SUCCESS)) {
            principal = [[Principal alloc] initWithString: principalString klVersion: kerberosVersion_V5];
            if (principal == NULL) { result = klMemFullErr; }
        }
    }

    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        controller = [[ChangePasswordController alloc] initWithPrincipal: principal];
        if (controller == NULL) { result = klFatalDialogErr; }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        [controller setApplicationNameString: CallingApplicationName (inApplicationTask, applicationPath)];
        [controller setApplicationIcon: CallingApplicationIcon (applicationPath)];
        
        [NSApp activateIgnoringOtherApps: YES];
        result = [controller runWindow];
    }

    if (err == KERN_SUCCESS) {
        *outResult = result;
    }

    if (applicationPath != NULL) { [applicationPath release]; }
    if (principal       != NULL) { [principal release]; }
    if (controller      != NULL) { [controller release]; }

    mach_server_quit_self ();
    return err;
}

// ---------------------------------------------------------------------------

kern_return_t KAIPCPrompter (mach_port_t      inServerPort,
                             task_t           inApplicationTask,
                             KLIPCInString    inApplicationPath, mach_msg_type_number_t inApplicationPathCnt,
                             KLIPCBoolean     inAllowHomeDirectoryAccess,
                             KLIPCInString    inName,            mach_msg_type_number_t  inNameCnt,
                             KLIPCInString    inBanner,          mach_msg_type_number_t  inBannerCnt,
                             KLIPCIndex       inNumPrompts,
                             KLIPCInString    inPrompts,         mach_msg_type_number_t  inPromptsCnt,
                             KLIPCInString    inHiddens,         mach_msg_type_number_t  inHiddensCnt,
                             KLIPCInIntArray  inReplySizes,      mach_msg_type_number_t  inReplySizesCnt,
                             KLIPCOutString  *outReplies,        mach_msg_type_number_t *outRepliesCnt,
                             KLIPCStatus     *outResult)
{
    kern_return_t   err = KERN_SUCCESS;
    KLStatus        result = klNoErr;
    
    PrompterController *controller = NULL;
    NSMutableArray     *promptsArray = NULL;
    NSString           *applicationPath = NULL;

    krb5_prompt        *prompts = NULL;
    krb5_data          *promptReplies = NULL;
    char               *promptReplyData = NULL;
    int                 promptReplyDataSize = 0;
    KLIPCIndex          i;
    
    char           *replies = NULL;

    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        result = __KLSetHomeDirectoryAccess (inAllowHomeDirectoryAccess);
    }
        
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        if (inApplicationPathCnt > 0) {
            applicationPath = [[NSString alloc] initWithUTF8String: inApplicationPath];
            if (applicationPath == NULL) { result = klMemFullErr; }
        }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        promptsArray = [[NSMutableArray alloc] init];
        if (promptsArray == NULL) { result = klMemFullErr; }
    }

    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        prompts = (krb5_prompt *) malloc (inNumPrompts * sizeof (krb5_prompt));
        if (prompts == NULL) { result = klMemFullErr; }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        promptReplies = (krb5_data *) malloc (inNumPrompts * sizeof (krb5_data));
        if (promptReplies == NULL) { result = klMemFullErr; }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        for (i = 0; i < inNumPrompts; i++)
            promptReplyDataSize += inReplySizes[i];
        
        promptReplyData = (char *) malloc (promptReplyDataSize);
        if (promptReplyData == NULL) { result = klMemFullErr; }
    }
    
    /* Build the array of prompts */
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        char	*currentPromptString = (char *)inPrompts;
        char	*currentPromptReplyData = promptReplyData;
        
        for (i = 0; i < inNumPrompts; i++) {
            prompts[i].prompt = currentPromptString;
            prompts[i].hidden = inHiddens[i] == '1' ? true : false;
            
            prompts[i].reply = &promptReplies[i];
            prompts[i].reply->length = inReplySizes[i];
            prompts[i].reply->data = currentPromptReplyData;
            
            Prompt *prompt = [[Prompt alloc] initWithPrompt: &prompts[i]];
            if (prompt == NULL) { result = klMemFullErr; break; }
            [promptsArray addObject: prompt];
            [prompt release];
            
            currentPromptString += strlen (currentPromptString) + 1;
            currentPromptReplyData += inReplySizes[i];
        }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        NSString *titleString  = (inNameCnt   > 0) ? [NSString stringWithUTF8String: inName]   : NULL;
        NSString *bannerString = (inBannerCnt > 0) ? [NSString stringWithUTF8String: inBanner] : NULL;
        
        controller = [[PrompterController alloc] initWithTitle: titleString
                                                        banner: bannerString
                                                       prompts: promptsArray];
        if (controller == NULL) { result = klFatalDialogErr; }
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        [NSApp activateIgnoringOtherApps: YES];
        result = [controller runWindow];
    }
    
    if ((result == klNoErr) && (err == KERN_SUCCESS)) {
        err = vm_allocate (mach_task_self (), (vm_address_t *) &replies, promptReplyDataSize, true);
    }
    
    if (err == KERN_SUCCESS) {
        if (result == klNoErr) {
            for (i = 0; i < inNumPrompts; i++) {
                Prompt *prompt = [promptsArray objectAtIndex: i];
                [prompt saveResponseInPrompt: &prompts[i]];
            }        

            memmove (replies, promptReplyData, promptReplyDataSize);
            *outReplies = replies;
            *outRepliesCnt = promptReplyDataSize;
            replies = NULL;
        }
        *outResult = result;
    }
    
    if (replies         != NULL) { vm_deallocate (mach_task_self (), (vm_address_t) replies, promptReplyDataSize); }
    if (prompts         != NULL) { free (prompts); }
    if (promptReplies   != NULL) { free (promptReplies); }
    if (promptReplyData != NULL) { free (promptReplyData); }
    if (applicationPath != NULL) { [applicationPath release]; }
    if (controller      != NULL) { [controller release]; }
    
    mach_server_quit_self ();
    return err;
}

// ---------------------------------------------------------------------------

kern_return_t KAIPCHandleError (mach_port_t            inServerPort,
                                task_t                 inApplicationTask,
                                KLIPCInString          inApplicationPath, mach_msg_type_number_t inApplicationPathCnt,
                                KLIPCBoolean           inAllowHomeDirectoryAccess,
                                KLIPCStatus            inError,
                                KLIPCDialogIdentifier  inDialogIdentifier,
                                KLIPCBoolean           inShowAlert,
                                KLIPCStatus           *outResult)
{
    kern_return_t err = KERN_SUCCESS;
    KLStatus      result = klNoErr;
    
    if (inShowAlert) {
        if ((result == klNoErr) && (err == KERN_SUCCESS)) {
            result = __KLSetHomeDirectoryAccess (inAllowHomeDirectoryAccess);
        }

        if ((result == klNoErr) && (err == KERN_SUCCESS)) {
            KerberosAction action = KerberosNoAction;

            switch (inDialogIdentifier) {
                case loginLibrary_LoginDialog:
                    action = KerberosGetTicketsAction;
                    
                case loginLibrary_ChangePasswordDialog:
                    action = KerberosChangePasswordAction;
                    break;
            }
            
            [NSApp activateIgnoringOtherApps: YES];
            [ErrorAlert alertForError: inError action: action];
        }
    }

    if (err == KERN_SUCCESS) {
        *outResult = result;
    }

    mach_server_quit_self ();
    return err;
}
