Example #1
0
krb5_error_code KRB5_CALLCONV
krb5_get_init_creds_password(krb5_context context,
                             krb5_creds *creds,
                             krb5_principal client,
                             const char *password,
                             krb5_prompter_fct prompter,
                             void *data,
                             krb5_deltat start_time,
                             const char *in_tkt_service,
                             krb5_get_init_creds_opt *options)
{
    krb5_error_code ret, ret2;
    int use_master;
    krb5_kdc_rep *as_reply;
    int tries;
    krb5_creds chpw_creds;
    krb5_get_init_creds_opt *chpw_opts = NULL;
    krb5_data pw0, pw1;
    char banner[1024], pw0array[1024], pw1array[1024];
    krb5_prompt prompt[2];
    krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
    char *message;

    use_master = 0;
    as_reply = NULL;
    memset(&chpw_creds, 0, sizeof(chpw_creds));

    pw0.data = pw0array;

    if (password && password[0]) {
        if (strlcpy(pw0.data, password, sizeof(pw0array)) >= sizeof(pw0array)) {
            ret = EINVAL;
            goto cleanup;
        }
        pw0.length = strlen(password);
    } else {
        pw0.data[0] = '\0';
        pw0.length = sizeof(pw0array);
    }

    pw1.data = pw1array;
    pw1.data[0] = '\0';
    pw1.length = sizeof(pw1array);

    /* first try: get the requested tkt from any kdc */

    ret = krb5int_get_init_creds(context, creds, client, prompter, data,
                                 start_time, in_tkt_service, options,
                                 krb5_get_as_key_password, (void *) &pw0,
                                 &use_master, &as_reply);

    /* check for success */

    if (ret == 0)
        goto cleanup;

    /* If all the kdc's are unavailable, or if the error was due to a
       user interrupt, fail */

    if ((ret == KRB5_KDC_UNREACH) ||
        (ret == KRB5_LIBOS_PWDINTR) ||
        (ret == KRB5_REALM_CANT_RESOLVE))
        goto cleanup;

    /* if the reply did not come from the master kdc, try again with
       the master kdc */

    if (!use_master) {
        TRACE_GIC_PWD_MASTER(context);
        use_master = 1;

        if (as_reply) {
            krb5_free_kdc_rep( context, as_reply);
            as_reply = NULL;
        }
        ret2 = krb5int_get_init_creds(context, creds, client, prompter, data,
                                      start_time, in_tkt_service, options,
                                      krb5_get_as_key_password, (void *) &pw0,
                                      &use_master, &as_reply);

        if (ret2 == 0) {
            ret = 0;
            goto cleanup;
        }

        /* if the master is unreachable, return the error from the
           slave we were able to contact or reset the use_master flag */

        if ((ret2 != KRB5_KDC_UNREACH) &&
            (ret2 != KRB5_REALM_CANT_RESOLVE) &&
            (ret2 != KRB5_REALM_UNKNOWN))
            ret = ret2;
        else
            use_master = 0;
    }

    /* at this point, we have an error from the master.  if the error
       is not password expired, or if it is but there's no prompter,
       return this error */

    if ((ret != KRB5KDC_ERR_KEY_EXP) ||
        (prompter == NULL))
        goto cleanup;

    /* historically the default has been to prompt for password change.
     * if the change password prompt option has not been set, we continue
     * to prompt.  Prompting is only disabled if the option has been set
     * and the value has been set to false.
     */
    if (options && !(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT))
        goto cleanup;
    TRACE_GIC_PWD_EXPIRED(context);

    /* ok, we have an expired password.  Give the user a few chances
       to change it */

    /* use a minimal set of options */

    ret = krb5_get_init_creds_opt_alloc(context, &chpw_opts);
    if (ret)
        goto cleanup;
    krb5_get_init_creds_opt_set_tkt_life(chpw_opts, 5*60);
    krb5_get_init_creds_opt_set_renew_life(chpw_opts, 0);
    krb5_get_init_creds_opt_set_forwardable(chpw_opts, 0);
    krb5_get_init_creds_opt_set_proxiable(chpw_opts, 0);

    if ((ret = krb5int_get_init_creds(context, &chpw_creds, client,
                                      prompter, data,
                                      start_time, "kadmin/changepw", chpw_opts,
                                      krb5_get_as_key_password, (void *) &pw0,
                                      &use_master, NULL)))
        goto cleanup;

    prompt[0].prompt = _("Enter new password");
    prompt[0].hidden = 1;
    prompt[0].reply = &pw0;
    prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD;

    prompt[1].prompt = _("Enter it again");
    prompt[1].hidden = 1;
    prompt[1].reply = &pw1;
    prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN;

    strlcpy(banner, _("Password expired.  You must change it now."),
            sizeof(banner));

    for (tries = 3; tries; tries--) {
        TRACE_GIC_PWD_CHANGEPW(context, tries);
        pw0.length = sizeof(pw0array);
        pw1.length = sizeof(pw1array);

        /* PROMPTER_INVOCATION */
        krb5int_set_prompt_types(context, prompt_types);
        ret = (*prompter)(context, data, 0, banner,
                          sizeof(prompt)/sizeof(prompt[0]), prompt);
        krb5int_set_prompt_types(context, 0);
        if (ret)
            goto cleanup;

        if (strcmp(pw0.data, pw1.data) != 0) {
            ret = KRB5_LIBOS_BADPWDMATCH;
            snprintf(banner, sizeof(banner),
                     _("%s.  Please try again."), error_message(ret));
        } else if (pw0.length == 0) {
            ret = KRB5_CHPW_PWDNULL;
            snprintf(banner, sizeof(banner),
                     _("%s.  Please try again."), error_message(ret));
        } else {
            int result_code;
            krb5_data code_string;
            krb5_data result_string;

            if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
                                            &result_code, &code_string,
                                            &result_string)))
                goto cleanup;

            /* the change succeeded.  go on */

            if (result_code == 0) {
                free(result_string.data);
                break;
            }

            /* set this in case the retry loop falls through */

            ret = KRB5_CHPW_FAIL;

            if (result_code != KRB5_KPASSWD_SOFTERROR) {
                free(result_string.data);
                goto cleanup;
            }

            /* the error was soft, so try again */

            if (krb5_chpw_message(context, &result_string, &message) != 0)
                message = NULL;

            /* 100 is I happen to know that no code_string will be longer
               than 100 chars */

            if (message != NULL && strlen(message) > (sizeof(banner) - 100))
                message[sizeof(banner) - 100] = '\0';

            snprintf(banner, sizeof(banner),
                     _("%.*s%s%s.  Please try again.\n"),
                     (int) code_string.length, code_string.data,
                     message ? ": " : "", message ? message : "");

            free(message);
            free(code_string.data);
            free(result_string.data);
        }
    }

    if (ret)
        goto cleanup;

    /* the password change was successful.  Get an initial ticket
       from the master.  this is the last try.  the return from this
       is final.  */

    TRACE_GIC_PWD_CHANGED(context);
    ret = krb5int_get_init_creds(context, creds, client, prompter, data,
                                 start_time, in_tkt_service, options,
                                 krb5_get_as_key_password, (void *) &pw0,
                                 &use_master, &as_reply);
    if (ret)
        goto cleanup;

cleanup:
    if (ret == 0)
        warn_pw_expiry(context, options, prompter, data, in_tkt_service,
                       as_reply);

    if (chpw_opts)
        krb5_get_init_creds_opt_free(context, chpw_opts);
    memset(pw0array, 0, sizeof(pw0array));
    memset(pw1array, 0, sizeof(pw1array));
    krb5_free_cred_contents(context, &chpw_creds);
    if (as_reply)
        krb5_free_kdc_rep(context, as_reply);

    return(ret);
}
int
main(void)
{
    krb5_context context;
    char *msg;

    setlocale(LC_MESSAGES, "C");

    check(krb5_init_context(&context));

    /* Valid utf-8 data in the result should be returned as is */
    check(krb5_chpw_message(context, &result_utf8, &msg));
    printf("  UTF8 valid:   %s\n", msg);
    check_msg(msg, "This is a valid string.");
    free(msg);

    /* Invalid data should have a generic message. */
    check(krb5_chpw_message(context, &result_invalid_utf8, &msg));
    printf("  UTF8 invalid: %s\n", msg);
    check_msg(msg, "contact your administrator");
    free(msg);

    /* AD data with complex data requirement */
    check(krb5_chpw_message(context, &result_ad_complex, &msg));
    printf("  AD complex:   %s\n", msg);
    check_msg(msg, "The password must include numbers or symbols.");
    check_msg(msg, "Don't include any part of your name in the password.");
    free(msg);

    /* AD data with min password length */
    check(krb5_chpw_message(context, &result_ad_length, &msg));
    printf("  AD length:    %s\n", msg);
    check_msg(msg, "The password must contain at least 13 characters.");
    free(msg);

    /* AD data with history requirements */
    check(krb5_chpw_message(context, &result_ad_history, &msg));
    printf("  AD history:   %s\n", msg);
    check_msg(msg, "The password must be different from the previous 9 "
              "passwords.");
    free(msg);

    /* AD data with minimum age */
    check(krb5_chpw_message(context, &result_ad_age, &msg));
    printf("  AD min age:   %s\n", msg);
    check_msg(msg, "The password can only be changed every 2 days.");
    free(msg);

    /* AD data with all */
    check(krb5_chpw_message(context, &result_ad_all, &msg));
    printf("  AD all:       %s\n", msg);
    check_msg(msg, "The password can only be changed once a day.");
    check_msg(msg, "The password must be different from the previous 13 "
              "passwords.");
    check_msg(msg, "The password must contain at least 5 characters.");
    check_msg(msg, "The password must include numbers or symbols.");
    check_msg(msg, "Don't include any part of your name in the password.");
    free(msg);

    krb5_free_context(context);
    exit(0);
}