summaryrefslogtreecommitdiffstats
path: root/net/sunrpc/svcauth_des.c
blob: 5af0ec05ae08be4e3e20503c2292470360c5cbf1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*
 * linux/net/sunrpc/svcauth_des.c
 *
 * Server-side AUTH_DES handling.
 * 
 * Copyright (C) 1996, 1997 Olaf Kirch <okir@monad.swb.de>
 */

#include <linux/types.h>
#include <linux/sched.h>
#include <linux/sunrpc/types.h>
#include <linux/sunrpc/xdr.h>
#include <linux/sunrpc/svcauth.h>
#include <linux/sunrpc/svcsock.h>

#define RPCDBG_FACILITY	RPCDBG_AUTH

/*
 * DES cedential cache.
 * The cache is indexed by fullname/key to allow for multiple sessions
 * by the same user from different hosts.
 * 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
 * multi-homed hosts that distribute traffic across their interfaces.
 */
struct des_cred {
	struct des_cred *	dc_next;
	char *			dc_fullname;
	u32			dc_nickname;
	des_cblock		dc_key;		/* conversation key */
	des_cblock		dc_xkey;	/* encrypted conv. key */
	des_key_schedule	dc_keysched;
};

#define ADN_FULLNAME		0
#define ADN_NICKNAME		1

/*
 * The default slack allowed when checking for replayed credentials
 * (in milliseconds).
 */
#define DES_REPLAY_SLACK	2000

/*
 * Make sure we don't place more than one call to the key server at
 * a time.
 */
static int			in_keycall = 0;

#define FAIL(err) \
	{ if (data) put_cred(data);			\
	  *authp = rpc_autherr_##err;			\
	  return;					\
	}

void
svcauth_des(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
{
	struct svc_buf	*argp = &rqstp->rq_argbuf;
	struct svc_buf	*resp = &rqstp->rq_resbuf;
	struct svc_cred	*cred = &rqstp->rq_cred;
	struct des_cred	*data = NULL;
	u32		cryptkey[2];
	u32		cryptbuf[4];
	u32		*p = argp->buf;
	int		len   = argp->len, slen, i;

	*authp = rpc_auth_ok;

	if ((argp->len -= 3) < 0) {
		*statp = rpc_garbage_args;
		return;
	}

	p++;					/* skip length field */
	namekind = ntohl(*p++);			/* fullname/nickname */

	/* Get the credentials */
	if (namekind == ADN_NICKNAME) {
		/* If we can't find the cached session key, initiate a
		 * new session. */
		if (!(data = get_cred_bynick(*p++)))
			FAIL(rejectedcred);
	} else if (namekind == ADN_FULLNAME) {
		p = xdr_decode_string(p, &fullname, &len, RPC_MAXNETNAMELEN);
		if (p == NULL)
			FAIL(badcred);
		cryptkey[0] = *p++;		/* get the encrypted key */
		cryptkey[1] = *p++;
		cryptbuf[2] = *p++;		/* get the encrypted window */
	} else {
		FAIL(badcred);
	}

	/* If we're just updating the key, silently discard the request. */
	if (data && data->dc_locked) {
		*authp = rpc_autherr_dropit;
		_put_cred(data);	/* release but don't unlock */
		return;
	}

	/* Get the verifier flavor and length */
	if (ntohl(*p++) != RPC_AUTH_DES && ntohl(*p++) != 12)
		FAIL(badverf);

	cryptbuf[0] = *p++;			/* encrypted time stamp */
	cryptbuf[1] = *p++;
	cryptbuf[3] = *p++;			/* 0 or window - 1 */

	if (namekind == ADN_NICKNAME) {
		status = des_ecb_encrypt((des_block *) cryptbuf,
					 (des_block *) cryptbuf,
					 data->dc_keysched, DES_DECRYPT);
	} else {
		/* We first have to decrypt the new session key and
		 * fill in the UNIX creds. */
		if (!(data = get_cred_byname(rqstp, authp, fullname, cryptkey)))
			return;
		status = des_cbc_encrypt((des_cblock *) cryptbuf,
					 (des_cblock *) cryptbuf, 16,
					 data->dc_keysched,
					 (des_cblock *) &ivec,
					 DES_DECRYPT);
	}
	if (status) {
		printk("svcauth_des: DES decryption failed (status %d)\n",
				status);
		FAIL(badverf);
	}

	/* Now check the whole lot */
	if (namekind == ADN_FULLNAME) {
		unsigned long	winverf;

		data->dc_window = ntohl(cryptbuf[2]);
		winverf = ntohl(cryptbuf[2]);
		if (window != winverf - 1) {
			printk("svcauth_des: bad window verifier!\n");
			FAIL(badverf);
		}
	}

	/* XDR the decrypted timestamp */
	cryptbuf[0] = ntohl(cryptbuf[0]);
	cryptbuf[1] = ntohl(cryptbuf[1]);
	if (cryptbuf[1] > 1000000) {
		dprintk("svcauth_des: bad usec value %u\n", cryptbuf[1]);
		if (namekind == ADN_NICKNAME)
			FAIL(rejectedverf);
		FAIL(badverf);
	}
	
	/*
	 * Check for replayed credentials. We must allow for reordering
	 * of requests by the network, and the OS scheduler, hence we
	 * cannot expect timestamps to be increasing monotonically.
	 * This opens a small security hole, therefore the replay_slack
	 * value shouldn't be too large.
	 */
	if ((delta = cryptbuf[0] - data->dc_timestamp[0]) <= 0) {
		switch (delta) {
		case -1:	
			delta = -1000000;
		case 0:
			delta += cryptbuf[1] - data->dc_timestamp[1];
			break;
		default:
			delta = -1000000;
		}
		if (delta < DES_REPLAY_SLACK)
			FAIL(rejectedverf);
#ifdef STRICT_REPLAY_CHECKS
		/* TODO: compare time stamp to last five timestamps cached
		 * and reject (drop?) request if a match is found. */
#endif
	}

	now = xtime;
	now.tv_secs -= data->dc_window;
	if (now.tv_secs < cryptbuf[0] ||
	    (now.tv_secs == cryptbuf[0] && now.tv_usec < cryptbuf[1]))
		FAIL(rejectedverf);

	/* Okay, we're done. Update the lot */
	if (namekind == ADN_FULLNAME)
		data->dc_valid = 1;
	data->dc_timestamp[0] = cryptbuf[0];
	data->dc_timestamp[1] = cryptbuf[1];

	put_cred(data);
	return;
garbage:
	*statp = rpc_garbage_args;
	return;
}

/*
 * Call the keyserver to obtain the decrypted conversation key and
 * UNIX creds. We use a Linux-specific keycall extension that does
 * both things in one go.
 */
static struct des_cred *
get_cred_byname(struct svc_rqst *rqstp, u32 *authp, char *fullname, u32 *cryptkey)
{
	static int	in_keycall = 0;
	struct des_cred	*cred;

	if (in_keycall) {
		*authp = rpc_autherr_dropit;
		return NULL;
	}
	in_keycall = 1;
	in_keycall = 0;
	return cred;
}