/*
 * Copyright 2008 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 "ServerDemux.h"
#import "kim_selection_hints_private.h"
#import "kim_options_private.h"
#import "KerberosAgentListener.h"
#import "KIMUtilities.h"

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

static kim_boolean caller_is_front_process (pid_t    in_pid, 
                                            NSString *in_path)
{
    kim_error err = KIM_NO_ERROR;
    kim_boolean is_front_process = FALSE;
    NSNumber *active_pid = NULL;
    
    NSBundle *bundle = [NSBundle bundleWithPath: in_path];
    if (bundle) {
        NSString *identifier = [bundle bundleIdentifier];
        if (identifier && 
            ([identifier compare: @"edu.mit.Kerberos.KerberosMenu"] == NSOrderedSame ||
             [identifier compare: @"com.apple.systemuiserver"]      == NSOrderedSame)) {
            return TRUE;
        }
    }
    
    if (!err) {
        NSDictionary *activeApplication = [[NSWorkspace sharedWorkspace] activeApplication];
        if (activeApplication) {
            active_pid = [activeApplication objectForKey: @"NSApplicationProcessIdentifier"];
        }
    }
    
    if (!err && active_pid) {
        is_front_process = ([active_pid intValue] == in_pid);
    }
    
    return is_front_process;
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static int32_t kim_handle_request_init (mach_port_t   in_client_port, 
                                        mach_port_t   in_reply_port, 
                                        k5_ipc_stream in_request_stream)
{
    int32_t err = 0;
    int32_t pid = 0;
    char *name = NULL;
    char *path = NULL;
    bool isFrontProcess = 0;

    if (!err) {
        err = krb5int_ipc_stream_read_int32 (in_request_stream, &pid);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &name);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &path);
    } 
    
    
    if (!err) {
        isFrontProcess = caller_is_front_process (pid, 
                                                  [NSString stringWithUTF8String: path]);
    }
    
    if (!err) {
        // performs selector on main thread
        [KerberosAgentListener addClientWithPort:in_client_port 
                                       replyPort:in_reply_port 
                                            name:name 
                                            path:path];
    }
    
    krb5int_ipc_stream_free_string (name);    
    krb5int_ipc_stream_free_string (path);

    return err;   
}

/* ------------------------------------------------------------------------ */

int32_t kim_handle_reply_init (mach_port_t   in_reply_port, 
                               int32_t       in_error)
{
    int32_t err = 0;
    k5_ipc_stream reply = NULL;
    
    if (!err) {
        err = krb5int_ipc_stream_new (&reply);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_write_int32 (reply, in_error);
    }
    
    if (!err) {
        err = k5_ipc_server_send_reply (in_reply_port, reply);
    }
    
    krb5int_ipc_stream_release (reply);
    
    return err;   
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static int32_t kim_handle_request_enter_identity (mach_port_t   in_client_port, 
                                                  mach_port_t   in_reply_port, 
                                                  k5_ipc_stream in_request_stream)
{
    int32_t err = 0;
    kim_options options = NULL;
    
    if (!err) {
        err = kim_options_create_from_stream (&options, in_request_stream);
    }
    
    if (!err) {
        // performs selector on main thread
        [KerberosAgentListener enterIdentityWithClientPort:in_client_port 
                                                 replyPort:in_reply_port
                                                   options:options];
    }
    
    return err;   
}

/* ------------------------------------------------------------------------ */

int32_t kim_handle_reply_enter_identity (mach_port_t   in_reply_port, 
                                         kim_identity  in_identity,
                                         kim_options   in_options,
                                         kim_boolean   in_change_password,
                                         int32_t       in_error)
{
    int32_t err = 0;
    k5_ipc_stream reply = NULL;
    kim_string identity_string = NULL;
    
    if (!err && !in_error) {
        err = kim_identity_get_string (in_identity, &identity_string);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_new (&reply);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_write_int32 (reply, in_error);
    }
    
    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_string (reply, identity_string);
    }

    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_uint32 (reply, in_change_password);
    }
    
    if (!err && !in_error) {
        err = kim_options_write_to_stream (in_options, reply);
    }
    
    if (!err) {
        err = k5_ipc_server_send_reply (in_reply_port, reply);
    }
    
    kim_string_free (&identity_string);
    krb5int_ipc_stream_release (reply);
    
    return err;   
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static int32_t kim_handle_request_select_identity (mach_port_t   in_client_port, 
                                                   mach_port_t   in_reply_port, 
                                                   k5_ipc_stream in_request_stream)
{
    int32_t err = 0;
    kim_selection_hints hints = NULL;

    if (!err) {
        err = kim_selection_hints_create_from_stream (&hints, 
                                                      in_request_stream);
    }    
    
    if (!err) {
        // performs selector on main thread
        [KerberosAgentListener selectIdentityWithClientPort:in_client_port 
                                                  replyPort:in_reply_port
                                                      hints:hints];
    }
    
    kim_selection_hints_free (&hints);
    
    return err;   
}

/* ------------------------------------------------------------------------ */

int32_t kim_handle_reply_select_identity (mach_port_t   in_reply_port, 
                                          kim_identity  in_identity,
                                          kim_options   in_options,
                                          kim_boolean   in_change_password,
                                          int32_t       in_error)
{
    int32_t err = 0;
    k5_ipc_stream reply = NULL;
    kim_string identity_string = NULL;
    
    if (!err && !in_error) {
        err = kim_identity_get_string (in_identity, &identity_string);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_new (&reply);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_write_int32 (reply, in_error);
    }
    
    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_string (reply, identity_string);
    }
    
    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_uint32 (reply, in_change_password);
    }

    if (!err && !in_error) {
        err = kim_options_write_to_stream (in_options, reply);
    }

    if (!err) {
        err = k5_ipc_server_send_reply (in_reply_port, reply);
    }
    
    kim_string_free (&identity_string);
    krb5int_ipc_stream_release (reply);
    
    return err;   
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static int32_t kim_handle_request_auth_prompt (mach_port_t   in_client_port, 
                                               mach_port_t   in_reply_port, 
                                               k5_ipc_stream in_request_stream)
{
    int32_t err = 0;
    char *identity_string = NULL;
    int32_t type = 0;
    int32_t allow_save_reply = 0;
    int32_t hide_reply = 0;
    char *title = NULL;
    char *message = NULL;
    char *description = NULL;
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &identity_string);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_int32 (in_request_stream, &type);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_int32 (in_request_stream, &allow_save_reply);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_int32 (in_request_stream, &hide_reply);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &title);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &message);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &description);
    }    
    
    if (!err) {
        // performs selector on main thread
        [KerberosAgentListener promptForAuthWithClientPort:in_client_port
                                                 replyPort:in_reply_port 
                                                  identity:identity_string 
                                                promptType:type 
                                                 allowSave:allow_save_reply
                                                 hideReply:hide_reply
                                                     title:title
                                                   message:message
                                               description:description];
    }
    
    krb5int_ipc_stream_free_string (identity_string);    
    krb5int_ipc_stream_free_string (title);    
    krb5int_ipc_stream_free_string (message);    
    krb5int_ipc_stream_free_string (description);    
    
    return err;   
}

/* ------------------------------------------------------------------------ */

int32_t kim_handle_reply_auth_prompt (mach_port_t   in_reply_port, 
                                      kim_string    in_prompt_response,
                                      kim_boolean   in_allow_save_response,
                                      int32_t       in_error)
{
    int32_t err = 0;
    k5_ipc_stream reply = NULL;
    
    if (!err) {
        err = krb5int_ipc_stream_new (&reply);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_write_int32 (reply, in_error);
    }
    
    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_string (reply, in_prompt_response);
    }
    
    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_int32 (reply, in_allow_save_response);
    }
    
    if (!err) {
        err = k5_ipc_server_send_reply (in_reply_port, reply);
    }
    
    krb5int_ipc_stream_release (reply);
    
    return err;   
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static int32_t kim_handle_request_change_password (mach_port_t   in_client_port, 
                                                   mach_port_t   in_reply_port, 
                                                   k5_ipc_stream in_request_stream)
{
    int32_t err = 0;
    char *identity_string = NULL;
    int32_t old_password_expired = 0;
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &identity_string);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_int32 (in_request_stream, 
                                        &old_password_expired);
    }    
    
    if (!err) {
        // performs selector on main thread
        [KerberosAgentListener changePasswordWithClientPort:in_client_port
                                                 replyPort:in_reply_port 
                                                  identity:identity_string 
                                                    expired:old_password_expired];
    }
    
    krb5int_ipc_stream_free_string (identity_string);   
    
    return err;   
}

/* ------------------------------------------------------------------------ */

int32_t kim_handle_reply_change_password (mach_port_t   in_reply_port, 
                                          kim_string    in_old_password,
                                          kim_string    in_new_password,
                                          kim_string    in_vfy_password,
                                          int32_t       in_error)
{
    int32_t err = 0;
    k5_ipc_stream reply = NULL;
    
    if (!err) {
        err = krb5int_ipc_stream_new (&reply);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_write_int32 (reply, in_error);
    }
    
    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_string (reply, in_old_password);
    }
    
    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_string (reply, in_new_password);
    }
    
    if (!err && !in_error) {
        err = krb5int_ipc_stream_write_string (reply, in_vfy_password);
    }
    
    if (!err) {
        err = k5_ipc_server_send_reply (in_reply_port, reply);
    }
    
    krb5int_ipc_stream_release (reply);
    
    return err;   
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static int32_t kim_handle_request_handle_error (mach_port_t   in_client_port, 
                                                mach_port_t   in_reply_port, 
                                                k5_ipc_stream in_request_stream)
{
    int32_t err = 0;
    char *identity_string = NULL;
    int32_t error = 0;
    char *message = NULL;
    char *description = NULL;
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &identity_string);
        /* Can be empty string "" if there is no identity */
        if (!err && !identity_string[0]) {
            krb5int_ipc_stream_free_string (identity_string);   
            identity_string = NULL;
        }
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_int32 (in_request_stream, &error);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &message);
    }    
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &description);
    }    
    
    if (!err) {
        // performs selector on main thread
        [KerberosAgentListener handleErrorWithClientPort:in_client_port
                                               replyPort:in_reply_port 
                                                identity:identity_string 
                                                   error:error
                                                 message:message
                                             description:description];
    }
    
    krb5int_ipc_stream_free_string (identity_string);   
    krb5int_ipc_stream_free_string (message);   
    krb5int_ipc_stream_free_string (description);   
    
    return err;   
}

/* ------------------------------------------------------------------------ */

int32_t kim_handle_reply_handle_error (mach_port_t   in_reply_port, 
                                       int32_t       in_error)
{
    int32_t err = 0;
    k5_ipc_stream reply = NULL;
    
    if (!err) {
        err = krb5int_ipc_stream_new (&reply);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_write_int32 (reply, in_error);
    }

    if (!err) {
        err = k5_ipc_server_send_reply (in_reply_port, reply);
    }
    
    krb5int_ipc_stream_release (reply);
    
    return err;   
}

#pragma mark -

/* ------------------------------------------------------------------------ */

static int32_t kim_handle_request_fini (mach_port_t   in_client_port, 
                                        mach_port_t   in_reply_port, 
                                        k5_ipc_stream in_request_stream)
{
    int32_t err = 0;

    [KerberosAgentListener removeClientMatchingPort:in_client_port replyPort:in_reply_port];
    
    return err;   
}

/* ------------------------------------------------------------------------ */

int32_t kim_handle_reply_fini (mach_port_t   in_reply_port, 
                               int32_t       in_error)
{
    int32_t err = 0;
    k5_ipc_stream reply = NULL;
    
    if (!err) {
        err = krb5int_ipc_stream_new (&reply);
    }
    
    if (!err) {
        err = krb5int_ipc_stream_write_int32 (reply, in_error);
    }
    
    if (!err) {
        err = k5_ipc_server_send_reply (in_reply_port, reply);
    }
    
    krb5int_ipc_stream_release (reply);
    
    return err;   
}

#pragma mark -

/* ------------------------------------------------------------------------ */

int32_t k5_ipc_server_add_client (mach_port_t in_client_port)
{
    int32_t err = 0;
    
    if (!err) {
        /* Don't need to do anything here since we have an init message */
    }
    
    return err;
}

/* ------------------------------------------------------------------------ */

int32_t k5_ipc_server_remove_client (mach_port_t in_client_port)
{
    int32_t err = 0;    
    
    if (!err) {
        /* Client exited.  Main thread should check for windows belonging to
         * in_client_port and close any it finds. */
        [KerberosAgentListener removeClientMatchingPort:in_client_port replyPort:0];
    }
    
    return err;
}

/* ------------------------------------------------------------------------ */

int32_t k5_ipc_server_handle_request (mach_port_t   in_client_port, 
                                      mach_port_t   in_reply_port, 
                                      k5_ipc_stream in_request_stream)
{
    int32_t err = 0;
    char *message_type = NULL;
    
    if (!err) {
        err = krb5int_ipc_stream_read_string (in_request_stream, &message_type);
    }
    
    if (!err) {
        if (!strcmp (message_type, "init")) {
            err = kim_handle_request_init (in_client_port,
                                           in_reply_port,
                                           in_request_stream);
            
        } else if (!strcmp (message_type, "enter_identity")) {
            err = kim_handle_request_enter_identity (in_client_port,
                                                     in_reply_port,
                                                     in_request_stream);
            
        } else if (!strcmp (message_type, "select_identity")) {
            err = kim_handle_request_select_identity (in_client_port,
                                                      in_reply_port,
                                                      in_request_stream);
            
        } else if (!strcmp (message_type, "auth_prompt")) {
            err = kim_handle_request_auth_prompt (in_client_port,
                                                  in_reply_port,
                                                  in_request_stream);
            
        } else if (!strcmp (message_type, "change_password")) {
            err = kim_handle_request_change_password (in_client_port,
                                                      in_reply_port,
                                                      in_request_stream);
            
        } else if (!strcmp (message_type, "handle_error")) {
            err = kim_handle_request_handle_error (in_client_port,
                                                   in_reply_port,
                                                   in_request_stream);
            
        } else if (!strcmp (message_type, "fini")) {
            err = kim_handle_request_fini (in_client_port,
                                           in_reply_port,
                                           in_request_stream);
            
        } else {
            err = EINVAL;
        }
    }
    
    krb5int_ipc_stream_free_string (message_type);
    
    return err;
}

#pragma mark -

/* ------------------------------------------------------------------------ */

int32_t kim_agent_listen_loop (void)
{
    return k5_ipc_server_listen_loop ();
}
