1 /*	$OpenBSD: raddauth.c,v 1.20 2005/11/19 23:02:25 millert Exp $	*/
2 
3 /*-
4  * Copyright (c) 1996, 1997 Berkeley Software Design, Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *      This product includes software developed by Berkeley Software Design,
17  *      Inc.
18  * 4. The name of Berkeley Software Design, Inc.  may not be used to endorse
19  *    or promote products derived from this software without specific prior
20  *    written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  *	BSDI $From: raddauth.c,v 1.6 1998/04/14 00:39:04 prb Exp $
35  */
36 /*
37  * Copyright(c) 1996 by tfm associates.
38  * All rights reserved.
39  *
40  * tfm associates
41  * P.O. Box 2086
42  * Eugene OR 97402-0031
43  *
44  * Redistribution and use in source and binary forms, with or without
45  * modification, are permitted provided that the following conditions
46  * are met:
47  * 1. Redistributions of source code must retain the above copyright
48  *    notice, this list of conditions and the following disclaimer.
49  * 2. Redistributions in binary form must reproduce the above copyright
50  *    notice, this list of conditions and the following disclaimer in the
51  *    documentation and/or other materials provided with the distribution.
52  * 3. The name of tfm associates may not be used to endorse or promote
53  *    products derived from this software without specific prior written
54  *    permission.
55  *
56  * THIS SOFTWARE IS PROVIDED BY TFM ASSOC``AS IS'' AND ANY EXPRESS OR
57  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
58  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
59  * IN NO EVENT SHALL TFM ASSOCIATES BE LIABLE FOR ANY DIRECT, INDIRECT,
60  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
61  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
62  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
63  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
64  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
65  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
66  */
67 
68 #include <sys/types.h>
69 #include <sys/param.h>
70 #include <sys/socket.h>
71 #include <netinet/in.h>
72 #include <arpa/inet.h>
73 
74 #include <ctype.h>
75 #include <err.h>
76 #include <errno.h>
77 #include <limits.h>
78 #include <login_cap.h>
79 #include <netdb.h>
80 #include <signal.h>
81 #include <stdio.h>
82 #include <stdlib.h>
83 #include <string.h>
84 #include <syslog.h>
85 #include <time.h>
86 #include <unistd.h>
87 #include <md5.h>
88 #include "login_radius.h"
89 
90 __RCSID("$MirOS: src/libexec/login_radius/raddauth.c,v 1.2 2010/09/21 21:24:14 tg Exp $");
91 
92 #define	MAXPWNETNAM		64	/* longest username */
93 #define MAXSECRETLEN		128	/* maximum length of secret */
94 
95 #define AUTH_VECTOR_LEN			16
96 #define AUTH_HDR_LEN			20
97 #define	AUTH_PASS_LEN			(256 - 16)
98 #define	PW_AUTHENTICATION_REQUEST	1
99 #define	PW_AUTHENTICATION_ACK		2
100 #define	PW_AUTHENTICATION_REJECT	3
101 #define PW_ACCESS_CHALLENGE		11
102 #define	PW_USER_NAME			1
103 #define	PW_PASSWORD			2
104 #define	PW_CLIENT_ID			4
105 #define	PW_CLIENT_PORT_ID		5
106 #define PW_PORT_MESSAGE			18
107 #define PW_STATE			24
108 
109 #ifndef	RADIUS_DIR
110 #define RADIUS_DIR		"/etc/raddb"
111 #endif
112 #define RADIUS_SERVERS		"servers"
113 
114 char *radius_dir = RADIUS_DIR;
115 char auth_secret[MAXSECRETLEN+1];
116 volatile sig_atomic_t timedout;
117 int alt_retries;
118 int retries;
119 int sockfd;
120 int timeout;
121 in_addr_t alt_server;
122 in_addr_t auth_server;
123 
124 typedef struct {
125 	u_char	code;
126 	u_char	id;
127 	u_short	length;
128 	u_char	vector[AUTH_VECTOR_LEN];
129 	u_char	data[4096 - AUTH_HDR_LEN];
130 } auth_hdr_t;
131 
132 void servtimeout(int);
133 in_addr_t get_ipaddr(char *);
134 in_addr_t gethost(void);
135 int rad_recv(char *, char *, u_char *);
136 void parse_challenge(auth_hdr_t *, char *, char *);
137 void rad_request(u_char, char *, char *, int, char *, char *);
138 void getsecret(void);
139 
140 /*
141  * challenge -- NULL for interactive service
142  * password -- NULL for interactive service and when requesting a challenge
143  */
144 int
raddauth(char * username,char * class,char * style,char * challenge,char * password,char ** emsg)145 raddauth(char *username, char *class, char *style, char *challenge,
146     char *password, char **emsg)
147 {
148 	u_char req_id;
149 	char *userstyle, *passwd, *pwstate;
150 	int auth_port;
151 	char vector[AUTH_VECTOR_LEN+1], _pwstate[1024], *p, *v;
152 	int i;
153 	login_cap_t *lc;
154 	struct servent *svp;
155 	struct sockaddr_in sin;
156 	struct sigaction sa;
157 
158 	memset(_pwstate, 0, sizeof(_pwstate));
159 	pwstate = password ? challenge : _pwstate;
160 
161 	if ((lc = login_getclass(class)) == NULL) {
162 		snprintf(_pwstate, sizeof(_pwstate),
163 		    "%s: no such class", class);
164 		*emsg = _pwstate;
165 		return (1);
166 	}
167 
168 	timeout = login_getcapnum(lc, "radius-timeout", 2, 2);
169 	retries = login_getcapnum(lc, "radius-retries", 6, 6);
170 	if (timeout < 1)
171 		timeout = 1;
172 	if (retries < 2)
173 		retries = 2;
174 
175 	if (challenge == NULL) {
176 		passwd = NULL;
177 		v = login_getcapstr(lc, "radius-challenge-styles",
178 		    NULL, NULL);
179 		i = strlen(style);
180 		while (v && (p = strstr(v, style)) != NULL) {
181 			if ((p == v || p[-1] == ',') &&
182 			    (p[i] == ',' || p[i] == '\0')) {
183 				passwd = "";
184 				break;
185 			}
186 			v = p+1;
187 		}
188 		if (passwd == NULL)
189 			passwd = getpass("Password:");
190 	} else
191 		passwd = password;
192 	if (password == NULL)
193 		passwd = "";
194 
195 	if ((v = login_getcapstr(lc, "radius-server", NULL, NULL)) == NULL){
196 		*emsg = "radius-server not configured";
197 		return (1);
198 	}
199 
200 	auth_server = get_ipaddr(v);
201 
202 	if ((v = login_getcapstr(lc, "radius-server-alt", NULL, NULL)) == NULL)
203 		alt_server = 0;
204 	else {
205 		alt_server = get_ipaddr(v);
206 		alt_retries = retries/2;
207 		retries >>= 1;
208 	}
209 
210 	/* get port number */
211 	svp = getservbyname ("radius", "udp");
212 	if (svp == NULL) {
213 		*emsg = "No such service: radius/udp";
214 		return (1);
215 	}
216 
217 	/* get the secret from the servers file */
218 	getsecret();
219 
220 	/* set up socket */
221 	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
222 		snprintf(_pwstate, sizeof(_pwstate), "%s", strerror(errno));
223 		*emsg = _pwstate;
224 		return (1);
225 	}
226 
227 	/* set up client structure */
228 	memset(&sin, 0, sizeof(sin));
229 	sin.sin_family = AF_INET;
230 	sin.sin_addr.s_addr = INADDR_ANY;
231 	sin.sin_port = svp->s_port;
232 
233 	req_id = (u_char) arc4random();
234 	auth_port = ttyslot();
235 	if (auth_port == 0)
236 		auth_port = (int)getppid();
237 	if (strcmp(style, "radius") != 0) {
238 		if (asprintf(&userstyle, "%s:%s", username, style) == -1)
239 			err(1, NULL);
240 	} else
241 		userstyle = username;
242 
243 	/* generate random vector */
244 	arc4random_buf(vector, AUTH_VECTOR_LEN);
245 	vector[AUTH_VECTOR_LEN] = '\0';
246 
247 	sigemptyset(&sa.sa_mask);
248 	sa.sa_handler = servtimeout;
249 	sa.sa_flags = 0;		/* don't restart system calls */
250 	(void)sigaction(SIGALRM, &sa, NULL);
251 retry:
252 	if (timedout) {
253 		timedout = 0;
254 		if (--retries <= 0) {
255 			/*
256 			 * If we ran out of tries but there is an alternate
257 			 * server, switch to it and try again.
258 			 */
259 			if (alt_retries) {
260 				auth_server = alt_server;
261 				retries = alt_retries;
262 				alt_retries = 0;
263 				getsecret();
264 			} else
265 				warnx("no response from authentication server");
266 		}
267 	}
268 
269 	if (retries > 0) {
270 		rad_request(req_id, userstyle, passwd, auth_port, vector,
271 		    pwstate);
272 
273 		switch (i = rad_recv(_pwstate, challenge, vector)) {
274 		case PW_AUTHENTICATION_ACK:
275 			/*
276 			 * Make sure we don't think a challenge was issued.
277 			 */
278 			if (challenge)
279 				*challenge = '\0';
280 			return (0);
281 
282 		case PW_AUTHENTICATION_REJECT:
283 			return (1);
284 
285 		case PW_ACCESS_CHALLENGE:
286 			/*
287 			 * If this is a response then reject them if
288 			 * we got a challenge.
289 			 */
290 			if (password)
291 				return (1);
292 			/*
293 			 * If we wanted a challenge, just return
294 			 */
295 			if (challenge) {
296 				if (strcmp(challenge, _pwstate) != 0)
297 					syslog(LOG_WARNING,
298 				    "challenge for %s does not match state",
299 				    userstyle);
300 				return (0);
301 			}
302 			req_id++;
303 			if ((passwd = getpass("")) == NULL)
304 				passwd = "";
305 			break;
306 
307 		default:
308 			if (timedout)
309 				goto retry;
310 			snprintf(_pwstate, sizeof(_pwstate),
311 			    "invalid response type %d\n", i);
312 			*emsg = _pwstate;
313 			return (1);
314 		}
315 	}
316 	return (1);
317 }
318 
319 /*
320  * Build a radius authentication digest and submit it to the radius server
321  */
322 void
rad_request(u_char id,char * name,char * password,int port,char * vector,char * state)323 rad_request(u_char id, char *name, char *password, int port, char *vector,
324     char *state)
325 {
326 	auth_hdr_t auth;
327 	int i, len, secretlen, total_length, p;
328 	struct servent *rad_port;
329 	struct sockaddr_in sin;
330 	u_char md5buf[MAXSECRETLEN+AUTH_VECTOR_LEN], digest[AUTH_VECTOR_LEN],
331 	    pass_buf[AUTH_PASS_LEN], *pw, *ptr;
332 	u_int length;
333 	in_addr_t ipaddr;
334 	MD5_CTX context;
335 
336 	memset(&auth, 0, sizeof(auth));
337 	auth.code = PW_AUTHENTICATION_REQUEST;
338 	auth.id = id;
339 	memcpy(auth.vector, vector, AUTH_VECTOR_LEN);
340 	total_length = AUTH_HDR_LEN;
341 	ptr = auth.data;
342 
343 	/* User name */
344 	*ptr++ = PW_USER_NAME;
345 	length = strlen(name);
346 	if (length > MAXPWNETNAM)
347 		length = MAXPWNETNAM;
348 	*ptr++ = length + 2;
349 	memcpy(ptr, name, length);
350 	ptr += length;
351 	total_length += length + 2;
352 
353 	/* Password */
354 	length = strlen(password);
355 	if (length > AUTH_PASS_LEN)
356 		length = AUTH_PASS_LEN;
357 
358 	p = (length + AUTH_VECTOR_LEN - 1) / AUTH_VECTOR_LEN;
359 	*ptr++ = PW_PASSWORD;
360 	*ptr++ = p * AUTH_VECTOR_LEN + 2;
361 
362 	memset(pass_buf, 0, sizeof(pass_buf));		/* must zero fill */
363 	strlcpy((char *)pass_buf, password, sizeof(pass_buf));
364 
365 	/* Calculate the md5 digest */
366 	secretlen = strlen(auth_secret);
367 	memcpy(md5buf, auth_secret, secretlen);
368 	memcpy(md5buf + secretlen, auth.vector, AUTH_VECTOR_LEN);
369 
370 	total_length += 2;
371 
372 	/* XOR the password into the md5 digest */
373 	pw = pass_buf;
374 	while (p-- > 0) {
375 		MD5Init(&context);
376 		MD5Update(&context, md5buf, secretlen + AUTH_VECTOR_LEN);
377 		MD5Final(digest, &context);
378 		for (i = 0; i < AUTH_VECTOR_LEN; ++i) {
379 			*ptr = digest[i] ^ *pw;
380 			md5buf[secretlen+i] = *ptr++;
381 			*pw++ = '\0';
382 		}
383 		total_length += AUTH_VECTOR_LEN;
384 	}
385 
386 	/* Client id */
387 	*ptr++ = PW_CLIENT_ID;
388 	*ptr++ = sizeof(in_addr_t) + 2;
389 	ipaddr = gethost();
390 	memcpy(ptr, &ipaddr, sizeof(in_addr_t));
391 	ptr += sizeof(in_addr_t);
392 	total_length += sizeof(in_addr_t) + 2;
393 
394 	/* client port */
395 	*ptr++ = PW_CLIENT_PORT_ID;
396 	*ptr++ = sizeof(in_addr_t) + 2;
397 	port = htonl(port);
398 	memcpy(ptr, &port, sizeof(int));
399 	ptr += sizeof(int);
400 	total_length += sizeof(int) + 2;
401 
402 	/* Append the state info */
403 	if ((state != NULL) && (strlen(state) > 0)) {
404 		len = strlen(state);
405 		*ptr++ = PW_STATE;
406 		*ptr++ = len + 2;
407 		memcpy(ptr, state, len);
408 		ptr += len;
409 		total_length += len + 2;
410 	}
411 
412 	auth.length = htons(total_length);
413 
414 	/* get radius port number */
415 	rad_port = getservbyname("radius", "udp");
416 	if (rad_port == NULL)
417 		errx(1, "no such service: radius/udp");
418 
419 	memset(&sin, 0, sizeof (sin));
420 	sin.sin_family = AF_INET;
421 	sin.sin_addr.s_addr = auth_server;
422 	sin.sin_port = rad_port->s_port;
423 	if (sendto(sockfd, &auth, total_length, 0, (struct sockaddr *)&sin,
424 	    sizeof(sin)) == -1)
425 		err(1, NULL);
426 }
427 
428 /*
429  * Receive UDP responses from the radius server
430  */
431 int
rad_recv(char * state,char * challenge,u_char * req_vector)432 rad_recv(char *state, char *challenge, u_char *req_vector)
433 {
434 	auth_hdr_t auth;
435 	socklen_t salen;
436 	struct sockaddr_in sin;
437 	u_char recv_vector[AUTH_VECTOR_LEN], test_vector[AUTH_VECTOR_LEN];
438 	MD5_CTX context;
439 
440 	salen = sizeof(sin);
441 
442 	alarm(timeout);
443 	if ((recvfrom(sockfd, &auth, sizeof(auth), 0,
444 	    (struct sockaddr *)&sin, &salen)) < AUTH_HDR_LEN) {
445 		if (timedout)
446 			return(-1);
447 		errx(1, "bogus auth packet from server");
448 	}
449 	alarm(0);
450 
451 	if (sin.sin_addr.s_addr != auth_server)
452 		errx(1, "bogus authentication server");
453 
454 	/* verify server's shared secret */
455 	memcpy(recv_vector, auth.vector, AUTH_VECTOR_LEN);
456 	memcpy(auth.vector, req_vector, AUTH_VECTOR_LEN);
457 	MD5Init(&context);
458 	MD5Update(&context, (u_char *)&auth, ntohs(auth.length));
459 	MD5Update(&context, auth_secret, strlen(auth_secret));
460 	MD5Final(test_vector, &context);
461 	if (memcmp(recv_vector, test_vector, AUTH_VECTOR_LEN) != 0)
462 		errx(1, "shared secret incorrect");
463 
464 	if (auth.code == PW_ACCESS_CHALLENGE)
465 		parse_challenge(&auth, state, challenge);
466 
467 	return (auth.code);
468 }
469 
470 /*
471  * Get IP address of local hostname
472  */
473 in_addr_t
gethost(void)474 gethost(void)
475 {
476 	char hostname[MAXHOSTNAMELEN];
477 
478 	if (gethostname(hostname, sizeof(hostname)))
479 		err(1, "gethost");
480 	return (get_ipaddr(hostname));
481 }
482 
483 /*
484  * Get an IP address in host in_addr_t notation from a hostname or dotted quad.
485  */
486 in_addr_t
get_ipaddr(char * host)487 get_ipaddr(char *host)
488 {
489 	struct hostent *hp;
490 
491 	if ((hp = gethostbyname(host)) == NULL)
492 		return (0);
493 
494 	return (((struct in_addr *)hp->h_addr)->s_addr);
495 }
496 
497 /*
498  * Get the secret from the servers file
499  */
500 void
getsecret(void)501 getsecret(void)
502 {
503 	FILE *servfd;
504 	char *host, *secret, buffer[MAXPATHLEN];
505 	size_t len;
506 
507 	snprintf(buffer, sizeof(buffer), "%s/%s",
508 	    radius_dir, RADIUS_SERVERS);
509 
510 	if ((servfd = fopen(buffer, "r")) == NULL) {
511 		syslog(LOG_ERR, "%s: %m", buffer);
512 		return;
513 	}
514 
515 	secret = NULL;			/* Keeps gcc happy */
516 	while ((host = fgetln(servfd, &len)) != NULL) {
517 		if (*host == '#') {
518 			memset(host, 0, len);
519 			continue;
520 		}
521 		if (host[len-1] == '\n')
522 			--len;
523 		else {
524 			/* No trailing newline, must allocate len+1 for NUL */
525 			if ((secret = malloc(len + 1)) == NULL) {
526 				memset(host, 0, len);
527 				continue;
528 			}
529 			memcpy(secret, host, len);
530 			memset(host, 0, len);
531 			host = secret;
532 		}
533 		while (len > 0 && isspace(host[--len]))
534 			;
535 		host[++len] = '\0';
536 		while (isspace(*host)) {
537 			++host;
538 			--len;
539 		}
540 		if (*host == '\0')
541 			continue;
542 		secret = host;
543 		while (*secret && !isspace(*secret))
544 			++secret;
545 		if (*secret)
546 			*secret++ = '\0';
547 		if (get_ipaddr(host) != auth_server) {
548 			memset(host, 0, len);
549 			continue;
550 		}
551 		while (isspace(*secret))
552 			++secret;
553 		if (*secret)
554 			break;
555 	}
556 	if (host) {
557 		strlcpy(auth_secret, secret, sizeof(auth_secret));
558 		memset(host, 0, len);
559 	}
560 	fclose(servfd);
561 }
562 
563 void
servtimeout(int signo)564 servtimeout(int signo)
565 {
566 
567 	timedout = 1;
568 }
569 
570 /*
571  * Parse a challenge received from the server
572  */
573 void
parse_challenge(auth_hdr_t * authhdr,char * state,char * challenge)574 parse_challenge(auth_hdr_t *authhdr, char *state, char *challenge)
575 {
576 	int length;
577 	int attribute, attribute_len;
578 	u_char *ptr;
579 
580 	ptr = authhdr->data;
581 	length = ntohs(authhdr->length) - AUTH_HDR_LEN;
582 
583 	*state = 0;
584 
585 	while (length > 0) {
586 		attribute = *ptr++;
587 		attribute_len = *ptr++;
588 		length -= attribute_len;
589 		attribute_len -= 2;
590 
591 		switch (attribute) {
592 		case PW_PORT_MESSAGE:
593 			if (challenge) {
594 				memcpy(challenge, ptr, attribute_len);
595 				challenge[attribute_len] = '\0';
596 			} else
597 				printf("%.*s", attribute_len, ptr);
598 			break;
599 		case PW_STATE:
600 			memcpy(state, ptr, attribute_len);
601 			state[attribute_len] = '\0';
602 			break;
603 		}
604 		ptr += attribute_len;
605 	}
606 }
607