/*
|
/*
|
* linux/net/sunrpc/svcauth_des.c
|
* linux/net/sunrpc/svcauth_des.c
|
*
|
*
|
* Server-side AUTH_DES handling.
|
* Server-side AUTH_DES handling.
|
*
|
*
|
* Copyright (C) 1996, 1997 Olaf Kirch <okir@monad.swb.de>
|
* Copyright (C) 1996, 1997 Olaf Kirch <okir@monad.swb.de>
|
*/
|
*/
|
|
|
#include <linux/types.h>
|
#include <linux/types.h>
|
#include <linux/sched.h>
|
#include <linux/sched.h>
|
#include <linux/sunrpc/types.h>
|
#include <linux/sunrpc/types.h>
|
#include <linux/sunrpc/xdr.h>
|
#include <linux/sunrpc/xdr.h>
|
#include <linux/sunrpc/svcauth.h>
|
#include <linux/sunrpc/svcauth.h>
|
#include <linux/sunrpc/svcsock.h>
|
#include <linux/sunrpc/svcsock.h>
|
|
|
#define RPCDBG_FACILITY RPCDBG_AUTH
|
#define RPCDBG_FACILITY RPCDBG_AUTH
|
|
|
/*
|
/*
|
* DES cedential cache.
|
* DES cedential cache.
|
* The cache is indexed by fullname/key to allow for multiple sessions
|
* The cache is indexed by fullname/key to allow for multiple sessions
|
* by the same user from different hosts.
|
* by the same user from different hosts.
|
* It would be tempting to use the client's IP address rather than the
|
* It would be tempting to use the client's IP address rather than the
|
* conversation key as an index, but that could become problematic for
|
* conversation key as an index, but that could become problematic for
|
* multi-homed hosts that distribute traffic across their interfaces.
|
* multi-homed hosts that distribute traffic across their interfaces.
|
*/
|
*/
|
struct des_cred {
|
struct des_cred {
|
struct des_cred * dc_next;
|
struct des_cred * dc_next;
|
char * dc_fullname;
|
char * dc_fullname;
|
u32 dc_nickname;
|
u32 dc_nickname;
|
des_cblock dc_key; /* conversation key */
|
des_cblock dc_key; /* conversation key */
|
des_cblock dc_xkey; /* encrypted conv. key */
|
des_cblock dc_xkey; /* encrypted conv. key */
|
des_key_schedule dc_keysched;
|
des_key_schedule dc_keysched;
|
};
|
};
|
|
|
#define ADN_FULLNAME 0
|
#define ADN_FULLNAME 0
|
#define ADN_NICKNAME 1
|
#define ADN_NICKNAME 1
|
|
|
/*
|
/*
|
* The default slack allowed when checking for replayed credentials
|
* The default slack allowed when checking for replayed credentials
|
* (in milliseconds).
|
* (in milliseconds).
|
*/
|
*/
|
#define DES_REPLAY_SLACK 2000
|
#define DES_REPLAY_SLACK 2000
|
|
|
/*
|
/*
|
* Make sure we don't place more than one call to the key server at
|
* Make sure we don't place more than one call to the key server at
|
* a time.
|
* a time.
|
*/
|
*/
|
static int in_keycall = 0;
|
static int in_keycall = 0;
|
|
|
#define FAIL(err) \
|
#define FAIL(err) \
|
{ if (data) put_cred(data); \
|
{ if (data) put_cred(data); \
|
*authp = rpc_autherr_##err; \
|
*authp = rpc_autherr_##err; \
|
return; \
|
return; \
|
}
|
}
|
|
|
void
|
void
|
svcauth_des(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
|
svcauth_des(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
|
{
|
{
|
struct svc_buf *argp = &rqstp->rq_argbuf;
|
struct svc_buf *argp = &rqstp->rq_argbuf;
|
struct svc_buf *resp = &rqstp->rq_resbuf;
|
struct svc_buf *resp = &rqstp->rq_resbuf;
|
struct svc_cred *cred = &rqstp->rq_cred;
|
struct svc_cred *cred = &rqstp->rq_cred;
|
struct des_cred *data = NULL;
|
struct des_cred *data = NULL;
|
u32 cryptkey[2];
|
u32 cryptkey[2];
|
u32 cryptbuf[4];
|
u32 cryptbuf[4];
|
u32 *p = argp->buf;
|
u32 *p = argp->buf;
|
int len = argp->len, slen, i;
|
int len = argp->len, slen, i;
|
|
|
*authp = rpc_auth_ok;
|
*authp = rpc_auth_ok;
|
|
|
if ((argp->len -= 3) < 0) {
|
if ((argp->len -= 3) < 0) {
|
*statp = rpc_garbage_args;
|
*statp = rpc_garbage_args;
|
return;
|
return;
|
}
|
}
|
|
|
p++; /* skip length field */
|
p++; /* skip length field */
|
namekind = ntohl(*p++); /* fullname/nickname */
|
namekind = ntohl(*p++); /* fullname/nickname */
|
|
|
/* Get the credentials */
|
/* Get the credentials */
|
if (namekind == ADN_NICKNAME) {
|
if (namekind == ADN_NICKNAME) {
|
/* If we can't find the cached session key, initiate a
|
/* If we can't find the cached session key, initiate a
|
* new session. */
|
* new session. */
|
if (!(data = get_cred_bynick(*p++)))
|
if (!(data = get_cred_bynick(*p++)))
|
FAIL(rejectedcred);
|
FAIL(rejectedcred);
|
} else if (namekind == ADN_FULLNAME) {
|
} else if (namekind == ADN_FULLNAME) {
|
p = xdr_decode_string(p, &fullname, &len, RPC_MAXNETNAMELEN);
|
p = xdr_decode_string(p, &fullname, &len, RPC_MAXNETNAMELEN);
|
if (p == NULL)
|
if (p == NULL)
|
FAIL(badcred);
|
FAIL(badcred);
|
cryptkey[0] = *p++; /* get the encrypted key */
|
cryptkey[0] = *p++; /* get the encrypted key */
|
cryptkey[1] = *p++;
|
cryptkey[1] = *p++;
|
cryptbuf[2] = *p++; /* get the encrypted window */
|
cryptbuf[2] = *p++; /* get the encrypted window */
|
} else {
|
} else {
|
FAIL(badcred);
|
FAIL(badcred);
|
}
|
}
|
|
|
/* If we're just updating the key, silently discard the request. */
|
/* If we're just updating the key, silently discard the request. */
|
if (data && data->dc_locked) {
|
if (data && data->dc_locked) {
|
*authp = rpc_autherr_dropit;
|
*authp = rpc_autherr_dropit;
|
_put_cred(data); /* release but don't unlock */
|
_put_cred(data); /* release but don't unlock */
|
return;
|
return;
|
}
|
}
|
|
|
/* Get the verifier flavor and length */
|
/* Get the verifier flavor and length */
|
if (ntohl(*p++) != RPC_AUTH_DES && ntohl(*p++) != 12)
|
if (ntohl(*p++) != RPC_AUTH_DES && ntohl(*p++) != 12)
|
FAIL(badverf);
|
FAIL(badverf);
|
|
|
cryptbuf[0] = *p++; /* encrypted time stamp */
|
cryptbuf[0] = *p++; /* encrypted time stamp */
|
cryptbuf[1] = *p++;
|
cryptbuf[1] = *p++;
|
cryptbuf[3] = *p++; /* 0 or window - 1 */
|
cryptbuf[3] = *p++; /* 0 or window - 1 */
|
|
|
if (namekind == ADN_NICKNAME) {
|
if (namekind == ADN_NICKNAME) {
|
status = des_ecb_encrypt((des_block *) cryptbuf,
|
status = des_ecb_encrypt((des_block *) cryptbuf,
|
(des_block *) cryptbuf,
|
(des_block *) cryptbuf,
|
data->dc_keysched, DES_DECRYPT);
|
data->dc_keysched, DES_DECRYPT);
|
} else {
|
} else {
|
/* We first have to decrypt the new session key and
|
/* We first have to decrypt the new session key and
|
* fill in the UNIX creds. */
|
* fill in the UNIX creds. */
|
if (!(data = get_cred_byname(rqstp, authp, fullname, cryptkey)))
|
if (!(data = get_cred_byname(rqstp, authp, fullname, cryptkey)))
|
return;
|
return;
|
status = des_cbc_encrypt((des_cblock *) cryptbuf,
|
status = des_cbc_encrypt((des_cblock *) cryptbuf,
|
(des_cblock *) cryptbuf, 16,
|
(des_cblock *) cryptbuf, 16,
|
data->dc_keysched,
|
data->dc_keysched,
|
(des_cblock *) &ivec,
|
(des_cblock *) &ivec,
|
DES_DECRYPT);
|
DES_DECRYPT);
|
}
|
}
|
if (status) {
|
if (status) {
|
printk("svcauth_des: DES decryption failed (status %d)\n",
|
printk("svcauth_des: DES decryption failed (status %d)\n",
|
status);
|
status);
|
FAIL(badverf);
|
FAIL(badverf);
|
}
|
}
|
|
|
/* Now check the whole lot */
|
/* Now check the whole lot */
|
if (namekind == ADN_FULLNAME) {
|
if (namekind == ADN_FULLNAME) {
|
unsigned long winverf;
|
unsigned long winverf;
|
|
|
data->dc_window = ntohl(cryptbuf[2]);
|
data->dc_window = ntohl(cryptbuf[2]);
|
winverf = ntohl(cryptbuf[2]);
|
winverf = ntohl(cryptbuf[2]);
|
if (window != winverf - 1) {
|
if (window != winverf - 1) {
|
printk("svcauth_des: bad window verifier!\n");
|
printk("svcauth_des: bad window verifier!\n");
|
FAIL(badverf);
|
FAIL(badverf);
|
}
|
}
|
}
|
}
|
|
|
/* XDR the decrypted timestamp */
|
/* XDR the decrypted timestamp */
|
cryptbuf[0] = ntohl(cryptbuf[0]);
|
cryptbuf[0] = ntohl(cryptbuf[0]);
|
cryptbuf[1] = ntohl(cryptbuf[1]);
|
cryptbuf[1] = ntohl(cryptbuf[1]);
|
if (cryptbuf[1] > 1000000) {
|
if (cryptbuf[1] > 1000000) {
|
dprintk("svcauth_des: bad usec value %u\n", cryptbuf[1]);
|
dprintk("svcauth_des: bad usec value %u\n", cryptbuf[1]);
|
if (namekind == ADN_NICKNAME)
|
if (namekind == ADN_NICKNAME)
|
FAIL(rejectedverf);
|
FAIL(rejectedverf);
|
FAIL(badverf);
|
FAIL(badverf);
|
}
|
}
|
|
|
/*
|
/*
|
* Check for replayed credentials. We must allow for reordering
|
* Check for replayed credentials. We must allow for reordering
|
* of requests by the network, and the OS scheduler, hence we
|
* of requests by the network, and the OS scheduler, hence we
|
* cannot expect timestamps to be increasing monotonically.
|
* cannot expect timestamps to be increasing monotonically.
|
* This opens a small security hole, therefore the replay_slack
|
* This opens a small security hole, therefore the replay_slack
|
* value shouldn't be too large.
|
* value shouldn't be too large.
|
*/
|
*/
|
if ((delta = cryptbuf[0] - data->dc_timestamp[0]) <= 0) {
|
if ((delta = cryptbuf[0] - data->dc_timestamp[0]) <= 0) {
|
switch (delta) {
|
switch (delta) {
|
case -1:
|
case -1:
|
delta = -1000000;
|
delta = -1000000;
|
case 0:
|
case 0:
|
delta += cryptbuf[1] - data->dc_timestamp[1];
|
delta += cryptbuf[1] - data->dc_timestamp[1];
|
break;
|
break;
|
default:
|
default:
|
delta = -1000000;
|
delta = -1000000;
|
}
|
}
|
if (delta < DES_REPLAY_SLACK)
|
if (delta < DES_REPLAY_SLACK)
|
FAIL(rejectedverf);
|
FAIL(rejectedverf);
|
#ifdef STRICT_REPLAY_CHECKS
|
#ifdef STRICT_REPLAY_CHECKS
|
/* TODO: compare time stamp to last five timestamps cached
|
/* TODO: compare time stamp to last five timestamps cached
|
* and reject (drop?) request if a match is found. */
|
* and reject (drop?) request if a match is found. */
|
#endif
|
#endif
|
}
|
}
|
|
|
now = xtime;
|
now = xtime;
|
now.tv_secs -= data->dc_window;
|
now.tv_secs -= data->dc_window;
|
if (now.tv_secs < cryptbuf[0] ||
|
if (now.tv_secs < cryptbuf[0] ||
|
(now.tv_secs == cryptbuf[0] && now.tv_usec < cryptbuf[1]))
|
(now.tv_secs == cryptbuf[0] && now.tv_usec < cryptbuf[1]))
|
FAIL(rejectedverf);
|
FAIL(rejectedverf);
|
|
|
/* Okay, we're done. Update the lot */
|
/* Okay, we're done. Update the lot */
|
if (namekind == ADN_FULLNAME)
|
if (namekind == ADN_FULLNAME)
|
data->dc_valid = 1;
|
data->dc_valid = 1;
|
data->dc_timestamp[0] = cryptbuf[0];
|
data->dc_timestamp[0] = cryptbuf[0];
|
data->dc_timestamp[1] = cryptbuf[1];
|
data->dc_timestamp[1] = cryptbuf[1];
|
|
|
put_cred(data);
|
put_cred(data);
|
return;
|
return;
|
garbage:
|
garbage:
|
*statp = rpc_garbage_args;
|
*statp = rpc_garbage_args;
|
return;
|
return;
|
}
|
}
|
|
|
/*
|
/*
|
* Call the keyserver to obtain the decrypted conversation key and
|
* Call the keyserver to obtain the decrypted conversation key and
|
* UNIX creds. We use a Linux-specific keycall extension that does
|
* UNIX creds. We use a Linux-specific keycall extension that does
|
* both things in one go.
|
* both things in one go.
|
*/
|
*/
|
static struct des_cred *
|
static struct des_cred *
|
get_cred_byname(struct svc_rqst *rqstp, u32 *authp, char *fullname, u32 *cryptkey)
|
get_cred_byname(struct svc_rqst *rqstp, u32 *authp, char *fullname, u32 *cryptkey)
|
{
|
{
|
static int in_keycall = 0;
|
static int in_keycall = 0;
|
struct des_cred *cred;
|
struct des_cred *cred;
|
|
|
if (in_keycall) {
|
if (in_keycall) {
|
*authp = rpc_autherr_dropit;
|
*authp = rpc_autherr_dropit;
|
return NULL;
|
return NULL;
|
}
|
}
|
in_keycall = 1;
|
in_keycall = 1;
|
in_keycall = 0;
|
in_keycall = 0;
|
return cred;
|
return cred;
|
}
|
}
|
|
|