233 lines
6.6 KiB
C

/* This file is part of the Project Athena Zephyr Notification System.
* It contains source for the ZCheckAuthentication function.
*
* Created by: Robert French
*
* $Source$
* $Author$
*
* Copyright (c) 1987,1991 by the Massachusetts Institute of Technology.
* For copying and distribution information, see the file
* "mit-copyright.h".
*/
/* $Header$ */
#ifndef lint
static const char rcsid_ZCheckAuthentication_c[] =
"$Id$";
#endif
#include <internal.h>
#if defined(HAVE_KRB5) && !HAVE_KRB5_FREE_DATA
#define krb5_free_data(ctx, dat) free((dat)->data)
#endif
/* Check authentication of the notice.
If it looks authentic but fails the Kerberos check, return -1.
If it looks authentic and passes the Kerberos check, return 1.
If it doesn't look authentic, return 0
When not using Kerberos, return true if the notice claims to be authentic.
Only used by clients; the server uses its own routine.
*/
#ifdef HAVE_KRB5
static Code_t Z_CheckZcodeAuthentication(ZNotice_t *notice,
struct sockaddr_in *from,
krb5_keyblock *keyblock)
{
krb5_error_code result;
krb5_enctype enctype;
krb5_cksumtype cksumtype;
krb5_data cksumbuf;
int valid;
char *cksum0_base, *cksum1_base = NULL, *cksum2_base;
char *x;
unsigned char *asn1_data, *key_data, *cksum_data;
int asn1_len, key_len, cksum0_len = 0, cksum1_len = 0, cksum2_len = 0;
/* Figure out what checksum type to use */
key_data = Z_keydata(keyblock);
key_len = Z_keylen(keyblock);
result = Z_ExtractEncCksum(keyblock, &enctype, &cksumtype);
if (result) {
return (ZAUTH_FAILED);
}
/* Assemble the things to be checksummed */
/* first part is from start of packet through z_default_format:
* - z_version
* - z_num_other_fields
* - z_kind
* - z_uid
* - z_port
* - z_auth
* - z_authent_len
* - z_ascii_authent
* - z_class
* - z_class_inst
* - z_opcode
* - z_sender
* - z_recipient
* - z_default_format
*/
cksum0_base = notice->z_packet;
x = notice->z_default_format;
cksum0_len = x + strlen(x) + 1 - cksum0_base;
/* second part is from z_multinotice through other fields:
* - z_multinotice
* - z_multiuid
* - z_sender_(sock)addr
* - z_charset
* - z_other_fields[]
*/
if (notice->z_num_hdr_fields > 15 ) {
cksum1_base = notice->z_multinotice;
if (notice->z_num_other_fields)
x = notice->z_other_fields[notice->z_num_other_fields - 1];
else {
/* see also server/kstuff.c:ZCheck{Realm,}Authentication */
/* XXXXXXXXXXXXXXXXXXXXXXX */
if (notice->z_num_hdr_fields > 16)
x = cksum1_base + strlen(cksum1_base) + 1; /* multinotice */
if (notice->z_num_hdr_fields > 17)
x = x + strlen(x) + 1; /* multiuid */
if (notice->z_num_hdr_fields > 18)
x = x + strlen(x) + 1; /* sender */
}
cksum1_len = x + strlen(x) + 1 - cksum1_base; /* charset / extra field */
}
/* last part is the message body */
cksum2_base = notice->z_message;
cksum2_len = notice->z_message_len;
/* The following code checks for old-style checksums, which will go
away once Kerberos 4 does. */
if ((!notice->z_ascii_checksum || *notice->z_ascii_checksum != 'Z') &&
key_len == 8 &&
(enctype == (krb5_enctype)ENCTYPE_DES_CBC_CRC ||
enctype == (krb5_enctype)ENCTYPE_DES_CBC_MD4 ||
enctype == (krb5_enctype)ENCTYPE_DES_CBC_MD5)) {
/* try old-format checksum (covers cksum0 only) */
ZChecksum_t our_checksum;
our_checksum = z_quad_cksum((unsigned char *)cksum0_base, NULL, cksum0_len, 0,
key_data);
if (our_checksum == notice->z_checksum) {
return ZAUTH_YES;
}
}
cksumbuf.length = cksum0_len + cksum1_len + cksum2_len;
cksumbuf.data = malloc(cksumbuf.length);
if (!cksumbuf.data) {
return ZAUTH_NO;
}
/* HOLDING: cksumbuf.data */
cksum_data = (unsigned char *)cksumbuf.data;
memcpy(cksum_data, cksum0_base, cksum0_len);
if (cksum1_len)
memcpy(cksum_data + cksum0_len, cksum1_base, cksum1_len);
memcpy(cksum_data + cksum0_len + cksum1_len,
cksum2_base, cksum2_len);
/* decode zcoded checksum */
/* The encoded form is always longer than the original */
asn1_len = strlen(notice->z_ascii_checksum) + 1;
asn1_data = malloc(asn1_len);
if (!asn1_data) {
free(cksumbuf.data);
return ZAUTH_FAILED;
}
/* HOLDING: asn1_data, cksumbuf.data */
result = ZReadZcode((unsigned char *)notice->z_ascii_checksum,
asn1_data, asn1_len, &asn1_len);
if (result != ZERR_NONE) {
free(asn1_data);
free(cksumbuf.data);
return ZAUTH_FAILED;
}
/* HOLDING: asn1_data, cksumbuf.data */
valid = Z_krb5_verify_cksum(keyblock, &cksumbuf, cksumtype,
Z_KEYUSAGE_SRV_CKSUM, asn1_data, asn1_len);
free(asn1_data);
free(cksumbuf.data);
if (valid)
return ZAUTH_YES;
else
return ZAUTH_FAILED;
}
#endif
Code_t ZCheckZcodeAuthentication(ZNotice_t *notice,
struct sockaddr_in *from)
{
#ifdef HAVE_KRB5
Code_t answer;
krb5_creds *creds;
struct _Z_SessionKey *savedkey, *todelete;
#endif
/* If the value is already known, return it. */
if (notice->z_checked_auth != ZAUTH_UNSET)
return (notice->z_checked_auth);
if (!notice->z_auth)
return (ZAUTH_NO);
if (!notice->z_ascii_checksum)
return (ZAUTH_NO);
#ifdef HAVE_KRB5
/* Try each of the saved session keys. */
for (savedkey = Z_keys_head; savedkey != NULL; savedkey = savedkey->next) {
answer = Z_CheckZcodeAuthentication(notice, from, savedkey->keyblock);
if (answer == ZAUTH_YES) {
/* Save the time of the first use of each key. */
if (!savedkey->first_use) {
savedkey->first_use = time(NULL);
} else {
/*
* Any keys sent sufficiently long before this one is stale. If
* we know it has been long enough since the server learned of
* this key, we can prune keys made stale by this one.
*/
if (time(NULL) > savedkey->first_use + KEY_TIMEOUT) {
while (Z_keys_tail &&
Z_keys_tail->send_time + KEY_TIMEOUT < savedkey->send_time) {
todelete = Z_keys_tail;
Z_keys_tail = Z_keys_tail->prev;
Z_keys_tail->next = NULL;
krb5_free_keyblock(Z_krb5_ctx, todelete->keyblock);
free(todelete);
}
}
}
return answer;
}
}
/*
* If each of those fails, pull from the ccache. This is to preserve the
* behavior of things like zwgc/zctl where another program actually
* generates the subscription notices.
*/
if (ZGetCreds(&creds))
return ZAUTH_NO;
answer = Z_CheckZcodeAuthentication(notice, from, Z_credskey(creds));
krb5_free_creds(Z_krb5_ctx, creds);
return answer;
#else
return (notice->z_auth ? ZAUTH_YES : ZAUTH_NO);
#endif
}