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