xref: /plan9/sys/src/cmd/auth/secureidcheck.c (revision 7c70c028d2d46a27a61ae88e6df0eb0935d9da7a)
1 /*
2  * This code uses RADIUS as a portable way to validate tokens such as SecurID.
3  * It is relatively simple to send a UDP packet and get a response, but various
4  * things can go wrong.  Speaking the proprietary ACE protocol would allow
5  * handling "next token code" and other error messages.  More importantly, the
6  * timeout threshold is inherently hard to pick.  We observe responses taking
7  * longer than 10 seconds in normal times.  That is a long time to wait before
8  * retrying on a second server.  Moreover, if the UDP response is lost, retrying
9  * on a second server will also fail because the valid token code may be
10  * presented only once.  This whole approach is flawed, but best we can do.
11  */
12 /* RFC2138 */
13 #include <u.h>
14 #include <libc.h>
15 #include <ip.h>
16 #include <ctype.h>
17 #include <mp.h>
18 #include <libsec.h>
19 #include <bio.h>
20 #include <ndb.h>
21 
22 #define AUTHLOG "auth"
23 
24 enum{
25 	R_AccessRequest	=1,	/* Packet code */
26 	R_AccessAccept	=2,
27 	R_AccessReject	=3,
28 	R_AccessChallenge=11,
29 	R_UserName	=1,
30 	R_UserPassword	=2,
31 	R_NASIPAddress	=4,
32 	R_ReplyMessage	=18,
33 	R_State		=24,
34 	R_NASIdentifier	=32,
35 };
36 
37 typedef struct Secret{
38 	uchar	*s;
39 	int	len;
40 } Secret;
41 
42 typedef struct Attribute{
43 	struct Attribute *next;
44 	uchar	type;
45 	uchar	len;		/* number of bytes in value */
46 	uchar	val[256];
47 } Attribute;
48 
49 typedef struct Packet{
50 	uchar	code, ID;
51 	uchar	authenticator[16];
52 	Attribute first;
53 } Packet;
54 
55 /* assumes pass is at most 16 chars */
56 void
hide(Secret * shared,uchar * auth,Secret * pass,uchar * x)57 hide(Secret *shared, uchar *auth, Secret *pass, uchar *x)
58 {
59 	DigestState *M;
60 	int i, n = pass->len;
61 
62 	M = md5(shared->s, shared->len, nil, nil);
63 	md5(auth, 16, x, M);
64 	if(n > 16)
65 		n = 16;
66 	for(i = 0; i < n; i++)
67 		x[i] ^= pass->s[i];
68 }
69 
70 int
authcmp(Secret * shared,uchar * buf,int m,uchar * auth)71 authcmp(Secret *shared, uchar *buf, int m, uchar *auth)
72 {
73 	DigestState *M;
74 	uchar x[16];
75 
76 	M = md5(buf, 4, nil, nil);	/* Code+ID+Length */
77 	M = md5(auth, 16, nil, M);	/* RequestAuth */
78 	M = md5(buf+20, m-20, nil, M);	/* Attributes */
79 	md5(shared->s, shared->len, x, M);
80 	return memcmp(x, buf+4, 16);
81 }
82 
83 Packet*
newRequest(uchar * auth)84 newRequest(uchar *auth)
85 {
86 	static uchar ID = 0;
87 	Packet *p;
88 
89 	p = (Packet*)malloc(sizeof(*p));
90 	if(p == nil)
91 		return nil;
92 	p->code = R_AccessRequest;
93 	p->ID = ++ID;
94 	memmove(p->authenticator, auth, 16);
95 	p->first.next = nil;
96 	p->first.type = 0;
97 	return p;
98 }
99 
100 void
freePacket(Packet * p)101 freePacket(Packet *p)
102 {
103 	Attribute *a, *x;
104 
105 	if(!p)
106 		return;
107 	a = p->first.next;
108 	while(a){
109 		x = a;
110 		a = a->next;
111 		free(x);
112 	}
113 	free(p);
114 }
115 
116 int
ding(void *,char * msg)117 ding(void*, char *msg)
118 {
119 	syslog(0, AUTHLOG, "ding %s", msg);
120 	if(strstr(msg, "alarm"))
121 		return 1;
122 	return 0;
123 }
124 
125 Packet *
rpc(char * dest,Secret * shared,Packet * req)126 rpc(char *dest, Secret *shared, Packet *req)
127 {
128 	uchar buf[4096], buf2[4096], *b, *e;
129 	Packet *resp;
130 	Attribute *a;
131 	int m, n, fd, try;
132 
133 	/* marshal request */
134 	e = buf + sizeof buf;
135 	buf[0] = req->code;
136 	buf[1] = req->ID;
137 	memmove(buf+4, req->authenticator, 16);
138 	b = buf+20;
139 	for(a = &req->first; a; a = a->next){
140 		if(b + 2 + a->len > e)
141 			return nil;
142 		*b++ = a->type;
143 		*b++ = 2 + a->len;
144 		memmove(b, a->val, a->len);
145 		b += a->len;
146 	}
147 	n = b-buf;
148 	buf[2] = n>>8;
149 	buf[3] = n;
150 
151 	/* send request, wait for reply */
152 	fd = dial(dest, 0, 0, 0);
153 	if(fd < 0){
154 		syslog(0, AUTHLOG, "%s: rpc can't get udp channel", dest);
155 		return nil;
156 	}
157 	atnotify(ding, 1);
158 	m = -1;
159 	for(try = 0; try < 2; try++){
160 		/*
161 		 * increased timeout from 4sec to 15sec because
162 		 * corporate server really takes that long.
163 		 */
164 		alarm(15000);
165 		m = write(fd, buf, n);
166 		if(m != n){
167 			syslog(0, AUTHLOG, "%s: rpc write err %d %d: %r",
168 				dest, m, n);
169 			m = -1;
170 			break;
171 		}
172 		m = read(fd, buf2, sizeof buf2);
173 		alarm(0);
174 		if(m < 0){
175 			syslog(0, AUTHLOG, "%s rpc read err %d: %r", dest, m);
176 			break;			/* failure */
177 		}
178 		if(m == 0 || buf2[1] != buf[1]){	/* need matching ID */
179 			syslog(0, AUTHLOG, "%s unmatched reply %d", dest, m);
180 			continue;
181 		}
182 		if(authcmp(shared, buf2, m, buf+4) == 0)
183 			break;
184 		syslog(0, AUTHLOG, "%s bad rpc chksum", dest);
185 	}
186 	close(fd);
187 	if(m <= 0)
188 		return nil;
189 
190 	/* unmarshal reply */
191 	b = buf2;
192 	e = buf2+m;
193 	resp = (Packet*)malloc(sizeof(*resp));
194 	if(resp == nil)
195 		return nil;
196 	resp->code = *b++;
197 	resp->ID = *b++;
198 	n = *b++;
199 	n = (n<<8) | *b++;
200 	if(m != n){
201 		syslog(0, AUTHLOG, "rpc got %d bytes, length said %d", m, n);
202 		if(m > n)
203 			e = buf2+n;
204 	}
205 	memmove(resp->authenticator, b, 16);
206 	b += 16;
207 	a = &resp->first;
208 	a->type = 0;
209 	for(;;){
210 		if(b >= e){
211 			a->next = nil;
212 			break;		/* exit loop */
213 		}
214 		a->type = *b++;
215 		a->len = (*b++) - 2;
216 		if(b + a->len > e){	/* corrupt packet */
217 			a->next = nil;
218 			freePacket(resp);
219 			return nil;
220 		}
221 		memmove(a->val, b, a->len);
222 		b += a->len;
223 		if(b < e){		/* any more attributes? */
224 			a->next = (Attribute*)malloc(sizeof(*a));
225 			if(a->next == nil){
226 				free(req);
227 				return nil;
228 			}
229 			a = a->next;
230 		}
231 	}
232 	return resp;
233 }
234 
235 int
setAttribute(Packet * p,uchar type,uchar * s,int n)236 setAttribute(Packet *p, uchar type, uchar *s, int n)
237 {
238 	Attribute *a;
239 
240 	a = &p->first;
241 	if(a->type != 0){
242 		a = (Attribute*)malloc(sizeof(*a));
243 		if(a == nil)
244 			return -1;
245 		a->next = p->first.next;
246 		p->first.next = a;
247 	}
248 	a->type = type;
249 	a->len = n;
250 	if(a->len > 253)	/* RFC2138, section 5 */
251 		a->len = 253;
252 	memmove(a->val, s, a->len);
253 	return 0;
254 }
255 
256 /* return a reply message attribute string */
257 char*
replymsg(Packet * p)258 replymsg(Packet *p)
259 {
260 	Attribute *a;
261 	static char buf[255];
262 
263 	for(a = &p->first; a; a = a->next)
264 		if(a->type == R_ReplyMessage){
265 			if(a->len >= sizeof buf)
266 				a->len = sizeof(buf)-1;
267 			memmove(buf, a->val, a->len);
268 			buf[a->len] = 0;
269 		}
270 	return buf;
271 }
272 
273 /* for convenience while debugging */
274 char *replymess;
275 Attribute *stateattr;
276 
277 void
logPacket(Packet * p)278 logPacket(Packet *p)
279 {
280 	int i;
281 	char *np, *e;
282 	char buf[255], pbuf[4*1024];
283 	uchar *au = p->authenticator;
284 	Attribute *a;
285 
286 	e = pbuf + sizeof(pbuf);
287 
288 	np = seprint(pbuf, e, "Packet ID=%d auth=%x %x %x... ",
289 		p->ID, au[0], au[1], au[2]);
290 	switch(p->code){
291 	case R_AccessRequest:
292 		np = seprint(np, e, "request\n");
293 		break;
294 	case R_AccessAccept:
295 		np = seprint(np, e, "accept\n");
296 		break;
297 	case R_AccessReject:
298 		np = seprint(np, e, "reject\n");
299 		break;
300 	case R_AccessChallenge:
301 		np = seprint(np, e, "challenge\n");
302 		break;
303 	default:
304 		np = seprint(np, e, "code=%d\n", p->code);
305 		break;
306 	}
307 	replymess = "0000000";
308 	for(a = &p->first; a; a = a->next){
309 		if(a->len > 253 )
310 			a->len = 253;
311 		memmove(buf, a->val, a->len);
312 		np = seprint(np, e, " [%d]", a->type);
313 		for(i = 0; i < a->len; i++)
314 			if(isprint(a->val[i]))
315 				np = seprint(np, e, "%c", a->val[i]);
316 			else
317 				np = seprint(np, e, "\\%o", a->val[i]);
318 		np = seprint(np, e, "\n");
319 		buf[a->len] = 0;
320 		if(a->type == R_ReplyMessage)
321 			replymess = strdup(buf);
322 		else if(a->type == R_State)
323 			stateattr = a;
324 	}
325 
326 	syslog(0, AUTHLOG, "%s", pbuf);
327 }
328 
329 static uchar*
getipv4addr(void)330 getipv4addr(void)
331 {
332 	Ipifc *nifc;
333 	Iplifc *lifc;
334 	static Ipifc *ifc;
335 
336 	ifc = readipifc("/net", ifc, -1);
337 	for(nifc = ifc; nifc; nifc = nifc->next)
338 		for(lifc = nifc->lifc; lifc; lifc = lifc->next)
339 			if (ipcmp(lifc->ip, IPnoaddr) != 0 &&
340 			    ipcmp(lifc->ip, v4prefix) != 0)
341 				return lifc->ip;
342 	return nil;
343 }
344 
345 extern Ndb *db;
346 
347 /* returns 0 on success, error message on failure */
348 char*
secureidcheck(char * user,char * response)349 secureidcheck(char *user, char *response)
350 {
351 	char *radiussecret = nil;
352 	char *rv = "authentication failed";
353 	char dest[3*IPaddrlen+20], ruser[64];
354 	uchar *ip;
355 	uchar x[16];
356 	ulong u[4];
357 	Ndbs s;
358 	Ndbtuple *t = nil, *nt, *tt;
359 	Packet *req = nil, *resp = nil;
360 	Secret shared, pass;
361 	static Ndb *netdb;
362 
363 	if(netdb == nil)
364 		netdb = ndbopen(0);
365 
366 	/* bad responses make them disable the fob, avoid silly checks */
367 	if(strlen(response) < 4 || strpbrk(response,"abcdefABCDEF") != nil)
368 		goto out;
369 
370 	/* get radius secret */
371 	radiussecret = ndbgetvalue(db, &s, "radius", "lra-radius", "secret", &t);
372 	if(radiussecret == nil){
373 		syslog(0, AUTHLOG, "secureidcheck: nil radius secret: %r");
374 		goto out;
375 	}
376 
377 	/* translate user name if we have to */
378 	strcpy(ruser, user);
379 	for(nt = t; nt; nt = nt->entry)
380 		if(strcmp(nt->attr, "uid") == 0 && strcmp(nt->val, user) == 0)
381 			for(tt = nt->line; tt != nt; tt = tt->line)
382 				if(strcmp(tt->attr, "rid") == 0){
383 					strcpy(ruser, tt->val);
384 					break;
385 				}
386 	ndbfree(t);
387 	t = nil;
388 
389 	u[0] = fastrand();
390 	u[1] = fastrand();
391 	u[2] = fastrand();
392 	u[3] = fastrand();
393 	req = newRequest((uchar*)u);
394 	if(req == nil)
395 		goto out;
396 	shared.s = (uchar*)radiussecret;
397 	shared.len = strlen(radiussecret);
398 	ip = getipv4addr();
399 	if(ip == nil){
400 		syslog(0, AUTHLOG, "no interfaces: %r\n");
401 		goto out;
402 	}
403 	if(setAttribute(req, R_NASIPAddress, ip + IPv4off, 4) < 0)
404 		goto out;
405 
406 	if(setAttribute(req, R_UserName, (uchar*)ruser, strlen(ruser)) < 0)
407 		goto out;
408 	pass.s = (uchar*)response;
409 	pass.len = strlen(response);
410 	hide(&shared, req->authenticator, &pass, x);
411 	if(setAttribute(req, R_UserPassword, x, 16) < 0)
412 		goto out;
413 
414 	t = ndbsearch(netdb, &s, "sys", "lra-radius");
415 	if(t == nil){
416 		syslog(0, AUTHLOG, "secureidcheck: nil radius sys search: %r\n");
417 		goto out;
418 	}
419 	for(nt = t; nt; nt = nt->entry){
420 		if(strcmp(nt->attr, "ip") != 0)
421 			continue;
422 
423 		snprint(dest, sizeof dest, "udp!%s!radius", nt->val);
424 		resp = rpc(dest, &shared, req);
425 		if(resp == nil){
426 			syslog(0, AUTHLOG, "%s nil response", dest);
427 			continue;
428 		}
429 		if(resp->ID != req->ID){
430 			syslog(0, AUTHLOG, "%s mismatched ID  req=%d resp=%d",
431 				dest, req->ID, resp->ID);
432 			freePacket(resp);
433 			resp = nil;
434 			continue;
435 		}
436 
437 		switch(resp->code){
438 		case R_AccessAccept:
439 			syslog(0, AUTHLOG, "%s accepted ruser=%s", dest, ruser);
440 			rv = nil;
441 			break;
442 		case R_AccessReject:
443 			syslog(0, AUTHLOG, "%s rejected ruser=%s %s",
444 				dest, ruser, replymsg(resp));
445 			rv = "secureid failed";
446 			break;
447 		case R_AccessChallenge:
448 			syslog(0, AUTHLOG, "%s challenge ruser=%s %s",
449 				dest, ruser, replymsg(resp));
450 			rv = "secureid out of sync";
451 			break;
452 		default:
453 			syslog(0, AUTHLOG, "%s code=%d ruser=%s %s",
454 				dest, resp->code, ruser, replymsg(resp));
455 			break;
456 		}
457 		break;	/* we have a proper reply, no need to ask again */
458 	}
459 out:
460 	if (t)
461 		ndbfree(t);
462 	free(radiussecret);
463 	freePacket(req);
464 	freePacket(resp);
465 	return rv;
466 }
467