/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2017 Joyent Inc * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * svcauth_des.c, server-side des authentication * * We insure for the service the following: * (1) The timestamp microseconds do not exceed 1 million. * (2) The timestamp plus the window is less than the current time. * (3) The timestamp is not less than the one previously * seen in the current session. * * It is up to the server to determine if the window size is * too small. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USEC_PER_SEC 1000000 #define BEFORE(t1, t2) timercmp(t1, t2, < /* COMMENT HERE TO DEFEAT CSTYLE */) /* * Cache of conversation keys and some other useful items. * The hash table size is controled via authdes_cachesz variable. * The authdes_cachesz has to be the power of 2. */ #define AUTHDES_CACHE_TABLE_SZ 1024 static int authdes_cachesz = AUTHDES_CACHE_TABLE_SZ; #define HASH(key) ((key) & (authdes_cachesz - 1)) /* low water mark for the number of cache entries */ static int low_cache_entries = 128; struct authdes_cache_entry { uint32_t nickname; /* nick name id */ uint32_t window; /* credential lifetime window */ des_block key; /* conversation key */ time_t ref_time; /* time referenced previously */ char *rname; /* client's name */ caddr_t localcred; /* generic local credential */ struct authdes_cache_entry *prev, *next; /* hash table linked list */ struct authdes_cache_entry *lru_prev, *lru_next; /* LRU linked list */ kmutex_t lock; /* cache entry lock */ }; static struct authdes_cache_entry **authdes_cache; /* [authdes_cachesz] */ static struct authdes_cache_entry *lru_first = NULL; static struct authdes_cache_entry *lru_last = NULL; static kmutex_t authdes_lock; /* cache table lock */ static struct kmem_cache *authdes_cache_handle; static uint32_t Nickname = 0; static struct authdes_cache_entry *authdes_cache_new(char *, des_block *, uint32_t); static struct authdes_cache_entry *authdes_cache_get(uint32_t); static void authdes_cache_reclaim(void *); static void sweep_cache(); /* * After 12 hours, check and delete cache entries that have been * idled for more than 10 hours. */ static time_t authdes_sweep_interval = 12*60*60; static time_t authdes_cache_time = 10*60*60; static time_t authdes_last_swept = 0; /* * cache statistics */ static int authdes_ncache = 0; /* number of current cached entries */ static int authdes_ncachehits = 0; /* #times cache hit */ static int authdes_ncachemisses = 0; /* #times cache missed */ #define NOT_DEAD(ptr) ASSERT((((intptr_t)(ptr)) != 0xdeadbeef)) #define IS_ALIGNED(ptr) ASSERT((((intptr_t)(ptr)) & 3) == 0) /* * Service side authenticator for AUTH_DES */ enum auth_stat _svcauth_des(struct svc_req *rqst, struct rpc_msg *msg) { int32_t *ixdr; des_block cryptbuf[2]; struct authdes_cred *cred; struct authdes_verf verf; int status; des_block *sessionkey; des_block ivec; uint32_t window, winverf, namelen; bool_t nick; struct timeval timestamp, current_time; struct authdes_cache_entry *nick_entry; struct area { struct authdes_cred area_cred; char area_netname[MAXNETNAMELEN+1]; } *area; timestruc_t now; mutex_enter(&authdes_lock); if (authdes_cache == NULL) { authdes_cache = kmem_zalloc( sizeof (struct authdes_cache_entry *) * authdes_cachesz, KM_SLEEP); } mutex_exit(&authdes_lock); CTASSERT(sizeof (struct area) <= RQCRED_SIZE); /* LINTED pointer alignment */ area = (struct area *)rqst->rq_clntcred; cred = (struct authdes_cred *)&area->area_cred; /* * Get the credential */ /* LINTED pointer alignment */ ixdr = (int32_t *)msg->rm_call.cb_cred.oa_base; cred->adc_namekind = IXDR_GET_ENUM(ixdr, enum authdes_namekind); switch (cred->adc_namekind) { case ADN_FULLNAME: namelen = IXDR_GET_U_INT32(ixdr); if (namelen > MAXNETNAMELEN) return (AUTH_BADCRED); cred->adc_fullname.name = area->area_netname; bcopy(ixdr, cred->adc_fullname.name, namelen); cred->adc_fullname.name[namelen] = 0; ixdr += (RNDUP(namelen) / BYTES_PER_XDR_UNIT); cred->adc_fullname.key.key.high = (uint32_t)*ixdr++; cred->adc_fullname.key.key.low = (uint32_t)*ixdr++; cred->adc_fullname.window = (uint32_t)*ixdr++; nick = FALSE; break; case ADN_NICKNAME: cred->adc_nickname = (uint32_t)*ixdr++; nick = TRUE; break; default: return (AUTH_BADCRED); } /* * Get the verifier */ /* LINTED pointer alignment */ ixdr = (int32_t *)msg->rm_call.cb_verf.oa_base; verf.adv_xtimestamp.key.high = (uint32_t)*ixdr++; verf.adv_xtimestamp.key.low = (uint32_t)*ixdr++; verf.adv_int_u = (uint32_t)*ixdr++; /* * Get the conversation key */ if (!nick) { /* ADN_FULLNAME */ sessionkey = &cred->adc_fullname.key; if (key_decryptsession(cred->adc_fullname.name, sessionkey) != RPC_SUCCESS) { return (AUTH_BADCRED); /* key not found */ } } else { /* ADN_NICKNAME */ mutex_enter(&authdes_lock); if (!(nick_entry = authdes_cache_get(cred->adc_nickname))) { RPCLOG(1, "_svcauth_des: nickname %d not in the cache\n", cred->adc_nickname); mutex_exit(&authdes_lock); return (AUTH_BADCRED); /* need refresh */ } sessionkey = &nick_entry->key; mutex_enter(&nick_entry->lock); mutex_exit(&authdes_lock); } /* * Decrypt the timestamp */ cryptbuf[0] = verf.adv_xtimestamp; if (!nick) { /* ADN_FULLNAME */ cryptbuf[1].key.high = cred->adc_fullname.window; cryptbuf[1].key.low = verf.adv_winverf; ivec.key.high = ivec.key.low = 0; status = cbc_crypt((char *)sessionkey, (char *)cryptbuf, 2 * sizeof (des_block), DES_DECRYPT, (char *)&ivec); } else { /* ADN_NICKNAME */ status = ecb_crypt((char *)sessionkey, (char *)cryptbuf, sizeof (des_block), DES_DECRYPT); } if (DES_FAILED(status)) { RPCLOG0(1, "_svcauth_des: decryption failure\n"); if (nick) { mutex_exit(&nick_entry->lock); } return (AUTH_FAILED); /* system error */ } /* * XDR the decrypted timestamp */ ixdr = (int32_t *)cryptbuf; timestamp.tv_sec = IXDR_GET_INT32(ixdr); timestamp.tv_usec = IXDR_GET_INT32(ixdr); /* * Check for valid credentials and verifiers. * They could be invalid because the key was flushed * out of the cache, and so a new session should begin. * Be sure and send AUTH_REJECTED{CRED, VERF} if this is the case. */ if (!nick) { /* ADN_FULLNAME */ window = IXDR_GET_U_INT32(ixdr); winverf = IXDR_GET_U_INT32(ixdr); if (winverf != window - 1) { RPCLOG(1, "_svcauth_des: window verifier mismatch %d\n", winverf); return (AUTH_BADCRED); /* garbled credential */ } } else { /* ADN_NICKNAME */ window = nick_entry->window; } if (timestamp.tv_usec >= USEC_PER_SEC) { RPCLOG(1, "_svcauth_des: invalid usecs %ld\n", timestamp.tv_usec); /* cached out (bad key), or garbled verifier */ if (nick) { mutex_exit(&nick_entry->lock); } return (nick ? AUTH_REJECTEDVERF : AUTH_BADVERF); } gethrestime(&now); current_time.tv_sec = now.tv_sec; current_time.tv_usec = now.tv_nsec / 1000; current_time.tv_sec -= window; /* allow for expiration */ if (!BEFORE(¤t_time, ×tamp)) { RPCLOG0(1, "_svcauth_des: timestamp expired\n"); /* replay, or garbled credential */ if (nick) { mutex_exit(&nick_entry->lock); } return (nick ? AUTH_REJECTEDVERF : AUTH_BADCRED); } /* * xdr the timestamp before encrypting */ ixdr = (int32_t *)cryptbuf; IXDR_PUT_INT32(ixdr, timestamp.tv_sec - 1); IXDR_PUT_INT32(ixdr, timestamp.tv_usec); /* * encrypt the timestamp */ status = ecb_crypt((char *)sessionkey, (char *)cryptbuf, sizeof (des_block), DES_ENCRYPT); if (DES_FAILED(status)) { RPCLOG0(1, "_svcauth_des: encryption failure\n"); if (nick) { mutex_exit(&nick_entry->lock); } return (AUTH_FAILED); /* system error */ } verf.adv_xtimestamp = cryptbuf[0]; /* * If a ADN_FULLNAME, create a new nickname cache entry. */ if (!nick) { mutex_enter(&authdes_lock); if (!(nick_entry = authdes_cache_new(cred->adc_fullname.name, sessionkey, window))) { RPCLOG0(1, "_svcauth_des: can not create new cache entry\n"); mutex_exit(&authdes_lock); return (AUTH_FAILED); } mutex_enter(&nick_entry->lock); mutex_exit(&authdes_lock); } verf.adv_nickname = nick_entry->nickname; /* * Serialize the reply verifier, and update rqst */ /* LINTED pointer alignment */ ixdr = (int32_t *)msg->rm_call.cb_verf.oa_base; *ixdr++ = (int32_t)verf.adv_xtimestamp.key.high; *ixdr++ = (int32_t)verf.adv_xtimestamp.key.low; *ixdr++ = (int32_t)verf.adv_int_u; rqst->rq_xprt->xp_verf.oa_flavor = AUTH_DES; rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base; rqst->rq_xprt->xp_verf.oa_length = (uint_t)((char *)ixdr - msg->rm_call.cb_verf.oa_base); if (rqst->rq_xprt->xp_verf.oa_length > MAX_AUTH_BYTES) { RPCLOG0(1, "_svcauth_des: invalid oa length\n"); mutex_exit(&nick_entry->lock); return (AUTH_BADVERF); } /* * We succeeded and finish cooking the credential. * nicknames are cooked into fullnames */ if (!nick) { cred->adc_nickname = nick_entry->nickname; cred->adc_fullname.window = window; } else { /* ADN_NICKNAME */ cred->adc_namekind = ADN_FULLNAME; cred->adc_fullname.name = nick_entry->rname; cred->adc_fullname.key = nick_entry->key; cred->adc_fullname.window = nick_entry->window; } mutex_exit(&nick_entry->lock); /* * For every authdes_sweep_interval, delete cache entries that have been * idled for authdes_cache_time. */ mutex_enter(&authdes_lock); if ((gethrestime_sec() - authdes_last_swept) > authdes_sweep_interval) sweep_cache(); mutex_exit(&authdes_lock); return (AUTH_OK); /* we made it! */ } /* * Initialization upon loading the rpcsec module. */ void svcauthdes_init(void) { mutex_init(&authdes_lock, NULL, MUTEX_DEFAULT, NULL); /* * Allocate des cache handle */ authdes_cache_handle = kmem_cache_create("authdes_cache_handle", sizeof (struct authdes_cache_entry), 0, NULL, NULL, authdes_cache_reclaim, NULL, NULL, 0); } /* * Final actions upon exiting the rpcsec module. */ void svcauthdes_fini(void) { mutex_destroy(&authdes_lock); kmem_cache_destroy(authdes_cache_handle); } /* * Local credential handling stuff. * NOTE: bsd unix dependent. * Other operating systems should put something else here. */ struct bsdcred { uid_t uid; /* cached uid */ gid_t gid; /* cached gid */ short valid; /* valid creds */ short grouplen; /* length of cached groups */ gid_t groups[1]; /* cached groups - allocate ngroups_max */ }; /* * Map a des credential into a unix cred. * We cache the credential here so the application does * not have to make an rpc call every time to interpret * the credential. */ int kauthdes_getucred(const struct authdes_cred *adc, cred_t *cr) { uid_t i_uid; gid_t i_gid; int i_grouplen; struct bsdcred *cred; struct authdes_cache_entry *nickentry; mutex_enter(&authdes_lock); if (!(nickentry = authdes_cache_get(adc->adc_nickname))) { RPCLOG0(1, "authdes_getucred: invalid nickname\n"); mutex_exit(&authdes_lock); return (0); } mutex_enter(&nickentry->lock); mutex_exit(&authdes_lock); /* LINTED pointer alignment */ cred = (struct bsdcred *)nickentry->localcred; if (!cred->valid) { /* * not in cache: lookup */ if (netname2user(adc->adc_fullname.name, &i_uid, &i_gid, &i_grouplen, &cred->groups[0]) != RPC_SUCCESS) { /* * Probably a host principal, since at this * point we have valid keys. Note that later * if the principal is not in the root list * for NFS, we will be mapped to that exported * file system's anonymous user, typically * NOBODY. keyserv KEY_GETCRED will fail for a * root-netnames so we assume root here. * Currently NFS is the only caller of this * routine. If other RPC services call this * routine, it is up to that service to * differentiate between local and remote * roots. */ i_uid = 0; i_gid = 0; i_grouplen = 0; } RPCLOG0(2, "authdes_getucred: missed ucred cache\n"); cred->uid = i_uid; cred->gid = i_gid; cred->grouplen = (short)i_grouplen; cred->valid = 1; } /* * cached credentials */ if (crsetugid(cr, cred->uid, cred->gid) != 0 || crsetgroups(cr, cred->grouplen, &cred->groups[0]) != 0) { mutex_exit(&nickentry->lock); return (0); } mutex_exit(&nickentry->lock); return (1); } /* * Create a new cache_entry and put it in authdes_cache table. * Caller should have already locked the authdes_cache table. */ struct authdes_cache_entry * authdes_cache_new(char *fullname, des_block *sessionkey, uint32_t window) { struct authdes_cache_entry *new, *head; struct bsdcred *ucred; int index; if (!(new = kmem_cache_alloc(authdes_cache_handle, KM_SLEEP))) { return (NULL); } if (!(new->rname = kmem_alloc(strlen(fullname) + 1, KM_NOSLEEP))) { kmem_cache_free(authdes_cache_handle, new); return (NULL); } if (!(ucred = kmem_alloc(sizeof (struct bsdcred) + (ngroups_max - 1) * sizeof (gid_t), KM_NOSLEEP))) { kmem_free(new->rname, strlen(fullname) + 1); kmem_cache_free(authdes_cache_handle, new); return (NULL); } (void) strcpy(new->rname, fullname); ucred->valid = 0; new->localcred = (caddr_t)ucred; new->key = *sessionkey; new->window = window; new->ref_time = gethrestime_sec(); new->nickname = Nickname++; mutex_init(&new->lock, NULL, MUTEX_DEFAULT, NULL); /* put new into the hash table */ index = HASH(new->nickname); head = authdes_cache[index]; if ((new->next = head) != NULL) { head->prev = new; } authdes_cache[index] = new; new->prev = NULL; /* update the LRU list */ new->lru_prev = NULL; if ((new->lru_next = lru_first) != NULL) { lru_first->lru_prev = new; } else { lru_last = new; } lru_first = new; authdes_ncache++; return (new); } /* * Get an existing cache entry from authdes_cache table. * The caller should have locked the authdes_cache table. */ struct authdes_cache_entry * authdes_cache_get(uint32_t nickname) { struct authdes_cache_entry *cur = NULL; int index = HASH(nickname); ASSERT(MUTEX_HELD(&authdes_lock)); for (cur = authdes_cache[index]; cur; cur = cur->next) { if ((cur->nickname == nickname)) { /* find it, update the LRU list */ if (cur != lru_first) { cur->lru_prev->lru_next = cur->lru_next; if (cur->lru_next != NULL) { cur->lru_next->lru_prev = cur->lru_prev; } else { lru_last = cur->lru_prev; } cur->lru_prev = NULL; cur->lru_next = lru_first; lru_first->lru_prev = cur; lru_first = cur; } cur->ref_time = gethrestime_sec(); authdes_ncachehits++; return (cur); } } authdes_ncachemisses++; return (NULL); } /* * authdes_cache_reclaim() is called by the kernel memory allocator * when memory is low. This routine will reclaim 25% of the least recent * used cache entries above the low water mark (low_cache_entries). * If the cache entries have already hit the low water mark, it will * return 1 cache entry. */ /*ARGSUSED*/ void authdes_cache_reclaim(void *pdata) { struct authdes_cache_entry *p; int n, i; mutex_enter(&authdes_lock); n = authdes_ncache - low_cache_entries; n = n > 0 ? n/4 : 1; for (i = 0; i < n; i++) { if ((p = lru_last) == lru_first) break; /* Update the hash linked list */ if (p->prev == NULL) { authdes_cache[HASH(p->nickname)] = p->next; } else { p->prev->next = p->next; } if (p->next != NULL) { p->next->prev = p->prev; } /* update the LRU linked list */ p->lru_prev->lru_next = NULL; lru_last = p->lru_prev; kmem_free(p->rname, strlen(p->rname) + 1); kmem_free(p->localcred, sizeof (struct bsdcred) + (ngroups_max - 1) * sizeof (gid_t)); mutex_destroy(&p->lock); kmem_cache_free(authdes_cache_handle, p); authdes_ncache--; } mutex_exit(&authdes_lock); RPCLOG(4, "_svcauth_des: %d cache entries reclaimed...\n", authdes_ncache); } /* * Walk through the LRU doubly-linked list and delete the cache * entries that have not been used for more than authdes_cache_time. * * Caller should have locked the cache table. */ void sweep_cache() { struct authdes_cache_entry *p; ASSERT(MUTEX_HELD(&authdes_lock)); while ((p = lru_last) != lru_first) { IS_ALIGNED(p); NOT_DEAD(p); /* * If the last LRU entry idled less than authdes_cache_time, * we are done with the sweeping. */ if (p->ref_time + authdes_cache_time > gethrestime_sec()) break; /* update the hash linked list */ if (p->prev == NULL) { authdes_cache[HASH(p->nickname)] = p->next; } else { p->prev->next = p->next; } if (p->next != NULL) { p->next->prev = p->prev; } /* update the LRU linked list */ p->lru_prev->lru_next = NULL; lru_last = p->lru_prev; kmem_free(p->rname, strlen(p->rname) + 1); kmem_free(p->localcred, sizeof (struct bsdcred) + (ngroups_max - 1) * sizeof (gid_t)); mutex_destroy(&p->lock); kmem_cache_free(authdes_cache_handle, p); authdes_ncache--; } authdes_last_swept = gethrestime_sec(); RPCLOG(4, "_svcauth_des: sweeping cache...#caches left = %d\n", authdes_ncache); }