| /* |
| * 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> |
| |
| #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; |
| } |