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]
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
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
52 #include <rpc/des_crypt.h>
54 #include <sys/types.h>
55 #include <sys/param.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
;
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 *);
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 */
117 * Service side authenticator for AUTH_DES
120 __svcauth_des(struct svc_req
*rqst
, struct rpc_msg
*msg
)
123 des_block cryptbuf
[2];
124 struct authdes_cred
*cred
;
125 struct authdes_verf verf
;
127 struct cache_entry
*entry
;
130 des_block
*sessionkey
, init_sessionkey
;
133 struct timeval timestamp
;
136 struct authdes_cred area_cred
;
137 char area_netname
[MAXNETNAMELEN
+1];
139 int fullname_rcvd
= 0;
142 (void) mutex_lock(&authdes_lock
);
143 if (_rpc_authdes_cache
== NULL
) {
144 int ret
= cache_init();
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
);
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
) {
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
++;
179 cred
->adc_nickname
= (uint32_t)*ixdr
++;
182 return (AUTH_BADCRED
);
185 if ((uint_t
)msg
->rm_call
.cb_verf
.oa_length
== 0)
186 return (AUTH_BADVERF
);
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 */
203 char pkey_data
[1024];
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
218 if (!__getpublickey_cached("nobody",
219 pkey_data
, &from_cache
)) {
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
,
240 __getpublickey_flush(cred
->adc_fullname
.name
);
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
,
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
);
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
;
311 window
= IXDR_GET_U_INT32(ixdr
);
312 winverf
= IXDR_GET_U_INT32(ixdr
);
313 if (winverf
!= window
- 1) {
315 __getpublickey_flush(
316 cred
->adc_fullname
.name
);
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
,
330 if (cache_spot_id
< 0) {
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
;
338 } else { /* ADN_NICKNAME */
339 window
= _rpc_authdes_cache
[sid
].window
;
343 if ((ulong_t
)timestamp
.tv_usec
>= USEC_PER_SEC
) {
344 if (fullname_rcvd
&& from_cache
) {
345 __getpublickey_flush(cred
->adc_fullname
.name
);
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(×tamp
,
357 &_rpc_authdes_cache
[sid
].laststamp
)) {
358 if (fullname_rcvd
&& from_cache
) {
359 __getpublickey_flush(cred
->adc_fullname
.name
);
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(¤t
, NULL
);
370 current
.tv_sec
-= window
; /* allow for expiration */
371 if (!BEFORE(¤t
, ×tamp
)) {
372 if (fullname_rcvd
&& from_cache
) {
373 __getpublickey_flush(cred
->adc_fullname
.name
);
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
) {
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
;
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
)
446 entry
->rname
= malloc(strlen(cred
->adc_fullname
.name
) + 1);
447 if (entry
->rname
!= NULL
) {
448 (void) strcpy(entry
->rname
, cred
->adc_fullname
.name
);
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
480 /* LOCK HELD ON ENTRY: authdes_lock */
482 assert(MUTEX_HELD(&authdes_lock
));
484 malloc(sizeof (struct cache_entry
) * authdes_cachesz
);
485 if (_rpc_authdes_cache
== NULL
) {
486 __msgout(LOG_CRIT
, "cache_init:", "out of memory");
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];
517 * Find the lru victim
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
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 */
548 } else if (cache_head
== curr
) {
549 cache_head
= cache_head
->next
;
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.
568 cache_spot(des_block
*key
, char *name
, struct timeval
*timestamp
)
570 struct cache_entry
*cp
;
574 /* LOCK HELD ON ENTRY: authdes_lock */
576 assert(MUTEX_HELD(&authdes_lock
));
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
&&
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
++;
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 */
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 */
613 invalidate(char *cred
)
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
628 authdes_getucred(const struct authdes_cred
*adc
, uid_t
*uid
, gid_t
*gid
,
629 short *grouplen
, gid_t
*groups
)
636 struct bsdcred
*cred
;
638 sid
= adc
->adc_nickname
;
639 if (sid
>= authdes_cachesz
) {
640 __msgout2(__getucredstr
, "invalid nickname");
643 (void) mutex_lock(&authdes_lock
);
644 /* LINTED pointer cast */
645 cred
= (struct bsdcred
*)_rpc_authdes_cache
[sid
].localcred
;
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
);
655 __msgout2(__getucredstr
, "out of memory");
656 (void) mutex_unlock(&authdes_lock
);
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
);
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
);
684 if (cred
->grouplen
== UNKNOWN
) {
686 * Already lookup up, but no match found
688 (void) mutex_unlock(&authdes_lock
);
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
);
707 __msgout(int level
, const char *str
, const char *strarg
)
709 (void) syslog(level
, "%s %s", str
, strarg
);
714 __msgout2(const char *str
, const char *str2
)
716 (void) syslog(LOG_DEBUG
, "%s %s", str
, str2
);