root/usr.bin/passwd/krb5_passwd.c
/* $NetBSD: krb5_passwd.c,v 1.20 2012/04/22 23:43:51 christos Exp $ */

/*
 * Copyright (c) 2000, 2005 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Johan Danielsson; and by Jason R. Thorpe.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 *
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* uses the `Kerberos Change Password Protocol' */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <pwd.h>
#include <unistd.h>

#include <openssl/ui.h>
#include <krb5.h>

#include "extern.h"

static void
pwkrb5_warn(const char *msg, krb5_context context, krb5_error_code ret)
{
    const char *errtxt = krb5_get_error_message(context, ret);
    if (errtxt != NULL) {
            warnx("%s: %s", msg, errtxt);
            krb5_free_error_message(context, errtxt);
    } else
            warnx("%s: %d", msg, ret);
}

#ifdef USE_PAM

void
pwkrb5_usage(const char *prefix)
{

        (void) fprintf(stderr, "%s %s [-d krb5 | -k] [principal]\n",
            prefix, getprogname());
}

void
pwkrb5_argv0_usage(const char *prefix)
{

        (void) fprintf(stderr, "%s %s [principal]\n",
            prefix, getprogname());
}

void
pwkrb5_process(const char *username, int argc, char **argv)
{
        krb5_context context;
        krb5_error_code ret;
        krb5_get_init_creds_opt *opt;
        krb5_principal principal;
        krb5_creds cred;
        int result_code;
        krb5_data result_code_string, result_string;
        char pwbuf[BUFSIZ];
        int ch;

        while ((ch = getopt(argc, argv, "5ku:")) != -1) {
                switch (ch) {
                case '5':
                        /*
                         * Compatibility option that historically
                         * specified to use Kerberos 5.  Silently
                         * ignore it.
                         */
                        break;

                case 'k':
                        /*
                         * Absorb the -k that may have gotten us here.
                         */
                        break;

                case 'u':
                        /*
                         * Historical option to specify principal.
                         */
                        username = optarg;
                        break;

                default:
                        usage();
                        /* NOTREACHED */
                }
        }

        argc -= optind;
        argv += optind;

        switch (argc) {
        case 0:
                /* username already provided */
                break;
        case 1:
                /* overrides -u <principal> */
                username = argv[0];
                break;
        default:
                usage();
                /* NOTREACHED */
        }

        ret = krb5_init_context(&context);
        if (ret != 0) {
                if (ret == ENXIO)
                        errx(1, "Kerberos 5 not in use.");
                errx(1, "Unable to initialize Kerberos 5: %s", strerror(ret));
        }

        ret = krb5_get_init_creds_opt_alloc(context, &opt);
        if (ret) {
                pwkrb5_warn("failed to allocate opts", context, ret);
                goto bad;
        }
    
        krb5_get_init_creds_opt_set_tkt_life(opt, 300L);
        krb5_get_init_creds_opt_set_forwardable(opt, FALSE);
        krb5_get_init_creds_opt_set_proxiable(opt, FALSE);

        ret = krb5_parse_name(context, username, &principal);
        if (ret) {
                krb5_get_init_creds_opt_free(context, opt);
                pwkrb5_warn("failed to parse principal", context, ret);
                goto bad;
        }

        ret = krb5_get_init_creds_password(context,
                                           &cred,
                                           principal,
                                           NULL,
                                           krb5_prompter_posix,
                                           NULL,
                                           0L,
                                           "kadmin/changepw",
                                           opt);

        krb5_get_init_creds_opt_free(context, opt);
        switch (ret) {
        case 0:
                break;

        case KRB5_LIBOS_PWDINTR :
                /* XXX */
                goto bad;

        case KRB5KRB_AP_ERR_BAD_INTEGRITY :
        case KRB5KRB_AP_ERR_MODIFIED :
                fprintf(stderr, "Password incorrect\n");
                goto bad;

        default:
                pwkrb5_warn("failed to get credentials", context, ret);
                goto bad;
        }

        krb5_data_zero(&result_code_string);
        krb5_data_zero(&result_string);

        /* XXX use getpass? It has a broken interface. */
        if (UI_UTIL_read_pw_string(pwbuf, sizeof(pwbuf),
                                   "New password: ", 1) != 0)
                goto bad;

        ret = krb5_set_password(context, &cred, pwbuf, NULL,
                                &result_code,
                                &result_code_string,
                                &result_string);
        if (ret) {
                pwkrb5_warn("unable to set password", context, ret);
                goto bad;
        }

        printf("%s%s%.*s\n",
            krb5_passwd_result_to_string(context, result_code),
            result_string.length > 0 ? " : " : "",
            (int)result_string.length,
            result_string.length > 0 ? (char *)result_string.data : "");

        krb5_data_free(&result_code_string);
        krb5_data_free(&result_string);

        krb5_free_cred_contents(context, &cred);
        krb5_free_context(context);
        if (result_code)
                exit(1);
        return;

 bad:
        krb5_free_context(context);
        exit(1);
}

#else /* ! USE_PAM */

static krb5_context defcontext;
static krb5_principal defprinc;
static int kusage = PW_USE;

int
krb5_init(const char *progname)
{
    return krb5_init_context(&defcontext);
}

int
krb5_arg (char ch, const char *opt)
{
    krb5_error_code ret;
    switch(ch) {
    case '5':
    case 'k':
        kusage = PW_USE_FORCE;
        return 1;
    case 'u':
        ret = krb5_parse_name(defcontext, opt, &defprinc);
        if(ret) {
            krb5_warn(defcontext, ret, "%s", opt);
            return 0;
        }
        return 1;
    }
    return 0;
}

int
krb5_arg_end(void)
{
    return kusage;
}

void
krb5_end(void)
{
    if (defcontext == NULL)
        return;
    if(defprinc)
        krb5_free_principal(defcontext, defprinc);
    krb5_free_context(defcontext);
}

int
krb5_chpw(const char *username)
{
    krb5_error_code ret;
    krb5_context context;
    krb5_principal principal;
    krb5_get_init_creds_opt *opt;
    krb5_creds cred;
    int result_code;
    krb5_data result_code_string, result_string;
    char pwbuf[BUFSIZ];

    ret = krb5_init_context (&context);
    if (ret) {
        pwkrb5_warn("failed kerberos initialisation", context, ret);
        return 1;
    }

    ret = krb5_get_init_creds_opt_alloc (context, &opt);
    if (ret) {
        pwkrb5_warn("failed to allocate credential opt", context, ret);
        return 1;
    }
    
    krb5_get_init_creds_opt_set_tkt_life (opt, 300);
    krb5_get_init_creds_opt_set_forwardable (opt, FALSE);
    krb5_get_init_creds_opt_set_proxiable (opt, FALSE);

    if(username != NULL) {
        ret = krb5_parse_name (context, username, &principal);
        if (ret) {
            krb5_get_init_creds_opt_free (context, opt);
            pwkrb5_warn("failed to parse principal", context, ret);
            return 1;
        }
    } else
        principal = defprinc;
    
    ret = krb5_get_init_creds_password (context,
                                        &cred,
                                        principal,
                                        NULL,
                                        krb5_prompter_posix,
                                        NULL,
                                        0,
                                        "kadmin/changepw",
                                        opt);

    krb5_get_init_creds_opt_free (context, opt);
    switch (ret) {
    case 0:
        break;
    case KRB5_LIBOS_PWDINTR :
        /* XXX */
        return 1;
    case KRB5KRB_AP_ERR_BAD_INTEGRITY :
    case KRB5KRB_AP_ERR_MODIFIED :
        fprintf(stderr, "Password incorrect\n");
        return 1;
        break;
    default:
        pwkrb5_warn("failed to get credentials", context, ret);
        return 1;
    }
    krb5_data_zero (&result_code_string);
    krb5_data_zero (&result_string);

    /* XXX use getpass? It has a broken interface. */
    if(UI_UTIL_read_pw_string(pwbuf, sizeof(pwbuf), "New password: ", 1) != 0)
        return 1;

    ret = krb5_set_password (context, &cred, pwbuf, NULL,
                             &result_code,
                             &result_code_string,
                             &result_string);
    if (ret)
        krb5_err (context, 1, ret, "krb5_set_password");

    printf ("%s%s%.*s\n", krb5_passwd_result_to_string(context, result_code),
            result_string.length > 0 ? " : " : "",
            (int)result_string.length,
            result_string.length > 0 ? (char *)result_string.data : "");

    krb5_data_free (&result_code_string);
    krb5_data_free (&result_string);
    
    krb5_free_cred_contents (context, &cred);
    krb5_free_context (context);
    return result_code;
}

#endif /* USE_PAM */