kim_ui_cli.c   [plain text]


/*
 * $Header$
 *
 * 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.
 */

#ifdef KIM_BUILTIN_UI

#include "kim_private.h"

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

static kim_error kim_ui_cli_read_string (kim_string   *out_string, 
                                         kim_boolean   in_hide_reply, 
                                         const char   *in_format, ...)
{
    kim_error err = KIM_NO_ERROR;
    krb5_context k5context = NULL;
    krb5_prompt prompts[1];
    char prompt_string [BUFSIZ];
    krb5_data reply_data;
    char reply_string [BUFSIZ];
    
    if (!err && !out_string) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !in_format ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = krb5_init_context (&k5context);
    }
    
    if (!err) {
        unsigned int count;
        va_list args;
        
        va_start (args, in_format);
        count = vsnprintf (prompt_string, sizeof (prompt_string), 
                                  in_format, args);
        va_end (args);
        
        if (count > sizeof (prompt_string)) {
            kim_debug_printf ("%s(): WARNING! Prompt should be %d characters\n", 
                              __FUNCTION__, count);
            prompt_string [sizeof (prompt_string) - 1] = '\0';
        }
    }
    
    if (!err) {
        /* Build the prompt structures */
        prompts[0].prompt        = prompt_string;
        prompts[0].hidden        = in_hide_reply;
        prompts[0].reply         = &reply_data;
        prompts[0].reply->data   = reply_string;
        prompts[0].reply->length = sizeof (reply_string);
        
        err = krb5_prompter_posix (k5context, NULL, NULL, NULL, 1, prompts);
        if (err == KRB5_LIBOS_PWDINTR || err == KRB5_LIBOS_CANTREADPWD) { 
            err = check_error (KIM_USER_CANCELED_ERR); 
        }
    }
    
    if (!err) {
        err = kim_string_create_from_buffer (out_string, 
                                             prompts[0].reply->data, 
                                             prompts[0].reply->length);
    }
    
    if (k5context) { krb5_free_context (k5context); }
    
    return check_error (err);
}

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

kim_error kim_ui_cli_init (kim_ui_context *io_context)
{
    if (io_context) {
        io_context->tcontext = NULL;
    }
    
    return KIM_NO_ERROR;
}

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

kim_error kim_ui_cli_enter_identity (kim_ui_context *in_context,
                                     kim_options     io_options,
                                     kim_identity   *out_identity,
                                     kim_boolean    *out_change_password)
{
    kim_error err = KIM_NO_ERROR;
    kim_string enter_identity_string = NULL;
    kim_string identity_string = NULL;
    
    if (!err && !io_options         ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_identity       ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_change_password) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_os_string_create_localized (&enter_identity_string, 
                                              "Please enter your Kerberos identity");
    }
    
    if (!err) {
        err = kim_ui_cli_read_string (&identity_string, 
                                      0, enter_identity_string);
    }
    
    if (!err) {
        err = kim_identity_create_from_string (out_identity, identity_string);
    }
    
    if (!err) {
        *out_change_password = 0;
    }
    
    kim_string_free (&identity_string);
    kim_string_free (&enter_identity_string);
    
    return check_error (err);
}

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

kim_error kim_ui_cli_select_identity (kim_ui_context      *in_context,
                                      kim_selection_hints  io_hints,
                                      kim_identity        *out_identity,
                                      kim_boolean         *out_change_password)
{
    kim_error err = KIM_NO_ERROR;
    kim_options options = NULL;
    
    if (!err && !io_hints           ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_identity       ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_change_password) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        err = kim_selection_hints_get_options (io_hints, &options);
    }
    
    if (!err) {
        err = kim_ui_cli_enter_identity (in_context, options, 
                                         out_identity,
                                         out_change_password);
    }
    
    if (!err) {
        err = kim_selection_hints_set_options (io_hints, options);
    }
    
    kim_options_free (&options);
    
    return check_error (err);
}

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

kim_error kim_ui_cli_auth_prompt (kim_ui_context      *in_context,
                                  kim_identity         in_identity,
                                  kim_prompt_type      in_type,
                                  kim_boolean          in_allow_save_reply, 
                                  kim_boolean          in_hide_reply, 
                                  kim_string           in_title,
                                  kim_string           in_message,
                                  kim_string           in_description,
                                  char               **out_reply,
                                  kim_boolean         *out_save_reply)
{
    kim_error err = KIM_NO_ERROR;
    
    if (!err && !in_identity) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_reply  ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    /* in_title, in_message or in_description may be NULL */
    
    if (!err) {
        if (in_type == kim_prompt_type_password) {
            kim_string enter_password_format = NULL;
            kim_string identity_string = NULL;
            
            err = kim_os_string_create_localized (&enter_password_format, 
                                                  "Please enter the password for %s");
            
            if (!err) {
                err = kim_identity_get_display_string (in_identity, 
                                                       &identity_string);
            }
            
            if (!err) {
                err = kim_ui_cli_read_string ((kim_string *) out_reply, 
                                              1, enter_password_format, 
                                              identity_string);
            }    
            
            kim_string_free (&identity_string);
            kim_string_free (&enter_password_format);
            
        } else {
            krb5_context k5context = NULL;
            krb5_prompt prompts[1];
            krb5_data reply_data;
            char reply_string [BUFSIZ];

            prompts[0].prompt        = (char *) in_description;
            prompts[0].hidden        = in_hide_reply;
            prompts[0].reply         = &reply_data;
            prompts[0].reply->data   = reply_string;
            prompts[0].reply->length = sizeof (reply_string);

            err = krb5_init_context (&k5context);

            if (!err) {
                err = krb5_prompter_posix (k5context, in_context, in_title, 
                                           in_message, 1, prompts);
                if (err == KRB5_LIBOS_PWDINTR || err == KRB5_LIBOS_CANTREADPWD) { 
                    err = check_error (KIM_USER_CANCELED_ERR); 
                }
            }
            
            if (!err) {
                err = kim_string_create_from_buffer ((kim_string *) out_reply, 
                                                     prompts[0].reply->data, 
                                                     prompts[0].reply->length);
                if (!err) {
                    /* always allow password saving */
                    *out_save_reply = (in_allow_save_reply && 
                                       in_type == kim_prompt_type_password);
                }
            }
            
            if (k5context) { krb5_free_context (k5context); }
        }
    }
    
    return check_error (err);
}

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

static kim_error kim_ui_cli_ask_change_password (kim_string in_identity_string)
{
    kim_error err = KIM_NO_ERROR;
    kim_string ask_change_password = NULL;
    kim_string yes = NULL;
    kim_string no = NULL;
    kim_string unknown_response = NULL;
    kim_boolean done = 0;
    kim_comparison no_comparison, yes_comparison;
    
    if (!err) {
        err = kim_os_string_create_localized (&ask_change_password, 
                                              "Your password has expired, would you like to change it? (yes/no)");        
    }
    
    if (!err) {
        err = kim_os_string_create_localized (&yes, "yes");        
    }
    
    if (!err) {
        err = kim_os_string_create_localized (&no, "no");        
    }
    
    if (!err) {
        err = kim_os_string_create_localized (&unknown_response, 
                                              "%s is not a response I understand.  Please try again.");        
    }
    
    while (!err && !done) {
        kim_string answer = NULL;
        
        err = kim_ui_cli_read_string (&answer, 0, ask_change_password);
    
        if (!err) {
            err = kim_os_string_compare (answer, no, 
                                         1 /* case insensitive */, 
                                         &no_comparison);
        }
        
        if (!err && kim_comparison_is_equal_to (no_comparison)) {
            err = check_error (KIM_USER_CANCELED_ERR);
        }

        if (!err) {
            err = kim_os_string_compare (answer, yes, 
                                         1 /* case insensitive */, 
                                         &yes_comparison);
        }
        
        if (!err) {
            if (kim_comparison_is_equal_to (yes_comparison)) {
                done = 1;
            } else {
                fprintf (stdout, unknown_response, answer);
                fprintf (stdout, "\n");                
            }
        }
        
        kim_string_free (&answer);
    }
    
    kim_string_free (&ask_change_password);
    kim_string_free (&yes);
    kim_string_free (&no);
    kim_string_free (&unknown_response);

    return check_error (err);
}

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

kim_error kim_ui_cli_change_password (kim_ui_context  *in_context,
                                      kim_identity     in_identity,
                                      kim_boolean      in_old_password_expired,
                                      char           **out_old_password,
                                      char           **out_new_password,
                                      char           **out_verify_password)
{
    kim_error err = KIM_NO_ERROR;
    kim_string enter_old_password_format = NULL;
    kim_string enter_new_password_format = NULL;
    kim_string enter_verify_password_format = NULL;
    kim_string identity_string = NULL;
    kim_string old_password = NULL;
    kim_string new_password = NULL;
    kim_string verify_password = NULL;
    kim_boolean done = 0;
    
    if (!err && !in_identity        ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_old_password   ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_new_password   ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !out_verify_password) { err = check_error (KIM_NULL_PARAMETER_ERR); }

    if (!err) {
        err = kim_identity_get_display_string (in_identity, &identity_string);
    }

    if (!err && in_old_password_expired) {
        err = kim_ui_cli_ask_change_password (identity_string);
    }
    
    if (!err) {
        err = kim_os_string_create_localized (&enter_old_password_format, 
                                              "Please enter the old password for %s");
    }
    
    if (!err) {
        err = kim_os_string_create_localized (&enter_new_password_format, 
                                              "Please enter the new password for %s");
    }
    
    if (!err) {
        err = kim_os_string_create_localized (&enter_verify_password_format, 
                                              "Verifying, please re-enter the new password for %s again");
    }
    
    while (!err && !done) {
        kim_boolean was_prompted = 0;  /* ignore because we always prompt */
        
        kim_string_free (&old_password);

        err = kim_ui_cli_read_string (&old_password, 
                                      1, enter_old_password_format, 
                                      identity_string);
        
        if (!err && strlen (old_password) < 1) {
            /* Empty password: Synthesize bad password err */
            err = KRB5KRB_AP_ERR_BAD_INTEGRITY; 
        }
        
        if (!err) {
            err = kim_credential_create_for_change_password ((kim_credential *) &in_context->tcontext,
                                                             in_identity,
                                                             old_password,
                                                             in_context,
                                                             &was_prompted);
        }
        
        if (err && err != KIM_USER_CANCELED_ERR) {
            /*  new creds failed, report error to user */
            err = kim_ui_handle_kim_error (in_context, in_identity, 
                                           kim_ui_error_type_change_password,
                                           err);

        } else {
            done = 1;
       }
    }
    
    if (!err) {
        err = kim_ui_cli_read_string (&new_password, 
                                      1, enter_new_password_format, 
                                      identity_string);
    }    
    
    if (!err) {
        err = kim_ui_cli_read_string (&verify_password, 
                                      1, enter_verify_password_format, 
                                      identity_string);
    }    
    
    if (!err) {
        *out_old_password = (char *) old_password;
        old_password = NULL;
        *out_new_password = (char *) new_password;
        new_password = NULL;
        *out_verify_password = (char *) verify_password;
        verify_password = NULL;
    }
    
    kim_string_free (&old_password);
    kim_string_free (&new_password);
    kim_string_free (&verify_password);
    kim_string_free (&identity_string);
    kim_string_free (&enter_old_password_format);
    kim_string_free (&enter_new_password_format);
    kim_string_free (&enter_verify_password_format);
    
    return check_error (err);
}

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

kim_error kim_ui_cli_handle_error (kim_ui_context *in_context,
                                   kim_identity    in_identity,
                                   kim_error       in_error,
                                   kim_string      in_error_message,
                                   kim_string      in_error_description)
{
    kim_error err = KIM_NO_ERROR;
    
    if (!err && !in_error_message    ) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    if (!err && !in_error_description) { err = check_error (KIM_NULL_PARAMETER_ERR); }
    
    if (!err) {
        fprintf (stdout, "%s\n%s\n\n", in_error_message, in_error_description);
    }
    
    return check_error (err);
}

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

void kim_ui_cli_free_string (kim_ui_context  *in_context,
                             char           **io_string)
{
    kim_string_free ((kim_string *) io_string);
}

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

kim_error kim_ui_cli_fini (kim_ui_context *io_context)
{
    if (io_context) {
        kim_credential_free ((kim_credential *) &io_context->tcontext);
    }
    
    return KIM_NO_ERROR;
}

#endif /* KIM_BUILTIN_UI */