Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
/*
2
 * APOP, CRAM - MD5 challenge/response authentication
3
 *
4
 * The client does not authenticate the server, hence no CAI
5
 *
6
 * Client protocol:
7
 *	write challenge: randomstring@domain
8
 *	read response: 2*MD5dlen hex digits
9
 *
10
 * Server protocol:
11
 *	read challenge: randomstring@domain
12
 *	write user: user
13
 *	write response: 2*MD5dlen hex digits
14
 */
15
 
16
#include "dat.h"
17
 
18
struct State
19
{
20
	int asfd;
21
	int astype;
22
	Key *key;
23
	Ticket	t;
24
	Ticketreq	tr;
25
	char chal[128];
26
	char	resp[64];
27
	char *user;
28
};
29
 
30
enum
31
{
32
	CNeedChal,
33
	CHaveResp,
34
 
35
	SHaveChal,
36
	SNeedUser,
37
	SNeedResp,
38
 
39
	Maxphase,
40
};
41
 
42
static char *phasenames[Maxphase] = {
43
[CNeedChal]	"CNeedChal",
44
[CHaveResp]	"CHaveResp",
45
 
46
[SHaveChal]	"SHaveChal",
47
[SNeedUser]	"SNeedUser",
48
[SNeedResp]	"SNeedResp",
49
};
50
 
51
static int dochal(State*);
52
static int doreply(State*, char*, char*);
53
 
54
static int
55
apopinit(Proto *p, Fsstate *fss)
56
{
57
	int iscli, ret;
58
	State *s;
59
 
60
	if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
61
		return failure(fss, nil);
62
 
63
	s = emalloc(sizeof *s);
64
	fss->phasename = phasenames;
65
	fss->maxphase = Maxphase;
66
	s->asfd = -1;
67
	if(p == &apop)
68
		s->astype = AuthApop;
69
	else if(p == &cram)
70
		s->astype = AuthCram;
71
	else
72
		abort();
73
 
74
	if(iscli)
75
		fss->phase = CNeedChal;
76
	else{
77
		if((ret = findp9authkey(&s->key, fss)) != RpcOk){
78
			free(s);
79
			return ret;
80
		}
81
		if(dochal(s) < 0){
82
			free(s);
83
			return failure(fss, nil);
84
		}
85
		fss->phase = SHaveChal;
86
	}
87
	fss->ps = s;
88
	return RpcOk;
89
}
90
 
91
static int
92
apopwrite(Fsstate *fss, void *va, uint n)
93
{
94
	char *a, *v;
95
	int i, ret;
96
	uchar digest[MD5dlen];
97
	DigestState *ds;
98
	Key *k;
99
	State *s;
100
	Keyinfo ki;
101
 
102
	s = fss->ps;
103
	a = va;
104
	switch(fss->phase){
105
	default:
106
		return phaseerror(fss, "write");
107
 
108
	case CNeedChal:
109
		ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", fss->proto->keyprompt);
110
		if(ret != RpcOk)
111
			return ret;
112
		v = _strfindattr(k->privattr, "!password");
113
		if(v == nil)
114
			return failure(fss, "key has no password");
115
		setattrs(fss->attr, k->attr);
116
		switch(s->astype){
117
		default:
118
			abort();
119
		case AuthCram:
120
			hmac_md5((uchar*)a, n, (uchar*)v, strlen(v),
121
				digest, nil);
122
			snprint(s->resp, sizeof s->resp, "%.*H", MD5dlen, digest);
123
			break;
124
		case AuthApop:
125
			ds = md5((uchar*)a, n, nil, nil);
126
			md5((uchar*)v, strlen(v), digest, ds);
127
			for(i=0; i<MD5dlen; i++)
128
				sprint(&s->resp[2*i], "%2.2x", digest[i]);
129
			break;
130
		}
131
		closekey(k);
132
		fss->phase = CHaveResp;
133
		return RpcOk;
134
 
135
	case SNeedUser:
136
		if((v = _strfindattr(fss->attr, "user")) && strcmp(v, a) != 0)
137
			return failure(fss, "bad user");
138
		fss->attr = setattr(fss->attr, "user=%q", a);
139
		s->user = estrdup(a);
140
		fss->phase = SNeedResp;
141
		return RpcOk;
142
 
143
	case SNeedResp:
144
		if(n != 2*MD5dlen)
145
			return failure(fss, "response not MD5 digest");
146
		if(doreply(s, s->user, a) < 0){
147
			fss->phase = SNeedUser;
148
			return failure(fss, nil);
149
		}
150
		fss->haveai = 1;
151
		fss->ai.cuid = s->t.cuid;
152
		fss->ai.suid = s->t.suid;
153
		fss->ai.nsecret = 0;
154
		fss->ai.secret = nil;
155
		fss->phase = Established;
156
		return RpcOk;
157
	}
158
}
159
 
160
static int
161
apopread(Fsstate *fss, void *va, uint *n)
162
{
163
	State *s;
164
 
165
	s = fss->ps;
166
	switch(fss->phase){
167
	default:
168
		return phaseerror(fss, "read");
169
 
170
	case CHaveResp:
171
		if(*n > strlen(s->resp))
172
			*n = strlen(s->resp);
173
		memmove(va, s->resp, *n);
174
		fss->phase = Established;
175
		fss->haveai = 0;
176
		return RpcOk;
177
 
178
	case SHaveChal:
179
		if(*n > strlen(s->chal))
180
			*n = strlen(s->chal);
181
		memmove(va, s->chal, *n);
182
		fss->phase = SNeedUser;
183
		return RpcOk;
184
	}
185
}
186
 
187
static void
188
apopclose(Fsstate *fss)
189
{
190
	State *s;
191
 
192
	s = fss->ps;
193
	if(s->asfd >= 0){
194
		close(s->asfd);
195
		s->asfd = -1;
196
	}
197
	if(s->key != nil){
198
		closekey(s->key);
199
		s->key = nil;
200
	}
201
	if(s->user != nil){
202
		free(s->user);
203
		s->user = nil;
204
	}
205
	free(s);
206
}
207
 
208
static int
209
dochal(State *s)
210
{
211
	char *dom, *user, trbuf[TICKREQLEN];
212
 
213
	s->asfd = -1;
214
 
215
	/* send request to authentication server and get challenge */
216
	/* send request to authentication server and get challenge */
217
	if((dom = _strfindattr(s->key->attr, "dom")) == nil
218
	|| (user = _strfindattr(s->key->attr, "user")) == nil){
219
		werrstr("apop/dochal cannot happen");
220
		goto err;
221
	}
222
 
223
	s->asfd = _authdial(nil, dom);
224
 
225
	/* could generate our own challenge on error here */
226
	if(s->asfd < 0)
227
		goto err;
228
 
229
	memset(&s->tr, 0, sizeof(s->tr));
230
	s->tr.type = s->astype;
231
	safecpy(s->tr.authdom, dom, sizeof s->tr.authdom);
232
	safecpy(s->tr.hostid, user, sizeof(s->tr.hostid));
233
	convTR2M(&s->tr, trbuf);
234
 
235
	if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
236
		goto err;
237
	if(_asrdresp(s->asfd, s->chal, sizeof s->chal) <= 5)
238
		goto err;
239
	return 0;
240
 
241
err:
242
	if(s->asfd >= 0)
243
		close(s->asfd);
244
	s->asfd = -1;
245
	return -1;
246
}
247
 
248
static int
249
doreply(State *s, char *user, char *response)
250
{
251
	char ticket[TICKETLEN+AUTHENTLEN];
252
	char trbuf[TICKREQLEN];
253
	int n;
254
	Authenticator a;
255
 
256
	memrandom(s->tr.chal, CHALLEN);
257
	safecpy(s->tr.uid, user, sizeof(s->tr.uid));
258
	convTR2M(&s->tr, trbuf);
259
	if((n=write(s->asfd, trbuf, TICKREQLEN)) != TICKREQLEN){
260
		if(n >= 0)
261
			werrstr("short write to auth server");
262
		goto err;
263
	}
264
	/* send response to auth server */
265
	if(strlen(response) != MD5dlen*2){
266
		werrstr("response not MD5 digest");
267
		goto err;
268
	}
269
	if((n=write(s->asfd, response, MD5dlen*2)) != MD5dlen*2){
270
		if(n >= 0)
271
			werrstr("short write to auth server");
272
		goto err;
273
	}
274
	if(_asrdresp(s->asfd, ticket, TICKETLEN+AUTHENTLEN) < 0){
275
		/* leave connection open so we can try again */
276
		return -1;
277
	}
278
	close(s->asfd);
279
	s->asfd = -1;
280
 
281
	convM2T(ticket, &s->t, (char*)s->key->priv);
282
	if(s->t.num != AuthTs
283
	|| memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0){
284
		if(s->key->successes == 0)
285
			disablekey(s->key);
286
		werrstr(Easproto);
287
		goto err;
288
	}
289
	s->key->successes++;
290
	convM2A(ticket+TICKETLEN, &a, s->t.key);
291
	if(a.num != AuthAc
292
	|| memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
293
	|| a.id != 0){
294
		werrstr(Easproto);
295
		goto err;
296
	}
297
 
298
	return 0;
299
err:
300
	if(s->asfd >= 0)
301
		close(s->asfd);
302
	s->asfd = -1;
303
	return -1;
304
}
305
 
306
Proto apop = {
307
.name=	"apop",
308
.init=		apopinit,
309
.write=	apopwrite,
310
.read=	apopread,
311
.close=	apopclose,
312
.addkey=	replacekey,
313
.keyprompt=	"!password?"
314
};
315
 
316
Proto cram = {
317
.name=	"cram",
318
.init=		apopinit,
319
.write=	apopwrite,
320
.read=	apopread,
321
.close=	apopclose,
322
.addkey=	replacekey,
323
.keyprompt=	"!password?"
324
};