xref: /illumos-gate/usr/src/cmd/keyserv/keyserv_cache.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
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 
42 struct cachekey {
43 	struct cachekey_header	*ch;
44 	keylen_t		keylen;
45 	algtype_t		algtype;
46 	mutex_t			mp;
47 	struct cachekey		*next;
48 };
49 
50 static struct cachekey		*cache = 0;
51 static mutex_t			cache_lock = DEFAULTMUTEX;
52 static cond_t			cache_cv = DEFAULTCV;
53 static u_long			cache_refcnt = 0;
54 
55 struct skck {
56 	des_block	common[3];
57 	des_block	verifier;	/* Checksum */
58 	struct dhkey	secret;
59 };
60 
61 struct 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 
84 struct 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 
104 static struct cachekey_header	*create_cache_file_ch(keylen_t keylen,
105 							algtype_t algtype,
106 							int sizespec);
107 
108 static struct cachekey_header	*remap_cache_file_ch(struct cachekey_header *ch,
109 						u_int newrecs);
110 
111 static 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 
117 static struct cachekey3_list	*cache_retrieve_ch(struct cachekey_header *ch,
118 						uid_t uid,
119 						keybuf3 *public,
120 						des_block key);
121 
122 static int			cache_remove_ch(struct cachekey_header *ch,
123 						uid_t uid,
124 						keybuf3 *public);
125 
126 static struct cachekey		*get_cache_header(keylen_t keylen,
127 							algtype_t algtype);
128 
129 static void			release_cache_header(struct cachekey *);
130 
131 static int			cache_remap_addresses_ch(
132 					struct cachekey_header *);
133 
134 static struct cachekey_disklist	*find_cache_item(struct cachekey_header **,
135 						uid_t, struct dhkey *);
136 
137 static struct dhkey		*keybuf3_2_dhkey(keybuf3 *);
138 
139 static u_int			hashval(uid_t);
140 
141 static void			list_remove(struct cachekey_disklist *,
142 						struct cachekey_disklist **,
143 						struct cachekey_disklist **,
144 						u_int *);
145 
146 static void			list_remove_hash(struct cachekey_disklist *,
147 						struct cachekey_disklist **,
148 						struct cachekey_disklist **,
149 						u_int *);
150 
151 static void			list_insert(struct cachekey_disklist *,
152 						struct cachekey_disklist **,
153 						struct cachekey_disklist **,
154 						u_int *);
155 
156 static void			list_insert_hash(struct cachekey_disklist *,
157 						struct cachekey_disklist **,
158 						struct cachekey_disklist **,
159 						u_int *);
160 
161 static struct cachekey3_list *	copy_cl_item(struct cachekey_header *ch,
162 						struct cachekey_disklist *cd,
163 						des_block key);
164 
165 extern int			hex2bin(u_char *, u_char *, int);
166 extern 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 
200 struct cachekey_header *
201 create_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 
401 static int
402 cache_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  */
457 static struct cachekey_header *
458 remap_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
581 void
582 print_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 
648 void
649 print_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 
669 static u_int
670 hashval(uid_t uid)
671 {
672 	return (uid % NUMHASHBUCKETS);
673 }
674 
675 
676 static void
677 list_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 
705 static void
706 list_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 
734 static void
735 list_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 
763 static void
764 list_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  */
799 static struct cachekey_disklist *
800 find_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 
851 static struct cachekey_header *
852 cache_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 
922 static struct cachekey3_list *
923 copy_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 
990 static struct cachekey3_list *
991 cache_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  */
1040 static int
1041 cache_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  */
1120 static struct cachekey *
1121 get_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 
1162 static void
1163 release_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 
1180 int
1181 create_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 
1202 int
1203 cache_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 
1232 struct cachekey3_list *
1233 cache_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 
1258 int
1259 cache_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 
1280 static struct dhkey *
1281 keybuf3_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