1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 1997 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <fcntl.h>
32#include <unistd.h>
33#include <sys/mman.h>
34#include <thread.h>
35#include <synch.h>
36#include <stdlib.h>
37#include <syslog.h>
38#include <rpc/des_crypt.h>
39
40#include "keyserv_cache.h"
41
42struct cachekey {
43	struct cachekey_header	*ch;
44	keylen_t		keylen;
45	algtype_t		algtype;
46	mutex_t			mp;
47	struct cachekey		*next;
48};
49
50static struct cachekey		*cache = 0;
51static mutex_t			cache_lock = DEFAULTMUTEX;
52static cond_t			cache_cv = DEFAULTCV;
53static u_long			cache_refcnt = 0;
54
55struct skck {
56	des_block	common[3];
57	des_block	verifier;	/* Checksum */
58	struct dhkey	secret;
59};
60
61struct cachekey_disklist {
62	uid_t				uid;
63	struct cachekey_disklist	*prev;		/* LRU order */
64	struct cachekey_disklist	*next;
65	struct cachekey_disklist	*prevhash;	/* Hash chain */
66	struct cachekey_disklist	*nexthash;
67	struct dhkey			public;
68	/*
69	 * Storage for encrypted skck structure here. The length will be
70	 * 8 * ( ( ( sizeof(struct skck) - 1 + secret.length ) - 1 ) / 8 + 1 )
71	 */
72};
73
74/* Length of skck structure for given key length (in bits) */
75#define	SKCK_LEN(keylen)	ALIGN8(sizeof (struct skck) + KEYLEN(keylen))
76/* Length of a cachekey_disklist record for given key length (in bits) */
77#define	CACHEKEY_RECLEN(keylen)	ALIGN8(sizeof (struct cachekey_disklist) - 1 + \
78					KEYLEN(keylen) + SKCK_LEN(keylen))
79#define	NUMHASHBUCKETS	253
80#define	CHUNK_NUMREC	64
81
82#define	CACHEKEY_HEADER_VERSION	0
83
84struct cachekey_header {		/* First in each key cache file */
85	u_int		version;	/* version number of interface */
86	u_int		headerlength;	/* size of this header */
87	keylen_t	keylen;		/* in bits */
88	algtype_t	algtype;	/* algorithm type */
89	size_t		reclength;	/* cache file record size in bytes */
90	int		fd;		/* file descriptor */
91	caddr_t		address;	/* mmap()ed here */
92	size_t		length;		/* bytes mapped */
93	size_t		maxsize;	/* don't grow beyond this */
94	u_int		inuse_count;
95	struct cachekey_disklist	*inuse;	/* LRU order */
96	struct cachekey_disklist	*inuse_end;
97	u_int				free_count;
98	struct cachekey_disklist	*free;
99	struct cachekey_disklist	*bucket[NUMHASHBUCKETS];
100	struct cachekey_disklist	array[1];	/* Start of array */
101};
102
103
104static struct cachekey_header	*create_cache_file_ch(keylen_t keylen,
105							algtype_t algtype,
106							int sizespec);
107
108static struct cachekey_header	*remap_cache_file_ch(struct cachekey_header *ch,
109						u_int newrecs);
110
111static struct cachekey_header	*cache_insert_ch(struct cachekey_header *ch,
112						uid_t uid, deskeyarray common,
113						des_block key,
114						keybuf3 *public,
115						keybuf3 *secret);
116
117static struct cachekey3_list	*cache_retrieve_ch(struct cachekey_header *ch,
118						uid_t uid,
119						keybuf3 *public,
120						des_block key);
121
122static int			cache_remove_ch(struct cachekey_header *ch,
123						uid_t uid,
124						keybuf3 *public);
125
126static struct cachekey		*get_cache_header(keylen_t keylen,
127							algtype_t algtype);
128
129static void			release_cache_header(struct cachekey *);
130
131static int			cache_remap_addresses_ch(
132					struct cachekey_header *);
133
134static struct cachekey_disklist	*find_cache_item(struct cachekey_header **,
135						uid_t, struct dhkey *);
136
137static struct dhkey		*keybuf3_2_dhkey(keybuf3 *);
138
139static u_int			hashval(uid_t);
140
141static void			list_remove(struct cachekey_disklist *,
142						struct cachekey_disklist **,
143						struct cachekey_disklist **,
144						u_int *);
145
146static void			list_remove_hash(struct cachekey_disklist *,
147						struct cachekey_disklist **,
148						struct cachekey_disklist **,
149						u_int *);
150
151static void			list_insert(struct cachekey_disklist *,
152						struct cachekey_disklist **,
153						struct cachekey_disklist **,
154						u_int *);
155
156static void			list_insert_hash(struct cachekey_disklist *,
157						struct cachekey_disklist **,
158						struct cachekey_disklist **,
159						u_int *);
160
161static struct cachekey3_list *	copy_cl_item(struct cachekey_header *ch,
162						struct cachekey_disklist *cd,
163						des_block key);
164
165extern int			hex2bin(u_char *, u_char *, int);
166extern int			bin2hex(u_char *, u_char *, int);
167
168/*
169 * The folowing set of macros implement address validity checking. A valid
170 * address is defined to be either 0, or to fall on a record boundary. In
171 * the latter case, the the difference between the address and the start of
172 * the record array is divisible by the record length.
173 */
174#define	FILEOFFSET(ckh)			((u_long)(ckh) - \
175					(u_long)((ckh)->address))
176#define	ADJUSTEDADDR(addr, ckh)		((u_long)(addr) + FILEOFFSET(ckh))
177#define	ARRAYOFFSET(addr, ckh)		(ADJUSTEDADDR(addr, ckh) - \
178					(u_long)&((ckh)->array[0]))
179#define	INVALID_ADDRESS(addr, ckh)	((addr == 0) ? 0 : \
180			(ARRAYOFFSET(addr, ckh) % (ckh)->reclength) != 0)
181
182/* Add offset to old address */
183#define	MOVE_ADDR(old, offset)	((old) == 0) ? 0 : \
184				(void *)((u_long)(old) + (offset))
185
186/* Number of records in use or on free list */
187#define	NUMRECS(ck_header)	((ck_header)->inuse_count + \
188				(ck_header)->free_count)
189
190/* Max number of records the mapped file could hold */
191#define	MAPRECS(ck_header)	(((ck_header)->length - \
192				sizeof (struct cachekey_header)) / \
193				(ck_header)->reclength)
194/* Max number of records the file will hold if extended to the maxsize */
195#define	MAXRECS(ck_header)	(((ck_header)->maxsize - \
196				sizeof (struct cachekey_header)) / \
197				(ck_header)->reclength)
198
199
200struct cachekey_header *
201create_cache_file_ch(keylen_t keylen, algtype_t algtype, int sizespec)
202{
203	char				filename[MAXPATHLEN];
204	struct cachekey_header		*ch;
205	int				fd, newfile = 0, i, checkvalid = 1;
206	struct stat			statbuf;
207	size_t				reclength, length;
208	struct cachekey_header		*oldbase = 0;
209	struct cachekey_disklist	*cd;
210	size_t maxsize;
211
212	/* Construct cache file name */
213	if (snprintf(filename, sizeof (filename), "/var/nis/.keyserv_%d-%d",
214			keylen, algtype) > sizeof (filename)) {
215		syslog(LOG_WARNING,
216		"error constructing file name for mech %d-%d", keylen, algtype);
217		return (0);
218	}
219
220	/* Open/create the file */
221	if ((fd = open(filename, O_RDWR|O_CREAT, 0600)) < 0) {
222		syslog(LOG_WARNING, "cache file open error for mech %d-%d: %m",
223			keylen, algtype);
224		return (0);
225	}
226
227	/* We want exclusive use of the file */
228	if (lockf(fd, F_LOCK, 0) < 0) {
229		syslog(LOG_WARNING, "cache file lock error for mech %d-%d: %m",
230			keylen, algtype);
231		close(fd);
232		return (0);
233	}
234
235	/* Zero size means a new file */
236	if (fstat(fd, &statbuf) < 0) {
237		syslog(LOG_WARNING, "cache file fstat error for mech %d-%d: %m",
238			keylen, algtype);
239		close(fd);
240		return (0);
241	}
242
243	reclength = CACHEKEY_RECLEN(keylen);
244	if (sizespec < 0) {
245		/* specifies the number of records in file */
246		maxsize = ALIGN8(sizeof (struct cachekey_header)) +
247			-sizespec*reclength;
248	} else {
249		/* specifies size of file in MB */
250		maxsize = sizespec*1024*1024;
251	}
252	length    = ALIGN8(sizeof (struct cachekey_header)) +
253			reclength*CHUNK_NUMREC;
254	if (length > maxsize) {
255		/*
256		 * First record resides partly in the header, so the length
257		 * cannot be allowed to be less than header plus one record.
258		 */
259		if (maxsize > ALIGN8(sizeof (struct cachekey_header)+reclength))
260			length = maxsize;
261		else {
262			length  = ALIGN8(sizeof (struct cachekey_header)+
263					reclength);
264			maxsize = length;
265		}
266	}
267
268	if (statbuf.st_size == 0) {
269		/* Extend the file if we just created it */
270		if (ftruncate(fd, length) < 0) {
271			syslog(LOG_WARNING,
272				"cache file ftruncate error for mech %d-%d: %m",
273				keylen, algtype);
274			close(fd);
275			return (0);
276		}
277		newfile = 1;
278	} else {
279		/*
280		 * Temporarily mmap the header, to sanity check and obtain
281		 * the address where it was mapped the last time.
282		 */
283		if ((ch = (void *)mmap(0, sizeof (struct cachekey_header),
284				PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) ==
285			MAP_FAILED) {
286			syslog(LOG_WARNING,
287				"cache file mmap1 error for mech %d-%d: %m",
288				keylen, algtype);
289			close(fd);
290			return (0);
291		}
292		if (ch->version != CACHEKEY_HEADER_VERSION ||
293			ch->headerlength != sizeof (struct cachekey_header) ||
294			ch->keylen != keylen ||
295			ch->algtype != algtype ||
296			ch->reclength != reclength ||
297			ch->length < sizeof (struct cachekey_header) ||
298			ch->maxsize < ch->length ||
299			INVALID_ADDRESS(ch->inuse, ch) ||
300			INVALID_ADDRESS(ch->free, ch)) {
301			syslog(LOG_WARNING,
302			"cache file consistency error for mech %d-%d",
303				keylen, algtype);
304			munmap((caddr_t)ch, sizeof (struct cachekey_header));
305			close(fd);
306			return (0);
307		}
308		oldbase = (void *)ch->address;
309		length  = ch->length;
310		if (munmap((caddr_t)ch, sizeof (struct cachekey_header)) < 0) {
311			syslog(LOG_WARNING,
312				"cache file munmap error for mech %d-%d: %m",
313				keylen, algtype);
314			close(fd);
315			return (0);
316		}
317	}
318
319	/* Map the file */
320	if ((ch = (void *)mmap((caddr_t)oldbase, length,
321		PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
322		syslog(LOG_WARNING,
323			"cache file mmap2 error for mech %d-%d: %m",
324				keylen, algtype);
325		close(fd);
326		return (0);
327	}
328
329	ch->fd		= fd;
330	ch->maxsize	= maxsize;
331
332	if (newfile) {
333		ch->version		= CACHEKEY_HEADER_VERSION;
334		ch->headerlength	= sizeof (struct cachekey_header);
335		ch->keylen		= keylen;
336		ch->algtype		= algtype;
337		ch->reclength		= reclength;
338		ch->length		= length;
339		ch->address		= (caddr_t)ch;
340		ch->inuse_count		= 0;
341		ch->inuse		= 0;
342		ch->inuse_end		= 0;
343		ch->free		= 0;
344		ch->free_count		= 0;
345		for (i = 0; i < NUMHASHBUCKETS; i++) {
346			ch->bucket[i] = 0;
347		}
348
349		cd = &(ch->array[0]);
350		for (i = 0; i < MAPRECS(ch);
351			i++, cd = MOVE_ADDR(cd, ch->reclength)) {
352			cd->uid		= (uid_t)-1;
353			cd->prev	= MOVE_ADDR(cd, -(ch->reclength));
354			cd->next	= MOVE_ADDR(cd, +(ch->reclength));
355			cd->prevhash	= 0;
356			cd->nexthash	= 0;
357		}
358		/*
359		 * Last record next pointer, and first record prev pointer,
360		 * are both NULL.
361		 */
362		cd		= MOVE_ADDR(cd, -(ch->reclength));
363		cd->next	= 0;
364		cd		= &(ch->array[0]);
365		cd->prev	= 0;
366
367		ch->free_count	= MAPRECS(ch);
368		ch->free	= &(ch->array[0]);
369
370		(void) msync((caddr_t)ch, ch->length, MS_SYNC);
371
372	} else if (ch->length > maxsize) {
373		/* File should shrink */
374		if ((ch = remap_cache_file_ch(ch, MAXRECS(ch))) == 0) {
375			return (0);
376		}
377		checkvalid = 0;
378	}
379
380	/*
381	 * cache_remap_addresses() also checks address consistency, so call
382	 * it even if the remap is a no-op. However, if we've called
383	 * remap_cache_file_ch(), it will have invoked cache_remap_addresses()
384	 * already, so we don't have to do that again.
385	 */
386	if (checkvalid &&
387		cache_remap_addresses_ch(ch) == 0) {
388		syslog(LOG_WARNING, "cache file invalid for mech %d-%d",
389			keylen, algtype);
390		(void) munmap((caddr_t)ch, ch->length);
391		close(fd);
392		return (0);
393	}
394
395	(void) msync((caddr_t)ch, ch->length, MS_SYNC);
396
397	return (ch);
398}
399
400
401static int
402cache_remap_addresses_ch(struct cachekey_header *ch)
403{
404	int				i;
405	u_long				offset;
406	struct cachekey_disklist	*cd;
407
408	offset = (u_long)ch - (u_long)ch->address;
409
410	if (INVALID_ADDRESS(ch->inuse, ch) ||
411		INVALID_ADDRESS(ch->inuse_end, ch) ||
412		INVALID_ADDRESS(ch->free, ch)) {
413		return (0);
414	}
415
416	ch->inuse	= MOVE_ADDR(ch->inuse, offset);
417	ch->inuse_end	= MOVE_ADDR(ch->inuse_end, offset);
418	ch->free	= MOVE_ADDR(ch->free, offset);
419
420	cd = &(ch->array[0]);
421	for (i = 0; i < NUMRECS(ch); i++) {
422		if (INVALID_ADDRESS(cd->prev, ch) ||
423			INVALID_ADDRESS(cd->next, ch) ||
424			INVALID_ADDRESS(cd->prevhash, ch) ||
425			INVALID_ADDRESS(cd->nexthash, ch)) {
426			return (0);
427		}
428		cd->prev	= MOVE_ADDR(cd->prev, offset);
429		cd->next	= MOVE_ADDR(cd->next, offset);
430		cd->prevhash	= MOVE_ADDR(cd->prevhash, offset);
431		cd->nexthash	= MOVE_ADDR(cd->nexthash, offset);
432		cd = MOVE_ADDR(cd, ch->reclength);
433	}
434
435	for (i = 0; i < NUMHASHBUCKETS; i++) {
436		if (INVALID_ADDRESS(ch->bucket[i], ch)) {
437			return (0);
438		}
439		ch->bucket[i] = MOVE_ADDR(ch->bucket[i], offset);
440	}
441
442	/*
443	 * To prevent disaster if this function is invoked again, we
444	 * update ch->address, so that offset will be zero if we do
445	 * get called once more, and the mapped file hasn't moved.
446	 */
447	ch->address = (caddr_t)ch;
448
449	return (1);
450}
451
452
453/*
454 * Remap cache file with space for 'newrecs' records. The mmap:ed address
455 * may have to move; the new address is returned.
456 */
457static struct cachekey_header *
458remap_cache_file_ch(struct cachekey_header *ch, u_int newrecs)
459{
460	size_t				newsize, oldsize;
461	u_int				currecs;
462	int				i, fd;
463	struct cachekey_header		*newch;
464	caddr_t				oldaddr;
465	struct cachekey_disklist	*cd = 0;
466
467	if (ch == 0)
468		return (0);
469
470
471	/*
472	 * Since the first record partly resides in the cachekey_header,
473	 * newrecs cannot be less than 1.
474	 */
475	if (newrecs < 1)
476		newrecs = 1;
477
478	newsize = ALIGN8(sizeof (struct cachekey_header)) +
479			(ch->reclength)*newrecs;
480	currecs = NUMRECS(ch);
481
482	if (newsize > ch->maxsize) {
483		/* Would exceed maximum allowed */
484		newsize = ch->maxsize;
485	}
486
487	/* Save stuff we need while the file is unmapped */
488	oldsize	= ch->length;
489	oldaddr	= (caddr_t)ch;
490	fd	= ch->fd;
491
492	if (newsize > ch->length) {
493		/* Extending the file */
494		cd = &(ch->array[0]);
495	} else if (newsize == ch->length) {
496		/* Already OK */
497		return (ch);
498	} else {
499		size_t				tmpsize;
500		struct cachekey_disklist	*fcd;
501		/*
502		 * Shrink the file by removing records from the end.
503		 * First, we have to make sure the file contains valid
504		 * addresses.
505		 */
506		if (cache_remap_addresses_ch(ch) == 0) {
507			syslog(LOG_WARNING, "cache file invalid for mech %d-%d",
508			ch->keylen, ch->algtype);
509			close(ch->fd);
510			munmap((caddr_t)ch, ch->length);
511			return (0);
512		}
513		fcd = MOVE_ADDR(&(ch->array[0]),
514				ch->reclength*(MAPRECS(ch)-1));
515		tmpsize = (u_long)fcd - (u_long)ch + ch->reclength;
516		while (tmpsize > newsize && fcd > &(ch->array[0])) {
517			if (fcd->uid == (uid_t)-1) {
518				list_remove(fcd, &(ch->free), 0,
519					&(ch->free_count));
520			} else {
521				list_remove_hash(fcd,
522					&(ch->bucket[hashval(fcd->uid)]), 0, 0);
523				list_remove(fcd, &(ch->inuse), &(ch->inuse_end),
524						&(ch->inuse_count));
525			}
526			tmpsize -= ch->reclength;
527			fcd = MOVE_ADDR(fcd, -(ch->reclength));
528		}
529		ch->length = newsize;
530		(void) msync((caddr_t)ch, ch->length, MS_SYNC);
531	}
532
533	/* Unmap the file */
534	if (munmap((caddr_t)oldaddr, oldsize) < 0) {
535		return (0);
536	}
537	ch = 0;
538
539	/* Truncate/extend it */
540	if (ftruncate(fd, newsize) < 0) {
541		return (0);
542	}
543
544	/* Map it again */
545	if ((newch = (void *)mmap(oldaddr, newsize,
546			PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) ==
547	MAP_FAILED) {
548		return (0);
549	}
550
551	/* Update with new values */
552	newch->length	= newsize;
553
554	if (cache_remap_addresses_ch(newch) == 0) {
555		syslog(LOG_WARNING, "cache file invalid for mech %d-%d",
556			newch->keylen, newch->algtype);
557		newch->length = oldsize;
558		close(newch->fd);
559		munmap((caddr_t)newch, newsize);
560		return (0);
561	}
562
563	/* If extending the file, add new records to the free list */
564	if (cd != 0) {
565		cd = MOVE_ADDR(&(newch->array[0]), currecs*newch->reclength);
566		for (i = currecs; i < MAPRECS(newch); i++) {
567			cd->uid		= (uid_t)-1;
568			list_insert(cd, &(newch->free), 0,
569					&(newch->free_count));
570			cd		= MOVE_ADDR(cd, newch->reclength);
571		}
572	}
573
574	(void) msync(newch->address, newch->length, MS_SYNC);
575
576	return (newch);
577}
578
579
580#ifdef DEBUG
581void
582print_cache_ch(struct cachekey_header *ch)
583{
584	int				i, inuse, inuse_err, free, free_err;
585	int				pb;
586	struct cachekey_disklist	*cd;
587
588	printf(
589"\nkeylen = %d, algtype = %d, version = %d, headerlen = %d, reclen = %d\n",
590		ch->keylen, ch->algtype, ch->version, ch->headerlength,
591		ch->reclength);
592	printf("fd = %d, address = 0x%x, mapped length = %d, maxsize = %d\n",
593		ch->fd, ch->address, ch->length, ch->maxsize);
594	printf("inuse = %d, free = %d\n", ch->inuse_count, ch->free_count);
595
596	printf("Active hash buckets:\n");
597
598	for (i = 0, inuse = 0, inuse_err = 0; i < NUMHASHBUCKETS; i++) {
599		cd = ch->bucket[i];
600		pb = -1;
601		if (cd != 0) {
602			pb = 0;
603			printf("\t%d: ", i);
604		}
605		while (cd != 0) {
606			pb++;
607			printf("%d ", cd->uid);
608			if (cd->uid != (uid_t)-1) {
609				inuse++;
610			} else {
611				inuse_err++;
612			}
613			cd = cd->nexthash;
614		}
615		if (pb >= 0)
616			printf(" (%d)\n", pb);
617	}
618
619	printf("\ncounted hash inuse = %d, errors = %d\n", inuse, inuse_err);
620
621	cd = ch->inuse;
622	inuse = inuse_err = 0;
623	while (cd != 0) {
624		if (cd->uid != (uid_t)-1) {
625			inuse++;
626		} else {
627			inuse_err++;
628		}
629		cd = cd->next;
630	}
631	printf("counted LRU  inuse = %d, errors = %d\n", inuse, inuse_err);
632
633	cd = ch->free;
634	free = free_err = 0;
635	while (cd != 0) {
636		if (cd->uid == (uid_t)-1) {
637			free++;
638		} else {
639			free_err++;
640			fprintf(stderr, "free = %d, err = %d, cd->uid = %d\n",
641				free, free_err, cd->uid);
642		}
643		cd = cd->next;
644	}
645	printf("counted      free = %d, errors = %d\n", free, free_err);
646}
647
648void
649print_cache(keylen_t keylen, algtype_t algtype)
650{
651	struct cachekey	*c;
652
653	if ((c = get_cache_header(keylen, algtype)) == 0)
654		return;
655
656	if (c->ch == 0) {
657		release_cache_header(c);
658		return;
659	}
660
661	print_cache_ch(c->ch);
662
663	release_cache_header(c);
664}
665#endif
666
667
668
669static u_int
670hashval(uid_t uid)
671{
672	return (uid % NUMHASHBUCKETS);
673}
674
675
676static void
677list_remove(
678	struct cachekey_disklist *item,
679	struct cachekey_disklist **head,
680	struct cachekey_disklist **tail,
681	u_int *count)
682{
683	if (item == NULL) return;
684
685	/* Handle previous item, if any */
686	if (item->prev == 0)
687		*head = item->next;
688	else
689		item->prev->next = item->next;
690
691	/* Take care of the next item, if any */
692	if (item->next != 0)
693		item->next->prev = item->prev;
694
695	/* Handle tail pointer, if supplied */
696	if (tail != 0 && *tail == item)
697		*tail = item->prev;
698
699	item->prev = item->next = 0;
700	if (count != 0)
701		(*count)--;
702}
703
704
705static void
706list_remove_hash(
707	struct cachekey_disklist *item,
708	struct cachekey_disklist **head,
709	struct cachekey_disklist **tail,
710	u_int *count)
711{
712	if (item == NULL) return;
713
714	/* Handle previous item, if any */
715	if (item->prevhash == 0)
716		*head = item->nexthash;
717	else
718		item->prevhash->nexthash = item->nexthash;
719
720	/* Take care of the next item, if any */
721	if (item->nexthash != 0)
722		item->nexthash->prevhash = item->prevhash;
723
724	/* Handle tail pointer, if supplied */
725	if (tail != 0 && *tail == item)
726		*tail = item->prevhash;
727
728	item->prevhash = item->nexthash = 0;
729	if (count != 0)
730		(*count)--;
731}
732
733
734static void
735list_insert(
736	struct cachekey_disklist *item,
737	struct cachekey_disklist **head,
738	struct cachekey_disklist **tail,
739	u_int *count)
740{
741	if (item == NULL) return;
742
743	/* Insert at tail, if supplied */
744	if (tail != 0) {
745		item->prev = *tail;
746		if (item->prev != 0)
747			item->prev->next = item;
748		item->next	= 0;
749		*tail		= item;
750		if (*head == 0)
751			*head	= item;
752	} else {
753		item->next = *head;
754		if (item->next != 0)
755			item->next->prev = item;
756		item->prev	= 0;
757		*head		= item;
758	}
759	if (count != 0)
760		(*count)++;
761}
762
763static void
764list_insert_hash(
765	struct cachekey_disklist *item,
766	struct cachekey_disklist **head,
767	struct cachekey_disklist **tail,
768	u_int *count)
769{
770	if (item == NULL) return;
771
772	/* Insert at tail, if supplied */
773	if (tail != 0) {
774		item->prevhash = *tail;
775		if (item->prevhash != 0)
776			item->prevhash->nexthash = item;
777		item->nexthash	= 0;
778		*tail		= item;
779		if (*head == 0)
780			*head	= item;
781	} else {
782		item->nexthash	= *head;
783		if (item->nexthash != 0)
784			item->nexthash->prevhash = item;
785		item->prevhash	= 0;
786		*head		= item;
787	}
788	if (count != 0)
789		(*count)++;
790}
791
792
793/*
794 * Find the cache item specified by the header, uid, and public key. If
795 * no such uid/public item exists, return a pointer to an empty record.
796 * In either case, the item returned has been removed from any and all
797 * lists.
798 */
799static struct cachekey_disklist *
800find_cache_item(struct cachekey_header **ch, uid_t uid, struct dhkey *public)
801{
802	u_int				hash;
803	struct cachekey_disklist	*cd;
804
805	hash = hashval(uid);
806
807	if ((ch == NULL) || ((*ch) == NULL)) {
808		return (0);
809	}
810	for (cd = (*ch)->bucket[hash]; cd != 0; cd = cd->nexthash) {
811		if (uid == cd->uid &&
812			public->length == cd->public.length &&
813			memcmp(public->key, cd->public.key,
814				cd->public.length) == 0) {
815			list_remove_hash(cd, &((*ch)->bucket[hash]), 0, 0);
816			list_remove(cd, &((*ch)->inuse), &((*ch)->inuse_end),
817					&((*ch)->inuse_count));
818			return (cd);
819		}
820	}
821
822	if ((cd = (*ch)->free) != 0) {
823		list_remove(cd, &((*ch)->free), 0, &((*ch)->free_count));
824		return (cd);
825	}
826
827	/* Try to extend the file by CHUNK_NUMREC records */
828	if (((*ch) = remap_cache_file_ch(*ch, NUMRECS(*ch)+CHUNK_NUMREC)) == 0)
829		return (0);
830
831	/* If the extend worked, there should now be at least one free record */
832	if ((cd = (*ch)->free) != 0) {
833		list_remove(cd, &((*ch)->free), 0, &((*ch)->free_count));
834		return (cd);
835	}
836
837	/* Sacrifice the LRU item, if there is one */
838	if ((cd = (*ch)->inuse) == 0)
839		return (0);
840
841	/* Extract from hash list */
842	list_remove_hash(cd, &((*ch)->bucket[hashval(cd->uid)]), 0, 0);
843	/* Extract from LRU list */
844	list_remove(cd, &((*ch)->inuse), &((*ch)->inuse_end),
845			&((*ch)->inuse_count));
846
847	return (cd);
848}
849
850
851static struct cachekey_header *
852cache_insert_ch(
853	struct cachekey_header *ch,
854	uid_t uid,
855	deskeyarray common,
856	des_block key,
857	keybuf3 *public,
858	keybuf3 *secret)
859{
860	struct cachekey_disklist	*cd;
861	struct cachekey_header		*newch;
862	int				i, err;
863	struct skck			*skck;
864	des_block			ivec;
865	struct dhkey			*pk;
866	struct dhkey			*sk;
867
868
869	if (ch == 0 || uid == (uid_t)-1) {
870		return (0);
871	}
872
873	if (common.deskeyarray_len > sizeof (skck->common)/sizeof (des_block) ||
874		(pk = keybuf3_2_dhkey(public)) == 0 ||
875		(sk = keybuf3_2_dhkey(secret)) == 0) {
876		return (0);
877	}
878
879	newch = ch;
880	if ((cd = find_cache_item(&newch, uid, pk)) == 0) {
881		free(pk);
882		free(sk);
883		return (newch);
884	}
885
886	/*
887	 * The item may have been free, or may have been the LRU sacrificial
888	 * lamb, so reset all fields.
889	 */
890	cd->uid = uid;
891	memcpy(&(cd->public), pk, DHKEYSIZE(pk));
892
893	skck = MOVE_ADDR(&(cd->public), DHKEYSIZE(pk));
894	for (i = 0; i < common.deskeyarray_len; i++) {
895		skck->common[i] = common.deskeyarray_val[i];
896	}
897	skck->verifier = key;
898	memcpy(&(skck->secret), sk, DHKEYSIZE(sk));
899	free(pk);
900	free(sk);
901	memcpy(ivec.c, key.c, sizeof (key.c));
902	err = cbc_crypt(key.c, (char *)skck, SKCK_LEN(newch->keylen),
903			DES_ENCRYPT|DES_HW, ivec.c);
904	if (DES_FAILED(err)) {
905		/* Re-insert on free list */
906		list_insert(cd, &(newch->free), 0, &(newch->free_count));
907		return (newch);
908	}
909
910	/* Re-insert on hash list */
911	list_insert_hash(cd, &(newch->bucket[hashval(cd->uid)]), 0, 0);
912	/* Insert at end of LRU list */
913	list_insert(cd, &(newch->inuse), &(newch->inuse_end),
914			&(newch->inuse_count));
915
916	(void) msync((caddr_t)newch, newch->length, MS_SYNC);
917
918	return (newch);
919}
920
921
922static struct cachekey3_list *
923copy_cl_item(struct cachekey_header *ch, struct cachekey_disklist *cd,
924		des_block key) {
925
926	struct cachekey3_list		*cl;
927	struct skck			*skck, *skck_cd;
928	int				i, err;
929	des_block			ivec;
930
931	/* Allocate the cachekey3_list structure */
932	if ((cl = malloc(CACHEKEY3_LIST_SIZE(ch->keylen))) == 0) {
933		return (0);
934	}
935
936	/* Allocate skck structure for decryption */
937	if ((skck = malloc(SKCK_LEN(ch->keylen))) == 0) {
938		free(cl);
939		return (0);
940	}
941
942	/* Decrypt and check verifier */
943	skck_cd = MOVE_ADDR(&(cd->public), DHKEYSIZE(&(cd->public)));
944	memcpy(skck, skck_cd, SKCK_LEN(ch->keylen));
945	memcpy(ivec.c, key.c, sizeof (ivec.c));
946	err = cbc_crypt(key.c, (char *)skck, SKCK_LEN(ch->keylen),
947			DES_DECRYPT|DES_HW, ivec.c);
948	if (DES_FAILED(err)) {
949		free(cl);
950		free(skck);
951		return (0);
952	}
953	if (memcmp(key.c, skck->verifier.c, sizeof (skck->verifier.c)) != 0) {
954		free(cl);
955		free(skck);
956		return (0);
957	}
958
959	/* Everything OK; copy values */
960	cl->public		= MOVE_ADDR(cl, sizeof (struct cachekey3_list));
961	cl->public->keybuf3_val	= MOVE_ADDR(cl->public, sizeof (keybuf3));
962	cl->secret		= MOVE_ADDR(cl->public->keybuf3_val,
963					ALIGN4(2*KEYLEN(ch->keylen)+1));
964	cl->secret->keybuf3_val	= MOVE_ADDR(cl->secret, sizeof (keybuf3));
965	cl->deskey.deskeyarray_val =
966				MOVE_ADDR(cl->secret->keybuf3_val,
967					ALIGN4(2*KEYLEN(ch->keylen)+1));
968	bin2hex(cd->public.key, (u_char *)cl->public->keybuf3_val,
969		cd->public.length);
970	cl->public->keybuf3_len = cd->public.length*2+1;
971
972	bin2hex(skck->secret.key, (u_char *)cl->secret->keybuf3_val,
973		skck->secret.length);
974	cl->secret->keybuf3_len = skck->secret.length*2+1;
975	cl->deskey.deskeyarray_len = sizeof (skck->common)/sizeof (des_block);
976	for (i = 0; i < cl->deskey.deskeyarray_len; i++) {
977		cl->deskey.deskeyarray_val[i] = skck->common[i];
978	}
979
980	cl->refcnt = 0;
981	cl->next   = 0;
982
983	free(skck);
984
985	return (cl);
986
987}
988
989
990static struct cachekey3_list *
991cache_retrieve_ch(struct cachekey_header *ch, uid_t uid, keybuf3 *public,
992		des_block key) {
993
994	struct cachekey_disklist	*cd;
995	struct cachekey3_list		*cl = 0, **cltmp = &cl;
996	u_int				hash;
997	struct dhkey			*pk = 0;
998
999	if (uid == (uid_t)-1 ||
1000		(public != 0 && (pk = keybuf3_2_dhkey(public)) == 0)) {
1001		return (0);
1002	}
1003
1004	hash = hashval(uid);
1005
1006	for (cd = ch->bucket[hash]; cd != 0; cd = cd->nexthash) {
1007		if (uid == cd->uid) {
1008			/* Match on public key as well ? */
1009			if (pk != 0) {
1010				if (memcmp(cd->public.key, pk->key,
1011						cd->public.length) != 0) {
1012					/* Keep looking... */
1013					continue;
1014				}
1015				cl = copy_cl_item(ch, cd, key);
1016				/* Match on public key => nothing more to do */
1017				break;
1018			}
1019			*cltmp = copy_cl_item(ch, cd, key);
1020			if (*cltmp == 0) {
1021				/* Return what we've got */
1022				break;
1023			}
1024			cltmp = &((*cltmp)->next);
1025			/* On to the next item */
1026		}
1027	}
1028
1029	if (pk != 0)
1030		free(pk);
1031
1032	return (cl);
1033}
1034
1035
1036/*
1037 * Remove specified item. 'public' == 0 => remove all items for uid.
1038 * Return number of items removed.
1039 */
1040static int
1041cache_remove_ch(struct cachekey_header *ch, uid_t uid, keybuf3 *public) {
1042
1043	struct cachekey_disklist	*cd, *cdtmp;
1044	u_int				hash;
1045	int				match = 0;
1046	struct dhkey			*pk = 0;
1047
1048	if (uid == (uid_t)-1 ||
1049		(public != 0 && (pk = keybuf3_2_dhkey(public)) == 0)) {
1050		return (0);
1051	}
1052
1053	hash = hashval(uid);
1054
1055	for (cd = ch->bucket[hash]; cd != 0; ) {
1056		if (uid == cd->uid) {
1057			/* Match on public key as well ? */
1058			if (pk != 0) {
1059				if (memcmp(cd->public.key, pk->key,
1060						cd->public.length) != 0) {
1061					/* Keep looking... */
1062					continue;
1063				}
1064				match++;
1065				list_remove_hash(cd, &(ch->bucket[hash]), 0, 0);
1066				list_remove(cd, &(ch->inuse), &(ch->inuse_end),
1067						&(ch->inuse_count));
1068				cd->uid = (uid_t)-1;
1069				list_insert(cd, &(ch->free), 0,
1070						&(ch->free_count));
1071				/* Match on public key => nothing more to do */
1072				break;
1073			}
1074			match++;
1075			/*
1076			 * XXX: Assume that the order of the hash list remains
1077			 * the same after removal of an item. If this isn't
1078			 * true, we really should start over from the start
1079			 * of the hash bucket.
1080			 */
1081			cdtmp = cd->nexthash;
1082			list_remove_hash(cd, &(ch->bucket[hash]), 0, 0);
1083			list_remove(cd, &(ch->inuse), &(ch->inuse_end),
1084					&(ch->inuse_count));
1085			cd->uid = (uid_t)-1;
1086			list_insert(cd, &(ch->free), 0,
1087					&(ch->free_count));
1088			/* On to the next item */
1089			cd = cdtmp;
1090		} else {
1091			cd = cd->nexthash;
1092		}
1093	}
1094
1095	free(pk);
1096	return (match);
1097}
1098
1099
1100#define	INCCACHEREFCNT	mutex_lock(&cache_lock); \
1101			cache_refcnt++; \
1102			mutex_unlock(&cache_lock)
1103
1104#if !defined(lint) && !defined(__lint)
1105#define	DECCACHEREFCNT	mutex_lock(&cache_lock); \
1106			if (cache_refcnt > 0) \
1107				if (cache_refcnt-- == 0) (void) cond_broadcast(&cache_cv); \
1108			mutex_unlock(&cache_lock)
1109#else
1110#define	DECCACHEREFCNT	mutex_lock(&cache_lock); \
1111			if (cache_refcnt-- == 0) (void) cond_broadcast(&cache_cv); \
1112			mutex_unlock(&cache_lock)
1113#endif
1114
1115/*
1116 * Return the cachekey structure for the specified keylen and algtype.
1117 * When returned, the lock in the structure has been activated. It's the
1118 * responsibility of the caller to unlock it by calling release_cache_header().
1119 */
1120static struct cachekey *
1121get_cache_header(keylen_t keylen, algtype_t algtype) {
1122
1123	struct cachekey		*c;
1124
1125	INCCACHEREFCNT;
1126
1127	for (c = cache; c != 0; c = c->next) {
1128		if (c->keylen == keylen && c->algtype == algtype) {
1129			mutex_lock(&c->mp);
1130			return (c);
1131		}
1132	}
1133
1134	/* Spin until there are no cache readers */
1135	mutex_lock(&cache_lock);
1136#if !defined(lint) && !defined(__lint)
1137	if (cache_refcnt > 0)
1138#endif
1139		cache_refcnt--;
1140	while (cache_refcnt != 0) {
1141		(void) cond_wait(&cache_cv, &cache_lock);
1142	}
1143
1144	if ((c = malloc(sizeof (struct cachekey))) != 0) {
1145		c->ch		= 0;
1146		c->keylen	= keylen;
1147		c->algtype	= algtype;
1148		mutex_init(&c->mp, 0, 0);
1149		c->next		= cache;
1150		cache		= c;
1151		mutex_lock(&c->mp);
1152		cache_refcnt++;
1153		mutex_unlock(&cache_lock);
1154		return (c);
1155	}
1156
1157	mutex_unlock(&cache_lock);
1158	return (0);
1159}
1160
1161
1162static void
1163release_cache_header(struct cachekey *ck) {
1164
1165	struct cachekey	*c;
1166
1167	if (ck == 0)
1168		return;
1169
1170	for (c = cache; c != 0; c = c->next) {
1171		if (c == ck) {
1172			mutex_unlock(&c->mp);
1173			DECCACHEREFCNT;
1174			break;
1175		}
1176	}
1177}
1178
1179
1180int
1181create_cache_file(keylen_t keylen, algtype_t algtype, int sizespec)
1182{
1183	struct cachekey	*c;
1184	int		ret;
1185
1186	if ((c = get_cache_header(keylen, algtype)) == 0)
1187		return (0);
1188
1189	if (c->ch != 0) {
1190		/* Already created and opened */
1191		release_cache_header(c);
1192		return (1);
1193	}
1194
1195	ret = (c->ch = create_cache_file_ch(keylen, algtype, sizespec)) != 0;
1196	release_cache_header(c);
1197
1198	return (ret);
1199}
1200
1201
1202int
1203cache_insert(
1204	keylen_t keylen,
1205	algtype_t algtype,
1206	uid_t uid,
1207	deskeyarray common,
1208	des_block key,
1209	keybuf3 *public,
1210	keybuf3 *secret)
1211{
1212	struct cachekey	*c;
1213	int		ret;
1214
1215	if ((c = get_cache_header(keylen, algtype)) == 0)
1216		return (0);
1217
1218	if (c->ch == 0) {
1219		release_cache_header(c);
1220		return (0);
1221	}
1222
1223	ret = (c->ch =
1224		cache_insert_ch(c->ch, uid, common, key, public, secret)) != 0;
1225
1226	release_cache_header(c);
1227
1228	return (ret);
1229}
1230
1231
1232struct cachekey3_list *
1233cache_retrieve(
1234	keylen_t keylen,
1235	algtype_t algtype,
1236	uid_t uid,
1237	keybuf3 *public,
1238	des_block key)
1239{
1240	struct cachekey		*c;
1241	struct cachekey3_list	*cl;
1242
1243	if ((c = get_cache_header(keylen, algtype)) == 0)
1244		return (0);
1245
1246	if (c->ch == 0) {
1247		release_cache_header(c);
1248		return (0);
1249	}
1250
1251	cl = cache_retrieve_ch(c->ch, uid, public, key);
1252
1253	release_cache_header(c);
1254
1255	return (cl);
1256}
1257
1258int
1259cache_remove(keylen_t keylen, algtype_t algtype, uid_t uid, keybuf3 *public)
1260{
1261	struct cachekey	*c;
1262	int		ret;
1263
1264	if ((c = get_cache_header(keylen, algtype)) == 0)
1265		return (0);
1266
1267	if (c->ch == 0) {
1268		release_cache_header(c);
1269		return (0);
1270	}
1271
1272	ret = cache_remove_ch(c->ch, uid, public);
1273
1274	release_cache_header(c);
1275
1276	return (ret);
1277}
1278
1279
1280static struct dhkey *
1281keybuf3_2_dhkey(keybuf3 *hexkey)
1282{
1283	struct dhkey	*binkey;
1284
1285	/* hexkey->keybuf3_len*4 is the key length in bits */
1286	if ((binkey = malloc(DHKEYALLOC(hexkey->keybuf3_len*4))) == 0)
1287		return (0);
1288
1289	/* Set to zero to keep dbx and Purify access checking happy */
1290	memset(binkey, 0, DHKEYALLOC(hexkey->keybuf3_len*4));
1291
1292	binkey->length = hexkey->keybuf3_len/2;
1293	hex2bin((u_char *)hexkey->keybuf3_val, binkey->key,
1294		(int)binkey->length);
1295
1296	return (binkey);
1297}
1298