#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <math.h>
#include <stdio.h>
#include "ntp.h"
#include "ntp_fp.h"
#include "ntpd.h"
#include "ntp_lists.h"
#include "ntp_string.h"
#include "ntp_malloc.h"
#include "ntp_stdlib.h"
#include "ntp_keyacc.h"
typedef struct savekey symkey;
struct savekey {
symkey * hlink;
DECL_DLIST_LINK(symkey, llink);
u_char * secret;
KeyAccT * keyacclist;
u_long lifetime;
keyid_t keyid;
u_short type;
size_t secretsize;
u_short flags;
};
#define symkey_payload secret
#define KEY_TRUSTED 0x001
#ifdef DEBUG
typedef struct symkey_alloc_tag symkey_alloc;
struct symkey_alloc_tag {
symkey_alloc * link;
void * mem;
};
symkey_alloc * authallocs;
#endif
static u_short auth_log2(size_t);
static void auth_resize_hashtable(void);
static void allocsymkey(keyid_t, u_short,
u_short, u_long, size_t, u_char *, KeyAccT *);
static void freesymkey(symkey *);
#ifdef DEBUG
static void free_auth_mem(void);
#endif
symkey key_listhead; ;
#define DEF_AUTHHASHSIZE 64
#define KEYHASH(keyid) ((keyid) & authhashmask)
int authhashdisabled;
u_short authhashbuckets = DEF_AUTHHASHSIZE;
u_short authhashmask = DEF_AUTHHASHSIZE - 1;
symkey **key_hash;
u_long authkeynotfound;
u_long authkeylookups;
u_long authnumkeys;
u_long authkeyexpired;
u_long authkeyuncached;
u_long authnokey;
u_long authencryptions;
u_long authdecryptions;
symkey *authfreekeys;
int authnumfreekeys;
#define MEMINC 16
keyid_t cache_keyid;
u_char *cache_secret;
size_t cache_secretsize;
int cache_type;
u_short cache_flags;
KeyAccT *cache_keyacclist;
KeyAccT*
keyacc_new_push(
KeyAccT * head,
const sockaddr_u * addr
)
{
KeyAccT * node = emalloc(sizeof(KeyAccT));
memcpy(&node->addr, addr, sizeof(sockaddr_u));
node->next = head;
return node;
}
KeyAccT*
keyacc_pop_free(
KeyAccT *head
)
{
KeyAccT * next = NULL;
if (head) {
next = head->next;
free(head);
}
return next;
}
KeyAccT*
keyacc_all_free(
KeyAccT * head
)
{
while (head)
head = keyacc_pop_free(head);
return head;
}
int
keyacc_contains(
const KeyAccT *head,
const sockaddr_u *addr,
int defv)
{
if (head) {
do {
if (SOCK_EQ(&head->addr, addr))
return TRUE;
} while (NULL != (head = head->next));
return FALSE;
} else {
return !!defv;
}
}
void
init_auth(void)
{
size_t newalloc;
newalloc = authhashbuckets * sizeof(key_hash[0]);
key_hash = erealloc(key_hash, newalloc);
memset(key_hash, '\0', newalloc);
INIT_DLIST(key_listhead, llink);
#ifdef DEBUG
atexit(&free_auth_mem);
#endif
}
#ifdef DEBUG
static void
free_auth_mem(void)
{
symkey * sk;
symkey_alloc * alloc;
symkey_alloc * next_alloc;
while (NULL != (sk = HEAD_DLIST(key_listhead, llink))) {
freesymkey(sk);
}
free(key_hash);
key_hash = NULL;
cache_keyid = 0;
cache_flags = 0;
cache_keyacclist = NULL;
for (alloc = authallocs; alloc != NULL; alloc = next_alloc) {
next_alloc = alloc->link;
free(alloc->mem);
}
authfreekeys = NULL;
authnumfreekeys = 0;
}
#endif
void
auth_moremem(
int keycount
)
{
symkey * sk;
int i;
#ifdef DEBUG
void * base;
symkey_alloc * allocrec;
# define MOREMEM_EXTRA_ALLOC (sizeof(*allocrec))
#else
# define MOREMEM_EXTRA_ALLOC (0)
#endif
i = (keycount > 0)
? keycount
: MEMINC;
sk = eallocarrayxz(i, sizeof(*sk), MOREMEM_EXTRA_ALLOC);
#ifdef DEBUG
base = sk;
#endif
authnumfreekeys += i;
for (; i > 0; i--, sk++) {
LINK_SLIST(authfreekeys, sk, llink.f);
}
#ifdef DEBUG
allocrec = (void *)sk;
allocrec->mem = base;
LINK_SLIST(authallocs, allocrec, link);
#endif
}
void
auth_prealloc_symkeys(
int keycount
)
{
int allocated;
int additional;
allocated = authnumkeys + authnumfreekeys;
additional = keycount - allocated;
if (additional > 0)
auth_moremem(additional);
auth_resize_hashtable();
}
static u_short
auth_log2(size_t x)
{
int s;
int r = 0;
size_t m = ~(size_t)0;
for (s = sizeof(size_t) / 2 * CHAR_BIT; s != 0; s >>= 1) {
m <<= s;
if (x & m)
r += s;
else
x <<= s;
}
return (u_short)r;
}
static void
authcache_flush_id(
keyid_t id
)
{
if (cache_keyid == id) {
cache_keyid = 0;
cache_type = 0;
cache_flags = 0;
cache_secret = NULL;
cache_secretsize = 0;
cache_keyacclist = NULL;
}
}
static void
auth_resize_hashtable(void)
{
u_long totalkeys;
u_short hashbits;
u_short hash;
size_t newalloc;
symkey * sk;
totalkeys = authnumkeys + authnumfreekeys;
hashbits = auth_log2(totalkeys / 4) + 1;
hashbits = max(4, hashbits);
hashbits = min(15, hashbits);
authhashbuckets = 1 << hashbits;
authhashmask = authhashbuckets - 1;
newalloc = authhashbuckets * sizeof(key_hash[0]);
key_hash = erealloc(key_hash, newalloc);
memset(key_hash, '\0', newalloc);
ITER_DLIST_BEGIN(key_listhead, sk, llink, symkey)
hash = KEYHASH(sk->keyid);
LINK_SLIST(key_hash[hash], sk, hlink);
ITER_DLIST_END()
}
static void
allocsymkey(
keyid_t id,
u_short flags,
u_short type,
u_long lifetime,
size_t secretsize,
u_char * secret,
KeyAccT * ka
)
{
symkey * sk;
symkey ** bucket;
bucket = &key_hash[KEYHASH(id)];
if (authnumfreekeys < 1)
auth_moremem(-1);
UNLINK_HEAD_SLIST(sk, authfreekeys, llink.f);
DEBUG_ENSURE(sk != NULL);
sk->keyid = id;
sk->flags = flags;
sk->type = type;
sk->secretsize = secretsize;
sk->secret = secret;
sk->keyacclist = ka;
sk->lifetime = lifetime;
LINK_SLIST(*bucket, sk, hlink);
LINK_TAIL_DLIST(key_listhead, sk, llink);
authnumfreekeys--;
authnumkeys++;
}
static void
freesymkey(
symkey * sk
)
{
symkey ** bucket;
symkey * unlinked;
if (NULL == sk)
return;
authcache_flush_id(sk->keyid);
keyacc_all_free(sk->keyacclist);
bucket = &key_hash[KEYHASH(sk->keyid)];
if (sk->secret != NULL) {
memset(sk->secret, '\0', sk->secretsize);
free(sk->secret);
}
UNLINK_SLIST(unlinked, *bucket, sk, hlink, symkey);
DEBUG_ENSURE(sk == unlinked);
UNLINK_DLIST(sk, llink);
memset((char *)sk + offsetof(symkey, symkey_payload), '\0',
sizeof(*sk) - offsetof(symkey, symkey_payload));
LINK_SLIST(authfreekeys, sk, llink.f);
authnumkeys--;
authnumfreekeys++;
}
struct savekey *
auth_findkey(
keyid_t id
)
{
symkey * sk;
for (sk = key_hash[KEYHASH(id)]; sk != NULL; sk = sk->hlink)
if (id == sk->keyid)
return sk;
return NULL;
}
int
auth_havekey(
keyid_t id
)
{
return
(0 == id) ||
(cache_keyid == id) ||
(NULL != auth_findkey(id));
}
int
authhavekey(
keyid_t id
)
{
symkey * sk;
authkeylookups++;
if (0 == id || cache_keyid == id)
return !!(KEY_TRUSTED & cache_flags);
authkeyuncached++;
sk = auth_findkey(id);
if ((sk == NULL) || (sk->type == 0)) {
authkeynotfound++;
return FALSE;
}
if ( ! (KEY_TRUSTED & sk->flags)) {
authnokey++;
return FALSE;
}
cache_keyid = sk->keyid;
cache_type = sk->type;
cache_flags = sk->flags;
cache_secret = sk->secret;
cache_secretsize = sk->secretsize;
cache_keyacclist = sk->keyacclist;
return TRUE;
}
void
authtrust(
keyid_t id,
u_long trust
)
{
symkey * sk;
u_long lifetime;
sk = auth_findkey(id);
if (!trust && sk == NULL)
return;
if (sk != NULL) {
authcache_flush_id(id);
if (trust > 0) {
sk->flags |= KEY_TRUSTED;
if (trust > 1)
sk->lifetime = current_time + trust;
else
sk->lifetime = 0;
} else {
freesymkey(sk);
}
return;
}
if (trust > 1) {
lifetime = current_time + trust;
} else {
lifetime = 0;
}
allocsymkey(id, KEY_TRUSTED, 0, lifetime, 0, NULL, NULL);
}
int
authistrusted(
keyid_t id
)
{
symkey * sk;
if (id == cache_keyid)
return !!(KEY_TRUSTED & cache_flags);
authkeyuncached++;
sk = auth_findkey(id);
if (sk == NULL || !(KEY_TRUSTED & sk->flags)) {
authkeynotfound++;
return FALSE;
}
return TRUE;
}
int
authistrustedip(
keyid_t keyno,
sockaddr_u * sau
)
{
symkey * sk;
if (keyno == cache_keyid) {
return (KEY_TRUSTED & cache_flags) &&
keyacc_contains(cache_keyacclist, sau, TRUE);
} else {
authkeyuncached++;
sk = auth_findkey(keyno);
INSIST(NULL != sk);
return (KEY_TRUSTED & sk->flags) &&
keyacc_contains(sk->keyacclist, sau, TRUE);
}
}
void
MD5auth_setkey(
keyid_t keyno,
int keytype,
const u_char *key,
size_t secretsize,
KeyAccT *ka
)
{
symkey * sk;
u_char * secret;
DEBUG_ENSURE(keytype <= USHRT_MAX);
DEBUG_ENSURE(secretsize < 4 * 1024);
sk = auth_findkey(keyno);
if (sk != NULL && keyno == sk->keyid) {
if (NULL != sk->secret) {
memset(sk->secret, 0, sk->secretsize);
free(sk->secret);
}
sk->secret = emalloc(secretsize + 1);
sk->type = (u_short)keytype;
sk->secretsize = secretsize;
if (ka != sk->keyacclist) {
keyacc_all_free(sk->keyacclist);
sk->keyacclist = ka;
}
#ifndef DISABLE_BUG1243_FIX
memcpy(sk->secret, key, secretsize);
#else
strncpy((char *)sk->secret, (const char *)key,
secretsize);
#endif
authcache_flush_id(keyno);
return;
}
secret = emalloc(secretsize + 1);
#ifndef DISABLE_BUG1243_FIX
memcpy(secret, key, secretsize);
#else
strncpy((char *)secret, (const char *)key, secretsize);
#endif
allocsymkey(keyno, 0, (u_short)keytype, 0,
secretsize, secret, ka);
#ifdef DEBUG
if (debug >= 4) {
size_t j;
printf("auth_setkey: key %d type %d len %d ", (int)keyno,
keytype, (int)secretsize);
for (j = 0; j < secretsize; j++) {
printf("%02x", secret[j]);
}
printf("\n");
}
#endif
}
void
auth_delkeys(void)
{
symkey * sk;
ITER_DLIST_BEGIN(key_listhead, sk, llink, symkey)
if (sk->keyid > NTP_MAXKEY) {
continue;
}
if (KEY_TRUSTED & sk->flags) {
if (sk->secret != NULL) {
memset(sk->secret, 0, sk->secretsize);
free(sk->secret);
sk->secret = NULL;
}
sk->keyacclist = keyacc_all_free(sk->keyacclist);
sk->secretsize = 0;
sk->lifetime = 0;
} else {
freesymkey(sk);
}
ITER_DLIST_END()
}
void
auth_agekeys(void)
{
symkey * sk;
ITER_DLIST_BEGIN(key_listhead, sk, llink, symkey)
if (sk->lifetime > 0 && current_time > sk->lifetime) {
freesymkey(sk);
authkeyexpired++;
}
ITER_DLIST_END()
DPRINTF(1, ("auth_agekeys: at %lu keys %lu expired %lu\n",
current_time, authnumkeys, authkeyexpired));
}
size_t
authencrypt(
keyid_t keyno,
u_int32 * pkt,
size_t length
)
{
authencryptions++;
pkt[length / 4] = htonl(keyno);
if (0 == keyno) {
return 4;
}
if (!authhavekey(keyno)) {
return 0;
}
return MD5authencrypt(cache_type, cache_secret, pkt, length);
}
int
authdecrypt(
keyid_t keyno,
u_int32 * pkt,
size_t length,
size_t size
)
{
authdecryptions++;
if (0 == keyno || !authhavekey(keyno) || size < 4) {
return FALSE;
}
return MD5authdecrypt(cache_type, cache_secret, pkt, length,
size);
}