diff options
Diffstat (limited to 'src/kadmin/cli/kadmin.c')
| -rw-r--r-- | src/kadmin/cli/kadmin.c | 1988 | 
1 files changed, 1988 insertions, 0 deletions
| diff --git a/src/kadmin/cli/kadmin.c b/src/kadmin/cli/kadmin.c new file mode 100644 index 000000000000..c53c677a82d0 --- /dev/null +++ b/src/kadmin/cli/kadmin.c @@ -0,0 +1,1988 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright 1994, 2008 by the 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. + */ +/* + * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +/* Base functions for a kadmin command line interface using the OVSecure + * library */ + +/* for "_" macro */ +#include "k5-platform.h" +#include <krb5.h> +#include <kadm5/admin.h> +#include <adm_proto.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> +#include <math.h> +#include <unistd.h> +#include <pwd.h> +/* #include <sys/timeb.h> */ +#include <time.h> +#include "kadmin.h" + +static krb5_boolean script_mode = FALSE; +int exit_status = 0; +char *def_realm = NULL; +char *whoami = NULL; + +void *handle = NULL; +krb5_context context; +char *ccache_name = NULL; + +int locked = 0; + +static void +info(const char *fmt, ...) +#if !defined(__cplusplus) && (__GNUC__ > 2) +    __attribute__((__format__(__printf__, 1, 2))) +#endif +    ; + +static void +error(const char *fmt, ...) +#if !defined(__cplusplus) && (__GNUC__ > 2) +    __attribute__((__format__(__printf__, 1, 2))) +#endif +    ; + +/* Like printf, but suppressed if script_mode is set. */ +static void +info(const char *fmt, ...) +{ +    va_list ap; + +    if (script_mode) +        return; +    va_start(ap, fmt); +    vprintf(fmt, ap); +    va_end(ap); +} + +/* Like fprintf to stderr; also set exit_status if script_mode is set. */ +static void +error(const char *fmt, ...) +{ +    va_list ap; + +    if (script_mode) +        exit_status = 1; +    va_start(ap, fmt); +    vfprintf(stderr, fmt, ap); +    va_end(ap); +} + +static void +usage() +{ +    error(_("Usage: %s [-r realm] [-p principal] [-q query] " +            "[clnt|local args]\n" +            "              [command args...]\n" +            "\tclnt args: [-s admin_server[:port]] " +            "[[-c ccache]|[-k [-t keytab]]]|[-n]\n" +            "\tlocal args: [-x db_args]* [-d dbname] " +            "[-e \"enc:salt ...\"] [-m]" +            "where,\n\t[-x db_args]* - any number of database specific " +            "arguments.\n" +            "\t\t\tLook at each database documentation for supported " +            "arguments\n"), whoami); +    exit(1); +} + +static char * +strdur(time_t duration) +{ +    static char out[50]; +    int neg, days, hours, minutes, seconds; + +    if (duration < 0) { +        duration *= -1; +        neg = 1; +    } else +        neg = 0; +    days = duration / (24 * 3600); +    duration %= 24 * 3600; +    hours = duration / 3600; +    duration %= 3600; +    minutes = duration / 60; +    duration %= 60; +    seconds = duration; +    snprintf(out, sizeof(out), "%s%d %s %02d:%02d:%02d", neg ? "-" : "", +             days, days == 1 ? "day" : "days", +             hours, minutes, seconds); +    return out; +} + +static char * +strdate(krb5_timestamp when) +{ +    struct tm *tm; +    static char out[40]; + +    time_t lcltim = when; +    tm = localtime(&lcltim); +    strftime(out, sizeof(out), "%a %b %d %H:%M:%S %Z %Y", tm); +    return out; +} + +/* Parse a date string using getdate.y.  On failure, output an error message + * and return (time_t)-1. */ +static time_t +parse_date(char *str, time_t now) +{ +    time_t date; + +    date = get_date_rel(str, now); +    if (date == (time_t)-1) +        error(_("Invalid date specification \"%s\".\n"), str); +    return date; +} + +/* + * Parse a time interval.  Use krb5_string_to_deltat() if it works; otherwise + * use getdate.y and subtract now, with sanity checks.  On failure, output an + * error message and return (time_t)-1. + */ +static time_t +parse_interval(char *str, time_t now) +{ +    time_t date; +    krb5_deltat delta; + +    if (krb5_string_to_deltat(str, &delta) == 0) +        return delta; + +    date = parse_date(str, now); +    if (date == (time_t)-1) +        return date; + +    /* Interpret an absolute time of 0 (e.g. "never") as an interval of 0. */ +    if (date == 0) +        return 0; + +    /* Don't return a negative interval if the date is in the past. */ +    if (date < now) { +        error(_("Interval specification \"%s\" is in the past.\n"), str); +        return (time_t)-1; +    } + +    return date - now; +} + +/* this is a wrapper to go around krb5_parse_principal so we can set +   the default realm up properly */ +static krb5_error_code +kadmin_parse_name(char *name, krb5_principal *principal) +{ +    char *cp, *fullname; +    krb5_error_code retval; +    int result; + +    /* assumes def_realm is initialized! */ +    cp = strchr(name, '@'); +    while (cp) { +        if (cp - name && *(cp - 1) != '\\') +            break; +        else +            cp = strchr(cp + 1, '@'); +    } +    if (cp == NULL) +        result = asprintf(&fullname, "%s@%s", name, def_realm); +    else +        result = asprintf(&fullname, "%s", name); +    if (result < 0) +        return ENOMEM; +    retval = krb5_parse_name(context, fullname, principal); +    free(fullname); +    return retval; +} + +static void +extended_com_err_fn(const char *myprog, errcode_t code, +                    const char *fmt, va_list args) +{ +    const char *emsg; + +    if (code) { +        emsg = krb5_get_error_message(context, code); +        error("%s: %s ", myprog, emsg); +        krb5_free_error_message(context, emsg); +    } else { +        error("%s: ", myprog); +    } +    vfprintf(stderr, fmt, args); +    error("\n"); +} + +/* Create a principal using the oldest appropriate kadm5 API. */ +static krb5_error_code +create_princ(kadm5_principal_ent_rec *princ, long mask, int n_ks, +             krb5_key_salt_tuple *ks, char *pass) +{ +    if (ks) +        return kadm5_create_principal_3(handle, princ, mask, n_ks, ks, pass); +    else +        return kadm5_create_principal(handle, princ, mask, pass); +} + +/* Randomize a principal's password using the appropriate kadm5 API. */ +krb5_error_code +randkey_princ(void *lhandle, krb5_principal princ, krb5_boolean keepold, +              int n_ks, krb5_key_salt_tuple *ks, krb5_keyblock **key, +              int *n_keys) +{ +    krb5_error_code ret; + +    /* Try the newer API first, because the Solaris kadmind only creates DES +     * keys when the old API is used. */ +    ret = kadm5_randkey_principal_3(lhandle, princ, keepold, n_ks, ks, key, +                                    n_keys); + +    /* Fall back to the old version if we get an error and aren't using any new +     * parameters. */ +    if (ret == KADM5_RPC_ERROR && !keepold && ks == NULL) +        ret = kadm5_randkey_principal(lhandle, princ, key, n_keys); + +    return ret; +} + +static krb5_boolean +policy_exists(const char *name) +{ +    kadm5_policy_ent_rec pol; + +    if (kadm5_get_policy(handle, (char *)name, &pol) != 0) +        return FALSE; +    kadm5_free_policy_ent(handle, &pol); +    return TRUE; +} + +void +kadmin_startup(int argc, char *argv[], char **request_out, char ***args_out) +{ +    extern char *optarg; +    char *princstr = NULL, *keytab_name = NULL, *query = NULL; +    char *password = NULL; +    char *luser, *canon, *cp; +    int optchar, freeprinc = 0, use_keytab = 0, use_anonymous = 0; +    struct passwd *pw; +    kadm5_ret_t retval; +    krb5_ccache cc; +    krb5_principal princ; +    kadm5_config_params params; +    char **db_args = NULL; +    int db_args_size = 0; +    char *db_name = NULL; +    char *svcname, *realm; + +    memset(¶ms, 0, sizeof(params)); + +    set_com_err_hook(extended_com_err_fn); + +    retval = kadm5_init_krb5_context(&context); +    if (retval) { +        com_err(whoami, retval, _("while initializing krb5 library")); +        exit(1); +    } + +    while ((optchar = getopt(argc, argv, +                             "+x:r:p:knq:w:d:s:mc:t:e:ON")) != EOF) { +        switch (optchar) { +        case 'x': +            db_args_size++; +            db_args = realloc(db_args, sizeof(char*) * (db_args_size + 1)); +            if (db_args == NULL) { +                error(_("%s: Cannot initialize. Not enough memory\n"), whoami); +                exit(1); +            } +            db_args[db_args_size - 1] = optarg; +            db_args[db_args_size] = NULL; +            break; + +        case 'r': +            def_realm = optarg; +            break; +        case 'p': +            princstr = optarg; +            break; +        case 'c': +            ccache_name = optarg; +            break; +        case 'k': +            use_keytab++; +            break; +        case 'n': +            use_anonymous++; +            break; +        case 't': +            keytab_name = optarg; +            break; +        case 'w': +            password = optarg; +            break; +        case 'q': +            query = optarg; +            break; +        case 'd': +            /* db_name has to be passed as part of the db_args. */ +            free(db_name); +            asprintf(&db_name, "dbname=%s", optarg); + +            db_args_size++; +            db_args = realloc(db_args, sizeof(char*) * (db_args_size + 1)); +            if (db_args == NULL) { +                error(_("%s: Cannot initialize. Not enough memory\n"), whoami); +                exit(1); +            } +            db_args[db_args_size - 1] = db_name; +            db_args[db_args_size] = NULL; +            break; +        case 's': +            params.admin_server = optarg; +            params.mask |= KADM5_CONFIG_ADMIN_SERVER; +            break; +        case 'm': +            params.mkey_from_kbd = 1; +            params.mask |= KADM5_CONFIG_MKEY_FROM_KBD; +            break; +        case 'e': +            retval = krb5_string_to_keysalts(optarg, NULL, NULL, 0, +                                             ¶ms.keysalts, +                                             ¶ms.num_keysalts); +            if (retval) { +                com_err(whoami, retval, _("while parsing keysalts %s"), +                        optarg); +                exit(1); +            } +            params.mask |= KADM5_CONFIG_ENCTYPES; +            break; +        case 'O': +            params.mask |= KADM5_CONFIG_OLD_AUTH_GSSAPI; +            break; +        case 'N': +            params.mask |= KADM5_CONFIG_AUTH_NOFALLBACK; +            break; +        default: +            usage(); +        } +    } +    if ((ccache_name && use_keytab) || +        (keytab_name && !use_keytab) || +        (ccache_name && use_anonymous) || +        (use_anonymous && use_keytab)) +        usage(); + +    if (query != NULL && argv[optind] != NULL) { +        error(_("%s: -q is exclusive with command-line query"), whoami); +        usage(); +    } + +    if (argv[optind] != NULL) +        script_mode = TRUE; + +    if (def_realm == NULL && krb5_get_default_realm(context, &def_realm)) { +        error(_("%s: unable to get default realm\n"), whoami); +        exit(1); +    } + +    params.mask |= KADM5_CONFIG_REALM; +    params.realm = def_realm; + +    if (params.mask & KADM5_CONFIG_OLD_AUTH_GSSAPI) +        svcname = KADM5_ADMIN_SERVICE; +    else +        svcname = NULL; + +    /* +     * Set cc to an open credentials cache, either specified by the -c +     * argument or the default. +     */ +    if (ccache_name == NULL) { +        retval = krb5_cc_default(context, &cc); +        if (retval) { +            com_err(whoami, retval, +                    _("while opening default credentials cache")); +            exit(1); +        } +    } else { +        retval = krb5_cc_resolve(context, ccache_name, &cc); +        if (retval) { +            com_err(whoami, retval, _("while opening credentials cache %s"), +                    ccache_name); +            exit(1); +        } +    } + +    /* +     * If no principal name is specified: If authenticating anonymously, use +     * the anonymouse principal for the local realm, else if a ccache was +     * specified and its primary principal name can be read, it is used, else +     * if a keytab was specified, the principal name is host/hostname, +     * otherwise append "/admin" to the primary name of the default ccache, +     * $USER, or pw_name. +     * +     * Gee, 100+ lines to figure out the client principal name.  This +     * should be compressed... +     */ + +    if (princstr == NULL) { +        if (use_anonymous) { +            if (asprintf(&princstr, "%s/%s@%s", KRB5_WELLKNOWN_NAMESTR, +                         KRB5_ANONYMOUS_PRINCSTR, def_realm) < 0) { +                error(_("%s: out of memory\n"), whoami); +                exit(1); +            } +            freeprinc++; +        } else if (ccache_name != NULL && +            !krb5_cc_get_principal(context, cc, &princ)) { +            retval = krb5_unparse_name(context, princ, &princstr); +            if (retval) { +                com_err(whoami, retval, +                        _("while canonicalizing principal name")); +                exit(1); +            } +            krb5_free_principal(context, princ); +            freeprinc++; +        } else if (use_keytab != 0) { +            retval = krb5_sname_to_principal(context, NULL, "host", +                                             KRB5_NT_SRV_HST, &princ); +            if (retval) { +                com_err(whoami, retval, _("creating host service principal")); +                exit(1); +            } +            retval = krb5_unparse_name(context, princ, &princstr); +            if (retval) { +                com_err(whoami, retval, +                        _("while canonicalizing principal name")); +                exit(1); +            } +            krb5_free_principal(context, princ); +            freeprinc++; +        } else if (!krb5_cc_get_principal(context, cc, &princ)) { +            if (krb5_unparse_name(context, princ, &canon)) { +                error(_("%s: unable to canonicalize principal\n"), whoami); +                exit(1); +            } +            /* Strip out realm of principal if it's there. */ +            realm = strchr(canon, '@'); +            while (realm) { +                if (realm > canon && *(realm - 1) != '\\') +                    break; +                realm = strchr(realm + 1, '@'); +            } +            if (realm) +                *realm++ = '\0'; +            cp = strchr(canon, '/'); +            while (cp) { +                if (cp > canon && *(cp - 1) != '\\') +                    break; +                cp = strchr(cp + 1, '/'); +            } +            if (cp != NULL) +                *cp = '\0'; +            if (asprintf(&princstr, "%s/admin%s%s", canon, +                         (realm) ? "@" : "", +                         (realm) ? realm : "") < 0) { +                error(_("%s: out of memory\n"), whoami); +                exit(1); +            } +            free(canon); +            krb5_free_principal(context, princ); +            freeprinc++; +        } else if ((luser = getenv("USER"))) { +            if (asprintf(&princstr, "%s/admin@%s", luser, def_realm) < 0) { +                error(_("%s: out of memory\n"), whoami); +                exit(1); +            } +            freeprinc++; +        } else if ((pw = getpwuid(getuid()))) { +            if (asprintf(&princstr, "%s/admin@%s", pw->pw_name, +                         def_realm) < 0) { +                error(_("%s: out of memory\n"), whoami); +                exit(1); +            } +            freeprinc++; +        } else { +            error(_("%s: unable to figure out a principal name\n"), whoami); +            exit(1); +        } +    } + +    retval = krb5_klog_init(context, "admin_server", whoami, 0); +    if (retval) { +        com_err(whoami, retval, _("while setting up logging")); +        exit(1); +    } + +    /* +     * Initialize the kadm5 connection.  If we were given a ccache, +     * use it.  Otherwise, use/prompt for the password. +     */ +    if (ccache_name) { +        info(_("Authenticating as principal %s with existing " +               "credentials.\n"), princstr); +        retval = kadm5_init_with_creds(context, princstr, cc, svcname, ¶ms, +                                       KADM5_STRUCT_VERSION, +                                       KADM5_API_VERSION_4, db_args, &handle); +    } else if (use_anonymous) { +        info(_("Authenticating as principal %s with password; " +               "anonymous requested.\n"), princstr); +        retval = kadm5_init_anonymous(context, princstr, svcname, ¶ms, +                                      KADM5_STRUCT_VERSION, +                                      KADM5_API_VERSION_4, db_args, &handle); +    } else if (use_keytab) { +        if (keytab_name != NULL) { +            info(_("Authenticating as principal %s with keytab %s.\n"), +                 princstr, keytab_name); +        } else { +            info(_("Authenticating as principal %s with default keytab.\n"), +                 princstr); +        } +        retval = kadm5_init_with_skey(context, princstr, keytab_name, svcname, +                                      ¶ms, KADM5_STRUCT_VERSION, +                                      KADM5_API_VERSION_4, db_args, &handle); +    } else { +        info(_("Authenticating as principal %s with password.\n"), +             princstr); +        retval = kadm5_init_with_password(context, princstr, password, svcname, +                                          ¶ms, KADM5_STRUCT_VERSION, +                                          KADM5_API_VERSION_4, db_args, +                                          &handle); +    } +    if (retval) { +        com_err(whoami, retval, _("while initializing %s interface"), whoami); +        if (retval == KADM5_BAD_CLIENT_PARAMS || +            retval == KADM5_BAD_SERVER_PARAMS) +            usage(); +        exit(1); +    } +    if (freeprinc) +        free(princstr); + +    free(params.keysalts); +    free(db_name); +    free(db_args); + +    retval = krb5_cc_close(context, cc); +    if (retval) { +        com_err(whoami, retval, _("while closing ccache %s"), ccache_name); +        exit(1); +    } + +    retval = kadm5_init_iprop(handle, 0); +    if (retval) { +        com_err(whoami, retval, _("while mapping update log")); +        exit(1); +    } + +    *request_out = query; +    *args_out = argv + optind; +} + +int +quit() +{ +    kadm5_ret_t retval; + +    if (locked) { +        retval = kadm5_unlock(handle); +        if (retval) { +            com_err("quit", retval, _("while unlocking locked database")); +            return 1; +        } +        locked = 0; +    } + +    kadm5_destroy(handle); +    if (ccache_name != NULL && !script_mode) { +        fprintf(stderr, "\n\a\a\a%s", +                _("Administration credentials NOT DESTROYED.\n")); +    } + +    /* insert more random cleanup here */ +    krb5_klog_close(context); +    krb5_free_context(context); +    return 0; +} + +void +kadmin_lock(int argc, char *argv[]) +{ +    kadm5_ret_t retval; + +    if (locked) +        return; +    retval = kadm5_lock(handle); +    if (retval) { +        com_err("lock", retval, ""); +        return; +    } +    locked = 1; +} + +void +kadmin_unlock(int argc, char *argv[]) +{ +    kadm5_ret_t retval; + +    if (!locked) +        return; +    retval = kadm5_unlock(handle); +    if (retval) { +        com_err("unlock", retval, ""); +        return; +    } +    locked = 0; +} + +void +kadmin_delprinc(int argc, char *argv[]) +{ +    kadm5_ret_t retval; +    krb5_principal princ = NULL; +    char *canon = NULL; +    char reply[5]; + +    if (! (argc == 2 || +           (argc == 3 && !strcmp("-force", argv[1])))) { +        error(_("usage: delete_principal [-force] principal\n")); +        return; +    } +    retval = kadmin_parse_name(argv[argc - 1], &princ); +    if (retval) { +        com_err("delete_principal", retval, _("while parsing principal name")); +        return; +    } +    retval = krb5_unparse_name(context, princ, &canon); +    if (retval) { +        com_err("delete_principal", retval, +                _("while canonicalizing principal")); +        goto cleanup; +    } +    if (argc == 2 && !script_mode) { +        printf(_("Are you sure you want to delete the principal \"%s\"? " +                 "(yes/no): "), canon); +        fgets(reply, sizeof (reply), stdin); +        if (strcmp("yes\n", reply)) { +            fprintf(stderr, _("Principal \"%s\" not deleted\n"), canon); +            goto cleanup; +        } +    } +    retval = kadm5_delete_principal(handle, princ); +    if (retval) { +        com_err("delete_principal", retval, +                _("while deleting principal \"%s\""), canon); +        goto cleanup; +    } +    info(_("Principal \"%s\" deleted.\n"), canon); +    info(_("Make sure that you have removed this principal from all ACLs " +           "before reusing.\n")); + +cleanup: +    krb5_free_principal(context, princ); +    free(canon); +} + +void +kadmin_renameprinc(int argc, char *argv[]) +{ +    kadm5_ret_t retval; +    krb5_principal oprinc = NULL, nprinc = NULL; +    char *ocanon = NULL, *ncanon = NULL; +    char reply[5]; + +    if (!(argc == 3 || (argc == 4 && !strcmp("-force", argv[1])))) { +        error(_("usage: rename_principal [-force] old_principal " +                "new_principal\n")); +        return; +    } +    retval = kadmin_parse_name(argv[argc - 2], &oprinc); +    if (retval) { +        com_err("rename_principal", retval, +                _("while parsing old principal name")); +        goto cleanup; +    } +    retval = kadmin_parse_name(argv[argc - 1], &nprinc); +    if (retval) { +        com_err("rename_principal", retval, +                _("while parsing new principal name")); +        goto cleanup; +    } +    retval = krb5_unparse_name(context, oprinc, &ocanon); +    if (retval) { +        com_err("rename_principal", retval, +                _("while canonicalizing old principal")); +        goto cleanup; +    } +    retval = krb5_unparse_name(context, nprinc, &ncanon); +    if (retval) { +        com_err("rename_principal", retval, +                _("while canonicalizing new principal")); +        goto cleanup; +    } +    if (argc == 3 && !script_mode) { +        printf(_("Are you sure you want to rename the principal \"%s\" " +                 "to \"%s\"? (yes/no): "), ocanon, ncanon); +        fgets(reply, sizeof(reply), stdin); +        if (strcmp("yes\n", reply)) { +            fprintf(stderr, _("Principal \"%s\" not renamed\n"), ocanon); +            goto cleanup; +        } +    } +    retval = kadm5_rename_principal(handle, oprinc, nprinc); +    if (retval) { +        com_err("rename_principal", retval, +                _("while renaming principal \"%s\" to \"%s\""), +                ocanon, ncanon); +        goto cleanup; +    } +    info(_("Principal \"%s\" renamed to \"%s\".\n"), ocanon, ncanon); +    info(_("Make sure that you have removed the old principal from all ACLs " +           "before reusing.\n")); + +cleanup: +    krb5_free_principal(context, nprinc); +    krb5_free_principal(context, oprinc); +    free(ncanon); +    free(ocanon); +} + +static void +cpw_usage(const char *str) +{ +    if (str) +        error("%s\n", str); +    error(_("usage: change_password [-randkey] [-keepold] " +            "[-e keysaltlist] [-pw password] principal\n")); +} + +void +kadmin_cpw(int argc, char *argv[]) +{ +    kadm5_ret_t retval; +    static char newpw[1024]; +    static char prompt1[1024], prompt2[1024]; +    char *canon = NULL, *pwarg = NULL; +    int n_ks_tuple = 0, randkey = 0; +    krb5_boolean keepold = FALSE; +    krb5_key_salt_tuple *ks_tuple = NULL; +    krb5_principal princ = NULL; +    char **db_args = NULL; +    int db_args_size = 0; + +    if (argc < 2) { +        cpw_usage(NULL); +        return; +    } +    for (argv++, argc--; argc > 1; argc--, argv++) { +        if (!strcmp("-x", *argv)) { +            argc--; +            if (argc < 1) { +                cpw_usage(_("change_password: missing db argument")); +                goto cleanup; +            } +            db_args_size++; +            db_args = realloc(db_args, sizeof(char*) * (db_args_size + 1)); +            if (db_args == NULL) { +                error(_("change_password: Not enough memory\n")); +                exit(1); +            } +            db_args[db_args_size - 1] = *++argv; +            db_args[db_args_size] = NULL; +        } else if (!strcmp("-pw", *argv)) { +            argc--; +            if (argc < 1) { +                cpw_usage(_("change_password: missing password arg")); +                goto cleanup; +            } +            pwarg = *++argv; +        } else if (!strcmp("-randkey", *argv)) { +            randkey++; +        } else if (!strcmp("-keepold", *argv)) { +            keepold = TRUE; +        } else if (!strcmp("-e", *argv)) { +            argc--; +            if (argc < 1) { +                cpw_usage(_("change_password: missing keysaltlist arg")); +                goto cleanup; +            } +            retval = krb5_string_to_keysalts(*++argv, NULL, NULL, 0, +                                             &ks_tuple, &n_ks_tuple); +            if (retval) { +                com_err("change_password", retval, +                        _("while parsing keysalts %s"), *argv); +                goto cleanup; +            } +        } else { +            cpw_usage(NULL); +            goto cleanup; +        } +    } +    if (*argv == NULL) { +        com_err("change_password", 0, _("missing principal name")); +        cpw_usage(NULL); +        goto cleanup; +    } +    retval = kadmin_parse_name(*argv, &princ); +    if (retval) { +        com_err("change_password", retval, _("while parsing principal name")); +        goto cleanup; +    } +    retval = krb5_unparse_name(context, princ, &canon); +    if (retval) { +        com_err("change_password", retval, +                _("while canonicalizing principal")); +        goto cleanup; +    } +    if (pwarg != NULL) { +        if (keepold || ks_tuple != NULL) { +            retval = kadm5_chpass_principal_3(handle, princ, keepold, +                                              n_ks_tuple, ks_tuple, pwarg); +        } else { +            retval = kadm5_chpass_principal(handle, princ, pwarg); +        } +        if (retval) { +            com_err("change_password", retval, +                    _("while changing password for \"%s\"."), canon); +            goto cleanup; +        } +        info(_("Password for \"%s\" changed.\n"), canon); +    } else if (randkey) { +        retval = randkey_princ(handle, princ, keepold, n_ks_tuple, ks_tuple, +                               NULL, NULL); +        if (retval) { +            com_err("change_password", retval, +                    _("while randomizing key for \"%s\"."), canon); +            goto cleanup; +        } +        info(_("Key for \"%s\" randomized.\n"), canon); +    } else { +        unsigned int i = sizeof (newpw) - 1; + +        snprintf(prompt1, sizeof(prompt1), +                 _("Enter password for principal \"%s\""), canon); +        snprintf(prompt2, sizeof(prompt2), +                 _("Re-enter password for principal \"%s\""), canon); +        retval = krb5_read_password(context, prompt1, prompt2, +                                    newpw, &i); +        if (retval) { +            com_err("change_password", retval, +                    _("while reading password for \"%s\"."), canon); +            goto cleanup; +        } +        if (keepold || ks_tuple != NULL) { +            retval = kadm5_chpass_principal_3(handle, princ, keepold, +                                              n_ks_tuple, ks_tuple, +                                              newpw); +        } else { +            retval = kadm5_chpass_principal(handle, princ, newpw); +        } +        memset(newpw, 0, sizeof (newpw)); +        if (retval) { +            com_err("change_password", retval, +                    _("while changing password for \"%s\"."), canon); +            goto cleanup; +        } +        info(_("Password for \"%s\" changed.\n"), canon); +    } +cleanup: +    free(canon); +    free(db_args); +    krb5_free_principal(context, princ); +    free(ks_tuple); +} + +static void +kadmin_free_tl_data(krb5_int16 *n_tl_datap, krb5_tl_data **tl_datap) +{ +    krb5_tl_data *tl_data = *tl_datap, *next; +    int n_tl_data = *n_tl_datap; +    int i; + +    *n_tl_datap = 0; +    *tl_datap = NULL; + +    for (i = 0; tl_data && (i < n_tl_data); i++) { +        next = tl_data->tl_data_next; +        free(tl_data->tl_data_contents); +        free(tl_data); +        tl_data = next; +    } +} + +/* Construct a tl_data element and add it to the tail of *tl_datap. */ +static void +add_tl_data(krb5_int16 *n_tl_datap, krb5_tl_data **tl_datap, +            krb5_int16 tl_type, krb5_ui_2 len, krb5_octet *contents) +{ +    krb5_tl_data *tl_data; +    krb5_octet *copy; + +    copy = malloc(len); +    tl_data = calloc(1, sizeof(*tl_data)); +    if (copy == NULL || tl_data == NULL) { +        error(_("Not enough memory\n")); +        exit(1); +    } +    memcpy(copy, contents, len); + +    tl_data->tl_data_type = tl_type; +    tl_data->tl_data_length = len; +    tl_data->tl_data_contents = copy; +    tl_data->tl_data_next = NULL; + +    for (; *tl_datap != NULL; tl_datap = &(*tl_datap)->tl_data_next); +    *tl_datap = tl_data; +    (*n_tl_datap)++; +} + +static void +unlock_princ(kadm5_principal_ent_t princ, long *mask, const char *caller) +{ +    krb5_error_code retval; +    krb5_timestamp now; +    krb5_octet timebuf[4]; + +    /* Zero out the failed auth count. */ +    princ->fail_auth_count = 0; +    *mask |= KADM5_FAIL_AUTH_COUNT; + +    /* Record the timestamp of this unlock operation so that slave KDCs will +     * see it, since fail_auth_count is unreplicated. */ +    retval = krb5_timeofday(context, &now); +    if (retval) { +        com_err(caller, retval, _("while getting time")); +        exit(1); +    } +    store_32_le((krb5_int32)now, timebuf); +    add_tl_data(&princ->n_tl_data, &princ->tl_data, +                KRB5_TL_LAST_ADMIN_UNLOCK, 4, timebuf); +    *mask |= KADM5_TL_DATA; +} + +/* + * Parse addprinc or modprinc arguments.  Some output fields may be + * filled in on error. + */ +static int +kadmin_parse_princ_args(int argc, char *argv[], kadm5_principal_ent_t oprinc, +                        long *mask, char **pass, krb5_boolean *randkey, +                        krb5_boolean *nokey, krb5_key_salt_tuple **ks_tuple, +                        int *n_ks_tuple, char *caller) +{ +    int i; +    time_t now, date, interval; +    krb5_error_code retval; + +    *mask = 0; +    *pass = NULL; +    *n_ks_tuple = 0; +    *ks_tuple = NULL; +    time(&now); +    *randkey = FALSE; +    *nokey = FALSE; +    for (i = 1; i < argc - 1; i++) { +        if (!strcmp("-x",argv[i])) { +            if (++i > argc - 2) +                return -1; + +            add_tl_data(&oprinc->n_tl_data, &oprinc->tl_data, +                        KRB5_TL_DB_ARGS, strlen(argv[i]) + 1, +                        (krb5_octet *)argv[i]); +            *mask |= KADM5_TL_DATA; +            continue; +        } +        if (!strcmp("-expire", argv[i])) { +            if (++i > argc - 2) +                return -1; +            date = parse_date(argv[i], now); +            if (date == (time_t)-1) +                return -1; +            oprinc->princ_expire_time = date; +            *mask |= KADM5_PRINC_EXPIRE_TIME; +            continue; +        } +        if (!strcmp("-pwexpire", argv[i])) { +            if (++i > argc - 2) +                return -1; +            date = parse_date(argv[i], now); +            if (date == (time_t)-1) +                return -1; +            oprinc->pw_expiration = date; +            *mask |= KADM5_PW_EXPIRATION; +            continue; +        } +        if (!strcmp("-maxlife", argv[i])) { +            if (++i > argc - 2) +                return -1; +            interval = parse_interval(argv[i], now); +            if (interval == (time_t)-1) +                return -1; +            oprinc->max_life = interval; +            *mask |= KADM5_MAX_LIFE; +            continue; +        } +        if (!strcmp("-maxrenewlife", argv[i])) { +            if (++i > argc - 2) +                return -1; +            interval = parse_interval(argv[i], now); +            if (interval == (time_t)-1) +                return -1; +            oprinc->max_renewable_life = interval; +            *mask |= KADM5_MAX_RLIFE; +            continue; +        } +        if (!strcmp("-kvno", argv[i])) { +            if (++i > argc - 2) +                return -1; +            oprinc->kvno = atoi(argv[i]); +            *mask |= KADM5_KVNO; +            continue; +        } +        if (!strcmp("-policy", argv[i])) { +            if (++i > argc - 2) +                return -1; +            oprinc->policy = argv[i]; +            *mask |= KADM5_POLICY; +            continue; +        } +        if (!strcmp("-clearpolicy", argv[i])) { +            oprinc->policy = NULL; +            *mask |= KADM5_POLICY_CLR; +            continue; +        } +        if (!strcmp("-pw", argv[i])) { +            if (++i > argc - 2) +                return -1; +            *pass = argv[i]; +            continue; +        } +        if (!strcmp("-randkey", argv[i])) { +            *randkey = TRUE; +            continue; +        } +        if (!strcmp("-nokey", argv[i])) { +            *nokey = TRUE; +            continue; +        } +        if (!strcmp("-unlock", argv[i])) { +            unlock_princ(oprinc, mask, caller); +            continue; +        } +        if (!strcmp("-e", argv[i])) { +            if (++i > argc - 2) +                return -1; +            retval = krb5_string_to_keysalts(argv[i], NULL, NULL, 0, +                                             ks_tuple, n_ks_tuple); +            if (retval) { +                com_err(caller, retval, _("while parsing keysalts %s"), +                        argv[i]); +                return -1; +            } +            continue; +        } +        retval = krb5_flagspec_to_mask(argv[i], &oprinc->attributes, +                                       &oprinc->attributes); +        if (retval) +            return -1; +        else +            *mask |= KADM5_ATTRIBUTES; +    } +    if (i != argc - 1) +        return -1; +    retval = kadmin_parse_name(argv[i], &oprinc->principal); +    if (retval) { +        com_err(caller, retval, _("while parsing principal")); +        return -1; +    } +    return 0; +} + +static void +kadmin_addprinc_usage() +{ +    error(_("usage: add_principal [options] principal\n")); +    error(_("\toptions are:\n")); +    error(_("\t\t[-randkey|-nokey] [-x db_princ_args]* [-expire expdate] " +            "[-pwexpire pwexpdate] [-maxlife maxtixlife]\n" +            "\t\t[-kvno kvno] [-policy policy] [-clearpolicy]\n" +            "\t\t[-pw password] [-maxrenewlife maxrenewlife]\n" +            "\t\t[-e keysaltlist]\n\t\t[{+|-}attribute]\n")); +    error(_("\tattributes are:\n")); +    error(_("\t\tallow_postdated allow_forwardable allow_tgs_req " +            "allow_renewable\n" +            "\t\tallow_proxiable allow_dup_skey allow_tix requires_preauth\n" +            "\t\trequires_hwauth needchange allow_svr " +            "password_changing_service\n" +            "\t\tok_as_delegate ok_to_auth_as_delegate no_auth_data_required\n" +            "\t\tlockdown_keys\n" +            "\nwhere,\n\t[-x db_princ_args]* - any number of database " +            "specific arguments.\n" +            "\t\t\tLook at each database documentation for supported " +            "arguments\n")); +} + +static void +kadmin_modprinc_usage() +{ +    error(_("usage: modify_principal [options] principal\n")); +    error(_("\toptions are:\n")); +    error(_("\t\t[-x db_princ_args]* [-expire expdate] " +            "[-pwexpire pwexpdate] [-maxlife maxtixlife]\n" +            "\t\t[-kvno kvno] [-policy policy] [-clearpolicy]\n" +            "\t\t[-maxrenewlife maxrenewlife] [-unlock] [{+|-}attribute]\n")); +    error(_("\tattributes are:\n")); +    error(_("\t\tallow_postdated allow_forwardable allow_tgs_req " +            "allow_renewable\n" +            "\t\tallow_proxiable allow_dup_skey allow_tix requires_preauth\n" +            "\t\trequires_hwauth needchange allow_svr " +            "password_changing_service\n" +            "\t\tok_as_delegate ok_to_auth_as_delegate no_auth_data_required\n" +            "\t\tlockdown_keys\n" +            "\nwhere,\n\t[-x db_princ_args]* - any number of database " +            "specific arguments.\n" +            "\t\t\tLook at each database documentation for supported " +            "arguments\n")); +} + +/* Create a dummy password for old-style (pre-1.8) randkey creation. */ +static void +prepare_dummy_password(char *buf, size_t sz) +{ +    size_t i; + +    /* Must try to pass any password policy in place, and be valid UTF-8. */ +    strlcpy(buf, "6F a[", sz); +    for (i = strlen(buf); i < sz - 1; i++) +        buf[i] = 'a' + (i % 26); +    buf[sz - 1] = '\0'; +} + +void +kadmin_addprinc(int argc, char *argv[]) +{ +    kadm5_principal_ent_rec princ; +    long mask; +    krb5_boolean randkey = FALSE, nokey = FALSE, old_style_randkey = FALSE; +    int n_ks_tuple; +    krb5_key_salt_tuple *ks_tuple = NULL; +    char *pass, *canon = NULL; +    krb5_error_code retval; +    char newpw[1024], dummybuf[256]; +    static char prompt1[1024], prompt2[1024]; + +    /* Zero all fields in request structure */ +    memset(&princ, 0, sizeof(princ)); + +    princ.attributes = 0; +    if (kadmin_parse_princ_args(argc, argv, &princ, &mask, &pass, &randkey, +                                &nokey, &ks_tuple, &n_ks_tuple, +                                "add_principal")) { +        kadmin_addprinc_usage(); +        goto cleanup; +    } + +    retval = krb5_unparse_name(context, princ.principal, &canon); +    if (retval) { +        com_err("add_principal", retval, _("while canonicalizing principal")); +        goto cleanup; +    } + +    if (mask & KADM5_POLICY) { +        /* Warn if the specified policy does not exist. */ +        if (!script_mode && !policy_exists(princ.policy)) { +            fprintf(stderr, _("WARNING: policy \"%s\" does not exist\n"), +                    princ.policy); +        } +    } else if (!(mask & KADM5_POLICY_CLR)) { +        /* If the policy "default" exists, assign it. */ +        if (policy_exists("default")) { +            if (!script_mode) { +                fprintf(stderr, _("NOTICE: no policy specified for %s; " +                                  "assigning \"default\"\n"), canon); +            } +            princ.policy = "default"; +            mask |= KADM5_POLICY; +        } else if (!script_mode) { +            fprintf(stderr, _("WARNING: no policy specified for %s; " +                              "defaulting to no policy\n"), canon); +        } +    } +    /* Don't send KADM5_POLICY_CLR to the server. */ +    mask &= ~KADM5_POLICY_CLR; + +    if (nokey) { +        pass = NULL; +        mask |= KADM5_KEY_DATA; +    } else if (randkey) { +        pass = NULL; +    } else if (pass == NULL) { +        unsigned int sz = sizeof(newpw) - 1; + +        snprintf(prompt1, sizeof(prompt1), +                 _("Enter password for principal \"%s\""), canon); +        snprintf(prompt2, sizeof(prompt2), +                 _("Re-enter password for principal \"%s\""), canon); +        retval = krb5_read_password(context, prompt1, prompt2, newpw, &sz); +        if (retval) { +            com_err("add_principal", retval, +                    _("while reading password for \"%s\"."), canon); +            goto cleanup; +        } +        pass = newpw; +    } +    mask |= KADM5_PRINCIPAL; +    retval = create_princ(&princ, mask, n_ks_tuple, ks_tuple, pass); +    if (retval == EINVAL && randkey) { +        /* +         * The server doesn't support randkey creation.  Create the principal +         * with a dummy password and disallow tickets. +         */ +        prepare_dummy_password(dummybuf, sizeof(dummybuf)); +        princ.attributes |= KRB5_KDB_DISALLOW_ALL_TIX; +        mask |= KADM5_ATTRIBUTES; +        pass = dummybuf; +        retval = create_princ(&princ, mask, n_ks_tuple, ks_tuple, pass); +        old_style_randkey = 1; +    } +    if (retval == KADM5_BAD_MASK && nokey) { +        error(_("Admin server does not support -nokey while creating " +                "\"%s\"\n"), canon); +        goto cleanup; +    } +    if (retval) { +        com_err("add_principal", retval, "while creating \"%s\".", canon); +        goto cleanup; +    } +    if (old_style_randkey) { +        /* Randomize the password and re-enable tickets. */ +        retval = randkey_princ(handle, princ.principal, FALSE, n_ks_tuple, +                               ks_tuple, NULL, NULL); +        if (retval) { +            com_err("add_principal", retval, +                    _("while randomizing key for \"%s\"."), canon); +            goto cleanup; +        } +        princ.attributes &= ~KRB5_KDB_DISALLOW_ALL_TIX; /* clear notix */ +        mask = KADM5_ATTRIBUTES; +        retval = kadm5_modify_principal(handle, &princ, mask); +        if (retval) { +            com_err("add_principal", retval, +                    _("while clearing DISALLOW_ALL_TIX for \"%s\"."), canon); +            goto cleanup; +        } +    } +    info("Principal \"%s\" created.\n", canon); + +cleanup: +    krb5_free_principal(context, princ.principal); +    free(ks_tuple); +    free(canon); +    kadmin_free_tl_data(&princ.n_tl_data, &princ.tl_data); +} + +void +kadmin_modprinc(int argc, char *argv[]) +{ +    kadm5_principal_ent_rec princ, oldprinc; +    krb5_principal kprinc = NULL; +    long mask; +    krb5_error_code retval; +    char *pass, *canon = NULL; +    krb5_boolean randkey = FALSE, nokey = FALSE; +    int n_ks_tuple = 0; +    krb5_key_salt_tuple *ks_tuple = NULL; + +    if (argc < 2) { +        kadmin_modprinc_usage(); +        return; +    } + +    memset(&oldprinc, 0, sizeof(oldprinc)); +    memset(&princ, 0, sizeof(princ)); + +    retval = kadmin_parse_name(argv[argc - 1], &kprinc); +    if (retval) { +        com_err("modify_principal", retval, _("while parsing principal")); +        return; +    } +    retval = krb5_unparse_name(context, kprinc, &canon); +    if (retval) { +        com_err("modify_principal", retval, +                _("while canonicalizing principal")); +        goto cleanup; +    } +    retval = kadm5_get_principal(handle, kprinc, &oldprinc, +                                 KADM5_PRINCIPAL_NORMAL_MASK); +    if (retval) { +        com_err("modify_principal", retval, _("while getting \"%s\"."), canon); +        goto cleanup; +    } +    princ.attributes = oldprinc.attributes; +    kadm5_free_principal_ent(handle, &oldprinc); +    retval = kadmin_parse_princ_args(argc, argv, +                                     &princ, &mask, +                                     &pass, &randkey, &nokey, +                                     &ks_tuple, &n_ks_tuple, +                                     "modify_principal"); +    if (retval || ks_tuple != NULL || randkey || nokey || pass) { +        kadmin_modprinc_usage(); +        goto cleanup; +    } +    if (mask & KADM5_POLICY) { +        /* Warn if the specified policy does not exist. */ +        if (!script_mode && !policy_exists(princ.policy)) { +            fprintf(stderr, _("WARNING: policy \"%s\" does not exist\n"), +                    princ.policy); +        } +    } +    if (mask) { +        /* Skip this if all we're doing is setting certhash. */ +        retval = kadm5_modify_principal(handle, &princ, mask); +    } +    if (retval) { +        com_err("modify_principal", retval, _("while modifying \"%s\"."), +                canon); +        goto cleanup; +    } +    info(_("Principal \"%s\" modified.\n"), canon); +cleanup: +    krb5_free_principal(context, kprinc); +    krb5_free_principal(context, princ.principal); +    kadmin_free_tl_data(&princ.n_tl_data, &princ.tl_data); +    free(canon); +    free(ks_tuple); +} + +void +kadmin_getprinc(int argc, char *argv[]) +{ +    kadm5_principal_ent_rec dprinc; +    krb5_principal princ = NULL; +    krb5_error_code retval; +    const char *polname, *noexist; +    char *canon = NULL, *princstr = NULL, *modprincstr = NULL; +    char **sp = NULL, **attrstrs = NULL; +    int i; + +    if (!(argc == 2 || (argc == 3 && !strcmp("-terse", argv[1])))) { +        error(_("usage: get_principal [-terse] principal\n")); +        return; +    } + +    memset(&dprinc, 0, sizeof(dprinc)); + +    retval = kadmin_parse_name(argv[argc - 1], &princ); +    if (retval) { +        com_err("get_principal", retval, _("while parsing principal")); +        return; +    } +    retval = krb5_unparse_name(context, princ, &canon); +    if (retval) { +        com_err("get_principal", retval, _("while canonicalizing principal")); +        goto cleanup; +    } +    retval = kadm5_get_principal(handle, princ, &dprinc, +                                 KADM5_PRINCIPAL_NORMAL_MASK | KADM5_KEY_DATA); +    if (retval) { +        com_err("get_principal", retval, _("while retrieving \"%s\"."), canon); +        goto cleanup; +    } +    retval = krb5_unparse_name(context, dprinc.principal, &princstr); +    if (retval) { +        com_err("get_principal", retval, _("while unparsing principal")); +        goto cleanup; +    } +    retval = krb5_unparse_name(context, dprinc.mod_name, &modprincstr); +    if (retval) { +        com_err("get_principal", retval, _("while unparsing principal")); +        goto cleanup; +    } +    if (argc == 2) { +        printf(_("Principal: %s\n"), princstr); +        printf(_("Expiration date: %s\n"), dprinc.princ_expire_time ? +               strdate(dprinc.princ_expire_time) : _("[never]")); +        printf(_("Last password change: %s\n"), dprinc.last_pwd_change ? +               strdate(dprinc.last_pwd_change) : _("[never]")); +        printf(_("Password expiration date: %s\n"), +               dprinc.pw_expiration ? +               strdate(dprinc.pw_expiration) : _("[never]")); +        printf(_("Maximum ticket life: %s\n"), strdur(dprinc.max_life)); +        printf(_("Maximum renewable life: %s\n"), +               strdur(dprinc.max_renewable_life)); +        printf(_("Last modified: %s (%s)\n"), strdate(dprinc.mod_date), +               modprincstr); +        printf(_("Last successful authentication: %s\n"), +               dprinc.last_success ? strdate(dprinc.last_success) : +               _("[never]")); +        printf("Last failed authentication: %s\n", +               dprinc.last_failed ? strdate(dprinc.last_failed) : +               "[never]"); +        printf(_("Failed password attempts: %d\n"), +               dprinc.fail_auth_count); +        printf(_("Number of keys: %d\n"), dprinc.n_key_data); +        for (i = 0; i < dprinc.n_key_data; i++) { +            krb5_key_data *key_data = &dprinc.key_data[i]; +            char enctype[BUFSIZ], salttype[BUFSIZ]; + +            if (krb5_enctype_to_name(key_data->key_data_type[0], FALSE, +                                     enctype, sizeof(enctype))) +                snprintf(enctype, sizeof(enctype), _("<Encryption type 0x%x>"), +                         key_data->key_data_type[0]); +            printf("Key: vno %d, %s", key_data->key_data_kvno, enctype); +            if (key_data->key_data_ver > 1 && +                key_data->key_data_type[1] != KRB5_KDB_SALTTYPE_NORMAL) { +                if (krb5_salttype_to_string(key_data->key_data_type[1], +                                            salttype, sizeof(salttype))) +                    snprintf(salttype, sizeof(salttype), _("<Salt type 0x%x>"), +                             key_data->key_data_type[1]); +                printf(":%s", salttype); +            } +            printf("\n"); +        } +        printf(_("MKey: vno %d\n"), dprinc.mkvno); + +        printf(_("Attributes:")); +        retval = krb5_flags_to_strings(dprinc.attributes, &attrstrs); +        if (retval) { +            com_err("get_principal", retval, _("while printing flags")); +            return; +        } +        for (sp = attrstrs; sp != NULL && *sp != NULL; sp++) { +            printf(" %s", *sp); +            free(*sp); +        } +        free(attrstrs); +        printf("\n"); +        polname = (dprinc.policy != NULL) ? dprinc.policy : _("[none]"); +        noexist = (dprinc.policy != NULL && !policy_exists(dprinc.policy)) ? +            _(" [does not exist]") : ""; +        printf(_("Policy: %s%s\n"), polname, noexist); +    } else { +        printf("\"%s\"\t%d\t%d\t%d\t%d\t\"%s\"\t%d\t%d\t%d\t%d\t\"%s\"" +               "\t%d\t%d\t%d\t%d\t%d", +               princstr, dprinc.princ_expire_time, dprinc.last_pwd_change, +               dprinc.pw_expiration, dprinc.max_life, modprincstr, +               dprinc.mod_date, dprinc.attributes, dprinc.kvno, +               dprinc.mkvno, dprinc.policy ? dprinc.policy : "[none]", +               dprinc.max_renewable_life, dprinc.last_success, +               dprinc.last_failed, dprinc.fail_auth_count, +               dprinc.n_key_data); +        for (i = 0; i < dprinc.n_key_data; i++) +            printf("\t%d\t%d\t%d\t%d", +                   dprinc.key_data[i].key_data_ver, +                   dprinc.key_data[i].key_data_kvno, +                   dprinc.key_data[i].key_data_type[0], +                   dprinc.key_data[i].key_data_type[1]); +        printf("\n"); +    } +cleanup: +    krb5_free_principal(context, princ); +    kadm5_free_principal_ent(handle, &dprinc); +    free(canon); +    free(princstr); +    free(modprincstr); +} + +void +kadmin_getprincs(int argc, char *argv[]) +{ +    krb5_error_code retval; +    char *expr, **names; +    int i, count; + +    expr = NULL; +    if (!(argc == 1 || (argc == 2 && (expr = argv[1])))) { +        error(_("usage: get_principals [expression]\n")); +        return; +    } +    retval = kadm5_get_principals(handle, expr, &names, &count); +    if (retval) { +        com_err("get_principals", retval, _("while retrieving list.")); +        return; +    } +    for (i = 0; i < count; i++) +        printf("%s\n", names[i]); +    kadm5_free_name_list(handle, names, count); +} + +static int +kadmin_parse_policy_args(int argc, char *argv[], kadm5_policy_ent_t policy, +                         long *mask, char *caller) +{ +    krb5_error_code retval; +    int i; +    time_t now, interval; + +    time(&now); +    *mask = 0; +    for (i = 1; i < argc - 1; i++) { +        if (!strcmp(argv[i], "-maxlife")) { +            if (++i > argc -2) +                return -1; +            interval = parse_interval(argv[i], now); +            if (interval == (time_t)-1) +                return -1; +            policy->pw_max_life = interval; +            *mask |= KADM5_PW_MAX_LIFE; +            continue; +        } else if (!strcmp(argv[i], "-minlife")) { +            if (++i > argc - 2) +                return -1; +            interval = parse_interval(argv[i], now); +            if (interval == (time_t)-1) +                return -1; +            policy->pw_min_life = interval; +            *mask |= KADM5_PW_MIN_LIFE; +            continue; +        } else if (!strcmp(argv[i], "-minlength")) { +            if (++i > argc - 2) +                return -1; +            policy->pw_min_length = atoi(argv[i]); +            *mask |= KADM5_PW_MIN_LENGTH; +            continue; +        } else if (!strcmp(argv[i], "-minclasses")) { +            if (++i > argc - 2) +                return -1; +            policy->pw_min_classes = atoi(argv[i]); +            *mask |= KADM5_PW_MIN_CLASSES; +            continue; +        } else if (!strcmp(argv[i], "-history")) { +            if (++i > argc - 2) +                return -1; +            policy->pw_history_num = atoi(argv[i]); +            *mask |= KADM5_PW_HISTORY_NUM; +            continue; +        } else if (strlen(argv[i]) == 11 && +                   !strcmp(argv[i], "-maxfailure")) { +            if (++i > argc - 2) +                return -1; +            policy->pw_max_fail = atoi(argv[i]); +            *mask |= KADM5_PW_MAX_FAILURE; +            continue; +        } else if (strlen(argv[i]) == 21 && +                   !strcmp(argv[i], "-failurecountinterval")) { +            if (++i > argc - 2) +                return -1; +            interval = parse_interval(argv[i], now); +            if (interval == (time_t)-1) +                return -1; +            policy->pw_failcnt_interval = interval; +            *mask |= KADM5_PW_FAILURE_COUNT_INTERVAL; +            continue; +        } else if (strlen(argv[i]) == 16 && +                   !strcmp(argv[i], "-lockoutduration")) { +            if (++i > argc - 2) +                return -1; +            interval = parse_interval(argv[i], now); +            if (interval == (time_t)-1) +                return -1; +            policy->pw_lockout_duration = interval; +            *mask |= KADM5_PW_LOCKOUT_DURATION; +            continue; +        } else if (!strcmp(argv[i], "-allowedkeysalts")) { +            krb5_key_salt_tuple *ks_tuple = NULL; +            int n_ks_tuple = 0; + +            if (++i > argc - 2) +                return -1; +            if (strcmp(argv[i], "-")) { +                retval = krb5_string_to_keysalts(argv[i], ",", NULL, 0, +                                                 &ks_tuple, &n_ks_tuple); +                if (retval) { +                    com_err(caller, retval, _("while parsing keysalts %s"), +                            argv[i]); +                    return -1; +                } +                free(ks_tuple); +                policy->allowed_keysalts = argv[i]; +            } +            *mask |= KADM5_POLICY_ALLOWED_KEYSALTS; +            continue; +        } else +            return -1; +    } +    if (i != argc -1) { +        error(_("%s: parser lost count!\n"), caller); +        return -1; +    } else +        return 0; +} + +static void +kadmin_addmodpol_usage(char *func) +{ +    error(_("usage; %s [options] policy\n"), func); +    error(_("\toptions are:\n")); +    error(_("\t\t[-maxlife time] [-minlife time] [-minlength length]\n" +            "\t\t[-minclasses number] [-history number]\n" +            "\t\t[-maxfailure number] [-failurecountinterval time]\n" +            "\t\t[-allowedkeysalts keysalts]\n")); +    error(_("\t\t[-lockoutduration time]\n")); +} + +void +kadmin_addpol(int argc, char *argv[]) +{ +    krb5_error_code retval; +    long mask; +    kadm5_policy_ent_rec policy; + +    memset(&policy, 0, sizeof(policy)); +    if (kadmin_parse_policy_args(argc, argv, &policy, &mask, "add_policy")) { +        kadmin_addmodpol_usage("add_policy"); +        return; +    } +    policy.policy = argv[argc - 1]; +    mask |= KADM5_POLICY; +    retval = kadm5_create_policy(handle, &policy, mask); +    if (retval) { +        com_err("add_policy", retval, _("while creating policy \"%s\"."), +                policy.policy); +    } +} + +void +kadmin_modpol(int argc, char *argv[]) +{ +    krb5_error_code retval; +    long mask; +    kadm5_policy_ent_rec policy; + +    memset(&policy, 0, sizeof(policy)); +    if (kadmin_parse_policy_args(argc, argv, &policy, &mask, +                                 "modify_policy")) { +        kadmin_addmodpol_usage("modify_policy"); +        return; +    } +    policy.policy = argv[argc - 1]; +    retval = kadm5_modify_policy(handle, &policy, mask); +    if (retval) { +        com_err("modify_policy", retval, _("while modifying policy \"%s\"."), +                policy.policy); +    } +} + +void +kadmin_delpol(int argc, char *argv[]) +{ +    krb5_error_code retval; +    char reply[5]; + +    if (!(argc == 2 || (argc == 3 && !strcmp("-force", argv[1])))) { +        error(_("usage: delete_policy [-force] policy\n")); +        return; +    } +    if (argc == 2 && !script_mode) { +        printf(_("Are you sure you want to delete the policy \"%s\"? " +                 "(yes/no): "), argv[1]); +        fgets(reply, sizeof(reply), stdin); +        if (strcmp("yes\n", reply)) { +            fprintf(stderr, _("Policy \"%s\" not deleted.\n"), argv[1]); +            return; +        } +    } +    retval = kadm5_delete_policy(handle, argv[argc - 1]); +    if (retval) { +        com_err("delete_policy:", retval, _("while deleting policy \"%s\""), +                argv[argc - 1]); +    } +} + +void +kadmin_getpol(int argc, char *argv[]) +{ +    krb5_error_code retval; +    kadm5_policy_ent_rec policy; + +    if (!(argc == 2 || (argc == 3 && !strcmp("-terse", argv[1])))) { +        error(_("usage: get_policy [-terse] policy\n")); +        return; +    } +    retval = kadm5_get_policy(handle, argv[argc - 1], &policy); +    if (retval) { +        com_err("get_policy", retval, _("while retrieving policy \"%s\"."), +                argv[argc - 1]); +        return; +    } +    if (argc == 2) { +        printf(_("Policy: %s\n"), policy.policy); +        printf(_("Maximum password life: %s\n"), strdur(policy.pw_max_life)); +        printf(_("Minimum password life: %s\n"), strdur(policy.pw_min_life)); +        printf(_("Minimum password length: %ld\n"), policy.pw_min_length); +        printf(_("Minimum number of password character classes: %ld\n"), +               policy.pw_min_classes); +        printf(_("Number of old keys kept: %ld\n"), policy.pw_history_num); +        printf(_("Maximum password failures before lockout: %lu\n"), +               (unsigned long)policy.pw_max_fail); +        printf(_("Password failure count reset interval: %s\n"), +               strdur(policy.pw_failcnt_interval)); +        printf(_("Password lockout duration: %s\n"), +               strdur(policy.pw_lockout_duration)); +        if (policy.allowed_keysalts != NULL) +            printf(_("Allowed key/salt types: %s\n"), policy.allowed_keysalts); +    } else { +        /* Output 0 where we used to output policy_refcnt. */ +        printf("\"%s\"\t%ld\t%ld\t%ld\t%ld\t%ld\t0\t%lu\t%ld\t%ld\t%s\n", +               policy.policy, policy.pw_max_life, policy.pw_min_life, +               policy.pw_min_length, policy.pw_min_classes, +               policy.pw_history_num, (unsigned long)policy.pw_max_fail, +               (long)policy.pw_failcnt_interval, +               (long)policy.pw_lockout_duration, +               (policy.allowed_keysalts == NULL) ? "-" : +               policy.allowed_keysalts); +    } +    kadm5_free_policy_ent(handle, &policy); +} + +void +kadmin_getpols(int argc, char *argv[]) +{ +    krb5_error_code retval; +    char *expr, **names; +    int i, count; + +    expr = NULL; +    if (!(argc == 1 || (argc == 2 && (expr = argv[1])))) { +        error(_("usage: get_policies [expression]\n")); +        return; +    } +    retval = kadm5_get_policies(handle, expr, &names, &count); +    if (retval) { +        com_err("get_policies", retval, _("while retrieving list.")); +        return; +    } +    for (i = 0; i < count; i++) +        printf("%s\n", names[i]); +    kadm5_free_name_list(handle, names, count); +} + +void +kadmin_getprivs(int argc, char *argv[]) +{ +    static char *privs[] = {"INQUIRE", "ADD", "MODIFY", "DELETE"}; +    krb5_error_code retval; +    size_t i; +    long plist; + +    if (argc != 1) { +        error(_("usage: get_privs\n")); +        return; +    } +    retval = kadm5_get_privs(handle, &plist); +    if (retval) { +        com_err("get_privs", retval, _("while retrieving privileges")); +        return; +    } +    printf(_("current privileges:")); +    for (i = 0; i < sizeof (privs) / sizeof (char *); i++) { +        if (plist & 1 << i) +            printf(" %s", privs[i]); +    } +    printf("\n"); +} + +void +kadmin_purgekeys(int argc, char *argv[]) +{ +    kadm5_ret_t retval; +    int keepkvno = -1; +    char *pname = NULL, *canon = NULL; +    krb5_principal princ; + +    if (argc == 4 && strcmp(argv[1], "-keepkvno") == 0) { +        keepkvno = atoi(argv[2]); +        pname = argv[3]; +    } else if (argc == 3 && strcmp(argv[1], "-all") == 0) { +        keepkvno = KRB5_INT32_MAX; +        pname = argv[2]; +    } else if (argc == 2) { +        pname = argv[1]; +    } +    if (pname == NULL) { +        error(_("usage: purgekeys [-all|-keepkvno oldest_kvno_to_keep] " +                "principal\n")); +        return; +    } + +    retval = kadmin_parse_name(pname, &princ); +    if (retval) { +        com_err("purgekeys", retval, _("while parsing principal")); +        return; +    } + +    retval = krb5_unparse_name(context, princ, &canon); +    if (retval) { +        com_err("purgekeys", retval, _("while canonicalizing principal")); +        goto cleanup; +    } + +    retval = kadm5_purgekeys(handle, princ, keepkvno); +    if (retval) { +        com_err("purgekeys", retval, +                _("while purging keys for principal \"%s\""), canon); +        goto cleanup; +    } + +    if (keepkvno == KRB5_INT32_MAX) +        info(_("All keys for principal \"%s\" removed.\n"), canon); +    else +        info(_("Old keys for principal \"%s\" purged.\n"), canon); +cleanup: +    krb5_free_principal(context, princ); +    free(canon); +    return; +} + +void +kadmin_getstrings(int argc, char *argv[]) +{ +    kadm5_ret_t retval; +    char *pname, *canon = NULL; +    krb5_principal princ = NULL; +    krb5_string_attr *strings = NULL; +    int count, i; + +    if (argc != 2) { +        error(_("usage: get_strings principal\n")); +        return; +    } +    pname = argv[1]; + +    retval = kadmin_parse_name(pname, &princ); +    if (retval) { +        com_err("get_strings", retval, _("while parsing principal")); +        return; +    } + +    retval = krb5_unparse_name(context, princ, &canon); +    if (retval) { +        com_err("get_strings", retval, _("while canonicalizing principal")); +        goto cleanup; +    } + +    retval = kadm5_get_strings(handle, princ, &strings, &count); +    if (retval) { +        com_err("get_strings", retval, +                _("while getting attributes for principal \"%s\""), canon); +        goto cleanup; +    } + +    if (count == 0) +        printf(_("(No string attributes.)\n")); +    for (i = 0; i < count; i++) +        printf("%s: %s\n", strings[i].key, strings[i].value); +    kadm5_free_strings(handle, strings, count); + +cleanup: +    krb5_free_principal(context, princ); +    free(canon); +    return; +} + +void +kadmin_setstring(int argc, char *argv[]) +{ +    kadm5_ret_t retval; +    char *pname, *canon = NULL, *key, *value; +    krb5_principal princ = NULL; + +    if (argc != 4) { +        error(_("usage: set_string principal key value\n")); +        return; +    } +    pname = argv[1]; +    key = argv[2]; +    value = argv[3]; + +    retval = kadmin_parse_name(pname, &princ); +    if (retval) { +        com_err("set_string", retval, _("while parsing principal")); +        return; +    } + +    retval = krb5_unparse_name(context, princ, &canon); +    if (retval) { +        com_err("set_string", retval, _("while canonicalizing principal")); +        goto cleanup; +    } + +    retval = kadm5_set_string(handle, princ, key, value); +    if (retval) { +        com_err("set_string", retval, +                _("while setting attribute on principal \"%s\""), canon); +        goto cleanup; +    } + +    info(_("Attribute set for principal \"%s\".\n"), canon); +cleanup: +    krb5_free_principal(context, princ); +    free(canon); +    return; +} + +void +kadmin_delstring(int argc, char *argv[]) +{ +    kadm5_ret_t retval; +    char *pname, *canon = NULL, *key; +    krb5_principal princ = NULL; + +    if (argc != 3) { +        error(_("usage: del_string principal key\n")); +        return; +    } +    pname = argv[1]; +    key = argv[2]; + +    retval = kadmin_parse_name(pname, &princ); +    if (retval) { +        com_err("delstring", retval, _("while parsing principal")); +        return; +    } + +    retval = krb5_unparse_name(context, princ, &canon); +    if (retval) { +        com_err("del_string", retval, _("while canonicalizing principal")); +        goto cleanup; +    } + +    retval = kadm5_set_string(handle, princ, key, NULL); +    if (retval) { +        com_err("del_string", retval, +                _("while deleting attribute from principal \"%s\""), canon); +        goto cleanup; +    } + +    info(_("Attribute removed from principal \"%s\".\n"), canon); +cleanup: +    krb5_free_principal(context, princ); +    free(canon); +    return; +} | 
