328 lines
8.6 KiB
C

/* This file is part of the Project Athena Zephyr Notification System.
* It contains source for the ZMakeAuthentication function.
*
* Created by: Robert French
*
* $Id$
*
* Copyright (c) 1987 by the Massachusetts Institute of Technology.
* For copying and distribution information, see the file
* "mit-copyright.h".
*/
#include <internal.h>
#ifndef lint
static const char rcsid_ZMakeAuthentication_c[] = "$Id$";
#endif
#ifdef HAVE_KRB4
#include <krb_err.h>
#endif
#if defined(HAVE_KRB5) && !HAVE_KRB5_FREE_DATA
#define krb5_free_data(ctx, dat) free((dat)->data)
#endif
Code_t
ZResetAuthentication(void)
{
return ZERR_NONE;
}
Code_t
ZMakeAuthentication(register ZNotice_t *notice,
char *buffer,
int buffer_len,
int *len)
{
#ifdef HAVE_KRB5
return ZMakeZcodeAuthentication(notice, buffer, buffer_len, len/*?XXX*/);
#else
#ifdef HAVE_KRB4
int result;
KTEXT_ST authent;
char *cstart, *cend;
ZChecksum_t checksum;
CREDENTIALS cred;
C_Block *session;
result = krb_mk_req(&authent, SERVER_SERVICE,
SERVER_INSTANCE, __Zephyr_realm, 0);
if (result != MK_AP_OK)
return (result+krb_err_base);
result = krb_get_cred(SERVER_SERVICE, SERVER_INSTANCE,
__Zephyr_realm, &cred);
if (result != KSUCCESS)
return (result+krb_err_base);
session = (C_Block *)cred.session;
notice->z_auth = 1;
notice->z_authent_len = authent.length;
notice->z_ascii_authent = (char *)malloc((unsigned)authent.length*3);
/* zero length authent is an error, so malloc(0) is not a problem */
if (!notice->z_ascii_authent)
return (ENOMEM);
if ((result = ZMakeAscii(notice->z_ascii_authent,
authent.length*3,
authent.dat,
authent.length)) != ZERR_NONE) {
free(notice->z_ascii_authent);
return (result);
}
result = Z_FormatRawHeader(notice, buffer, buffer_len, len, &cstart,
&cend);
free(notice->z_ascii_authent);
notice->z_authent_len = 0;
if (result)
return(result);
/* Compute a checksum over the header and message. */
checksum = des_quad_cksum((unsigned char *)buffer, NULL, cstart - buffer, 0, session);
checksum ^= des_quad_cksum((unsigned char *)cend, NULL, buffer + *len - cend, 0,
session);
checksum ^= des_quad_cksum((unsigned char *)notice->z_message, NULL, notice->z_message_len,
0, session);
notice->z_checksum = checksum;
ZMakeAscii32(cstart, buffer + buffer_len - cstart, checksum);
return (ZERR_NONE);
#else
notice->z_checksum = 0;
notice->z_auth = 1;
notice->z_authent_len = 0;
notice->z_ascii_authent = "";
return (Z_FormatRawHeader(notice, buffer, buffer_len, len, NULL, NULL));
#endif
#endif
}
Code_t
Z_MakeAuthenticationSaveKey(register ZNotice_t *notice,
char *buffer,
int buffer_len,
int *len)
{
#ifndef HAVE_KRB5
/* Key management not implemented for krb4. */
return ZMakeAuthentication(notice, buffer, buffer_len, len);
#else
Code_t result;
krb5_creds *creds = NULL;
krb5_keyblock *keyblock;
struct _Z_SessionKey *savedkey;
/* Look up creds and checksum the notice. */
if ((result = ZGetCreds(&creds)))
return result;
if ((result = Z_MakeZcodeAuthentication(notice, buffer, buffer_len, len,
creds))) {
krb5_free_creds(Z_krb5_ctx, creds);
return result;
}
/* Save the key. */
keyblock = Z_credskey(creds);
if (Z_keys_head &&
Z_keys_head->keyblock->enctype == keyblock->enctype &&
Z_keys_head->keyblock->length == keyblock->length &&
memcmp(Z_keys_head->keyblock->contents, keyblock->contents,
keyblock->length) == 0) {
/*
* Optimization: if the key hasn't changed, replace the current entry,
* rather than make a new one.
*/
Z_keys_head->send_time = time(NULL);
Z_keys_head->first_use = 0;
} else {
savedkey = (struct _Z_SessionKey *)malloc(sizeof(struct _Z_SessionKey));
if (!savedkey) {
krb5_free_creds(Z_krb5_ctx, creds);
return ENOMEM;
}
if ((result = krb5_copy_keyblock(Z_krb5_ctx, keyblock, &savedkey->keyblock))) {
free(savedkey);
krb5_free_creds(Z_krb5_ctx, creds);
return result;
}
savedkey->send_time = time(NULL);
savedkey->first_use = 0;
savedkey->prev = NULL;
savedkey->next = Z_keys_head;
if (Z_keys_head)
Z_keys_head->prev = savedkey;
Z_keys_head = savedkey;
if (!Z_keys_tail)
Z_keys_tail = savedkey;
}
krb5_free_creds(Z_krb5_ctx, creds);
return result;
#endif
}
/* only used by server? */
Code_t
ZMakeZcodeAuthentication(register ZNotice_t *notice,
char *buffer,
int buffer_len,
int *phdr_len)
{
return ZMakeZcodeRealmAuthentication(notice, buffer, buffer_len, phdr_len,
__Zephyr_realm);
}
Code_t
ZMakeZcodeRealmAuthentication(register ZNotice_t *notice,
char *buffer,
int buffer_len,
int *phdr_len,
char *realm)
{
#ifdef HAVE_KRB5
Code_t result;
krb5_creds *creds = NULL;
result = ZGetCredsRealm(&creds, realm);
if (!result)
result = Z_MakeZcodeAuthentication(notice, buffer, buffer_len, phdr_len,
creds);
if (creds != NULL)
krb5_free_creds(Z_krb5_ctx, creds);
return result;
#else /* HAVE_KRB5 */
return ZERR_INTERNAL;
#endif
}
#ifdef HAVE_KRB5
Code_t
Z_MakeZcodeAuthentication(register ZNotice_t *notice,
char *buffer,
int buffer_len,
int *phdr_len,
krb5_creds *creds)
{
krb5_error_code result = 0;
krb5_keyblock *keyblock;
krb5_auth_context authctx;
krb5_data *authent;
char *cksum_start, *cstart, *cend;
int cksum_len, zcode_len = 0, phdr_adj = 0;
notice->z_ascii_authent = NULL;
keyblock = Z_credskey(creds);
authent = (krb5_data *)malloc(sizeof(krb5_data));
if (authent == NULL)
result = ENOMEM;
authent->data = NULL; /* so that we can blithely krb5_fre_data_contents on
the way out */
if (!result)
result = krb5_auth_con_init(Z_krb5_ctx, &authctx);
if (!result) {
result = krb5_mk_req_extended(Z_krb5_ctx, &authctx, 0 /* options */,
0 /* in_data */, creds, authent);
krb5_auth_con_free(Z_krb5_ctx, authctx);
}
if (!result || result == KRB5KRB_AP_ERR_TKT_EXPIRED) {
notice->z_auth = 1;
if (result == 0) {
notice->z_authent_len = authent->length;
} else {
notice->z_authent_len = 0;
result = 0;
}
zcode_len = notice->z_authent_len * 2 + 2; /* 2x growth plus Z and null */
notice->z_ascii_authent = (char *)malloc(zcode_len);
if (notice->z_ascii_authent == NULL)
result = ENOMEM;
}
if (!result)
result = ZMakeZcode(notice->z_ascii_authent, zcode_len,
(unsigned char *)authent->data, notice->z_authent_len);
/* format the notice header, with a zero checksum */
if (!result)
result = Z_NewFormatRawHeader(notice, buffer, buffer_len, phdr_len,
&cksum_start, &cksum_len, &cstart, &cend);
notice->z_authent_len = 0;
if (!result)
result = Z_InsertZcodeChecksum(keyblock, notice, buffer, cksum_start,
cksum_len, cstart, cend, buffer_len,
&phdr_adj, 0);
if (!result)
*phdr_len += phdr_adj;
if (notice->z_ascii_authent != NULL)
free(notice->z_ascii_authent);
krb5_free_data_contents(Z_krb5_ctx, authent);
if (authent != NULL)
free(authent);
return result;
}
int
ZGetCreds(krb5_creds **creds_out)
{
return ZGetCredsRealm(creds_out, __Zephyr_realm);
}
int
ZGetCredsRealm(krb5_creds **creds_out,
char *realm)
{
krb5_creds creds_in;
krb5_creds creds_tmp;
krb5_ccache ccache; /* XXX make this a global or static?*/
int result;
result = krb5_cc_default(Z_krb5_ctx, &ccache);
if (result)
return result;
memset((char *)&creds_in, 0, sizeof(creds_in));
result = krb5_build_principal(Z_krb5_ctx, &creds_in.server,
strlen(realm),
realm,
SERVER_SERVICE, SERVER_INSTANCE,
NULL);
if (result) {
krb5_cc_close(Z_krb5_ctx, ccache);
return result;
}
result = krb5_cc_get_principal(Z_krb5_ctx, ccache, &creds_in.client);
if (!result) {
result = krb5_cc_retrieve_cred(Z_krb5_ctx, ccache,
#ifdef KRB5_TC_SUPPORTED_KTYPES
KRB5_TC_SUPPORTED_KTYPES, /* MIT */
#else
0, /* Heimdal or other Space KRB5 */
#endif
&creds_in, &creds_tmp);
if (!result) {
*creds_out = malloc(sizeof(creds_tmp));
if (*creds_out == NULL)
result = errno;
else
memcpy(*creds_out, &creds_tmp, sizeof(creds_tmp));
}
}
if (result == KRB5_CC_NOTFOUND || result == KRB5_CC_END)
result = krb5_get_credentials(Z_krb5_ctx, 0, ccache, &creds_in, creds_out);
krb5_cc_close(Z_krb5_ctx, ccache);
krb5_free_cred_contents(Z_krb5_ctx, &creds_in); /* I also hope this is ok */
return result;
}
#endif