/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 1997 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <thread.h>
#include <synch.h>
#include <stdlib.h>
#include <syslog.h>
#include <rpc/des_crypt.h>
#include "keyserv_cache.h"
struct cachekey {
struct cachekey_header *ch;
keylen_t keylen;
algtype_t algtype;
mutex_t mp;
struct cachekey *next;
};
static struct cachekey *cache = 0;
static mutex_t cache_lock = DEFAULTMUTEX;
static cond_t cache_cv = DEFAULTCV;
static u_long cache_refcnt = 0;
struct skck {
des_block common[3];
des_block verifier; /* Checksum */
struct dhkey secret;
};
struct cachekey_disklist {
uid_t uid;
struct cachekey_disklist *prev; /* LRU order */
struct cachekey_disklist *next;
struct cachekey_disklist *prevhash; /* Hash chain */
struct cachekey_disklist *nexthash;
struct dhkey public;
/*
* Storage for encrypted skck structure here. The length will be
* 8 * ( ( ( sizeof(struct skck) - 1 + secret.length ) - 1 ) / 8 + 1 )
*/
};
/* Length of skck structure for given key length (in bits) */
#define SKCK_LEN(keylen) ALIGN8(sizeof (struct skck) + KEYLEN(keylen))
/* Length of a cachekey_disklist record for given key length (in bits) */
#define CACHEKEY_RECLEN(keylen) ALIGN8(sizeof (struct cachekey_disklist) - 1 + \
KEYLEN(keylen) + SKCK_LEN(keylen))
#define NUMHASHBUCKETS 253
#define CHUNK_NUMREC 64
#define CACHEKEY_HEADER_VERSION 0
struct cachekey_header { /* First in each key cache file */
u_int version; /* version number of interface */
u_int headerlength; /* size of this header */
keylen_t keylen; /* in bits */
algtype_t algtype; /* algorithm type */
size_t reclength; /* cache file record size in bytes */
int fd; /* file descriptor */
caddr_t address; /* mmap()ed here */
size_t length; /* bytes mapped */
size_t maxsize; /* don't grow beyond this */
u_int inuse_count;
struct cachekey_disklist *inuse; /* LRU order */
struct cachekey_disklist *inuse_end;
u_int free_count;
struct cachekey_disklist *free;
struct cachekey_disklist *bucket[NUMHASHBUCKETS];
struct cachekey_disklist array[1]; /* Start of array */
};
static struct cachekey_header *create_cache_file_ch(keylen_t keylen,
algtype_t algtype,
int sizespec);
static struct cachekey_header *remap_cache_file_ch(struct cachekey_header *ch,
u_int newrecs);
static struct cachekey_header *cache_insert_ch(struct cachekey_header *ch,
uid_t uid, deskeyarray common,
des_block key,
keybuf3 *public,
keybuf3 *secret);
static struct cachekey3_list *cache_retrieve_ch(struct cachekey_header *ch,
uid_t uid,
keybuf3 *public,
des_block key);
static int cache_remove_ch(struct cachekey_header *ch,
uid_t uid,
keybuf3 *public);
static struct cachekey *get_cache_header(keylen_t keylen,
algtype_t algtype);
static void release_cache_header(struct cachekey *);
static int cache_remap_addresses_ch(
struct cachekey_header *);
static struct cachekey_disklist *find_cache_item(struct cachekey_header **,
uid_t, struct dhkey *);
static struct dhkey *keybuf3_2_dhkey(keybuf3 *);
static u_int hashval(uid_t);
static void list_remove(struct cachekey_disklist *,
struct cachekey_disklist **,
struct cachekey_disklist **,
u_int *);
static void list_remove_hash(struct cachekey_disklist *,
struct cachekey_disklist **,
struct cachekey_disklist **,
u_int *);
static void list_insert(struct cachekey_disklist *,
struct cachekey_disklist **,
struct cachekey_disklist **,
u_int *);
static void list_insert_hash(struct cachekey_disklist *,
struct cachekey_disklist **,
struct cachekey_disklist **,
u_int *);
static struct cachekey3_list * copy_cl_item(struct cachekey_header *ch,
struct cachekey_disklist *cd,
des_block key);
extern int hex2bin(u_char *, u_char *, int);
extern int bin2hex(u_char *, u_char *, int);
/*
* The folowing set of macros implement address validity checking. A valid
* address is defined to be either 0, or to fall on a record boundary. In
* the latter case, the the difference between the address and the start of
* the record array is divisible by the record length.
*/
#define FILEOFFSET(ckh) ((u_long)(ckh) - \
(u_long)((ckh)->address))
#define ADJUSTEDADDR(addr, ckh) ((u_long)(addr) + FILEOFFSET(ckh))
#define ARRAYOFFSET(addr, ckh) (ADJUSTEDADDR(addr, ckh) - \
(u_long)&((ckh)->array[0]))
#define INVALID_ADDRESS(addr, ckh) ((addr == 0) ? 0 : \
(ARRAYOFFSET(addr, ckh) % (ckh)->reclength) != 0)
/* Add offset to old address */
#define MOVE_ADDR(old, offset) ((old) == 0) ? 0 : \
(void *)((u_long)(old) + (offset))
/* Number of records in use or on free list */
#define NUMRECS(ck_header) ((ck_header)->inuse_count + \
(ck_header)->free_count)
/* Max number of records the mapped file could hold */
#define MAPRECS(ck_header) (((ck_header)->length - \
sizeof (struct cachekey_header)) / \
(ck_header)->reclength)
/* Max number of records the file will hold if extended to the maxsize */
#define MAXRECS(ck_header) (((ck_header)->maxsize - \
sizeof (struct cachekey_header)) / \
(ck_header)->reclength)
struct cachekey_header *
create_cache_file_ch(keylen_t keylen, algtype_t algtype, int sizespec)
{
char filename[MAXPATHLEN];
struct cachekey_header *ch;
int fd, newfile = 0, i, checkvalid = 1;
struct stat statbuf;
size_t reclength, length;
struct cachekey_header *oldbase = 0;
struct cachekey_disklist *cd;
size_t maxsize;
/* Construct cache file name */
if (snprintf(filename, sizeof (filename), "/var/nis/.keyserv_%d-%d",
keylen, algtype) > sizeof (filename)) {
syslog(LOG_WARNING,
"error constructing file name for mech %d-%d", keylen, algtype);
return (0);
}
/* Open/create the file */
if ((fd = open(filename, O_RDWR|O_CREAT, 0600)) < 0) {
syslog(LOG_WARNING, "cache file open error for mech %d-%d: %m",
keylen, algtype);
return (0);
}
/* We want exclusive use of the file */
if (lockf(fd, F_LOCK, 0) < 0) {
syslog(LOG_WARNING, "cache file lock error for mech %d-%d: %m",
keylen, algtype);
close(fd);
return (0);
}
/* Zero size means a new file */
if (fstat(fd, &statbuf) < 0) {
syslog(LOG_WARNING, "cache file fstat error for mech %d-%d: %m",
keylen, algtype);
close(fd);
return (0);
}
reclength = CACHEKEY_RECLEN(keylen);
if (sizespec < 0) {
/* specifies the number of records in file */
maxsize = ALIGN8(sizeof (struct cachekey_header)) +
-sizespec*reclength;
} else {
/* specifies size of file in MB */
maxsize = sizespec*1024*1024;
}
length = ALIGN8(sizeof (struct cachekey_header)) +
reclength*CHUNK_NUMREC;
if (length > maxsize) {
/*
* First record resides partly in the header, so the length
* cannot be allowed to be less than header plus one record.
*/
if (maxsize > ALIGN8(sizeof (struct cachekey_header)+reclength))
length = maxsize;
else {
length = ALIGN8(sizeof (struct cachekey_header)+
reclength);
maxsize = length;
}
}
if (statbuf.st_size == 0) {
/* Extend the file if we just created it */
if (ftruncate(fd, length) < 0) {
syslog(LOG_WARNING,
"cache file ftruncate error for mech %d-%d: %m",
keylen, algtype);
close(fd);
return (0);
}
newfile = 1;
} else {
/*
* Temporarily mmap the header, to sanity check and obtain
* the address where it was mapped the last time.
*/
if ((ch = (void *)mmap(0, sizeof (struct cachekey_header),
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) ==
MAP_FAILED) {
syslog(LOG_WARNING,
"cache file mmap1 error for mech %d-%d: %m",
keylen, algtype);
close(fd);
return (0);
}
if (ch->version != CACHEKEY_HEADER_VERSION ||
ch->headerlength != sizeof (struct cachekey_header) ||
ch->keylen != keylen ||
ch->algtype != algtype ||
ch->reclength != reclength ||
ch->length < sizeof (struct cachekey_header) ||
ch->maxsize < ch->length ||
INVALID_ADDRESS(ch->inuse, ch) ||
INVALID_ADDRESS(ch->free, ch)) {
syslog(LOG_WARNING,
"cache file consistency error for mech %d-%d",
keylen, algtype);
munmap((caddr_t)ch, sizeof (struct cachekey_header));
close(fd);
return (0);
}
oldbase = (void *)ch->address;
length = ch->length;
if (munmap((caddr_t)ch, sizeof (struct cachekey_header)) < 0) {
syslog(LOG_WARNING,
"cache file munmap error for mech %d-%d: %m",
keylen, algtype);
close(fd);
return (0);
}
}
/* Map the file */
if ((ch = (void *)mmap((caddr_t)oldbase, length,
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
syslog(LOG_WARNING,
"cache file mmap2 error for mech %d-%d: %m",
keylen, algtype);
close(fd);
return (0);
}
ch->fd = fd;
ch->maxsize = maxsize;
if (newfile) {
ch->version = CACHEKEY_HEADER_VERSION;
ch->headerlength = sizeof (struct cachekey_header);
ch->keylen = keylen;
ch->algtype = algtype;
ch->reclength = reclength;
ch->length = length;
ch->address = (caddr_t)ch;
ch->inuse_count = 0;
ch->inuse = 0;
ch->inuse_end = 0;
ch->free = 0;
ch->free_count = 0;
for (i = 0; i < NUMHASHBUCKETS; i++) {
ch->bucket[i] = 0;
}
cd = &(ch->array[0]);
for (i = 0; i < MAPRECS(ch);
i++, cd = MOVE_ADDR(cd, ch->reclength)) {
cd->uid = (uid_t)-1;
cd->prev = MOVE_ADDR(cd, -(ch->reclength));
cd->next = MOVE_ADDR(cd, +(ch->reclength));
cd->prevhash = 0;
cd->nexthash = 0;
}
/*
* Last record next pointer, and first record prev pointer,
* are both NULL.
*/
cd = MOVE_ADDR(cd, -(ch->reclength));
cd->next = 0;
cd = &(ch->array[0]);
cd->prev = 0;
ch->free_count = MAPRECS(ch);
ch->free = &(ch->array[0]);
(void) msync((caddr_t)ch, ch->length, MS_SYNC);
} else if (ch->length > maxsize) {
/* File should shrink */
if ((ch = remap_cache_file_ch(ch, MAXRECS(ch))) == 0) {
return (0);
}
checkvalid = 0;
}
/*
* cache_remap_addresses() also checks address consistency, so call
* it even if the remap is a no-op. However, if we've called
* remap_cache_file_ch(), it will have invoked cache_remap_addresses()
* already, so we don't have to do that again.
*/
if (checkvalid &&
cache_remap_addresses_ch(ch) == 0) {
syslog(LOG_WARNING, "cache file invalid for mech %d-%d",
keylen, algtype);
(void) munmap((caddr_t)ch, ch->length);
close(fd);
return (0);
}
(void) msync((caddr_t)ch, ch->length, MS_SYNC);
return (ch);
}
static int
cache_remap_addresses_ch(struct cachekey_header *ch)
{
int i;
u_long offset;
struct cachekey_disklist *cd;
offset = (u_long)ch - (u_long)ch->address;
if (INVALID_ADDRESS(ch->inuse, ch) ||
INVALID_ADDRESS(ch->inuse_end, ch) ||
INVALID_ADDRESS(ch->free, ch)) {
return (0);
}
ch->inuse = MOVE_ADDR(ch->inuse, offset);
ch->inuse_end = MOVE_ADDR(ch->inuse_end, offset);
ch->free = MOVE_ADDR(ch->free, offset);
cd = &(ch->array[0]);
for (i = 0; i < NUMRECS(ch); i++) {
if (INVALID_ADDRESS(cd->prev, ch) ||
INVALID_ADDRESS(cd->next, ch) ||
INVALID_ADDRESS(cd->prevhash, ch) ||
INVALID_ADDRESS(cd->nexthash, ch)) {
return (0);
}
cd->prev = MOVE_ADDR(cd->prev, offset);
cd->next = MOVE_ADDR(cd->next, offset);
cd->prevhash = MOVE_ADDR(cd->prevhash, offset);
cd->nexthash = MOVE_ADDR(cd->nexthash, offset);
cd = MOVE_ADDR(cd, ch->reclength);
}
for (i = 0; i < NUMHASHBUCKETS; i++) {
if (INVALID_ADDRESS(ch->bucket[i], ch)) {
return (0);
}
ch->bucket[i] = MOVE_ADDR(ch->bucket[i], offset);
}
/*
* To prevent disaster if this function is invoked again, we
* update ch->address, so that offset will be zero if we do
* get called once more, and the mapped file hasn't moved.
*/
ch->address = (caddr_t)ch;
return (1);
}
/*
* Remap cache file with space for 'newrecs' records. The mmap:ed address
* may have to move; the new address is returned.
*/
static struct cachekey_header *
remap_cache_file_ch(struct cachekey_header *ch, u_int newrecs)
{
size_t newsize, oldsize;
u_int currecs;
int i, fd;
struct cachekey_header *newch;
caddr_t oldaddr;
struct cachekey_disklist *cd = 0;
if (ch == 0)
return (0);
/*
* Since the first record partly resides in the cachekey_header,
* newrecs cannot be less than 1.
*/
if (newrecs < 1)
newrecs = 1;
newsize = ALIGN8(sizeof (struct cachekey_header)) +
(ch->reclength)*newrecs;
currecs = NUMRECS(ch);
if (newsize > ch->maxsize) {
/* Would exceed maximum allowed */
newsize = ch->maxsize;
}
/* Save stuff we need while the file is unmapped */
oldsize = ch->length;
oldaddr = (caddr_t)ch;
fd = ch->fd;
if (newsize > ch->length) {
/* Extending the file */
cd = &(ch->array[0]);
} else if (newsize == ch->length) {
/* Already OK */
return (ch);
} else {
size_t tmpsize;
struct cachekey_disklist *fcd;
/*
* Shrink the file by removing records from the end.
* First, we have to make sure the file contains valid
* addresses.
*/
if (cache_remap_addresses_ch(ch) == 0) {
syslog(LOG_WARNING, "cache file invalid for mech %d-%d",
ch->keylen, ch->algtype);
close(ch->fd);
munmap((caddr_t)ch, ch->length);
return (0);
}
fcd = MOVE_ADDR(&(ch->array[0]),
ch->reclength*(MAPRECS(ch)-1));
tmpsize = (u_long)fcd - (u_long)ch + ch->reclength;
while (tmpsize > newsize && fcd > &(ch->array[0])) {
if (fcd->uid == (uid_t)-1) {
list_remove(fcd, &(ch->free), 0,
&(ch->free_count));
} else {
list_remove_hash(fcd,
&(ch->bucket[hashval(fcd->uid)]), 0, 0);
list_remove(fcd, &(ch->inuse), &(ch->inuse_end),
&(ch->inuse_count));
}
tmpsize -= ch->reclength;
fcd = MOVE_ADDR(fcd, -(ch->reclength));
}
ch->length = newsize;
(void) msync((caddr_t)ch, ch->length, MS_SYNC);
}
/* Unmap the file */
if (munmap((caddr_t)oldaddr, oldsize) < 0) {
return (0);
}
ch = 0;
/* Truncate/extend it */
if (ftruncate(fd, newsize) < 0) {
return (0);
}
/* Map it again */
if ((newch = (void *)mmap(oldaddr, newsize,
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) ==
MAP_FAILED) {
return (0);
}
/* Update with new values */
newch->length = newsize;
if (cache_remap_addresses_ch(newch) == 0) {
syslog(LOG_WARNING, "cache file invalid for mech %d-%d",
newch->keylen, newch->algtype);
newch->length = oldsize;
close(newch->fd);
munmap((caddr_t)newch, newsize);
return (0);
}
/* If extending the file, add new records to the free list */
if (cd != 0) {
cd = MOVE_ADDR(&(newch->array[0]), currecs*newch->reclength);
for (i = currecs; i < MAPRECS(newch); i++) {
cd->uid = (uid_t)-1;
list_insert(cd, &(newch->free), 0,
&(newch->free_count));
cd = MOVE_ADDR(cd, newch->reclength);
}
}
(void) msync(newch->address, newch->length, MS_SYNC);
return (newch);
}
#ifdef DEBUG
void
print_cache_ch(struct cachekey_header *ch)
{
int i, inuse, inuse_err, free, free_err;
int pb;
struct cachekey_disklist *cd;
printf(
"\nkeylen = %d, algtype = %d, version = %d, headerlen = %d, reclen = %d\n",
ch->keylen, ch->algtype, ch->version, ch->headerlength,
ch->reclength);
printf("fd = %d, address = 0x%x, mapped length = %d, maxsize = %d\n",
ch->fd, ch->address, ch->length, ch->maxsize);
printf("inuse = %d, free = %d\n", ch->inuse_count, ch->free_count);
printf("Active hash buckets:\n");
for (i = 0, inuse = 0, inuse_err = 0; i < NUMHASHBUCKETS; i++) {
cd = ch->bucket[i];
pb = -1;
if (cd != 0) {
pb = 0;
printf("\t%d: ", i);
}
while (cd != 0) {
pb++;
printf("%d ", cd->uid);
if (cd->uid != (uid_t)-1) {
inuse++;
} else {
inuse_err++;
}
cd = cd->nexthash;
}
if (pb >= 0)
printf(" (%d)\n", pb);
}
printf("\ncounted hash inuse = %d, errors = %d\n", inuse, inuse_err);
cd = ch->inuse;
inuse = inuse_err = 0;
while (cd != 0) {
if (cd->uid != (uid_t)-1) {
inuse++;
} else {
inuse_err++;
}
cd = cd->next;
}
printf("counted LRU inuse = %d, errors = %d\n", inuse, inuse_err);
cd = ch->free;
free = free_err = 0;
while (cd != 0) {
if (cd->uid == (uid_t)-1) {
free++;
} else {
free_err++;
fprintf(stderr, "free = %d, err = %d, cd->uid = %d\n",
free, free_err, cd->uid);
}
cd = cd->next;
}
printf("counted free = %d, errors = %d\n", free, free_err);
}
void
print_cache(keylen_t keylen, algtype_t algtype)
{
struct cachekey *c;
if ((c = get_cache_header(keylen, algtype)) == 0)
return;
if (c->ch == 0) {
release_cache_header(c);
return;
}
print_cache_ch(c->ch);
release_cache_header(c);
}
#endif
static u_int
hashval(uid_t uid)
{
return (uid % NUMHASHBUCKETS);
}
static void
list_remove(
struct cachekey_disklist *item,
struct cachekey_disklist **head,
struct cachekey_disklist **tail,
u_int *count)
{
if (item == NULL) return;
/* Handle previous item, if any */
if (item->prev == 0)
*head = item->next;
else
item->prev->next = item->next;
/* Take care of the next item, if any */
if (item->next != 0)
item->next->prev = item->prev;
/* Handle tail pointer, if supplied */
if (tail != 0 && *tail == item)
*tail = item->prev;
item->prev = item->next = 0;
if (count != 0)
(*count)--;
}
static void
list_remove_hash(
struct cachekey_disklist *item,
struct cachekey_disklist **head,
struct cachekey_disklist **tail,
u_int *count)
{
if (item == NULL) return;
/* Handle previous item, if any */
if (item->prevhash == 0)
*head = item->nexthash;
else
item->prevhash->nexthash = item->nexthash;
/* Take care of the next item, if any */
if (item->nexthash != 0)
item->nexthash->prevhash = item->prevhash;
/* Handle tail pointer, if supplied */
if (tail != 0 && *tail == item)
*tail = item->prevhash;
item->prevhash = item->nexthash = 0;
if (count != 0)
(*count)--;
}
static void
list_insert(
struct cachekey_disklist *item,
struct cachekey_disklist **head,
struct cachekey_disklist **tail,
u_int *count)
{
if (item == NULL) return;
/* Insert at tail, if supplied */
if (tail != 0) {
item->prev = *tail;
if (item->prev != 0)
item->prev->next = item;
item->next = 0;
*tail = item;
if (*head == 0)
*head = item;
} else {
item->next = *head;
if (item->next != 0)
item->next->prev = item;
item->prev = 0;
*head = item;
}
if (count != 0)
(*count)++;
}
static void
list_insert_hash(
struct cachekey_disklist *item,
struct cachekey_disklist **head,
struct cachekey_disklist **tail,
u_int *count)
{
if (item == NULL) return;
/* Insert at tail, if supplied */
if (tail != 0) {
item->prevhash = *tail;
if (item->prevhash != 0)
item->prevhash->nexthash = item;
item->nexthash = 0;
*tail = item;
if (*head == 0)
*head = item;
} else {
item->nexthash = *head;
if (item->nexthash != 0)
item->nexthash->prevhash = item;
item->prevhash = 0;
*head = item;
}
if (count != 0)
(*count)++;
}
/*
* Find the cache item specified by the header, uid, and public key. If
* no such uid/public item exists, return a pointer to an empty record.
* In either case, the item returned has been removed from any and all
* lists.
*/
static struct cachekey_disklist *
find_cache_item(struct cachekey_header **ch, uid_t uid, struct dhkey *public)
{
u_int hash;
struct cachekey_disklist *cd;
hash = hashval(uid);
if ((ch == NULL) || ((*ch) == NULL)) {
return (0);
}
for (cd = (*ch)->bucket[hash]; cd != 0; cd = cd->nexthash) {
if (uid == cd->uid &&
public->length == cd->public.length &&
memcmp(public->key, cd->public.key,
cd->public.length) == 0) {
list_remove_hash(cd, &((*ch)->bucket[hash]), 0, 0);
list_remove(cd, &((*ch)->inuse), &((*ch)->inuse_end),
&((*ch)->inuse_count));
return (cd);
}
}
if ((cd = (*ch)->free) != 0) {
list_remove(cd, &((*ch)->free), 0, &((*ch)->free_count));
return (cd);
}
/* Try to extend the file by CHUNK_NUMREC records */
if (((*ch) = remap_cache_file_ch(*ch, NUMRECS(*ch)+CHUNK_NUMREC)) == 0)
return (0);
/* If the extend worked, there should now be at least one free record */
if ((cd = (*ch)->free) != 0) {
list_remove(cd, &((*ch)->free), 0, &((*ch)->free_count));
return (cd);
}
/* Sacrifice the LRU item, if there is one */
if ((cd = (*ch)->inuse) == 0)
return (0);
/* Extract from hash list */
list_remove_hash(cd, &((*ch)->bucket[hashval(cd->uid)]), 0, 0);
/* Extract from LRU list */
list_remove(cd, &((*ch)->inuse), &((*ch)->inuse_end),
&((*ch)->inuse_count));
return (cd);
}
static struct cachekey_header *
cache_insert_ch(
struct cachekey_header *ch,
uid_t uid,
deskeyarray common,
des_block key,
keybuf3 *public,
keybuf3 *secret)
{
struct cachekey_disklist *cd;
struct cachekey_header *newch;
int i, err;
struct skck *skck;
des_block ivec;
struct dhkey *pk;
struct dhkey *sk;
if (ch == 0 || uid == (uid_t)-1) {
return (0);
}
if (common.deskeyarray_len > sizeof (skck->common)/sizeof (des_block) ||
(pk = keybuf3_2_dhkey(public)) == 0 ||
(sk = keybuf3_2_dhkey(secret)) == 0) {
return (0);
}
newch = ch;
if ((cd = find_cache_item(&newch, uid, pk)) == 0) {
free(pk);
free(sk);
return (newch);
}
/*
* The item may have been free, or may have been the LRU sacrificial
* lamb, so reset all fields.
*/
cd->uid = uid;
memcpy(&(cd->public), pk, DHKEYSIZE(pk));
skck = MOVE_ADDR(&(cd->public), DHKEYSIZE(pk));
for (i = 0; i < common.deskeyarray_len; i++) {
skck->common[i] = common.deskeyarray_val[i];
}
skck->verifier = key;
memcpy(&(skck->secret), sk, DHKEYSIZE(sk));
free(pk);
free(sk);
memcpy(ivec.c, key.c, sizeof (key.c));
err = cbc_crypt(key.c, (char *)skck, SKCK_LEN(newch->keylen),
DES_ENCRYPT|DES_HW, ivec.c);
if (DES_FAILED(err)) {
/* Re-insert on free list */
list_insert(cd, &(newch->free), 0, &(newch->free_count));
return (newch);
}
/* Re-insert on hash list */
list_insert_hash(cd, &(newch->bucket[hashval(cd->uid)]), 0, 0);
/* Insert at end of LRU list */
list_insert(cd, &(newch->inuse), &(newch->inuse_end),
&(newch->inuse_count));
(void) msync((caddr_t)newch, newch->length, MS_SYNC);
return (newch);
}
static struct cachekey3_list *
copy_cl_item(struct cachekey_header *ch, struct cachekey_disklist *cd,
des_block key) {
struct cachekey3_list *cl;
struct skck *skck, *skck_cd;
int i, err;
des_block ivec;
/* Allocate the cachekey3_list structure */
if ((cl = malloc(CACHEKEY3_LIST_SIZE(ch->keylen))) == 0) {
return (0);
}
/* Allocate skck structure for decryption */
if ((skck = malloc(SKCK_LEN(ch->keylen))) == 0) {
free(cl);
return (0);
}
/* Decrypt and check verifier */
skck_cd = MOVE_ADDR(&(cd->public), DHKEYSIZE(&(cd->public)));
memcpy(skck, skck_cd, SKCK_LEN(ch->keylen));
memcpy(ivec.c, key.c, sizeof (ivec.c));
err = cbc_crypt(key.c, (char *)skck, SKCK_LEN(ch->keylen),
DES_DECRYPT|DES_HW, ivec.c);
if (DES_FAILED(err)) {
free(cl);
free(skck);
return (0);
}
if (memcmp(key.c, skck->verifier.c, sizeof (skck->verifier.c)) != 0) {
free(cl);
free(skck);
return (0);
}
/* Everything OK; copy values */
cl->public = MOVE_ADDR(cl, sizeof (struct cachekey3_list));
cl->public->keybuf3_val = MOVE_ADDR(cl->public, sizeof (keybuf3));
cl->secret = MOVE_ADDR(cl->public->keybuf3_val,
ALIGN4(2*KEYLEN(ch->keylen)+1));
cl->secret->keybuf3_val = MOVE_ADDR(cl->secret, sizeof (keybuf3));
cl->deskey.deskeyarray_val =
MOVE_ADDR(cl->secret->keybuf3_val,
ALIGN4(2*KEYLEN(ch->keylen)+1));
bin2hex(cd->public.key, (u_char *)cl->public->keybuf3_val,
cd->public.length);
cl->public->keybuf3_len = cd->public.length*2+1;
bin2hex(skck->secret.key, (u_char *)cl->secret->keybuf3_val,
skck->secret.length);
cl->secret->keybuf3_len = skck->secret.length*2+1;
cl->deskey.deskeyarray_len = sizeof (skck->common)/sizeof (des_block);
for (i = 0; i < cl->deskey.deskeyarray_len; i++) {
cl->deskey.deskeyarray_val[i] = skck->common[i];
}
cl->refcnt = 0;
cl->next = 0;
free(skck);
return (cl);
}
static struct cachekey3_list *
cache_retrieve_ch(struct cachekey_header *ch, uid_t uid, keybuf3 *public,
des_block key) {
struct cachekey_disklist *cd;
struct cachekey3_list *cl = 0, **cltmp = &cl;
u_int hash;
struct dhkey *pk = 0;
if (uid == (uid_t)-1 ||
(public != 0 && (pk = keybuf3_2_dhkey(public)) == 0)) {
return (0);
}
hash = hashval(uid);
for (cd = ch->bucket[hash]; cd != 0; cd = cd->nexthash) {
if (uid == cd->uid) {
/* Match on public key as well ? */
if (pk != 0) {
if (memcmp(cd->public.key, pk->key,
cd->public.length) != 0) {
/* Keep looking... */
continue;
}
cl = copy_cl_item(ch, cd, key);
/* Match on public key => nothing more to do */
break;
}
*cltmp = copy_cl_item(ch, cd, key);
if (*cltmp == 0) {
/* Return what we've got */
break;
}
cltmp = &((*cltmp)->next);
/* On to the next item */
}
}
if (pk != 0)
free(pk);
return (cl);
}
/*
* Remove specified item. 'public' == 0 => remove all items for uid.
* Return number of items removed.
*/
static int
cache_remove_ch(struct cachekey_header *ch, uid_t uid, keybuf3 *public) {
struct cachekey_disklist *cd, *cdtmp;
u_int hash;
int match = 0;
struct dhkey *pk = 0;
if (uid == (uid_t)-1 ||
(public != 0 && (pk = keybuf3_2_dhkey(public)) == 0)) {
return (0);
}
hash = hashval(uid);
for (cd = ch->bucket[hash]; cd != 0; ) {
if (uid == cd->uid) {
/* Match on public key as well ? */
if (pk != 0) {
if (memcmp(cd->public.key, pk->key,
cd->public.length) != 0) {
/* Keep looking... */
continue;
}
match++;
list_remove_hash(cd, &(ch->bucket[hash]), 0, 0);
list_remove(cd, &(ch->inuse), &(ch->inuse_end),
&(ch->inuse_count));
cd->uid = (uid_t)-1;
list_insert(cd, &(ch->free), 0,
&(ch->free_count));
/* Match on public key => nothing more to do */
break;
}
match++;
/*
* XXX: Assume that the order of the hash list remains
* the same after removal of an item. If this isn't
* true, we really should start over from the start
* of the hash bucket.
*/
cdtmp = cd->nexthash;
list_remove_hash(cd, &(ch->bucket[hash]), 0, 0);
list_remove(cd, &(ch->inuse), &(ch->inuse_end),
&(ch->inuse_count));
cd->uid = (uid_t)-1;
list_insert(cd, &(ch->free), 0,
&(ch->free_count));
/* On to the next item */
cd = cdtmp;
} else {
cd = cd->nexthash;
}
}
free(pk);
return (match);
}
#define INCCACHEREFCNT mutex_lock(&cache_lock); \
cache_refcnt++; \
mutex_unlock(&cache_lock)
#if !defined(lint) && !defined(__lint)
#define DECCACHEREFCNT mutex_lock(&cache_lock); \
if (cache_refcnt > 0) \
if (cache_refcnt-- == 0) (void) cond_broadcast(&cache_cv); \
mutex_unlock(&cache_lock)
#else
#define DECCACHEREFCNT mutex_lock(&cache_lock); \
if (cache_refcnt-- == 0) (void) cond_broadcast(&cache_cv); \
mutex_unlock(&cache_lock)
#endif
/*
* Return the cachekey structure for the specified keylen and algtype.
* When returned, the lock in the structure has been activated. It's the
* responsibility of the caller to unlock it by calling release_cache_header().
*/
static struct cachekey *
get_cache_header(keylen_t keylen, algtype_t algtype) {
struct cachekey *c;
INCCACHEREFCNT;
for (c = cache; c != 0; c = c->next) {
if (c->keylen == keylen && c->algtype == algtype) {
mutex_lock(&c->mp);
return (c);
}
}
/* Spin until there are no cache readers */
mutex_lock(&cache_lock);
#if !defined(lint) && !defined(__lint)
if (cache_refcnt > 0)
#endif
cache_refcnt--;
while (cache_refcnt != 0) {
(void) cond_wait(&cache_cv, &cache_lock);
}
if ((c = malloc(sizeof (struct cachekey))) != 0) {
c->ch = 0;
c->keylen = keylen;
c->algtype = algtype;
mutex_init(&c->mp, 0, 0);
c->next = cache;
cache = c;
mutex_lock(&c->mp);
cache_refcnt++;
mutex_unlock(&cache_lock);
return (c);
}
mutex_unlock(&cache_lock);
return (0);
}
static void
release_cache_header(struct cachekey *ck) {
struct cachekey *c;
if (ck == 0)
return;
for (c = cache; c != 0; c = c->next) {
if (c == ck) {
mutex_unlock(&c->mp);
DECCACHEREFCNT;
break;
}
}
}
int
create_cache_file(keylen_t keylen, algtype_t algtype, int sizespec)
{
struct cachekey *c;
int ret;
if ((c = get_cache_header(keylen, algtype)) == 0)
return (0);
if (c->ch != 0) {
/* Already created and opened */
release_cache_header(c);
return (1);
}
ret = (c->ch = create_cache_file_ch(keylen, algtype, sizespec)) != 0;
release_cache_header(c);
return (ret);
}
int
cache_insert(
keylen_t keylen,
algtype_t algtype,
uid_t uid,
deskeyarray common,
des_block key,
keybuf3 *public,
keybuf3 *secret)
{
struct cachekey *c;
int ret;
if ((c = get_cache_header(keylen, algtype)) == 0)
return (0);
if (c->ch == 0) {
release_cache_header(c);
return (0);
}
ret = (c->ch =
cache_insert_ch(c->ch, uid, common, key, public, secret)) != 0;
release_cache_header(c);
return (ret);
}
struct cachekey3_list *
cache_retrieve(
keylen_t keylen,
algtype_t algtype,
uid_t uid,
keybuf3 *public,
des_block key)
{
struct cachekey *c;
struct cachekey3_list *cl;
if ((c = get_cache_header(keylen, algtype)) == 0)
return (0);
if (c->ch == 0) {
release_cache_header(c);
return (0);
}
cl = cache_retrieve_ch(c->ch, uid, public, key);
release_cache_header(c);
return (cl);
}
int
cache_remove(keylen_t keylen, algtype_t algtype, uid_t uid, keybuf3 *public)
{
struct cachekey *c;
int ret;
if ((c = get_cache_header(keylen, algtype)) == 0)
return (0);
if (c->ch == 0) {
release_cache_header(c);
return (0);
}
ret = cache_remove_ch(c->ch, uid, public);
release_cache_header(c);
return (ret);
}
static struct dhkey *
keybuf3_2_dhkey(keybuf3 *hexkey)
{
struct dhkey *binkey;
/* hexkey->keybuf3_len*4 is the key length in bits */
if ((binkey = malloc(DHKEYALLOC(hexkey->keybuf3_len*4))) == 0)
return (0);
/* Set to zero to keep dbx and Purify access checking happy */
memset(binkey, 0, DHKEYALLOC(hexkey->keybuf3_len*4));
binkey->length = hexkey->keybuf3_len/2;
hex2bin((u_char *)hexkey->keybuf3_val, binkey->key,
(int)binkey->length);
return (binkey);
}