Files
heimdal/lib/gssapi/gss-token.c
Nicolas Williams 96b7ea671d gss: Fix warnings
2022-01-14 17:39:05 -06:00

678 lines
14 KiB
C

/* */
/*-
* Copyright (c) 1997-2011 Roland C. Dowdeswell
* All rights reserved.
*
* 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 and
* dedication in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*
*/
#include <errno.h>
#ifdef __APPLE__
#include <malloc/malloc.h>
#elif HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_krb5.h>
#include <krb5.h>
#include <base64.h>
#include <getarg.h>
#include <roken.h>
#include <vers.h>
#define GBAIL(x, _maj, _min) do { \
if (GSS_ERROR(_maj)) { \
char *the_gss_err; \
\
ret = 1; \
the_gss_err = gss_mk_err(_maj, _min, x); \
if (the_gss_err) \
fprintf(stderr, "%s\n", the_gss_err); \
else \
fprintf(stderr, "err making err\n"); \
free(the_gss_err); \
goto bail; \
} \
} while (0)
#define K5BAIL(x) do { \
kret = x; \
if (kret) { \
const char *k5err; \
\
k5err = krb5_get_error_message(kctx, kret); \
if (k5err) { \
fprintf(stderr, "%s in %s:%s\n", k5err, \
#x, __func__); \
krb5_free_error_message(kctx, k5err); \
} else { \
fprintf(stderr, "unknown error %d in " \
"%s:%s\n", kret, #x, __func__); \
} \
exit(1); /* XXXrcd: shouldn't exit */ \
} \
} while (0)
/*
* global variables
*/
int Sflag = 0;
int nflag = 0;
gss_OID global_mech = GSS_C_NO_OID;
static char *
gss_mk_err(OM_uint32 maj_stat, OM_uint32 min_stat, const char *preamble)
{
gss_buffer_desc status;
OM_uint32 new_stat;
OM_uint32 cur_stat;
OM_uint32 msg_ctx = 0;
OM_uint32 ret;
int type;
size_t newlen;
char *str = NULL;
char *tmp = NULL;
cur_stat = maj_stat;
type = GSS_C_GSS_CODE;
for (;;) {
/*
* GSS_S_FAILURE produces a rather unhelpful message, so
* we skip straight to the mech specific error in this case.
*/
if (type == GSS_C_GSS_CODE && cur_stat == GSS_S_FAILURE) {
type = GSS_C_MECH_CODE;
cur_stat = min_stat;
}
ret = gss_display_status(&new_stat, cur_stat, type,
GSS_C_NO_OID, &msg_ctx, &status);
if (GSS_ERROR(ret))
return str; /* XXXrcd: hmmm, not quite?? */
if (str)
newlen = strlen(str);
else
newlen = strlen(preamble);
newlen += status.length + 3;
tmp = str;
str = malloc(newlen);
if (!str) {
gss_release_buffer(&new_stat, &status);
return tmp; /* XXXrcd: hmmm, not quite?? */
}
snprintf(str, newlen, "%s%s%.*s", tmp?tmp:preamble,
tmp?", ":": ", (int)status.length, (char *)status.value);
gss_release_buffer(&new_stat, &status);
free(tmp);
/*
* If we are finished processing for maj_stat, then
* move onto min_stat.
*/
if (msg_ctx == 0 && type == GSS_C_GSS_CODE && min_stat != 0) {
type = GSS_C_MECH_CODE;
cur_stat = min_stat;
continue;
}
if (msg_ctx == 0)
break;
}
return str;
}
static char *
read_buffer(FILE *fp)
{
char buf[65536];
char *p;
char *ret = NULL;
size_t buflen;
size_t retlen = 0;
while (fgets(buf, sizeof(buf), fp) != NULL) {
if ((p = strchr(buf, '\n')) == NULL) {
fprintf(stderr, "Long line, exiting.\n");
exit(1);
}
*p = '\0';
buflen = strlen(buf);
if (buflen == 0)
break;
ret = realloc(ret, retlen + buflen + 1);
if (!ret) {
perror("realloc");
exit(1);
}
memcpy(ret + retlen, buf, buflen);
ret[retlen + buflen] = '\0';
retlen += buflen;
}
if (ferror(stdin)) {
perror("fgets");
exit(1);
}
return ret;
}
static int
write_and_free_token(gss_buffer_t out, int negotiate)
{
OM_uint32 min;
char *outstr = NULL;
char *p = out->value;
size_t len = out->length;
size_t inc;
int ret = 0;
int first = 1;
if (nflag)
goto bail;
/*
* According to RFC 2744 page 25, we simply don't output
* zero length output tokens.
*/
if (len == 0)
goto bail;
inc = len;
if (Sflag)
inc = Sflag;
do {
if (first)
first = 0;
else
printf("\n");
if (len < inc)
inc = len;
ret = rk_base64_encode(p, inc, &outstr);
if (ret < 0) {
fprintf(stderr, "Out of memory.\n");
ret = 1;
goto bail;
}
ret = 0;
printf("%s%s\n", negotiate?"Negotiate ":"", outstr);
free(outstr);
p += inc;
len -= inc;
} while (len > 0);
bail:
gss_release_buffer(&min, out);
return ret;
}
static int
read_token(gss_buffer_t in, int negotiate)
{
char *inbuf = NULL;
char *tmp;
size_t len;
int ret = 0;
/* We must flush before we block wanting input */
fflush(stdout);
*in = (gss_buffer_desc)GSS_C_EMPTY_BUFFER;
inbuf = read_buffer(stdin);
if (!inbuf)
/* Just a couple of \n's in a row or EOF, no error. */
return 0;
tmp = inbuf;
if (negotiate) {
if (strncasecmp("Negotiate ", inbuf, 10) != 0) {
fprintf(stderr, "Token doesn't begin with "
"\"Negotiate \"\n");
ret = -1;
goto bail;
}
tmp += 10;
}
len = strlen(tmp);
in->value = malloc(len + 1);
if (!in->value) {
fprintf(stderr, "Out of memory.\n");
ret = -1;
goto bail;
}
ret = rk_base64_decode(tmp, in->value);
if (ret < 0) {
free(in->value);
in->value = NULL;
if (errno == EOVERFLOW)
fprintf(stderr, "Token is too big\n");
else
fprintf(stderr, "Token encoding is not valid "
"base64\n");
goto bail;
} else {
in->length = ret;
}
ret = 0;
bail:
free(inbuf);
return ret;
}
static int
initiate_one(gss_name_t service, int delegate, int negotiate)
{
gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
gss_buffer_desc in;
gss_buffer_desc out;
OM_uint32 maj;
OM_uint32 min;
OM_uint32 flags = 0;
int first = 1;
int ret = 0;
if (delegate)
flags |= GSS_C_DELEG_FLAG;
do {
out.length = 0;
out.value = 0;
if (first) {
in.length = 0;
in.value = 0;
first = 0;
} else {
printf("\n");
ret = read_token(&in, negotiate);
if (ret)
return ret;
if (feof(stdin))
return -1;
}
maj = gss_init_sec_context(&min, GSS_C_NO_CREDENTIAL, &ctx,
service, global_mech, flags, 0,
GSS_C_NO_CHANNEL_BINDINGS, &in, NULL, &out,
NULL, NULL);
ret = write_and_free_token(&out, negotiate);
if (ret)
return ret;
GBAIL("gss_init_sec_context", maj, min);
} while (maj & GSS_S_CONTINUE_NEEDED);
bail:
if (ctx != GSS_C_NO_CONTEXT) {
/*
* XXXrcd: here we ignore the fact that we might have an
* output token as this program doesn't do terribly
* well in that case.
*/
gss_delete_sec_context(&min, &ctx, NULL);
}
return ret;
}
static krb5_error_code
copy_cache(krb5_context kctx, krb5_ccache from, krb5_ccache to)
{
krb5_error_code kret;
krb5_principal princ = NULL;
krb5_cc_cursor cursor;
krb5_creds cred;
K5BAIL(krb5_cc_get_principal(kctx, from, &princ));
K5BAIL(krb5_cc_initialize(kctx, to, princ));
K5BAIL(krb5_cc_start_seq_get(kctx, from, &cursor));
for (;;) {
kret = krb5_cc_next_cred(kctx, from, &cursor, &cred);
if (kret)
break;
kret = krb5_cc_store_cred(kctx, to, &cred);
krb5_free_cred_contents(kctx, &cred);
if (kret)
break;
}
krb5_cc_end_seq_get(kctx, from, &cursor);
if (kret == KRB5_CC_END)
kret = 0;
K5BAIL(kret);
if (princ)
krb5_free_principal(kctx, princ);
return kret;
}
static int
initiate_many(gss_name_t service, int delegate, int negotiate, int memcache,
size_t count)
{
krb5_error_code kret = 0;
krb5_context kctx = NULL;
krb5_ccache def_cache = NULL;
krb5_ccache mem_cache = NULL;
size_t i;
if (memcache) {
K5BAIL(krb5_init_context(&kctx));
K5BAIL(krb5_cc_default(kctx, &def_cache));
K5BAIL(krb5_cc_resolve(kctx, "MEMORY:mem_cache", &mem_cache));
putenv("KRB5CCNAME=MEMORY:mem_cache");
}
for (i=0; i < count; i++) {
if (memcache)
K5BAIL(copy_cache(kctx, def_cache, mem_cache));
kret = initiate_one(service, delegate, negotiate);
if (!nflag && i < count - 1)
printf("\n");
}
if (kctx)
krb5_free_context(kctx);
if (def_cache)
krb5_cc_close(kctx, def_cache);
if (mem_cache)
krb5_cc_close(kctx, mem_cache);
return kret;
}
static int
accept_one(gss_name_t service, const char *ccname, int negotiate)
{
gss_cred_id_t cred = NULL;
gss_cred_id_t deleg_creds = NULL;
gss_name_t client;
gss_OID mech_oid;
gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
gss_buffer_desc in = GSS_C_EMPTY_BUFFER;
gss_buffer_desc out;
gss_buffer_desc dname = GSS_C_EMPTY_BUFFER;
krb5_context kctx = NULL;
krb5_ccache ccache = NULL;
krb5_error_code kret;
OM_uint32 maj, min;
int ret = 0;
if (service) {
maj = gss_acquire_cred(&min, service, 0, NULL, GSS_C_ACCEPT,
&cred, NULL, NULL);
GBAIL("gss_acquire_cred", maj, min);
}
do {
if (feof(stdin))
return -1;
ret = read_token(&in, negotiate);
if (ret)
return ret;
out.length = 0;
out.value = 0;
maj = gss_accept_sec_context(&min, &ctx, cred, &in,
GSS_C_NO_CHANNEL_BINDINGS, &client, &mech_oid, &out,
NULL, NULL, &deleg_creds);
ret = write_and_free_token(&out, negotiate);
if (ret)
return ret;
GBAIL("gss_accept_sec_context", maj, min);
} while (maj & GSS_S_CONTINUE_NEEDED);
/*
* XXXrcd: not bothering to clean up because we're about to exit.
* Probably should fix this in case the code is used as
* an example by someone.
*/
maj = gss_display_name(&min, client, &dname, NULL);
GBAIL("gss_display_name", maj, min);
if (!nflag)
printf("Authenticated: %.*s\n", (int)dname.length,
(char *)dname.value);
(void) gss_release_buffer(&min, &dname);
if (ccname) {
#ifdef HAVE_GSS_STORE_CRED_INTO
gss_key_value_set_desc store;
gss_key_value_element_desc elem;
int overwrite_cred = 1;
int default_cred = 0;
elem.key = "ccache";
elem.value = ccname;
store.count = 1;
store.elements = &elem;
maj = gss_store_cred_into(&min, deleg_creds, GSS_C_INITIATE,
GSS_C_NO_OID, overwrite_cred, default_cred, &store, NULL,
NULL);
GBAIL("gss_store_cred_into", maj, min);
#else
K5BAIL(krb5_init_context(&kctx));
K5BAIL(krb5_cc_resolve(kctx, ccname, &ccache));
maj = gss_krb5_copy_ccache(&min, deleg_creds, ccache);
GBAIL("gss_krb5_copy_ccache", maj, min);
#endif
}
bail:
if (kctx)
krb5_free_context(kctx);
if (ccache)
krb5_cc_close(kctx, ccache);
if (cred)
gss_release_cred(&min, &cred);
if (deleg_creds)
gss_release_cred(&min, &deleg_creds);
free(in.value);
return ret;
}
static gss_name_t
import_service(char *service)
{
gss_buffer_desc name;
gss_name_t svc = NULL;
OM_uint32 maj;
OM_uint32 min;
int ret = 0;
name.length = strlen(service);
name.value = service;
maj = gss_import_name(&min, &name, GSS_C_NT_HOSTBASED_SERVICE, &svc);
GBAIL("gss_import_name", maj, min);
bail:
if (ret)
exit(1);
return svc;
}
static void
print_all_mechs(void)
{
OM_uint32 maj, min;
gss_OID_set mech_set;
size_t i;
int ret = 0;
maj = gss_indicate_mechs(&min, &mech_set);
GBAIL("gss_indicate_mechs", maj, min);
for (i=0; i < mech_set->count; i++)
printf("%s\n", gss_oid_to_name(&mech_set->elements[i]));
(void) gss_release_oid_set(&min, &mech_set);
bail:
exit(ret);
}
static void
usage(int ecode)
{
FILE *f = ecode == 0 ? stdout : stderr;
fprintf(f, "Usage: gss-token [-DNn] [-c count] service@host\n");
fprintf(f, " gss-token -r [-Nln] [-C ccache] [-c count] "
"[service@host]\n");
exit(ecode);
}
int
main(int argc, char **argv)
{
OM_uint32 min;
gss_name_t service = NULL;
size_t count = 1;
int Dflag = 0;
int Mflag = 0;
int Nflag = 0;
int hflag = 0;
int lflag = 0;
int rflag = 0;
int version_flag = 0;
int ret = 0;
int optidx = 0;
char *ccname = NULL;
char *mech = NULL;
struct getargs args[] = {
{ "help", 'h', arg_flag, &hflag, NULL, NULL },
{ "version", 0, arg_flag, &version_flag, NULL, NULL },
{ NULL, 'C', arg_string, &ccname, NULL, NULL },
{ NULL, 'D', arg_flag, &Dflag, NULL, NULL },
{ NULL, 'M', arg_flag, &Mflag, NULL, NULL },
{ NULL, 'N', arg_flag, &Nflag, NULL, NULL },
{ NULL, 'S', arg_integer, &Sflag, NULL, NULL },
{ NULL, 'c', arg_integer, &count, NULL, NULL },
{ NULL, 'l', arg_flag, &lflag, NULL, NULL },
{ NULL, 'm', arg_string, &mech, NULL, NULL },
{ NULL, 'n', arg_flag, &nflag, NULL, NULL },
{ NULL, 'r', arg_flag, &rflag, NULL, NULL },
};
setprogname(argv[0]);
if (argc == 1 ||
getarg(args, sizeof(args)/sizeof(args[0]), argc, argv, &optidx))
usage(1);
if (hflag)
usage(0);
if (version_flag) {
print_version(NULL);
return 0;
}
argc -= optidx;
argv += optidx;
if (mech) {
if (mech[0] == '?' && mech[1] == '\0') {
print_all_mechs();
exit(0);
}
global_mech = gss_name_to_oid(mech);
if (!global_mech) {
fprintf(stderr, "Invalid mech \"%s\".\n", mech);
usage(1);
}
}
if (argc > 0)
service = import_service(*argv);
if (!rflag) {
if (!argc) {
fprintf(stderr, "Without -r, hostbased_service must "
"be provided.\n");
usage(1);
}
if (ccname) {
fprintf(stderr, "Specifying a target ccache doesn't "
"make sense without -r.\n");
usage(1);
}
ret = initiate_many(service, Dflag, Nflag, Mflag, count);
goto done;
}
if (Dflag) {
fprintf(stderr, "Delegating credentials (-D) doesn't make "
"sense when reading tokens (-r).\n");
usage(1);
}
do {
ret = accept_one(service, ccname, Nflag);
} while (lflag && !ret && !feof(stdin));
done:
if (service)
gss_release_name(&min, &service);
return ret;
}