7812 Remove gender specific language
[unleashed.git] / usr / src / lib / libnsl / rpc / svcauth_des.c
blobb8e6fd31e9df78f184175e519e7c20cf4876e104
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
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.
27 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
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.
36 * svcauth_des.c, server-side des authentication
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.
44 * It is up to the server to determine if the window size is
45 * too small.
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>
61 #include <syslog.h>
63 extern int key_decryptsession_pk(const char *, netobj *, des_block *);
65 #define USEC_PER_SEC ((ulong_t)1000000L)
66 #define BEFORE(t1, t2) timercmp(t1, t2, < /* EMPTY */)
70 * LRU cache of conversation keys and some other useful items.
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 */
85 static const char __getucredstr[] = "authdes_getucred:";
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) */
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).
96 extern mutex_t authdes_lock;
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 *);
108 * cache statistics
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;
117 * Service side authenticator for AUTH_DES
119 enum auth_stat
120 __svcauth_des(struct svc_req *rqst, struct rpc_msg *msg)
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;
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);
150 (void) mutex_unlock(&authdes_lock);
152 /* LINTED pointer cast */
153 area = (struct area *)rqst->rq_clntcred;
154 cred = (struct authdes_cred *)&area->area_cred;
156 if ((uint_t)msg->rm_call.cb_cred.oa_length == 0)
157 return (AUTH_BADCRED);
159 * Get the credential
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);
185 if ((uint_t)msg->rm_call.cb_verf.oa_length == 0)
186 return (AUTH_BADVERF);
188 * Get the verifier
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++;
196 (void) mutex_lock(&authdes_lock);
199 * Get the conversation key
201 if (fullname_rcvd) { /* ADN_FULLNAME */
202 netobj pkey;
203 char pkey_data[1024];
205 again:
206 init_sessionkey = cred->adc_fullname.key;
207 sessionkey = &init_sessionkey;
209 if (!__getpublickey_cached(cred->adc_fullname.name,
210 pkey_data, &from_cache)) {
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.
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 */
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.
233 (void) strcpy(cred->adc_fullname.name, "nobody");
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;
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 */
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 */
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 */
262 sessionkey = &_rpc_authdes_cache[sid].key;
266 * Decrypt the timestamp
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);
280 if (DES_FAILED(status)) {
281 if (fullname_rcvd && from_cache) {
282 __getpublickey_flush(cred->adc_fullname.name);
283 goto again;
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 */
293 * XDR the decrypted timestamp
295 ixdr = (int32_t *)cryptbuf;
296 timestamp.tv_sec = IXDR_GET_INT32(ixdr);
297 timestamp.tv_usec = IXDR_GET_INT32(ixdr);
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.
306 struct timeval current;
307 int nick;
308 int winverf;
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;
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);
326 cache_spot_id = cache_spot(sessionkey,
327 cred->adc_fullname.name,
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;
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;
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);
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;
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 */
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;
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);
387 * Set up the reply verifier
389 verf.adv_nickname = sid;
392 * xdr the timestamp before encrypting
394 ixdr = (int32_t *)cryptbuf;
395 IXDR_PUT_INT32(ixdr, timestamp.tv_sec - 1);
396 IXDR_PUT_INT32(ixdr, timestamp.tv_usec);
399 * encrypt the timestamp
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 */
410 verf.adv_xtimestamp = cryptbuf[0];
413 * Serialize the reply verifier, and update rqst
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;
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);
435 * We succeeded, commit the data to the cache now and
436 * finish cooking the credential.
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);
454 entry->key = *sessionkey;
455 entry->window = window;
456 /* mark any cached cred invalid */
457 invalidate(entry->localcred);
458 } else { /* ADN_NICKNAME */
460 * nicknames are cooked into fullnames
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;
467 (void) mutex_unlock(&authdes_lock);
468 return (AUTH_OK); /* we made it! */
473 * Initialize the cache
475 static int
476 cache_init(void)
478 int i;
480 /* LOCK HELD ON ENTRY: authdes_lock */
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);
489 (void) memset(_rpc_authdes_cache, 0,
490 sizeof (struct cache_entry) * authdes_cachesz);
493 * Initialize the lru chain (linked-list)
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];
500 cache_head = &_rpc_authdes_cache[0];
501 cache_tail = &_rpc_authdes_cache[authdes_cachesz - 1];
504 * These elements of the chain need special attention...
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);
517 * Find the lru victim
519 static uint32_t
520 cache_victim(void)
522 /* LOCK HELD ON ENTRY: authdes_lock */
524 assert(MUTEX_HELD(&authdes_lock));
525 return (cache_head->index); /* list in lru order */
529 * Note that sid was referenced
531 static void
532 cache_ref(uint32_t sid)
534 struct cache_entry *curr = &_rpc_authdes_cache[sid];
537 /* LOCK HELD ON ENTRY: authdes_lock */
539 assert(MUTEX_HELD(&authdes_lock));
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).
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 */
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.
567 static int
568 cache_spot(des_block *key, char *name, struct timeval *timestamp)
570 struct cache_entry *cp;
571 int i;
572 uint32_t hi;
574 /* LOCK HELD ON ENTRY: authdes_lock */
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 */
587 svcauthdes_stats.ncachehits++;
588 return (i);
589 /* refresh */
592 svcauthdes_stats.ncachemisses++;
593 return (cache_victim());
598 * Local credential handling stuff.
599 * NOTE: bsd unix dependent.
600 * Other operating systems should put something else here.
602 #define UNKNOWN -2 /* grouplen, if cached cred is unknown user */
603 #define INVALID -1 /* grouplen, if cache entry is invalid */
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 */
612 static void
613 invalidate(char *cred)
615 if (cred == NULL)
616 return;
617 /* LINTED pointer cast */
618 ((struct bsdcred *)cred)->grouplen = INVALID;
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.
628 authdes_getucred(const struct authdes_cred *adc, uid_t *uid, gid_t *gid,
629 short *grouplen, gid_t *groups)
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;
638 sid = adc->adc_nickname;
639 if (sid >= authdes_cachesz) {
640 __msgout2(__getucredstr, "invalid nickname");
641 return (0);
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;
649 if (bsdcred_sz == 0) {
650 bsdcred_sz = sizeof (struct bsdcred) +
651 (sysconf(_SC_NGROUPS_MAX) - 1) * sizeof (gid_t);
653 cred = malloc(bsdcred_sz);
654 if (cred == NULL) {
655 __msgout2(__getucredstr, "out of memory");
656 (void) mutex_unlock(&authdes_lock);
657 return (0);
659 _rpc_authdes_cache[sid].localcred = (char *)cred;
660 cred->grouplen = INVALID;
662 if (cred->grouplen == INVALID) {
664 * not in cache: lookup
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);
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];
681 (void) mutex_unlock(&authdes_lock);
682 return (1);
684 if (cred->grouplen == UNKNOWN) {
686 * Already lookup up, but no match found
688 (void) mutex_unlock(&authdes_lock);
689 return (0);
693 * cached credentials
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];
701 (void) mutex_unlock(&authdes_lock);
702 return (1);
706 static void
707 __msgout(int level, const char *str, const char *strarg)
709 (void) syslog(level, "%s %s", str, strarg);
713 static void
714 __msgout2(const char *str, const char *str2)
716 (void) syslog(LOG_DEBUG, "%s %s", str, str2);