Path: blob/main/crypto/krb5/src/clients/kvno/kvno.c
34907 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* Copyright (C) 1998 by the FundsXpress, INC.3*4* All rights reserved.5*6* Export of this software from the United States of America may require7* a specific license from the United States Government. It is the8* responsibility of any person or organization contemplating export to9* obtain such a license before exporting.10*11* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and12* distribute this software and its documentation for any purpose and13* without fee is hereby granted, provided that the above copyright14* notice appear in all copies and that both that copyright notice and15* this permission notice appear in supporting documentation, and that16* the name of FundsXpress. not be used in advertising or publicity pertaining17* to distribution of the software without specific, written prior18* permission. FundsXpress makes no representations about the suitability of19* this software for any purpose. It is provided "as is" without express20* or implied warranty.21*22* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR23* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED24* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.25*/2627#include "k5-platform.h"28#include "k5-buf.h"29#include "k5-base64.h"30#include <locale.h>31#ifdef HAVE_UNISTD_H32#include <unistd.h>33#endif34#include <string.h>35#include <ctype.h>3637static char *prog;38static int quiet = 0;3940static void41xusage(void)42{43fprintf(stderr, _("usage: %s [-c ccache] [-e etype] [-k keytab] [-q] "44"[-u | -S sname]\n"45"\t[[{-F cert_file | {-I | -U} for_user} [-P]] | "46"--u2u ccache]\n"47"\t[--cached-only] [--no-store] [--out-cache] "48"service1 service2 ...\n"),49prog);50exit(1);51}5253static void do_v5_kvno(int argc, char *argv[], char *ccachestr, char *etypestr,54char *keytab_name, char *sname, int cached_only,55int canon, int no_store, int unknown, char *for_user,56int for_user_enterprise, char *for_user_cert_file,57int proxy, const char *out_ccname,58const char *u2u_ccname);5960#include <com_err.h>61static void extended_com_err_fn(const char *myprog, errcode_t code,62const char *fmt, va_list args);6364int65main(int argc, char *argv[])66{67enum { OPTION_U2U = 256, OPTION_OUT_CACHE = 257 };68const char *shopts = "uCc:e:hk:qPS:I:U:F:";69int option;70char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL;71char *sname = NULL, *for_user = NULL, *u2u_ccname = NULL;72char *for_user_cert_file = NULL, *out_ccname = NULL;73int canon = 0, unknown = 0, proxy = 0, for_user_enterprise = 0;74int impersonate = 0, cached_only = 0, no_store = 0;75struct option lopts[] = {76{ "cached-only", 0, &cached_only, 1 },77{ "no-store", 0, &no_store, 1 },78{ "out-cache", 1, NULL, OPTION_OUT_CACHE },79{ "u2u", 1, NULL, OPTION_U2U },80{ NULL, 0, NULL, 0 }81};8283setlocale(LC_ALL, "");84set_com_err_hook(extended_com_err_fn);8586prog = strrchr(argv[0], '/');87prog = prog ? (prog + 1) : argv[0];8889while ((option = getopt_long(argc, argv, shopts, lopts, NULL)) != -1) {90switch (option) {91case 'C':92canon = 1;93break;94case 'c':95ccachestr = optarg;96break;97case 'e':98etypestr = optarg;99break;100case 'h':101xusage();102break;103case 'k':104keytab_name = optarg;105break;106case 'q':107quiet = 1;108break;109case 'P':110proxy = 1; /* S4U2Proxy - constrained delegation */111break;112case 'S':113sname = optarg;114if (unknown == 1) {115fprintf(stderr,116_("Options -u and -S are mutually exclusive\n"));117xusage();118}119break;120case 'u':121unknown = 1;122if (sname != NULL) {123fprintf(stderr,124_("Options -u and -S are mutually exclusive\n"));125xusage();126}127break;128case 'I':129impersonate = 1;130for_user = optarg;131break;132case 'U':133impersonate = 1;134for_user_enterprise = 1;135for_user = optarg;136break;137case 'F':138impersonate = 1;139for_user_cert_file = optarg;140break;141case OPTION_U2U:142u2u_ccname = optarg;143break;144case OPTION_OUT_CACHE:145out_ccname = optarg;146break;147case 0:148/* If this option set a flag, do nothing else now. */149break;150default:151xusage();152break;153}154}155156if (u2u_ccname != NULL && impersonate) {157fprintf(stderr,158_("Options --u2u and -I|-U|-F are mutually exclusive\n"));159xusage();160}161162if (proxy) {163if (!impersonate) {164fprintf(stderr, _("Option -P (constrained delegation) requires "165"option -I|-U|-F (protocol transition)\n"));166xusage();167}168}169170if (argc - optind < 1)171xusage();172173do_v5_kvno(argc - optind, argv + optind, ccachestr, etypestr, keytab_name,174sname, cached_only, canon, no_store, unknown, for_user,175for_user_enterprise, for_user_cert_file, proxy, out_ccname,176u2u_ccname);177return 0;178}179180#include <k5-int.h>181static krb5_context context;182static void extended_com_err_fn(const char *myprog, errcode_t code,183const char *fmt, va_list args)184{185const char *emsg;186187emsg = krb5_get_error_message(context, code);188fprintf(stderr, "%s: %s ", myprog, emsg);189krb5_free_error_message(context, emsg);190vfprintf(stderr, fmt, args);191fprintf(stderr, "\n");192}193194/* Read a line from fp into buf. Trim any trailing whitespace, and return a195* pointer to the first non-whitespace character. */196static const char *197read_line(FILE *fp, char *buf, size_t bufsize)198{199char *end, *begin;200201if (fgets(buf, bufsize, fp) == NULL)202return NULL;203204end = buf + strlen(buf);205while (end > buf && isspace((uint8_t)end[-1]))206*--end = '\0';207208begin = buf;209while (isspace((uint8_t)*begin))210begin++;211212return begin;213}214215/* Read a certificate from file_name in PEM format, placing the DER216* representation of the certificate in *der_out. */217static krb5_error_code218read_pem_file(char *file_name, krb5_data *der_out)219{220krb5_error_code ret = 0;221FILE *fp = NULL;222const char *begin_line = "-----BEGIN CERTIFICATE-----";223const char *end_line = "-----END ", *line;224char linebuf[256], *b64;225struct k5buf buf = EMPTY_K5BUF;226uint8_t *der_cert;227size_t dlen;228229*der_out = empty_data();230231fp = fopen(file_name, "r");232if (fp == NULL)233return errno;234235for (;;) {236line = read_line(fp, linebuf, sizeof(linebuf));237if (line == NULL) {238ret = EINVAL;239k5_setmsg(context, ret, _("No begin line not found"));240goto cleanup;241}242if (strncmp(line, begin_line, strlen(begin_line)) == 0)243break;244}245246k5_buf_init_dynamic(&buf);247for (;;) {248line = read_line(fp, linebuf, sizeof(linebuf));249if (line == NULL) {250ret = EINVAL;251k5_setmsg(context, ret, _("No end line found"));252goto cleanup;253}254255if (strncmp(line, end_line, strlen(end_line)) == 0)256break;257258/* Header lines would be expected for an actual privacy-enhanced mail259* message, but not for a certificate. */260if (*line == '\0' || strchr(line, ':') != NULL) {261ret = EINVAL;262k5_setmsg(context, ret, _("Unexpected header line"));263goto cleanup;264}265266k5_buf_add(&buf, line);267}268269b64 = k5_buf_cstring(&buf);270if (b64 == NULL) {271ret = ENOMEM;272goto cleanup;273}274der_cert = k5_base64_decode(b64, &dlen);275if (der_cert == NULL) {276ret = EINVAL;277k5_setmsg(context, ret, _("Invalid base64"));278goto cleanup;279}280281*der_out = make_data(der_cert, dlen);282283cleanup:284fclose(fp);285k5_buf_free(&buf);286return ret;287}288289/* Request a single service ticket and display its status (unless quiet is290* set). On failure, display an error message and return non-zero. */291static krb5_error_code292kvno(const char *name, krb5_ccache ccache, krb5_principal me,293krb5_enctype etype, krb5_keytab keytab, const char *sname,294krb5_flags options, int unknown, krb5_principal for_user_princ,295krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket,296krb5_creds **creds_out)297{298krb5_error_code ret;299krb5_principal server = NULL;300krb5_ticket *ticket = NULL;301krb5_creds in_creds, *creds = NULL;302char *princ = NULL;303304*creds_out = NULL;305memset(&in_creds, 0, sizeof(in_creds));306307if (sname != NULL) {308ret = krb5_sname_to_principal(context, name, sname, KRB5_NT_SRV_HST,309&server);310} else {311ret = krb5_parse_name(context, name, &server);312}313if (ret) {314if (!quiet)315com_err(prog, ret, _("while parsing principal name %s"), name);316goto cleanup;317}318if (unknown)319krb5_princ_type(context, server) = KRB5_NT_UNKNOWN;320321ret = krb5_unparse_name(context, server, &princ);322if (ret) {323com_err(prog, ret, _("while formatting parsed principal name for "324"'%s'"), name);325goto cleanup;326}327328in_creds.keyblock.enctype = etype;329330if (u2u_ticket != NULL)331in_creds.second_ticket = *u2u_ticket;332333if (for_user_princ != NULL || for_user_cert != NULL) {334if (!proxy && !krb5_principal_compare(context, me, server)) {335ret = EINVAL;336com_err(prog, ret,337_("client and server principal names must match"));338goto cleanup;339}340341in_creds.client = for_user_princ;342in_creds.server = me;343ret = krb5_get_credentials_for_user(context, options, ccache,344&in_creds, for_user_cert, &creds);345} else {346in_creds.client = me;347in_creds.server = server;348ret = krb5_get_credentials(context, options, ccache, &in_creds,349&creds);350}351352if (ret) {353com_err(prog, ret, _("while getting credentials for %s"), princ);354goto cleanup;355}356357/* We need a native ticket. */358ret = krb5_decode_ticket(&creds->ticket, &ticket);359if (ret) {360com_err(prog, ret, _("while decoding ticket for %s"), princ);361goto cleanup;362}363364if (keytab != NULL) {365ret = krb5_server_decrypt_ticket_keytab(context, keytab, ticket);366if (ret) {367if (!quiet) {368fprintf(stderr, "%s: kvno = %d, keytab entry invalid\n", princ,369ticket->enc_part.kvno);370}371com_err(prog, ret, _("while decrypting ticket for %s"), princ);372goto cleanup;373}374if (!quiet) {375printf(_("%s: kvno = %d, keytab entry valid\n"), princ,376ticket->enc_part.kvno);377}378} else {379if (!quiet)380printf(_("%s: kvno = %d\n"), princ, ticket->enc_part.kvno);381}382383if (proxy) {384in_creds.client = creds->client;385creds->client = NULL;386krb5_free_creds(context, creds);387creds = NULL;388in_creds.server = server;389390ret = krb5_get_credentials_for_proxy(context, KRB5_GC_CANONICALIZE,391ccache, &in_creds, ticket,392&creds);393krb5_free_principal(context, in_creds.client);394if (ret) {395com_err(prog, ret, _("%s: constrained delegation failed"),396princ);397goto cleanup;398}399}400401*creds_out = creds;402creds = NULL;403404cleanup:405krb5_free_principal(context, server);406krb5_free_ticket(context, ticket);407krb5_free_creds(context, creds);408krb5_free_unparsed_name(context, princ);409return ret;410}411412/* Fetch the encoded local TGT for ccname's default client principal. */413static krb5_error_code414get_u2u_ticket(const char *ccname, krb5_data **ticket_out)415{416krb5_error_code ret;417krb5_ccache cc = NULL;418krb5_creds mcred, *creds = NULL;419420*ticket_out = NULL;421memset(&mcred, 0, sizeof(mcred));422423ret = krb5_cc_resolve(context, ccname, &cc);424if (ret)425goto cleanup;426ret = krb5_cc_get_principal(context, cc, &mcred.client);427if (ret)428goto cleanup;429ret = krb5_build_principal_ext(context, &mcred.server,430mcred.client->realm.length,431mcred.client->realm.data,432KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,433mcred.client->realm.length,434mcred.client->realm.data, 0);435if (ret)436goto cleanup;437ret = krb5_get_credentials(context, KRB5_GC_CACHED, cc, &mcred, &creds);438if (ret)439goto cleanup;440441ret = krb5_copy_data(context, &creds->ticket, ticket_out);442443cleanup:444if (cc != NULL)445krb5_cc_close(context, cc);446krb5_free_cred_contents(context, &mcred);447krb5_free_creds(context, creds);448return ret;449}450451static void452do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,453char *keytab_name, char *sname, int cached_only, int canon,454int no_store, int unknown, char *for_user, int for_user_enterprise,455char *for_user_cert_file, int proxy, const char *out_ccname,456const char *u2u_ccname)457{458krb5_error_code ret;459int i, errors, flags, initialized = 0;460krb5_enctype etype;461krb5_ccache ccache, mcc, out_ccache = NULL;462krb5_principal me;463krb5_keytab keytab = NULL;464krb5_principal for_user_princ = NULL;465krb5_flags options = 0;466krb5_data cert_data = empty_data(), *user_cert = NULL, *u2u_ticket = NULL;467krb5_creds *creds;468469if (canon)470options |= KRB5_GC_CANONICALIZE;471if (cached_only)472options |= KRB5_GC_CACHED;473if (no_store || out_ccname != NULL)474options |= KRB5_GC_NO_STORE;475476ret = krb5_init_context(&context);477if (ret) {478com_err(prog, ret, _("while initializing krb5 library"));479exit(1);480}481482if (etypestr) {483ret = krb5_string_to_enctype(etypestr, &etype);484if (ret) {485com_err(prog, ret, _("while converting etype"));486exit(1);487}488} else {489etype = 0;490}491492if (ccachestr)493ret = krb5_cc_resolve(context, ccachestr, &ccache);494else495ret = krb5_cc_default(context, &ccache);496if (ret) {497com_err(prog, ret, _("while opening ccache"));498exit(1);499}500501if (out_ccname != NULL) {502ret = krb5_cc_resolve(context, out_ccname, &out_ccache);503if (ret) {504com_err(prog, ret, _("while resolving output ccache"));505exit(1);506}507}508509if (keytab_name != NULL) {510ret = krb5_kt_resolve(context, keytab_name, &keytab);511if (ret) {512com_err(prog, ret, _("resolving keytab %s"), keytab_name);513exit(1);514}515}516517if (for_user) {518flags = for_user_enterprise ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0;519ret = krb5_parse_name_flags(context, for_user, flags, &for_user_princ);520if (ret) {521com_err(prog, ret, _("while parsing principal name %s"), for_user);522exit(1);523}524}525526if (for_user_cert_file != NULL) {527ret = read_pem_file(for_user_cert_file, &cert_data);528if (ret) {529com_err(prog, ret, _("while reading certificate file %s"),530for_user_cert_file);531exit(1);532}533user_cert = &cert_data;534}535536if (u2u_ccname != NULL) {537ret = get_u2u_ticket(u2u_ccname, &u2u_ticket);538if (ret) {539com_err(prog, ret, _("while getting user-to-user ticket from %s"),540u2u_ccname);541exit(1);542}543options |= KRB5_GC_USER_USER;544}545546ret = krb5_cc_get_principal(context, ccache, &me);547if (ret) {548com_err(prog, ret, _("while getting client principal name"));549exit(1);550}551552if (out_ccache != NULL) {553ret = krb5_cc_new_unique(context, "MEMORY", NULL, &mcc);554if (ret) {555com_err(prog, ret, _("while creating temporary output ccache"));556exit(1);557}558}559560errors = 0;561for (i = 0; i < count; i++) {562if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown,563for_user_princ, user_cert, proxy, u2u_ticket, &creds) != 0) {564errors++;565} else if (out_ccache != NULL) {566if (!initialized) {567ret = krb5_cc_initialize(context, mcc, creds->client);568if (ret) {569com_err(prog, ret, _("while initializing output ccache"));570exit(1);571}572initialized = 1;573}574if (count == 1)575ret = k5_cc_store_primary_cred(context, mcc, creds);576else577ret = krb5_cc_store_cred(context, mcc, creds);578if (ret) {579com_err(prog, ret, _("while storing creds in output ccache"));580exit(1);581}582}583584krb5_free_creds(context, creds);585}586587if (!errors && out_ccache != NULL) {588ret = krb5_cc_move(context, mcc, out_ccache);589if (ret) {590com_err(prog, ret, _("while writing output ccache"));591exit(1);592}593}594595if (keytab != NULL)596krb5_kt_close(context, keytab);597krb5_free_principal(context, me);598krb5_free_principal(context, for_user_princ);599krb5_cc_close(context, ccache);600krb5_free_data(context, u2u_ticket);601krb5_free_data_contents(context, &cert_data);602krb5_free_context(context);603604if (errors)605exit(1);606607exit(0);608}609610611