123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- /*
- * 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 */
- };
- #define authdes_cache RPC_THREAD_VARIABLE(authdes_cache_s)
- #define authdes_lru RPC_THREAD_VARIABLE(authdes_lru_s)
- static void cache_init (void); /* initialize the cache */
- static short cache_spot (des_block *, char *, struct rpc_timeval *);
- /* find an entry in the cache */
- static void cache_ref (uint32_t sid); /* note that sid was ref'd */
- static void invalidate (char *cred); /* invalidate entry in cache */
- /* Cache statistics. Accidental historic export without a matching
- declaration in any header file. */
- #ifndef SHARED
- static
- #endif
- 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 __attribute__ ((nocommon));
- #ifdef SHARED
- compat_symbol (libc, svcauthdes_stats, svcauthdes_stats, GLIBC_2_0);
- #endif
- /*
- * 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
- 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
- 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
- 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
- invalidate (char *cred)
- {
- if (cred == NULL)
- return;
- ((struct bsdcred *) cred)->grouplen = INVALID;
- }
|