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.
26 * Copyright 2017 Joyent Inc
28 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
29 /* All Rights Reserved */
31 * Portions of this source code were derived from Berkeley
32 * 4.3 BSD under license from the Regents of the University of
37 * svcauth_des.c, server-side des authentication
39 * We insure for the service the following:
40 * (1) The timestamp microseconds do not exceed 1 million.
41 * (2) The timestamp plus the window is less than the current time.
42 * (3) The timestamp is not less than the one previously
43 * seen in the current session.
45 * It is up to the server to determine if the window size is
53 #include <rpc/des_crypt.h>
55 #include <sys/types.h>
56 #include <sys/param.h>
61 #include <sys/debug.h>
65 extern int key_decryptsession_pk(const char *, netobj
*, des_block
*);
67 #define USEC_PER_SEC ((ulong_t)1000000L)
68 #define BEFORE(t1, t2) timercmp(t1, t2, < /* EMPTY */)
72 * LRU cache of conversation keys and some other useful items.
74 #define DEF_AUTHDES_CACHESZ 128
75 int authdes_cachesz
= DEF_AUTHDES_CACHESZ
;
77 des_block key
; /* conversation key */
78 char *rname
; /* client's name */
79 uint_t window
; /* credential lifetime window */
80 struct timeval laststamp
; /* detect replays of creds */
81 char *localcred
; /* generic local credential */
82 int index
; /* where are we in array? */
83 struct cache_entry
*prev
; /* prev entry on LRU list */
84 struct cache_entry
*next
; /* next entry on LRU list */
87 static const char __getucredstr
[] = "authdes_getucred:";
89 static struct cache_entry
*_rpc_authdes_cache
; /* [authdes_cachesz] */
90 static struct cache_entry
*cache_head
; /* cache (in LRU order) */
91 static struct cache_entry
*cache_tail
; /* cache (in LRU order) */
94 * A rwlock_t would seem to make more sense, but it turns out we always
95 * muck with the cache entries, so would always need a write lock (in
96 * which case, we might as well use a mutex).
98 extern mutex_t authdes_lock
;
101 static int cache_init(void); /* initialize the cache */
102 /* find an entry in the cache */
103 static int cache_spot(des_block
*, char *, struct timeval
*);
104 static void cache_ref(uint32_t); /* note that sid was ref'd */
105 static void invalidate(char *); /* invalidate entry in cache */
106 static void __msgout(int, const char *, const char *);
107 static void __msgout2(const char *, const char *);
113 ulong_t ncachehits
; /* times cache hit, and is not replay */
114 ulong_t ncachereplays
; /* times cache hit, and is replay */
115 ulong_t ncachemisses
; /* times cache missed */
119 * NOTE: this has to fit inside RQCRED_SIZE bytes. If you update this struct,
120 * double-check it still fits.
122 struct authdes_area
{
123 struct authdes_cred area_cred
;
124 char area_netname
[MAXNETNAMELEN
+1];
126 CTASSERT(sizeof (struct authdes_area
) <= RQCRED_SIZE
);
129 * Service side authenticator for AUTH_DES
132 __svcauth_des(struct svc_req
*rqst
, struct rpc_msg
*msg
)
135 des_block cryptbuf
[2];
136 struct authdes_cred
*cred
;
137 struct authdes_verf verf
;
139 struct cache_entry
*entry
;
142 des_block
*sessionkey
, init_sessionkey
;
145 struct authdes_area
*area
;
146 struct timeval timestamp
;
148 int fullname_rcvd
= 0;
151 (void) mutex_lock(&authdes_lock
);
152 if (_rpc_authdes_cache
== NULL
) {
153 int ret
= cache_init();
155 (void) mutex_unlock(&authdes_lock
);
156 return (AUTH_FAILED
);
159 (void) mutex_unlock(&authdes_lock
);
161 /* LINTED pointer cast */
162 area
= (struct authdes_area
*)rqst
->rq_clntcred
;
163 cred
= (struct authdes_cred
*)&area
->area_cred
;
165 if ((uint_t
)msg
->rm_call
.cb_cred
.oa_length
== 0)
166 return (AUTH_BADCRED
);
170 /* LINTED pointer cast */
171 ixdr
= (int32_t *)msg
->rm_call
.cb_cred
.oa_base
;
172 cred
->adc_namekind
= IXDR_GET_ENUM(ixdr
, enum authdes_namekind
);
173 switch (cred
->adc_namekind
) {
175 namelen
= IXDR_GET_U_INT32(ixdr
);
176 if (namelen
> MAXNETNAMELEN
)
177 return (AUTH_BADCRED
);
178 cred
->adc_fullname
.name
= area
->area_netname
;
179 (void) memcpy(cred
->adc_fullname
.name
, ixdr
, (uint_t
)namelen
);
180 cred
->adc_fullname
.name
[namelen
] = 0;
181 ixdr
+= (RNDUP(namelen
) / BYTES_PER_XDR_UNIT
);
182 cred
->adc_fullname
.key
.key
.high
= (uint32_t)*ixdr
++;
183 cred
->adc_fullname
.key
.key
.low
= (uint32_t)*ixdr
++;
184 cred
->adc_fullname
.window
= (uint32_t)*ixdr
++;
188 cred
->adc_nickname
= (uint32_t)*ixdr
++;
191 return (AUTH_BADCRED
);
194 if ((uint_t
)msg
->rm_call
.cb_verf
.oa_length
== 0)
195 return (AUTH_BADVERF
);
199 /* LINTED pointer cast */
200 ixdr
= (int32_t *)msg
->rm_call
.cb_verf
.oa_base
;
201 verf
.adv_xtimestamp
.key
.high
= (uint32_t)*ixdr
++;
202 verf
.adv_xtimestamp
.key
.low
= (uint32_t)*ixdr
++;
203 verf
.adv_int_u
= (uint32_t)*ixdr
++;
205 (void) mutex_lock(&authdes_lock
);
208 * Get the conversation key
210 if (fullname_rcvd
) { /* ADN_FULLNAME */
212 char pkey_data
[1024];
215 init_sessionkey
= cred
->adc_fullname
.key
;
216 sessionkey
= &init_sessionkey
;
218 if (!__getpublickey_cached(cred
->adc_fullname
.name
,
219 pkey_data
, &from_cache
)) {
221 * if the user has no public key, treat them as the
222 * unauthenticated identity - nobody. If this
223 * works, it means the client didn't find the
224 * user's keys and used nobody's secret key
227 if (!__getpublickey_cached("nobody",
228 pkey_data
, &from_cache
)) {
230 "_svcauth_des: no public key for nobody or ",
231 cred
->adc_fullname
.name
);
232 (void) mutex_unlock(&authdes_lock
);
233 return (AUTH_BADCRED
); /* no key */
237 * found a public key for nobody. change
238 * the fullname id to nobody, so the caller
239 * thinks the client specified nobody
240 * as the user identity.
242 (void) strcpy(cred
->adc_fullname
.name
, "nobody");
244 pkey
.n_bytes
= pkey_data
;
245 pkey
.n_len
= strlen(pkey_data
) + 1;
246 if (key_decryptsession_pk(cred
->adc_fullname
.name
, &pkey
,
249 __getpublickey_flush(cred
->adc_fullname
.name
);
253 "_svcauth_des: key_decryptsessionkey failed for",
254 cred
->adc_fullname
.name
);
255 (void) mutex_unlock(&authdes_lock
);
256 return (AUTH_BADCRED
); /* key not found */
258 } else { /* ADN_NICKNAME */
259 sid
= cred
->adc_nickname
;
260 if (sid
>= authdes_cachesz
) {
261 __msgout(LOG_INFO
, "_svcauth_des:", "bad nickname");
262 (void) mutex_unlock(&authdes_lock
);
263 return (AUTH_BADCRED
); /* garbled credential */
265 /* actually check that the entry is not null */
266 entry
= &_rpc_authdes_cache
[sid
];
267 if (entry
->rname
== NULL
) {
268 (void) mutex_unlock(&authdes_lock
);
269 return (AUTH_BADCRED
); /* cached out */
271 sessionkey
= &_rpc_authdes_cache
[sid
].key
;
275 * Decrypt the timestamp
277 cryptbuf
[0] = verf
.adv_xtimestamp
;
278 if (fullname_rcvd
) { /* ADN_FULLNAME */
279 cryptbuf
[1].key
.high
= cred
->adc_fullname
.window
;
280 cryptbuf
[1].key
.low
= verf
.adv_winverf
;
281 ivec
.key
.high
= ivec
.key
.low
= 0;
282 status
= cbc_crypt((char *)sessionkey
, (char *)cryptbuf
,
283 2 * (int)sizeof (des_block
), DES_DECRYPT
| DES_HW
,
286 status
= ecb_crypt((char *)sessionkey
, (char *)cryptbuf
,
287 (int)sizeof (des_block
), DES_DECRYPT
| DES_HW
);
289 if (DES_FAILED(status
)) {
290 if (fullname_rcvd
&& from_cache
) {
291 __getpublickey_flush(cred
->adc_fullname
.name
);
294 __msgout(LOG_ERR
, "_svcauth_des: DES decryption failure for",
295 fullname_rcvd
? cred
->adc_fullname
.name
:
296 _rpc_authdes_cache
[sid
].rname
);
297 (void) mutex_unlock(&authdes_lock
);
298 return (AUTH_FAILED
); /* system error */
302 * XDR the decrypted timestamp
304 ixdr
= (int32_t *)cryptbuf
;
305 timestamp
.tv_sec
= IXDR_GET_INT32(ixdr
);
306 timestamp
.tv_usec
= IXDR_GET_INT32(ixdr
);
309 * Check for valid credentials and verifiers.
310 * They could be invalid because the key was flushed
311 * out of the cache, and so a new session should begin.
312 * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case.
315 struct timeval current
;
320 window
= IXDR_GET_U_INT32(ixdr
);
321 winverf
= IXDR_GET_U_INT32(ixdr
);
322 if (winverf
!= window
- 1) {
324 __getpublickey_flush(
325 cred
->adc_fullname
.name
);
329 "_svcauth_des: corrupted window from",
330 cred
->adc_fullname
.name
);
331 (void) mutex_unlock(&authdes_lock
);
332 /* garbled credential or invalid secret key */
333 return (AUTH_BADCRED
);
335 cache_spot_id
= cache_spot(sessionkey
,
336 cred
->adc_fullname
.name
,
339 if (cache_spot_id
< 0) {
341 "_svcauth_des: replayed credential from",
342 cred
->adc_fullname
.name
);
343 (void) mutex_unlock(&authdes_lock
);
344 return (AUTH_REJECTEDCRED
); /* replay */
345 } else sid
= cache_spot_id
;
347 } else { /* ADN_NICKNAME */
348 window
= _rpc_authdes_cache
[sid
].window
;
352 if ((ulong_t
)timestamp
.tv_usec
>= USEC_PER_SEC
) {
353 if (fullname_rcvd
&& from_cache
) {
354 __getpublickey_flush(cred
->adc_fullname
.name
);
358 "_svcauth_des: invalid timestamp received from",
359 fullname_rcvd
? cred
->adc_fullname
.name
:
360 _rpc_authdes_cache
[sid
].rname
);
361 /* cached out (bad key), or garbled verifier */
362 (void) mutex_unlock(&authdes_lock
);
363 return (nick
? AUTH_REJECTEDVERF
: AUTH_BADVERF
);
365 if (nick
&& BEFORE(×tamp
,
366 &_rpc_authdes_cache
[sid
].laststamp
)) {
367 if (fullname_rcvd
&& from_cache
) {
368 __getpublickey_flush(cred
->adc_fullname
.name
);
372 "_svcauth_des: timestamp is earlier than the one previously seen from",
373 fullname_rcvd
? cred
->adc_fullname
.name
:
374 _rpc_authdes_cache
[sid
].rname
);
375 (void) mutex_unlock(&authdes_lock
);
376 return (AUTH_REJECTEDVERF
); /* replay */
378 (void) gettimeofday(¤t
, NULL
);
379 current
.tv_sec
-= window
; /* allow for expiration */
380 if (!BEFORE(¤t
, ×tamp
)) {
381 if (fullname_rcvd
&& from_cache
) {
382 __getpublickey_flush(cred
->adc_fullname
.name
);
386 "_svcauth_des: timestamp expired for",
387 fullname_rcvd
? cred
->adc_fullname
.name
:
388 _rpc_authdes_cache
[sid
].rname
);
389 /* replay, or garbled credential */
390 (void) mutex_unlock(&authdes_lock
);
391 return (nick
? AUTH_REJECTEDVERF
: AUTH_BADCRED
);
396 * Set up the reply verifier
398 verf
.adv_nickname
= sid
;
401 * xdr the timestamp before encrypting
403 ixdr
= (int32_t *)cryptbuf
;
404 IXDR_PUT_INT32(ixdr
, timestamp
.tv_sec
- 1);
405 IXDR_PUT_INT32(ixdr
, timestamp
.tv_usec
);
408 * encrypt the timestamp
410 status
= ecb_crypt((char *)sessionkey
, (char *)cryptbuf
,
411 (int)sizeof (des_block
), DES_ENCRYPT
| DES_HW
);
412 if (DES_FAILED(status
)) {
413 __msgout(LOG_ERR
, "_svcauth_des: DES encryption failure for",
414 fullname_rcvd
? cred
->adc_fullname
.name
:
415 _rpc_authdes_cache
[sid
].rname
);
416 (void) mutex_unlock(&authdes_lock
);
417 return (AUTH_FAILED
); /* system error */
419 verf
.adv_xtimestamp
= cryptbuf
[0];
422 * Serialize the reply verifier, and update rqst
424 /* LINTED pointer cast */
425 ixdr
= (int32_t *)msg
->rm_call
.cb_verf
.oa_base
;
426 *ixdr
++ = (int32_t)verf
.adv_xtimestamp
.key
.high
;
427 *ixdr
++ = (int32_t)verf
.adv_xtimestamp
.key
.low
;
428 *ixdr
++ = (int32_t)verf
.adv_int_u
;
430 rqst
->rq_xprt
->xp_verf
.oa_flavor
= AUTH_DES
;
431 rqst
->rq_xprt
->xp_verf
.oa_base
= msg
->rm_call
.cb_verf
.oa_base
;
432 rqst
->rq_xprt
->xp_verf
.oa_length
=
433 (char *)ixdr
- msg
->rm_call
.cb_verf
.oa_base
;
434 if (rqst
->rq_xprt
->xp_verf
.oa_length
> MAX_AUTH_BYTES
) {
436 "_svcauth_des: Authenticator length error",
437 fullname_rcvd
? cred
->adc_fullname
.name
:
438 _rpc_authdes_cache
[sid
].rname
);
439 (void) mutex_unlock(&authdes_lock
);
440 return (AUTH_REJECTEDVERF
);
444 * We succeeded, commit the data to the cache now and
445 * finish cooking the credential.
447 entry
= &_rpc_authdes_cache
[sid
];
448 entry
->laststamp
= timestamp
;
450 if (cred
->adc_namekind
== ADN_FULLNAME
) {
451 cred
->adc_fullname
.window
= window
;
452 cred
->adc_nickname
= sid
; /* save nickname */
454 entry
->rname
= malloc(strlen(cred
->adc_fullname
.name
) + 1);
455 if (entry
->rname
!= NULL
) {
456 (void) strcpy(entry
->rname
, cred
->adc_fullname
.name
);
458 __msgout(LOG_CRIT
, "_svcauth_des:", "out of memory");
459 (void) mutex_unlock(&authdes_lock
);
460 return (AUTH_FAILED
);
462 entry
->key
= *sessionkey
;
463 entry
->window
= window
;
464 /* mark any cached cred invalid */
465 invalidate(entry
->localcred
);
466 } else { /* ADN_NICKNAME */
468 * nicknames are cooked into fullnames
470 cred
->adc_namekind
= ADN_FULLNAME
;
471 cred
->adc_fullname
.name
= entry
->rname
;
472 cred
->adc_fullname
.key
= entry
->key
;
473 cred
->adc_fullname
.window
= entry
->window
;
475 (void) mutex_unlock(&authdes_lock
);
476 return (AUTH_OK
); /* we made it! */
481 * Initialize the cache
488 /* LOCK HELD ON ENTRY: authdes_lock */
490 assert(MUTEX_HELD(&authdes_lock
));
492 malloc(sizeof (struct cache_entry
) * authdes_cachesz
);
493 if (_rpc_authdes_cache
== NULL
) {
494 __msgout(LOG_CRIT
, "cache_init:", "out of memory");
497 (void) memset(_rpc_authdes_cache
, 0,
498 sizeof (struct cache_entry
) * authdes_cachesz
);
501 * Initialize the lru chain (linked-list)
503 for (i
= 1; i
< (authdes_cachesz
- 1); i
++) {
504 _rpc_authdes_cache
[i
].index
= i
;
505 _rpc_authdes_cache
[i
].next
= &_rpc_authdes_cache
[i
+ 1];
506 _rpc_authdes_cache
[i
].prev
= &_rpc_authdes_cache
[i
- 1];
508 cache_head
= &_rpc_authdes_cache
[0];
509 cache_tail
= &_rpc_authdes_cache
[authdes_cachesz
- 1];
512 * These elements of the chain need special attention...
514 cache_head
->index
= 0;
515 cache_tail
->index
= authdes_cachesz
- 1;
516 cache_head
->next
= &_rpc_authdes_cache
[1];
517 cache_head
->prev
= cache_tail
;
518 cache_tail
->next
= cache_head
;
519 cache_tail
->prev
= &_rpc_authdes_cache
[authdes_cachesz
- 2];
525 * Find the lru victim
530 /* LOCK HELD ON ENTRY: authdes_lock */
532 assert(MUTEX_HELD(&authdes_lock
));
533 return (cache_head
->index
); /* list in lru order */
537 * Note that sid was referenced
540 cache_ref(uint32_t sid
)
542 struct cache_entry
*curr
= &_rpc_authdes_cache
[sid
];
545 /* LOCK HELD ON ENTRY: authdes_lock */
547 assert(MUTEX_HELD(&authdes_lock
));
550 * move referenced item from its place on the LRU chain
551 * to the tail of the chain while checking for special
552 * conditions (mainly for performance).
554 if (cache_tail
== curr
) { /* no work to do */
556 } else if (cache_head
== curr
) {
557 cache_head
= cache_head
->next
;
560 (curr
->next
)->prev
= curr
->prev
; /* fix thy neighbor */
561 (curr
->prev
)->next
= curr
->next
;
562 curr
->next
= cache_head
; /* fix thy self... */
563 curr
->prev
= cache_tail
;
564 cache_head
->prev
= curr
; /* fix the head */
565 cache_tail
->next
= curr
; /* fix the tail */
566 cache_tail
= curr
; /* move the tail */
571 * Find a spot in the cache for a credential containing
572 * the items given. Return -1 if a replay is detected, otherwise
573 * return the spot in the cache.
576 cache_spot(des_block
*key
, char *name
, struct timeval
*timestamp
)
578 struct cache_entry
*cp
;
582 /* LOCK HELD ON ENTRY: authdes_lock */
584 assert(MUTEX_HELD(&authdes_lock
));
586 for (cp
= _rpc_authdes_cache
, i
= 0; i
< authdes_cachesz
; i
++, cp
++) {
587 if (cp
->key
.key
.high
== hi
&&
588 cp
->key
.key
.low
== key
->key
.low
&&
590 memcmp(cp
->rname
, name
, strlen(name
) + 1) == 0) {
591 if (BEFORE(timestamp
, &cp
->laststamp
)) {
592 svcauthdes_stats
.ncachereplays
++;
593 return (-1); /* replay */
595 svcauthdes_stats
.ncachehits
++;
600 svcauthdes_stats
.ncachemisses
++;
601 return (cache_victim());
606 * Local credential handling stuff.
607 * NOTE: bsd unix dependent.
608 * Other operating systems should put something else here.
610 #define UNKNOWN -2 /* grouplen, if cached cred is unknown user */
611 #define INVALID -1 /* grouplen, if cache entry is invalid */
614 uid_t uid
; /* cached uid */
615 gid_t gid
; /* cached gid */
616 short grouplen
; /* length of cached groups */
617 gid_t groups
[1]; /* cached groups allocate _SC_NGROUPS_MAX */
621 invalidate(char *cred
)
625 /* LINTED pointer cast */
626 ((struct bsdcred
*)cred
)->grouplen
= INVALID
;
630 * Map a des credential into a unix cred.
631 * We cache the credential here so the application does
632 * not have to make an rpc call every time to interpret
636 authdes_getucred(const struct authdes_cred
*adc
, uid_t
*uid
, gid_t
*gid
,
637 short *grouplen
, gid_t
*groups
)
644 struct bsdcred
*cred
;
646 sid
= adc
->adc_nickname
;
647 if (sid
>= authdes_cachesz
) {
648 __msgout2(__getucredstr
, "invalid nickname");
651 (void) mutex_lock(&authdes_lock
);
652 /* LINTED pointer cast */
653 cred
= (struct bsdcred
*)_rpc_authdes_cache
[sid
].localcred
;
655 static size_t bsdcred_sz
;
657 if (bsdcred_sz
== 0) {
658 bsdcred_sz
= sizeof (struct bsdcred
) +
659 (sysconf(_SC_NGROUPS_MAX
) - 1) * sizeof (gid_t
);
661 cred
= malloc(bsdcred_sz
);
663 __msgout2(__getucredstr
, "out of memory");
664 (void) mutex_unlock(&authdes_lock
);
667 _rpc_authdes_cache
[sid
].localcred
= (char *)cred
;
668 cred
->grouplen
= INVALID
;
670 if (cred
->grouplen
== INVALID
) {
672 * not in cache: lookup
674 if (!netname2user(adc
->adc_fullname
.name
, (uid_t
*)&i_uid
,
675 (gid_t
*)&i_gid
, &i_grouplen
, (gid_t
*)groups
)) {
676 __msgout2(__getucredstr
, "unknown netname");
677 /* mark as lookup up, but not found */
678 cred
->grouplen
= UNKNOWN
;
679 (void) mutex_unlock(&authdes_lock
);
682 __msgout2(__getucredstr
, "missed ucred cache");
683 *uid
= cred
->uid
= i_uid
;
684 *gid
= cred
->gid
= i_gid
;
685 *grouplen
= cred
->grouplen
= i_grouplen
;
686 for (i
= i_grouplen
- 1; i
>= 0; i
--) {
687 cred
->groups
[i
] = groups
[i
];
689 (void) mutex_unlock(&authdes_lock
);
692 if (cred
->grouplen
== UNKNOWN
) {
694 * Already lookup up, but no match found
696 (void) mutex_unlock(&authdes_lock
);
705 *grouplen
= cred
->grouplen
;
706 for (i
= cred
->grouplen
- 1; i
>= 0; i
--) {
707 groups
[i
] = cred
->groups
[i
];
709 (void) mutex_unlock(&authdes_lock
);
715 __msgout(int level
, const char *str
, const char *strarg
)
717 (void) syslog(level
, "%s %s", str
, strarg
);
722 __msgout2(const char *str
, const char *str2
)
724 (void) syslog(LOG_DEBUG
, "%s %s", str
, str2
);