/* kim/lib/kim_options.c */
/*
 * Copyright 2006 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.
 */

#include "kim_private.h"

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

struct kim_options_opaque {
    kim_time start_time;
    kim_lifetime lifetime;
    kim_boolean renewable;
    kim_lifetime renewal_lifetime;
    kim_boolean forwardable;
    kim_boolean proxiable;
    kim_boolean addressless;
    kim_string service_name;
    krb5_context init_cred_context;
    krb5_get_init_creds_opt *init_cred_options;
};

struct kim_options_opaque kim_options_initializer = {
0,
kim_default_lifetime,
kim_default_renewable,
kim_default_renewal_lifetime,
kim_default_forwardable,
kim_default_proxiable,
kim_default_addressless,
kim_empty_string,
NULL,
NULL };

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

static inline kim_error kim_options_allocate (kim_options *out_options)
{
    kim_error err = kim_library_init ();
    kim_options options = NULL;

    if (!err && !out_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        options = malloc (sizeof (*options));
        if (!options) { err = KIM_OUT_OF_MEMORY_ERR; }
    }

    if (!err) {
        *options = kim_options_initializer;
        *out_options = options;
        options = NULL;
    }

    kim_options_free (&options);

    return check_error (err);
}

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

kim_error kim_options_create_empty (kim_options *out_options)
{
    return check_error (kim_options_allocate (out_options));
}

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

kim_error kim_options_create (kim_options *out_options)
{
    kim_error err = KIM_NO_ERROR;
    kim_preferences preferences = NULL;
    kim_options options = KIM_OPTIONS_DEFAULT;

    if (!err && !out_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        err = kim_preferences_create (&preferences);
    }

    if (!err) {
        err = kim_preferences_get_options (preferences, &options);
    }

    if (!err && !options) {
        err = kim_options_allocate (&options);
    }

    if (!err) {
        *out_options = options;
        options = NULL; /* caller takes ownership */
    }

    kim_options_free (&options);
    kim_preferences_free (&preferences);

    return check_error (err);
}

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

kim_error kim_options_copy (kim_options *out_options,
                            kim_options  in_options)
{
    kim_error err = KIM_NO_ERROR;
    kim_options options = KIM_OPTIONS_DEFAULT;

    if (!err && !out_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err && in_options != KIM_OPTIONS_DEFAULT) {
        err = kim_options_allocate (&options);

        if (!err) {
            options->start_time = in_options->start_time;
            options->lifetime = in_options->lifetime;
            options->renewable = in_options->renewable;
            options->renewal_lifetime = in_options->renewal_lifetime;
            options->forwardable = in_options->forwardable;
            options->proxiable = in_options->proxiable;
            options->addressless = in_options->addressless;

            if (in_options->service_name) {
                err = kim_string_copy (&options->service_name,
                                       in_options->service_name);
            }
        }
    }

    if (!err) {
        *out_options = options;
        options = NULL;
    }

    kim_options_free (&options);

    return check_error (err);
}

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

kim_error kim_options_set_start_time (kim_options io_options,
                                      kim_time    in_start_time)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        io_options->start_time = in_start_time;
    }

    return check_error (err);
}

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

kim_error kim_options_get_start_time (kim_options  in_options,
                                      kim_time    *out_start_time)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !in_options    ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_start_time) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        *out_start_time = in_options->start_time;
    }

    return check_error (err);
}

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

kim_error kim_options_set_lifetime (kim_options  io_options,
                                    kim_lifetime in_lifetime)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        io_options->lifetime = in_lifetime;
    }

    return check_error (err);
}

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

kim_error kim_options_get_lifetime (kim_options   in_options,
                                    kim_lifetime *out_lifetime)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !in_options  ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_lifetime) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        *out_lifetime = in_options->lifetime;
    }

    return check_error (err);
}

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

kim_error kim_options_set_renewable (kim_options io_options,
                                     kim_boolean in_renewable)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        io_options->renewable = in_renewable;
    }

    return check_error (err);
}

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

kim_error kim_options_get_renewable (kim_options  in_options,
                                     kim_boolean *out_renewable)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !in_options   ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_renewable) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        *out_renewable = in_options->renewable;
    }

    return check_error (err);
}

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

kim_error kim_options_set_renewal_lifetime (kim_options  io_options,
                                            kim_lifetime in_renewal_lifetime)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        io_options->renewal_lifetime = in_renewal_lifetime;
    }

    return check_error (err);
}

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

kim_error kim_options_get_renewal_lifetime (kim_options   in_options,
                                            kim_lifetime *out_renewal_lifetime)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !in_options          ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_renewal_lifetime) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        *out_renewal_lifetime = in_options->renewal_lifetime;
    }

    return check_error (err);
}

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

kim_error kim_options_set_forwardable (kim_options io_options,
                                       kim_boolean in_forwardable)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        io_options->forwardable = in_forwardable;
    }

    return check_error (err);
}

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

kim_error kim_options_get_forwardable (kim_options  in_options,
                                       kim_boolean *out_forwardable)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !in_options     ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_forwardable) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        *out_forwardable = in_options->forwardable;
    }

    return check_error (err);
}

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

kim_error kim_options_set_proxiable (kim_options io_options,
                                     kim_boolean in_proxiable)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        io_options->proxiable = in_proxiable;
    }

    return check_error (err);
}

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

kim_error kim_options_get_proxiable (kim_options  in_options,
                                     kim_boolean *out_proxiable)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !in_options   ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_proxiable) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        *out_proxiable = in_options->proxiable;
    }

    return check_error (err);
}

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

kim_error kim_options_set_addressless (kim_options io_options,
                                       kim_boolean in_addressless)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        io_options->addressless = in_addressless;
    }

    return check_error (err);
}

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

kim_error kim_options_get_addressless (kim_options  in_options,
                                       kim_boolean *out_addressless)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !in_options     ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_addressless) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        *out_addressless = in_options->addressless;
    }

    return check_error (err);
}

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

kim_error kim_options_set_service_name (kim_options  io_options,
                                        kim_string   in_service_name)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
	kim_string_free (&io_options->service_name);
        if (in_service_name) {
            err = kim_string_copy (&io_options->service_name, in_service_name);
        } else {
            io_options->service_name = kim_empty_string;
        }
    }

    return check_error (err);
}

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

kim_error kim_options_get_service_name (kim_options  in_options,
                                        kim_string  *out_service_name)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !in_options      ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_service_name) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        if (in_options->service_name &&
            in_options->service_name != kim_empty_string) {
            err = kim_string_copy (out_service_name, in_options->service_name);
        } else {
            *out_service_name = NULL;
        }
    }

    return check_error (err);
}

#pragma mark -

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

char *kim_options_service_name (kim_options in_options)
{
    if (in_options) {
        if (in_options->service_name == kim_empty_string) {
            return NULL;
        } else {
            return (char *) in_options->service_name;
        }
    }
    check_error (KIM_NULL_PARAMETER_ERR);  /* log bad options input */
    return NULL;
}

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

kim_time kim_options_start_time (kim_options in_options)
{
    if (in_options) {
        return in_options->start_time;
    }
    check_error (KIM_NULL_PARAMETER_ERR);  /* log bad options input */
    return 0;
}

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

krb5_get_init_creds_opt *kim_options_init_cred_options (kim_options in_options)
{
    kim_error err = KIM_NO_ERROR;
    krb5_address **addresses = NULL;

    if (!err && !in_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err && !in_options->init_cred_context) {
        err = krb5_error (NULL,
                          krb5_init_context (&in_options->init_cred_context));
    }

    if (!err && !in_options->addressless) {
        err = krb5_error (in_options->init_cred_context,
                          krb5_os_localaddr (in_options->init_cred_context,
                                             &addresses));
    }

    if (!err && !in_options->init_cred_options) {
        err = krb5_error (in_options->init_cred_context,
                          krb5_get_init_creds_opt_alloc (in_options->init_cred_context,
                                                         &in_options->init_cred_options));
    }

    if (!err) {
        krb5_get_init_creds_opt_set_tkt_life (in_options->init_cred_options,
                                              in_options->lifetime);
        krb5_get_init_creds_opt_set_renew_life (in_options->init_cred_options,
                                                in_options->renewable ? in_options->renewal_lifetime : 0);
        krb5_get_init_creds_opt_set_forwardable (in_options->init_cred_options,
                                                 in_options->forwardable);
        krb5_get_init_creds_opt_set_proxiable (in_options->init_cred_options,
                                               in_options->proxiable);
        krb5_get_init_creds_opt_set_address_list (in_options->init_cred_options,
                                                  addresses);
        addresses = NULL;
    }

    if (addresses) { krb5_free_addresses (in_options->init_cred_context,
                                          addresses); }

    return !check_error (err) ? in_options->init_cred_options : NULL;
}

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

void kim_options_free (kim_options *io_options)
{
    if (io_options && *io_options) {
        kim_string_free (&(*io_options)->service_name);
        if ((*io_options)->init_cred_context) {
            if ((*io_options)->init_cred_options) {
                if ((*io_options)->init_cred_options->address_list) {
                    krb5_free_addresses ((*io_options)->init_cred_context,
                                         (*io_options)->init_cred_options->address_list);
                }
                krb5_get_init_creds_opt_free ((*io_options)->init_cred_context,
                                              (*io_options)->init_cred_options);
            }
            krb5_free_context ((*io_options)->init_cred_context);
        }

        free (*io_options);
        *io_options = NULL;
    }
}

#pragma mark -

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

kim_error kim_options_write_to_stream (kim_options   in_options,
                                       k5_ipc_stream io_stream)
{
    kim_error err = KIM_NO_ERROR;
    kim_options options = in_options;

    if (!err && !io_stream ) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err && !in_options) {
        err = kim_options_create (&options);
    }

    if (!err) {
        err = krb5int_ipc_stream_write_int64 (io_stream, options->start_time);
    }

    if (!err) {
        err = krb5int_ipc_stream_write_int64 (io_stream, options->lifetime);
    }

    if (!err) {
        err = krb5int_ipc_stream_write_int32 (io_stream, options->renewable);
    }

    if (!err) {
        err = krb5int_ipc_stream_write_int64 (io_stream,
                                         options->renewal_lifetime);
    }

    if (!err) {
        err = krb5int_ipc_stream_write_int32 (io_stream, options->forwardable);
    }

    if (!err) {
        err = krb5int_ipc_stream_write_int32 (io_stream, options->proxiable);
    }

    if (!err) {
        err = krb5int_ipc_stream_write_int32 (io_stream, options->addressless);
    }

    if (!err) {
        err = krb5int_ipc_stream_write_string (io_stream,  options->service_name);
    }

    if (options != in_options) { kim_options_free (&options); }

    return check_error (err);
}

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

kim_error kim_options_read_from_stream (kim_options    io_options,
                                        k5_ipc_stream  io_stream)
{
    kim_error err = KIM_NO_ERROR;

    if (!err && !io_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !io_stream ) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        err = krb5int_ipc_stream_read_int64 (io_stream, &io_options->start_time);
    }

    if (!err) {
        err = krb5int_ipc_stream_read_int64 (io_stream, &io_options->lifetime);
    }

    if (!err) {
        err = krb5int_ipc_stream_read_int32 (io_stream, &io_options->renewable);
    }

    if (!err) {
        err = krb5int_ipc_stream_read_int64 (io_stream,
                                        &io_options->renewal_lifetime);
    }

    if (!err) {
        err = krb5int_ipc_stream_read_int32 (io_stream, &io_options->forwardable);
    }

    if (!err) {
        err = krb5int_ipc_stream_read_int32 (io_stream, &io_options->proxiable);
    }

    if (!err) {
        err = krb5int_ipc_stream_read_int32 (io_stream, &io_options->addressless);
    }

    if (!err) {
        char *service_name = NULL;
        err = krb5int_ipc_stream_read_string (io_stream, &service_name);

        if (!err) {
            kim_string_free (&io_options->service_name);
            if (service_name[0]) {
                err = kim_string_copy (&io_options->service_name, service_name);
            } else {
                io_options->service_name = kim_empty_string;
            }
        }

        krb5int_ipc_stream_free_string (service_name);
    }

    return check_error (err);
}

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

kim_error kim_options_create_from_stream (kim_options   *out_options,
                                          k5_ipc_stream  io_stream)
{
    kim_error err = KIM_NO_ERROR;
    kim_options options = NULL;

    if (!err && !out_options) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !io_stream  ) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        err = kim_options_allocate (&options);
    }

    if (!err) {
        kim_options_read_from_stream (options, io_stream);
    }

    if (!err) {
        *out_options = options;
        options = NULL;
    }

    kim_options_free (&options);

    return check_error (err);
}
