Files
heimdal/lib/gssapi/gss-token.c
Nicolas Williams 5f63215d0d Always perform == or != operation on cmp function result
Although not required to address bad code generation in
some versions of gcc 9 and 10, a coding style that requires
explicit comparison of the result to zero before use is
both clearer and would have avoided the generation of bad
code.

This change converts all use of cmp function usage from

```
    if (strcmp(a, b) || !strcmp(c, d)) ...
```

to

```
    if (strcmp(a, b) != 0 || strcmp(c, d)) == 0
```

for all C library cmp functions and related:

 - strcmp(), strncmp()
 - strcasecmp(), strncasecmp()
 - stricmp(), strnicmp()
 - memcmp()

Change-Id: Ic60c15e1e3a07e4faaf10648eefe3adae2543188
2021-11-24 22:30:44 -05:00

676 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 0;
}
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;
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, dname;
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);
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]));
maj = 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;
}