diff options
Diffstat (limited to 'REORG.TODO/sunrpc/svcauth_des.c')
-rw-r--r-- | REORG.TODO/sunrpc/svcauth_des.c | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/REORG.TODO/sunrpc/svcauth_des.c b/REORG.TODO/sunrpc/svcauth_des.c new file mode 100644 index 0000000000..8e6cb86e39 --- /dev/null +++ b/REORG.TODO/sunrpc/svcauth_des.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2010, Oracle America, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * * Neither the name of the "Oracle America, Inc." nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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 <limits.h> +#include <string.h> +#include <stdint.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <rpc/rpc.h> +#include <rpc/xdr.h> +#include <rpc/auth.h> +#include <rpc/auth_des.h> +#include <rpc/svc_auth.h> +#include <rpc/svc.h> +#include <rpc/des_crypt.h> +#include <shlib-compat.h> + +#define debug(msg) /*printf("svcauth_des: %s\n", msg) */ + +#define USEC_PER_SEC ((uint32_t) 1000000L) +#define BEFORE(t1, t2) timercmp(t1, t2, <) + +/* + * LRU cache of conversation keys and some other useful items. + */ +#define AUTHDES_CACHESZ 64 +struct cache_entry + { + des_block key; /* conversation key */ + char *rname; /* client's name */ + u_int window; /* credential lifetime window */ + struct rpc_timeval laststamp; /* detect replays of creds */ + char *localcred; /* generic local credential */ + }; +#ifdef _RPC_THREAD_SAFE_ +#define authdes_cache RPC_THREAD_VARIABLE(authdes_cache_s) +#define authdes_lru RPC_THREAD_VARIABLE(authdes_lru_s) +#else +static struct cache_entry *authdes_cache; +static int *authdes_lru; +#endif + +static void cache_init (void) internal_function; /* initialize the cache */ +static short cache_spot (des_block *, char *, struct rpc_timeval *) + internal_function; /* find an entry in the cache */ +static void cache_ref (uint32_t sid) internal_function; + /* note that sid was ref'd */ + +static void invalidate (char *cred) internal_function; + /* invalidate entry in cache */ + +/* + * cache statistics + */ +struct + { + u_long ncachehits; /* times cache hit, and is not replay */ + u_long ncachereplays; /* times cache hit, and is replay */ + u_long ncachemisses; /* times cache missed */ + } +svcauthdes_stats; + +/* + * Service side authenticator for AUTH_DES + */ +enum auth_stat +_svcauth_des (register struct svc_req *rqst, register struct rpc_msg *msg) +{ + register uint32_t *ixdr; + des_block cryptbuf[2]; + register struct authdes_cred *cred; + struct authdes_verf verf; + int status; + register struct cache_entry *entry; + uint32_t sid = 0; + des_block *sessionkey; + des_block ivec; + u_int window; + struct rpc_timeval timestamp; + uint32_t namelen; + struct area + { + struct authdes_cred area_cred; + char area_netname[MAXNETNAMELEN + 1]; + } + *area; + + if (authdes_cache == NULL) + cache_init (); + if (authdes_cache == NULL) /* No free memory */ + return AUTH_FAILED; + + area = (struct area *) rqst->rq_clntcred; + cred = (struct authdes_cred *) &area->area_cred; + + /* + * Get the credential + */ + if (msg->rm_call.cb_cred.oa_length <= 0 || + msg->rm_call.cb_cred.oa_length > MAX_AUTH_BYTES) + return AUTH_BADCRED; + + ixdr = (uint32_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; + memcpy (cred->adc_fullname.name, (char *) ixdr, namelen); + cred->adc_fullname.name[namelen] = 0; + ixdr += (RNDUP (namelen) / BYTES_PER_XDR_UNIT); + cred->adc_fullname.key.key.high = *ixdr++; + cred->adc_fullname.key.key.low = *ixdr++; + cred->adc_fullname.window = *ixdr++; + break; + case ADN_NICKNAME: + cred->adc_nickname = *ixdr++; + break; + default: + return AUTH_BADCRED; + } + + /* + * Get the verifier + */ + if (msg->rm_call.cb_verf.oa_length <= 0 || + msg->rm_call.cb_verf.oa_length > MAX_AUTH_BYTES) + return AUTH_BADCRED; + + ixdr = (uint32_t *) msg->rm_call.cb_verf.oa_base; + verf.adv_xtimestamp.key.high = *ixdr++; + verf.adv_xtimestamp.key.low = *ixdr++; + verf.adv_int_u = *ixdr++; + + /* + * Get the conversation key + */ + if (cred->adc_namekind == ADN_FULLNAME) + { + netobj pkey; + char pkey_data[1024]; + + sessionkey = &cred->adc_fullname.key; + if (!getpublickey (cred->adc_fullname.name, pkey_data)) + { + debug("getpublickey"); + return AUTH_BADCRED; + } + pkey.n_bytes = pkey_data; + pkey.n_len = strlen (pkey_data) + 1; + if (key_decryptsession_pk (cred->adc_fullname.name, &pkey, + sessionkey) < 0) + { + debug ("decryptsessionkey"); + return AUTH_BADCRED; /* key not found */ + } + } + else + { /* ADN_NICKNAME */ + if (cred->adc_nickname >= AUTHDES_CACHESZ) + { + debug ("bad nickname"); + return AUTH_BADCRED; /* garbled credential */ + } + else + sid = cred->adc_nickname; + + /* XXX This could be wrong, but else we have a + security problem */ + if (authdes_cache[sid].rname == NULL) + return AUTH_BADCRED; + sessionkey = &authdes_cache[sid].key; + } + + + /* + * Decrypt the timestamp + */ + cryptbuf[0] = verf.adv_xtimestamp; + if (cred->adc_namekind == 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 | DES_HW, + (char *) &ivec); + } + else + status = ecb_crypt ((char *) sessionkey, (char *) cryptbuf, + sizeof (des_block), DES_DECRYPT | DES_HW); + + if (DES_FAILED (status)) + { + debug ("decryption failure"); + return AUTH_FAILED; /* system error */ + } + + /* + * XDR the decrypted timestamp + */ + ixdr = (uint32_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. + */ + { + struct timeval current; + int nick; + u_int winverf; + + if (cred->adc_namekind == ADN_FULLNAME) + { + short tmp_spot; + + window = IXDR_GET_U_INT32 (ixdr); + winverf = IXDR_GET_U_INT32 (ixdr); + if (winverf != window - 1) + { + debug ("window verifier mismatch"); + return AUTH_BADCRED; /* garbled credential */ + } + tmp_spot = cache_spot (sessionkey, cred->adc_fullname.name, + ×tamp); + if (tmp_spot < 0 || tmp_spot > AUTHDES_CACHESZ) + { + debug ("replayed credential"); + return AUTH_REJECTEDCRED; /* replay */ + } + sid = tmp_spot; + nick = 0; + } + else + { /* ADN_NICKNAME */ + window = authdes_cache[sid].window; + nick = 1; + } + + if (timestamp.tv_usec >= USEC_PER_SEC) + { + debug ("invalid usecs"); + /* cached out (bad key), or garbled verifier */ + return nick ? AUTH_REJECTEDVERF : AUTH_BADVERF; + } + if (nick && BEFORE (×tamp, &authdes_cache[sid].laststamp)) + { + debug ("timestamp before last seen"); + return AUTH_REJECTEDVERF; /* replay */ + } + __gettimeofday (¤t, (struct timezone *) NULL); + current.tv_sec -= window; /* allow for expiration */ + if (!BEFORE (¤t, ×tamp)) + { + debug ("timestamp expired"); + /* replay, or garbled credential */ + return nick ? AUTH_REJECTEDVERF : AUTH_BADCRED; + } + } + + /* + * Set up the reply verifier + */ + verf.adv_nickname = sid; + + /* + * xdr the timestamp before encrypting + */ + ixdr = (uint32_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 | DES_HW); + if (DES_FAILED (status)) + { + debug ("encryption failure"); + return AUTH_FAILED; /* system error */ + } + verf.adv_xtimestamp = cryptbuf[0]; + + /* + * Serialize the reply verifier, and update rqst + */ + ixdr = (uint32_t *) msg->rm_call.cb_verf.oa_base; + *ixdr++ = verf.adv_xtimestamp.key.high; + *ixdr++ = verf.adv_xtimestamp.key.low; + *ixdr++ = 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 = + (char *) ixdr - msg->rm_call.cb_verf.oa_base; + + /* + * We succeeded, commit the data to the cache now and + * finish cooking the credential. + */ + entry = &authdes_cache[sid]; + entry->laststamp = timestamp; + cache_ref (sid); + if (cred->adc_namekind == ADN_FULLNAME) + { + size_t full_len; + + cred->adc_fullname.window = window; + cred->adc_nickname = sid; /* save nickname */ + if (entry->rname != NULL) + mem_free (entry->rname, strlen (entry->rname) + 1); + full_len = strlen (cred->adc_fullname.name) + 1; + entry->rname = mem_alloc ((u_int) full_len); + if (entry->rname != NULL) + memcpy (entry->rname, cred->adc_fullname.name, full_len); + else + { + debug ("out of memory"); + return AUTH_FAILED; /* out of memory is bad */ + } + entry->key = *sessionkey; + entry->window = window; + invalidate (entry->localcred); /* mark any cached cred invalid */ + } + else + { /* ADN_NICKNAME */ + /* + * nicknames are cooked into fullnames + */ + cred->adc_namekind = ADN_FULLNAME; + cred->adc_fullname.name = entry->rname; + cred->adc_fullname.key = entry->key; + cred->adc_fullname.window = entry->window; + } + return AUTH_OK; /* we made it! */ +} + + +/* + * Initialize the cache + */ +static void +internal_function +cache_init (void) +{ + register int i; + + authdes_cache = (struct cache_entry *) + calloc (sizeof (struct cache_entry) * AUTHDES_CACHESZ, 1); + if (authdes_cache == NULL) + return; + + authdes_lru = (int *) mem_alloc (sizeof (int) * AUTHDES_CACHESZ); + /* + * Initialize the lru list + */ + for (i = 0; i < AUTHDES_CACHESZ; ++i) + authdes_lru[i] = i; +} + + +/* + * Find the lru victim + */ +static short +cache_victim (void) +{ + return authdes_lru[AUTHDES_CACHESZ - 1]; +} + +/* + * Note that sid was referenced + */ +static void +internal_function +cache_ref (register uint32_t sid) +{ + register int i; + register int curr; + register int prev; + + prev = authdes_lru[0]; + authdes_lru[0] = sid; + for (i = 1; prev != sid; ++i) + { + curr = authdes_lru[i]; + authdes_lru[i] = prev; + prev = curr; + } +} + +/* + * Find a spot in the cache for a credential containing + * the items given. Return -1 if a replay is detected, otherwise + * return the spot in the cache. + */ +static short +internal_function +cache_spot (register des_block *key, char *name, + struct rpc_timeval *timestamp) +{ + register struct cache_entry *cp; + register int i; + register uint32_t hi; + + hi = key->key.high; + for (cp = authdes_cache, i = 0; i < AUTHDES_CACHESZ; ++i, ++cp) + { + if (cp->key.key.high == hi && + cp->key.key.low == key->key.low && + cp->rname != NULL && + memcmp (cp->rname, name, strlen (name) + 1) == 0) + { + if (BEFORE (timestamp, &cp->laststamp)) + { + ++svcauthdes_stats.ncachereplays; + return -1; /* replay */ + } + ++svcauthdes_stats.ncachehits; + return i; /* refresh */ + } + } + ++svcauthdes_stats.ncachemisses; + return cache_victim (); /* new credential */ +} + +/* + * Local credential handling stuff. + * NOTE: bsd unix dependent. + * Other operating systems should put something else here. + */ +#define UNKNOWN -2 /* grouplen, if cached cred is unknown user */ +#define INVALID -1 /* grouplen, if cache entry is invalid */ + +struct bsdcred +{ + uid_t uid; /* cached uid */ + gid_t gid; /* cached gid */ + int grouplen; /* length of cached groups */ + int grouplen_max; /* length of allocated cached groups */ + gid_t groups[0]; /* cached groups */ +}; + +/* + * 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 +authdes_getucred (const struct authdes_cred *adc, uid_t * uid, gid_t * gid, + short *grouplen, gid_t * groups) +{ + unsigned sid; + register int i; + uid_t i_uid; + gid_t i_gid; + int i_grouplen; + struct bsdcred *cred; + + sid = adc->adc_nickname; + if (sid >= AUTHDES_CACHESZ) + { + debug ("invalid nickname"); + return 0; + } + cred = (struct bsdcred *) authdes_cache[sid].localcred; + if (cred == NULL || cred->grouplen == INVALID) + { + /* + * not in cache: lookup + */ + if (!netname2user (adc->adc_fullname.name, &i_uid, &i_gid, + &i_grouplen, groups)) + { + debug ("unknown netname"); + if (cred != NULL) + cred->grouplen = UNKNOWN; /* mark as lookup up, but not found */ + return 0; + } + + if (cred != NULL && cred->grouplen_max < i_grouplen) + { + /* We already have an allocated data structure. But it is + too small. */ + free (cred); + authdes_cache[sid].localcred = NULL; + cred = NULL; + } + + if (cred == NULL) + { + /* We should allocate room for at least NGROUPS groups. */ + int ngroups_max = MAX (i_grouplen, NGROUPS); + + cred = (struct bsdcred *) mem_alloc (sizeof (struct bsdcred) + + ngroups_max * sizeof (gid_t)); + if (cred == NULL) + return 0; + + authdes_cache[sid].localcred = (char *) cred; + cred->grouplen = INVALID; + cred->grouplen_max = ngroups_max; + } + + debug ("missed ucred cache"); + *uid = cred->uid = i_uid; + *gid = cred->gid = i_gid; + cred->grouplen = i_grouplen; + for (i = i_grouplen - 1; i >= 0; --i) + cred->groups[i] = groups[i]; + /* Make sure no too large values are reported. */ + *grouplen = MIN (SHRT_MAX, i_grouplen); + return 1; + } + else if (cred->grouplen == UNKNOWN) + { + /* + * Already lookup up, but no match found + */ + return 0; + } + + /* + * cached credentials + */ + *uid = cred->uid; + *gid = cred->gid; + + /* Another stupidity in the interface: *grouplen is of type short. + So we might have to cut the information passed up short. */ + int grouplen_copy = MIN (SHRT_MAX, cred->grouplen); + *grouplen = grouplen_copy; + for (i = grouplen_copy - 1; i >= 0; --i) + groups[i] = cred->groups[i]; + return 1; +} +libc_hidden_nolink_sunrpc (authdes_getucred, GLIBC_2_1) + +static void +internal_function +invalidate (char *cred) +{ + if (cred == NULL) + return; + ((struct bsdcred *) cred)->grouplen = INVALID; +} |