xref: /illumos-gate/usr/src/lib/libnsl/rpc/svcauth_des.c (revision 3e6960d70408b9f4e09714ed3341173673ed28b2)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright (c) 2016 by Delphix. All rights reserved.
26  */
27 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29 /*
30  * Portions of this source code were derived from Berkeley
31  * 4.3 BSD under license from the Regents of the University of
32  * California.
33  */
34 
35 /*
36  * svcauth_des.c, server-side des authentication
37  *
38  * We insure for the service the following:
39  * (1) The timestamp microseconds do not exceed 1 million.
40  * (2) The timestamp plus the window is less than the current time.
41  * (3) The timestamp is not less than the one previously
42  *	seen in the current session.
43  *
44  * It is up to the server to determine if the window size is
45  * too small.
46  *
47  */
48 
49 #include "mt.h"
50 #include "rpc_mt.h"
51 #include <assert.h>
52 #include <rpc/des_crypt.h>
53 #include <rpc/rpc.h>
54 #include <sys/types.h>
55 #include <sys/param.h>
56 #include <stdlib.h>
57 #include <unistd.h>
58 #include <string.h>
59 #include <strings.h>
60 
61 #include <syslog.h>
62 
63 extern int key_decryptsession_pk(const char *, netobj *, des_block *);
64 
65 #define	USEC_PER_SEC	((ulong_t)1000000L)
66 #define	BEFORE(t1, t2) timercmp(t1, t2, < /* EMPTY */)
67 
68 
69 /*
70  * LRU cache of conversation keys and some other useful items.
71  */
72 #define	DEF_AUTHDES_CACHESZ 128
73 int authdes_cachesz = DEF_AUTHDES_CACHESZ;
74 struct cache_entry {
75 	des_block key;			/* conversation key */
76 	char *rname;			/* client's name */
77 	uint_t window;			/* credential lifetime window */
78 	struct timeval laststamp;	/* detect replays of creds */
79 	char *localcred;		/* generic local credential */
80 	int index;			/* where are we in array? */
81 	struct cache_entry *prev;	/* prev entry on LRU list */
82 	struct cache_entry *next;	/* next entry on LRU list */
83 };
84 
85 static const char __getucredstr[] = "authdes_getucred:";
86 
87 static struct cache_entry *_rpc_authdes_cache;	/* [authdes_cachesz] */
88 static struct cache_entry *cache_head;	/* cache (in LRU order) */
89 static struct cache_entry *cache_tail;	/* cache (in LRU order) */
90 
91 /*
92  *	A rwlock_t would seem to make more sense, but it turns out we always
93  *	muck with the cache entries, so would always need a write lock (in
94  *	which case, we might as well use a mutex).
95  */
96 extern mutex_t	authdes_lock;
97 
98 
99 static int cache_init(void);		/* initialize the cache */
100 					/* find an entry in the cache */
101 static int cache_spot(des_block *, char *, struct timeval *);
102 static void cache_ref(uint32_t);	/* note that sid was ref'd */
103 static void invalidate(char *);		/* invalidate entry in cache */
104 static void __msgout(int, const char *, const char *);
105 static void __msgout2(const char *, const char *);
106 
107 /*
108  * cache statistics
109  */
110 struct {
111 	ulong_t ncachehits;	/* times cache hit, and is not replay */
112 	ulong_t ncachereplays;	/* times cache hit, and is replay */
113 	ulong_t ncachemisses;	/* times cache missed */
114 } svcauthdes_stats;
115 
116 /*
117  * Service side authenticator for AUTH_DES
118  */
119 enum auth_stat
120 __svcauth_des(struct svc_req *rqst, struct rpc_msg *msg)
121 {
122 	int32_t		*ixdr;
123 	des_block	cryptbuf[2];
124 	struct authdes_cred	*cred;
125 	struct authdes_verf	verf;
126 	int	status;
127 	struct cache_entry	*entry;
128 	uint32_t sid;
129 	int cache_spot_id;
130 	des_block	*sessionkey, init_sessionkey;
131 	des_block	ivec;
132 	uint_t	window;
133 	struct timeval	timestamp;
134 	uint32_t	namelen;
135 	struct area {
136 		struct authdes_cred area_cred;
137 		char area_netname[MAXNETNAMELEN+1];
138 	} *area;
139 	int	fullname_rcvd = 0;
140 	int from_cache = 0;
141 
142 	(void) mutex_lock(&authdes_lock);
143 	if (_rpc_authdes_cache == NULL) {
144 		int ret = cache_init();
145 		if (ret == -1) {
146 			(void) mutex_unlock(&authdes_lock);
147 			return (AUTH_FAILED);
148 		}
149 	}
150 	(void) mutex_unlock(&authdes_lock);
151 
152 	/* LINTED pointer cast */
153 	area = (struct area *)rqst->rq_clntcred;
154 	cred = (struct authdes_cred *)&area->area_cred;
155 
156 	if ((uint_t)msg->rm_call.cb_cred.oa_length == 0)
157 		return (AUTH_BADCRED);
158 	/*
159 	 * Get the credential
160 	 */
161 	/* LINTED pointer cast */
162 	ixdr = (int32_t *)msg->rm_call.cb_cred.oa_base;
163 	cred->adc_namekind = IXDR_GET_ENUM(ixdr, enum authdes_namekind);
164 	switch (cred->adc_namekind) {
165 	case ADN_FULLNAME:
166 		namelen = IXDR_GET_U_INT32(ixdr);
167 		if (namelen > MAXNETNAMELEN)
168 			return (AUTH_BADCRED);
169 		cred->adc_fullname.name = area->area_netname;
170 		(void) memcpy(cred->adc_fullname.name, ixdr, (uint_t)namelen);
171 		cred->adc_fullname.name[namelen] = 0;
172 		ixdr += (RNDUP(namelen) / BYTES_PER_XDR_UNIT);
173 		cred->adc_fullname.key.key.high = (uint32_t)*ixdr++;
174 		cred->adc_fullname.key.key.low = (uint32_t)*ixdr++;
175 		cred->adc_fullname.window = (uint32_t)*ixdr++;
176 		fullname_rcvd++;
177 		break;
178 	case ADN_NICKNAME:
179 		cred->adc_nickname = (uint32_t)*ixdr++;
180 		break;
181 	default:
182 		return (AUTH_BADCRED);
183 	}
184 
185 	if ((uint_t)msg->rm_call.cb_verf.oa_length == 0)
186 		return (AUTH_BADVERF);
187 	/*
188 	 * Get the verifier
189 	 */
190 	/* LINTED pointer cast */
191 	ixdr = (int32_t *)msg->rm_call.cb_verf.oa_base;
192 	verf.adv_xtimestamp.key.high = (uint32_t)*ixdr++;
193 	verf.adv_xtimestamp.key.low = (uint32_t)*ixdr++;
194 	verf.adv_int_u = (uint32_t)*ixdr++;
195 
196 	(void) mutex_lock(&authdes_lock);
197 
198 	/*
199 	 * Get the conversation key
200 	 */
201 	if (fullname_rcvd) {	/* ADN_FULLNAME */
202 		netobj	pkey;
203 		char	pkey_data[1024];
204 
205 again:
206 		init_sessionkey = cred->adc_fullname.key;
207 		sessionkey = &init_sessionkey;
208 
209 		if (!__getpublickey_cached(cred->adc_fullname.name,
210 				pkey_data, &from_cache)) {
211 			/*
212 			 * if the user has no public key, treat them as the
213 			 * unauthenticated identity - nobody. If this
214 			 * works, it means the client didn't find the
215 			 * user's keys and used nobody's secret key
216 			 * as a backup.
217 			 */
218 			if (!__getpublickey_cached("nobody",
219 						pkey_data, &from_cache)) {
220 				__msgout(LOG_INFO,
221 				"_svcauth_des: no public key for nobody or ",
222 				cred->adc_fullname.name);
223 				(void) mutex_unlock(&authdes_lock);
224 				return (AUTH_BADCRED); /* no key */
225 			}
226 
227 			/*
228 			 * found a public key for nobody. change
229 			 * the fullname id to nobody, so the caller
230 			 * thinks the client specified nobody
231 			 * as the user identity.
232 			 */
233 			(void) strcpy(cred->adc_fullname.name, "nobody");
234 		}
235 		pkey.n_bytes = pkey_data;
236 		pkey.n_len = strlen(pkey_data) + 1;
237 		if (key_decryptsession_pk(cred->adc_fullname.name, &pkey,
238 				sessionkey) < 0) {
239 			if (from_cache) {
240 				__getpublickey_flush(cred->adc_fullname.name);
241 				goto again;
242 			}
243 			__msgout(LOG_INFO,
244 			    "_svcauth_des: key_decryptsessionkey failed for",
245 			    cred->adc_fullname.name);
246 			(void) mutex_unlock(&authdes_lock);
247 			return (AUTH_BADCRED);	/* key not found */
248 		}
249 	} else { /* ADN_NICKNAME */
250 		sid = cred->adc_nickname;
251 		if (sid >= authdes_cachesz) {
252 			__msgout(LOG_INFO, "_svcauth_des:", "bad nickname");
253 			(void) mutex_unlock(&authdes_lock);
254 			return (AUTH_BADCRED);	/* garbled credential */
255 		}
256 		/* actually check that the entry is not null */
257 		entry = &_rpc_authdes_cache[sid];
258 		if (entry->rname == NULL) {
259 			(void) mutex_unlock(&authdes_lock);
260 			return (AUTH_BADCRED);	/* cached out */
261 		}
262 		sessionkey = &_rpc_authdes_cache[sid].key;
263 	}
264 
265 	/*
266 	 * Decrypt the timestamp
267 	 */
268 	cryptbuf[0] = verf.adv_xtimestamp;
269 	if (fullname_rcvd) {	/* ADN_FULLNAME */
270 		cryptbuf[1].key.high = cred->adc_fullname.window;
271 		cryptbuf[1].key.low = verf.adv_winverf;
272 		ivec.key.high = ivec.key.low = 0;
273 		status = cbc_crypt((char *)sessionkey, (char *)cryptbuf,
274 			2 * (int)sizeof (des_block), DES_DECRYPT | DES_HW,
275 			(char *)&ivec);
276 	} else {
277 		status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
278 			(int)sizeof (des_block), DES_DECRYPT | DES_HW);
279 	}
280 	if (DES_FAILED(status)) {
281 		if (fullname_rcvd && from_cache) {
282 			__getpublickey_flush(cred->adc_fullname.name);
283 			goto again;
284 		}
285 		__msgout(LOG_ERR, "_svcauth_des: DES decryption failure for",
286 			fullname_rcvd ? cred->adc_fullname.name :
287 			_rpc_authdes_cache[sid].rname);
288 		(void) mutex_unlock(&authdes_lock);
289 		return (AUTH_FAILED);	/* system error */
290 	}
291 
292 	/*
293 	 * XDR the decrypted timestamp
294 	 */
295 	ixdr = (int32_t *)cryptbuf;
296 	timestamp.tv_sec = IXDR_GET_INT32(ixdr);
297 	timestamp.tv_usec = IXDR_GET_INT32(ixdr);
298 
299 	/*
300 	 * Check for valid credentials and verifiers.
301 	 * They could be invalid because the key was flushed
302 	 * out of the cache, and so a new session should begin.
303 	 * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case.
304 	 */
305 	{
306 		struct timeval current;
307 		int	nick;
308 		int	winverf;
309 
310 		if (fullname_rcvd) {
311 			window = IXDR_GET_U_INT32(ixdr);
312 			winverf = IXDR_GET_U_INT32(ixdr);
313 			if (winverf != window - 1) {
314 				if (from_cache) {
315 					__getpublickey_flush(
316 						cred->adc_fullname.name);
317 					goto again;
318 				}
319 				__msgout(LOG_INFO,
320 					"_svcauth_des: corrupted window from",
321 					cred->adc_fullname.name);
322 				(void) mutex_unlock(&authdes_lock);
323 				/* garbled credential or invalid secret key */
324 				return (AUTH_BADCRED);
325 			}
326 			cache_spot_id = cache_spot(sessionkey,
327 						cred->adc_fullname.name,
328 
329 					&timestamp);
330 			if (cache_spot_id < 0) {
331 			__msgout(LOG_INFO,
332 				"_svcauth_des: replayed credential from",
333 				cred->adc_fullname.name);
334 				(void) mutex_unlock(&authdes_lock);
335 				return (AUTH_REJECTEDCRED);	/* replay */
336 			} else sid = cache_spot_id;
337 			nick = 0;
338 		} else {	/* ADN_NICKNAME */
339 			window = _rpc_authdes_cache[sid].window;
340 			nick = 1;
341 		}
342 
343 		if ((ulong_t)timestamp.tv_usec >= USEC_PER_SEC) {
344 			if (fullname_rcvd && from_cache) {
345 				__getpublickey_flush(cred->adc_fullname.name);
346 				goto again;
347 			}
348 		__msgout(LOG_INFO,
349 			"_svcauth_des: invalid timestamp received from",
350 			fullname_rcvd ? cred->adc_fullname.name :
351 				_rpc_authdes_cache[sid].rname);
352 			/* cached out (bad key), or garbled verifier */
353 			(void) mutex_unlock(&authdes_lock);
354 			return (nick ? AUTH_REJECTEDVERF : AUTH_BADVERF);
355 		}
356 		if (nick && BEFORE(&timestamp,
357 				&_rpc_authdes_cache[sid].laststamp)) {
358 			if (fullname_rcvd && from_cache) {
359 				__getpublickey_flush(cred->adc_fullname.name);
360 				goto again;
361 			}
362 			__msgout(LOG_INFO,
363 	"_svcauth_des: timestamp is earlier than the one previously seen from",
364 			fullname_rcvd ? cred->adc_fullname.name :
365 				_rpc_authdes_cache[sid].rname);
366 			(void) mutex_unlock(&authdes_lock);
367 			return (AUTH_REJECTEDVERF);	/* replay */
368 		}
369 		(void) gettimeofday(&current, NULL);
370 		current.tv_sec -= window;	/* allow for expiration */
371 		if (!BEFORE(&current, &timestamp)) {
372 			if (fullname_rcvd && from_cache) {
373 				__getpublickey_flush(cred->adc_fullname.name);
374 				goto again;
375 			}
376 			__msgout(LOG_INFO,
377 				"_svcauth_des: timestamp expired for",
378 				fullname_rcvd ? cred->adc_fullname.name :
379 					_rpc_authdes_cache[sid].rname);
380 			/* replay, or garbled credential */
381 			(void) mutex_unlock(&authdes_lock);
382 			return (nick ? AUTH_REJECTEDVERF : AUTH_BADCRED);
383 		}
384 	}
385 
386 	/*
387 	 * Set up the reply verifier
388 	 */
389 	verf.adv_nickname = sid;
390 
391 	/*
392 	 * xdr the timestamp before encrypting
393 	 */
394 	ixdr = (int32_t *)cryptbuf;
395 	IXDR_PUT_INT32(ixdr, timestamp.tv_sec - 1);
396 	IXDR_PUT_INT32(ixdr, timestamp.tv_usec);
397 
398 	/*
399 	 * encrypt the timestamp
400 	 */
401 	status = ecb_crypt((char *)sessionkey, (char *)cryptbuf,
402 				(int)sizeof (des_block), DES_ENCRYPT | DES_HW);
403 	if (DES_FAILED(status)) {
404 		__msgout(LOG_ERR, "_svcauth_des: DES encryption failure for",
405 			fullname_rcvd ? cred->adc_fullname.name :
406 			_rpc_authdes_cache[sid].rname);
407 		(void) mutex_unlock(&authdes_lock);
408 		return (AUTH_FAILED);	/* system error */
409 	}
410 	verf.adv_xtimestamp = cryptbuf[0];
411 
412 	/*
413 	 * Serialize the reply verifier, and update rqst
414 	 */
415 	/* LINTED pointer cast */
416 	ixdr = (int32_t *)msg->rm_call.cb_verf.oa_base;
417 	*ixdr++ = (int32_t)verf.adv_xtimestamp.key.high;
418 	*ixdr++ = (int32_t)verf.adv_xtimestamp.key.low;
419 	*ixdr++ = (int32_t)verf.adv_int_u;
420 
421 	rqst->rq_xprt->xp_verf.oa_flavor = AUTH_DES;
422 	rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base;
423 	rqst->rq_xprt->xp_verf.oa_length =
424 		(char *)ixdr - msg->rm_call.cb_verf.oa_base;
425 	if (rqst->rq_xprt->xp_verf.oa_length > MAX_AUTH_BYTES) {
426 		__msgout(LOG_ERR,
427 			"_svcauth_des: Authenticator length error",
428 			fullname_rcvd ? cred->adc_fullname.name :
429 			_rpc_authdes_cache[sid].rname);
430 		(void) mutex_unlock(&authdes_lock);
431 		return (AUTH_REJECTEDVERF);
432 	}
433 
434 	/*
435 	 * We succeeded, commit the data to the cache now and
436 	 * finish cooking the credential.
437 	 */
438 	entry = &_rpc_authdes_cache[sid];
439 	entry->laststamp = timestamp;
440 	cache_ref(sid);
441 	if (cred->adc_namekind == ADN_FULLNAME) {
442 		cred->adc_fullname.window = window;
443 		cred->adc_nickname = sid;	/* save nickname */
444 		if (entry->rname != NULL)
445 			free(entry->rname);
446 		entry->rname = malloc(strlen(cred->adc_fullname.name) + 1);
447 		if (entry->rname != NULL) {
448 			(void) strcpy(entry->rname, cred->adc_fullname.name);
449 		} else {
450 			__msgout(LOG_CRIT, "_svcauth_des:", "out of memory");
451 			(void) mutex_unlock(&authdes_lock);
452 			return (AUTH_FAILED);
453 		}
454 		entry->key = *sessionkey;
455 		entry->window = window;
456 		/* mark any cached cred invalid */
457 		invalidate(entry->localcred);
458 	} else { /* ADN_NICKNAME */
459 		/*
460 		 * nicknames are cooked into fullnames
461 		 */
462 		cred->adc_namekind = ADN_FULLNAME;
463 		cred->adc_fullname.name = entry->rname;
464 		cred->adc_fullname.key = entry->key;
465 		cred->adc_fullname.window = entry->window;
466 	}
467 	(void) mutex_unlock(&authdes_lock);
468 	return (AUTH_OK);	/* we made it! */
469 }
470 
471 
472 /*
473  * Initialize the cache
474  */
475 static int
476 cache_init(void)
477 {
478 	int i;
479 
480 /* LOCK HELD ON ENTRY: authdes_lock */
481 
482 	assert(MUTEX_HELD(&authdes_lock));
483 	_rpc_authdes_cache =
484 		malloc(sizeof (struct cache_entry) * authdes_cachesz);
485 	if (_rpc_authdes_cache == NULL) {
486 		__msgout(LOG_CRIT, "cache_init:", "out of memory");
487 		return (-1);
488 	}
489 	(void) memset(_rpc_authdes_cache, 0,
490 		sizeof (struct cache_entry) * authdes_cachesz);
491 
492 	/*
493 	 * Initialize the lru chain (linked-list)
494 	 */
495 	for (i = 1; i < (authdes_cachesz - 1); i++) {
496 		_rpc_authdes_cache[i].index = i;
497 		_rpc_authdes_cache[i].next = &_rpc_authdes_cache[i + 1];
498 		_rpc_authdes_cache[i].prev = &_rpc_authdes_cache[i - 1];
499 	}
500 	cache_head = &_rpc_authdes_cache[0];
501 	cache_tail = &_rpc_authdes_cache[authdes_cachesz - 1];
502 
503 	/*
504 	 * These elements of the chain need special attention...
505 	 */
506 	cache_head->index = 0;
507 	cache_tail->index = authdes_cachesz - 1;
508 	cache_head->next = &_rpc_authdes_cache[1];
509 	cache_head->prev = cache_tail;
510 	cache_tail->next = cache_head;
511 	cache_tail->prev = &_rpc_authdes_cache[authdes_cachesz - 2];
512 	return (0);
513 }
514 
515 
516 /*
517  * Find the lru victim
518  */
519 static uint32_t
520 cache_victim(void)
521 {
522 /* LOCK HELD ON ENTRY: authdes_lock */
523 
524 	assert(MUTEX_HELD(&authdes_lock));
525 	return (cache_head->index);			/* list in lru order */
526 }
527 
528 /*
529  * Note that sid was referenced
530  */
531 static void
532 cache_ref(uint32_t sid)
533 {
534 	struct cache_entry *curr = &_rpc_authdes_cache[sid];
535 
536 
537 /* LOCK HELD ON ENTRY: authdes_lock */
538 
539 	assert(MUTEX_HELD(&authdes_lock));
540 
541 	/*
542 	 * move referenced item from its place on the LRU chain
543 	 * to the tail of the chain while checking for special
544 	 * conditions (mainly for performance).
545 	 */
546 	if (cache_tail == curr) {			/* no work to do */
547 		/*EMPTY*/;
548 	} else if (cache_head == curr) {
549 		cache_head = cache_head->next;
550 		cache_tail = curr;
551 	} else {
552 		(curr->next)->prev = curr->prev;	/* fix thy neighbor */
553 		(curr->prev)->next = curr->next;
554 		curr->next = cache_head;		/* fix thy self... */
555 		curr->prev = cache_tail;
556 		cache_head->prev = curr;		/* fix the head  */
557 		cache_tail->next = curr;		/* fix the tail  */
558 		cache_tail = curr;			/* move the tail */
559 	}
560 }
561 
562 /*
563  * Find a spot in the cache for a credential containing
564  * the items given. Return -1 if a replay is detected, otherwise
565  * return the spot in the cache.
566  */
567 static int
568 cache_spot(des_block *key, char *name, struct timeval *timestamp)
569 {
570 	struct cache_entry *cp;
571 	int i;
572 	uint32_t hi;
573 
574 /* LOCK HELD ON ENTRY: authdes_lock */
575 
576 	assert(MUTEX_HELD(&authdes_lock));
577 	hi = key->key.high;
578 	for (cp = _rpc_authdes_cache, i = 0; i < authdes_cachesz; i++, cp++) {
579 		if (cp->key.key.high == hi &&
580 		    cp->key.key.low == key->key.low &&
581 		    cp->rname != NULL &&
582 		    memcmp(cp->rname, name, strlen(name) + 1) == 0) {
583 			if (BEFORE(timestamp, &cp->laststamp)) {
584 				svcauthdes_stats.ncachereplays++;
585 				return (-1);	/* replay */
586 			}
587 			svcauthdes_stats.ncachehits++;
588 			return (i);
589 			/* refresh */
590 		}
591 	}
592 	svcauthdes_stats.ncachemisses++;
593 	return (cache_victim());
594 }
595 
596 
597 /*
598  * Local credential handling stuff.
599  * NOTE: bsd unix dependent.
600  * Other operating systems should put something else here.
601  */
602 #define	UNKNOWN 	-2	/* grouplen, if cached cred is unknown user */
603 #define	INVALID		-1 	/* grouplen, if cache entry is invalid */
604 
605 struct bsdcred {
606 	uid_t uid;		/* cached uid */
607 	gid_t gid;		/* cached gid */
608 	short grouplen;	/* length of cached groups */
609 	gid_t groups[1];	/* cached groups allocate _SC_NGROUPS_MAX */
610 };
611 
612 static void
613 invalidate(char *cred)
614 {
615 	if (cred == NULL)
616 		return;
617 	/* LINTED pointer cast */
618 	((struct bsdcred *)cred)->grouplen = INVALID;
619 }
620 
621 /*
622  * Map a des credential into a unix cred.
623  * We cache the credential here so the application does
624  * not have to make an rpc call every time to interpret
625  * the credential.
626  */
627 int
628 authdes_getucred(const struct authdes_cred *adc, uid_t *uid, gid_t *gid,
629     short *grouplen, gid_t *groups)
630 {
631 	uint32_t sid;
632 	int i;
633 	uid_t i_uid;
634 	gid_t i_gid;
635 	int i_grouplen;
636 	struct bsdcred *cred;
637 
638 	sid = adc->adc_nickname;
639 	if (sid >= authdes_cachesz) {
640 		__msgout2(__getucredstr, "invalid nickname");
641 		return (0);
642 	}
643 	(void) mutex_lock(&authdes_lock);
644 	/* LINTED pointer cast */
645 	cred = (struct bsdcred *)_rpc_authdes_cache[sid].localcred;
646 	if (cred == NULL) {
647 		static size_t bsdcred_sz;
648 
649 		if (bsdcred_sz == 0) {
650 			bsdcred_sz = sizeof (struct bsdcred) +
651 			    (sysconf(_SC_NGROUPS_MAX) - 1) * sizeof (gid_t);
652 		}
653 		cred = malloc(bsdcred_sz);
654 		if (cred == NULL) {
655 			__msgout2(__getucredstr, "out of memory");
656 			(void) mutex_unlock(&authdes_lock);
657 			return (0);
658 		}
659 		_rpc_authdes_cache[sid].localcred = (char *)cred;
660 		cred->grouplen = INVALID;
661 	}
662 	if (cred->grouplen == INVALID) {
663 		/*
664 		 * not in cache: lookup
665 		 */
666 		if (!netname2user(adc->adc_fullname.name, (uid_t *)&i_uid,
667 			(gid_t *)&i_gid, &i_grouplen, (gid_t *)groups)) {
668 			__msgout2(__getucredstr, "unknown netname");
669 			/* mark as lookup up, but not found */
670 			cred->grouplen = UNKNOWN;
671 			(void) mutex_unlock(&authdes_lock);
672 			return (0);
673 		}
674 		__msgout2(__getucredstr, "missed ucred cache");
675 		*uid = cred->uid = i_uid;
676 		*gid = cred->gid = i_gid;
677 		*grouplen = cred->grouplen = i_grouplen;
678 		for (i = i_grouplen - 1; i >= 0; i--) {
679 			cred->groups[i] = groups[i];
680 		}
681 		(void) mutex_unlock(&authdes_lock);
682 		return (1);
683 	}
684 	if (cred->grouplen == UNKNOWN) {
685 		/*
686 		 * Already lookup up, but no match found
687 		 */
688 		(void) mutex_unlock(&authdes_lock);
689 		return (0);
690 	}
691 
692 	/*
693 	 * cached credentials
694 	 */
695 	*uid = cred->uid;
696 	*gid = cred->gid;
697 	*grouplen = cred->grouplen;
698 	for (i = cred->grouplen - 1; i >= 0; i--) {
699 		groups[i] = cred->groups[i];
700 	}
701 	(void) mutex_unlock(&authdes_lock);
702 	return (1);
703 }
704 
705 
706 static void
707 __msgout(int level, const char *str, const char *strarg)
708 {
709 	(void) syslog(level, "%s %s", str, strarg);
710 }
711 
712 
713 static void
714 __msgout2(const char *str, const char *str2)
715 {
716 	(void) syslog(LOG_DEBUG, "%s %s", str, str2);
717 }
718