xref: /openbsd-src/libexec/login_radius/raddauth.c (revision 671eecb85b8338e9d1c7ca0a7a8dac8753bfaa9d)
1 /*	$OpenBSD: raddauth.c,v 1.33 2024/07/18 02:45:31 yasuoka 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/socket.h>
70 #include <netinet/in.h>
71 #include <arpa/inet.h>
72 
73 #include <ctype.h>
74 #include <err.h>
75 #include <errno.h>
76 #include <stdint.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 <readpassphrase.h>
88 #include <openssl/hmac.h>
89 #include <openssl/md5.h>
90 #include "login_radius.h"
91 
92 
93 #define	MAXPWNETNAM		64	/* longest username */
94 #define MAXSECRETLEN		128	/* maximum length of secret */
95 
96 #define AUTH_VECTOR_LEN			16
97 #define AUTH_HDR_LEN			20
98 #define	AUTH_PASS_LEN			(256 - 16)
99 #define AUTH_MSGAUTH_LEN		16
100 #define	PW_AUTHENTICATION_REQUEST	1
101 #define	PW_AUTHENTICATION_ACK		2
102 #define	PW_AUTHENTICATION_REJECT	3
103 #define PW_ACCESS_CHALLENGE		11
104 #define	PW_USER_NAME			1
105 #define	PW_PASSWORD			2
106 #define	PW_CLIENT_ID			4
107 #define	PW_CLIENT_PORT_ID		5
108 #define PW_PORT_MESSAGE			18
109 #define PW_STATE			24
110 #define PW_MSG_AUTH			80
111 
112 #ifndef	RADIUS_DIR
113 #define RADIUS_DIR		"/etc/raddb"
114 #endif
115 #define RADIUS_SERVERS		"servers"
116 
117 char *radius_dir = RADIUS_DIR;
118 char auth_secret[MAXSECRETLEN+1];
119 volatile sig_atomic_t timedout;
120 int alt_retries;
121 int retries;
122 int sockfd;
123 int timeout;
124 in_addr_t alt_server;
125 in_addr_t auth_server;
126 in_port_t radius_port;
127 
128 typedef struct {
129 	u_char	code;
130 	u_char	id;
131 	u_short	length;
132 	u_char	vector[AUTH_VECTOR_LEN];
133 	u_char	data[4096 - AUTH_HDR_LEN];
134 } auth_hdr_t;
135 
136 void servtimeout(int);
137 in_addr_t get_ipaddr(char *);
138 in_addr_t gethost(void);
139 int rad_recv(char *, char *, u_char *);
140 void parse_challenge(auth_hdr_t *, char *, char *);
141 void rad_request(u_char, char *, char *, int, char *, char *);
142 void getsecret(void);
143 
144 /*
145  * challenge -- NULL for interactive service
146  * password -- NULL for interactive service and when requesting a challenge
147  */
148 int
149 raddauth(char *username, char *class, char *style, char *challenge,
150     char *password, char **emsg)
151 {
152 	static char _pwstate[1024];
153 	u_char req_id;
154 	char *userstyle, *passwd, *pwstate, *rad_service;
155 	char pbuf[AUTH_PASS_LEN+1];
156 	int auth_port;
157 	char vector[AUTH_VECTOR_LEN+1], *p, *v;
158 	int i;
159 	login_cap_t *lc;
160 	u_int32_t r;
161 	struct servent *svp;
162 	struct sockaddr_in sin;
163 	struct sigaction sa;
164 	const char *errstr;
165 
166 	memset(_pwstate, 0, sizeof(_pwstate));
167 	pwstate = password ? challenge : _pwstate;
168 
169 	if ((lc = login_getclass(class)) == NULL) {
170 		snprintf(_pwstate, sizeof(_pwstate),
171 		    "%s: no such class", class);
172 		*emsg = _pwstate;
173 		return (1);
174 	}
175 
176 	rad_service = login_getcapstr(lc, "radius-port", "radius", "radius");
177 	timeout = login_getcapnum(lc, "radius-timeout", 2, 2);
178 	retries = login_getcapnum(lc, "radius-retries", 6, 6);
179 
180 	if (timeout < 1)
181 		timeout = 1;
182 	if (retries < 2)
183 		retries = 2;
184 
185 	if (challenge == NULL) {
186 		passwd = NULL;
187 		v = login_getcapstr(lc, "radius-challenge-styles",
188 		    NULL, NULL);
189 		i = strlen(style);
190 		while (v && (p = strstr(v, style)) != NULL) {
191 			if ((p == v || p[-1] == ',') &&
192 			    (p[i] == ',' || p[i] == '\0')) {
193 				passwd = "";
194 				break;
195 			}
196 			v = p+1;
197 		}
198 		if (passwd == NULL)
199 			passwd = readpassphrase("Password:", pbuf, sizeof(pbuf),
200 			    RPP_ECHO_OFF);
201 	} else
202 		passwd = password;
203 	if (passwd == NULL)
204 		passwd = "";
205 
206 	if ((v = login_getcapstr(lc, "radius-server", NULL, NULL)) == NULL){
207 		*emsg = "radius-server not configured";
208 		return (1);
209 	}
210 
211 	auth_server = get_ipaddr(v);
212 
213 	if ((v = login_getcapstr(lc, "radius-server-alt", NULL, NULL)) == NULL)
214 		alt_server = 0;
215 	else {
216 		alt_server = get_ipaddr(v);
217 		alt_retries = retries/2;
218 		retries >>= 1;
219 	}
220 
221 	/* get port number */
222 	radius_port = strtonum(rad_service, 1, UINT16_MAX, &errstr);
223 	if (errstr) {
224 		svp = getservbyname(rad_service, "udp");
225 		if (svp == NULL) {
226 			snprintf(_pwstate, sizeof(_pwstate),
227 			    "No such service: %s/udp", rad_service);
228 			*emsg = _pwstate;
229 			return (1);
230 		}
231 		radius_port = svp->s_port;
232 	} else
233 		radius_port = htons(radius_port);
234 
235 	/* get the secret from the servers file */
236 	getsecret();
237 
238 	/* set up socket */
239 	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
240 		snprintf(_pwstate, sizeof(_pwstate), "%s", strerror(errno));
241 		*emsg = _pwstate;
242 		return (1);
243 	}
244 
245 	/* set up client structure */
246 	memset(&sin, 0, sizeof(sin));
247 	sin.sin_family = AF_INET;
248 	sin.sin_addr.s_addr = INADDR_ANY;
249 	sin.sin_port = radius_port;
250 
251 	req_id = (u_char) arc4random();
252 	auth_port = ttyslot();
253 	if (auth_port == 0)
254 		auth_port = (int)getppid();
255 	if (strcmp(style, "radius") != 0) {
256 		if (asprintf(&userstyle, "%s:%s", username, style) == -1)
257 			err(1, NULL);
258 	} else
259 		userstyle = username;
260 
261 	/* generate random vector */
262 	for (i = 0; i < AUTH_VECTOR_LEN;) {
263 		r = arc4random();
264 		memcpy(&vector[i], &r, sizeof(r));
265 		i += sizeof(r);
266 	}
267 	vector[AUTH_VECTOR_LEN] = '\0';
268 
269 	sigemptyset(&sa.sa_mask);
270 	sa.sa_handler = servtimeout;
271 	sa.sa_flags = 0;		/* don't restart system calls */
272 	(void)sigaction(SIGALRM, &sa, NULL);
273 retry:
274 	if (timedout) {
275 		timedout = 0;
276 		if (--retries <= 0) {
277 			/*
278 			 * If we ran out of tries but there is an alternate
279 			 * server, switch to it and try again.
280 			 */
281 			if (alt_retries) {
282 				auth_server = alt_server;
283 				retries = alt_retries;
284 				alt_retries = 0;
285 				getsecret();
286 			} else
287 				warnx("no response from authentication server");
288 		}
289 	}
290 
291 	if (retries > 0) {
292 		rad_request(req_id, userstyle, passwd, auth_port, vector,
293 		    pwstate);
294 
295 		switch (i = rad_recv(_pwstate, challenge, vector)) {
296 		case PW_AUTHENTICATION_ACK:
297 			/*
298 			 * Make sure we don't think a challenge was issued.
299 			 */
300 			if (challenge)
301 				*challenge = '\0';
302 			return (0);
303 
304 		case PW_AUTHENTICATION_REJECT:
305 			return (1);
306 
307 		case PW_ACCESS_CHALLENGE:
308 			/*
309 			 * If this is a response then reject them if
310 			 * we got a challenge.
311 			 */
312 			if (password)
313 				return (1);
314 			/*
315 			 * If we wanted a challenge, just return
316 			 */
317 			if (challenge) {
318 				if (strcmp(challenge, _pwstate) != 0)
319 					syslog(LOG_WARNING,
320 				    "challenge for %s does not match state",
321 				    userstyle);
322 				return (0);
323 			}
324 			req_id++;
325 			if ((passwd = readpassphrase("", pbuf, sizeof(pbuf),
326 				    RPP_ECHO_OFF)) == NULL)
327 				passwd = "";
328 			break;
329 
330 		default:
331 			if (timedout)
332 				goto retry;
333 			snprintf(_pwstate, sizeof(_pwstate),
334 			    "invalid response type %d\n", i);
335 			*emsg = _pwstate;
336 			return (1);
337 		}
338 	}
339 	return (1);
340 }
341 
342 /*
343  * Build a radius authentication digest and submit it to the radius server
344  */
345 void
346 rad_request(u_char id, char *name, char *password, int port, char *vector,
347     char *state)
348 {
349 	auth_hdr_t auth;
350 	int i, len, secretlen, total_length, p;
351 	struct sockaddr_in sin;
352 	u_char md5buf[MAXSECRETLEN+AUTH_VECTOR_LEN], digest[AUTH_VECTOR_LEN],
353 	    pass_buf[AUTH_PASS_LEN], *pw, *ptr, *ma;
354 	u_int length;
355 	in_addr_t ipaddr;
356 	MD5_CTX context;
357 
358 	memset(&auth, 0, sizeof(auth));
359 	auth.code = PW_AUTHENTICATION_REQUEST;
360 	auth.id = id;
361 	memcpy(auth.vector, vector, AUTH_VECTOR_LEN);
362 	total_length = AUTH_HDR_LEN;
363 	ptr = auth.data;
364 
365 	/* Preserve space for msgauth */
366 	*ptr++ = PW_MSG_AUTH;
367 	length = 16;
368 	*ptr++ = length + 2;
369 	ma = ptr;
370 	memset(ma, 0, 16);
371 	ptr += length;
372 	total_length += length + 2;
373 
374 	/* User name */
375 	*ptr++ = PW_USER_NAME;
376 	length = strlen(name);
377 	if (length > MAXPWNETNAM)
378 		length = MAXPWNETNAM;
379 	*ptr++ = length + 2;
380 	memcpy(ptr, name, length);
381 	ptr += length;
382 	total_length += length + 2;
383 
384 	/* Password */
385 	length = strlen(password);
386 	if (length > AUTH_PASS_LEN)
387 		length = AUTH_PASS_LEN;
388 
389 	p = (length + AUTH_VECTOR_LEN - 1) / AUTH_VECTOR_LEN;
390 	*ptr++ = PW_PASSWORD;
391 	*ptr++ = p * AUTH_VECTOR_LEN + 2;
392 
393 	memset(pass_buf, 0, sizeof(pass_buf));		/* must zero fill */
394 	strlcpy((char *)pass_buf, password, sizeof(pass_buf));
395 
396 	/* Calculate the md5 digest */
397 	secretlen = strlen(auth_secret);
398 	memcpy(md5buf, auth_secret, secretlen);
399 	memcpy(md5buf + secretlen, auth.vector, AUTH_VECTOR_LEN);
400 
401 	total_length += 2;
402 
403 	/* XOR the password into the md5 digest */
404 	pw = pass_buf;
405 	while (p-- > 0) {
406 		MD5_Init(&context);
407 		MD5_Update(&context, md5buf, secretlen + AUTH_VECTOR_LEN);
408 		MD5_Final(digest, &context);
409 		for (i = 0; i < AUTH_VECTOR_LEN; ++i) {
410 			*ptr = digest[i] ^ *pw;
411 			md5buf[secretlen+i] = *ptr++;
412 			*pw++ = '\0';
413 		}
414 		total_length += AUTH_VECTOR_LEN;
415 	}
416 	explicit_bzero(pass_buf, strlen(pass_buf));
417 
418 	/* Client id */
419 	*ptr++ = PW_CLIENT_ID;
420 	*ptr++ = sizeof(in_addr_t) + 2;
421 	ipaddr = gethost();
422 	memcpy(ptr, &ipaddr, sizeof(in_addr_t));
423 	ptr += sizeof(in_addr_t);
424 	total_length += sizeof(in_addr_t) + 2;
425 
426 	/* client port */
427 	*ptr++ = PW_CLIENT_PORT_ID;
428 	*ptr++ = sizeof(in_addr_t) + 2;
429 	port = htonl(port);
430 	memcpy(ptr, &port, sizeof(int));
431 	ptr += sizeof(int);
432 	total_length += sizeof(int) + 2;
433 
434 	/* Append the state info */
435 	if ((state != NULL) && (strlen(state) > 0)) {
436 		len = strlen(state);
437 		*ptr++ = PW_STATE;
438 		*ptr++ = len + 2;
439 		memcpy(ptr, state, len);
440 		ptr += len;
441 		total_length += len + 2;
442 	}
443 
444 	auth.length = htons(total_length);
445 
446 	/* Calc msgauth */
447 	if (HMAC(EVP_md5(), auth_secret, secretlen, (unsigned char *)&auth,
448 	    total_length, ma, NULL) == NULL)
449 		errx(1, "HMAC() failed");
450 
451 	memset(&sin, 0, sizeof (sin));
452 	sin.sin_family = AF_INET;
453 	sin.sin_addr.s_addr = auth_server;
454 	sin.sin_port = radius_port;
455 	if (sendto(sockfd, &auth, total_length, 0, (struct sockaddr *)&sin,
456 	    sizeof(sin)) == -1)
457 		err(1, NULL);
458 }
459 
460 /*
461  * Receive UDP responses from the radius server
462  */
463 int
464 rad_recv(char *state, char *challenge, u_char *req_vector)
465 {
466 	auth_hdr_t auth;
467 	socklen_t salen;
468 	struct sockaddr_in sin;
469 	u_char recv_vector[AUTH_VECTOR_LEN], test_vector[AUTH_VECTOR_LEN];
470 	MD5_CTX context;
471 	ssize_t total_length;
472 
473 	salen = sizeof(sin);
474 
475 	alarm(timeout);
476 	total_length = recvfrom(sockfd, &auth, sizeof(auth), 0,
477 	    (struct sockaddr *)&sin, &salen);
478 	alarm(0);
479 	if (total_length < AUTH_HDR_LEN) {
480 		if (timedout)
481 			return(-1);
482 		errx(1, "bogus auth packet from server");
483 	}
484 	if (ntohs(auth.length) > total_length)
485 		errx(1, "bogus auth packet from server");
486 
487 	if (sin.sin_addr.s_addr != auth_server)
488 		errx(1, "bogus authentication server");
489 
490 	/* verify server's shared secret */
491 	memcpy(recv_vector, auth.vector, AUTH_VECTOR_LEN);
492 	memcpy(auth.vector, req_vector, AUTH_VECTOR_LEN);
493 	MD5_Init(&context);
494 	MD5_Update(&context, (u_char *)&auth, ntohs(auth.length));
495 	MD5_Update(&context, auth_secret, strlen(auth_secret));
496 	MD5_Final(test_vector, &context);
497 	if (memcmp(recv_vector, test_vector, AUTH_VECTOR_LEN) != 0)
498 		errx(1, "shared secret incorrect");
499 
500 	if (auth.code == PW_ACCESS_CHALLENGE)
501 		parse_challenge(&auth, state, challenge);
502 
503 	return (auth.code);
504 }
505 
506 /*
507  * Get IP address of local hostname
508  */
509 in_addr_t
510 gethost(void)
511 {
512 	char hostname[HOST_NAME_MAX+1];
513 
514 	if (gethostname(hostname, sizeof(hostname)))
515 		err(1, "gethost");
516 	return (get_ipaddr(hostname));
517 }
518 
519 /*
520  * Get an IP address in host in_addr_t notation from a hostname or dotted quad.
521  */
522 in_addr_t
523 get_ipaddr(char *host)
524 {
525 	struct hostent *hp;
526 
527 	if ((hp = gethostbyname(host)) == NULL)
528 		return (0);
529 
530 	return (((struct in_addr *)hp->h_addr)->s_addr);
531 }
532 
533 /*
534  * Get the secret from the servers file
535  */
536 void
537 getsecret(void)
538 {
539 	FILE *servfd;
540 	char *host, *secret, buffer[PATH_MAX];
541 	size_t len;
542 
543 	snprintf(buffer, sizeof(buffer), "%s/%s",
544 	    radius_dir, RADIUS_SERVERS);
545 
546 	if ((servfd = fopen(buffer, "r")) == NULL) {
547 		syslog(LOG_ERR, "%s: %m", buffer);
548 		return;
549 	}
550 
551 	secret = NULL;			/* Keeps gcc happy */
552 	while ((host = fgetln(servfd, &len)) != NULL) {
553 		if (*host == '#') {
554 			memset(host, 0, len);
555 			continue;
556 		}
557 		if (host[len-1] == '\n')
558 			--len;
559 		else {
560 			/* No trailing newline, must allocate len+1 for NUL */
561 			if ((secret = malloc(len + 1)) == NULL) {
562 				memset(host, 0, len);
563 				continue;
564 			}
565 			memcpy(secret, host, len);
566 			memset(host, 0, len);
567 			host = secret;
568 		}
569 		while (len > 0 && isspace((unsigned char)host[--len]))
570 			;
571 		host[++len] = '\0';
572 		while (isspace((unsigned char)*host)) {
573 			++host;
574 			--len;
575 		}
576 		if (*host == '\0')
577 			continue;
578 		secret = host;
579 		while (*secret && !isspace((unsigned char)*secret))
580 			++secret;
581 		if (*secret)
582 			*secret++ = '\0';
583 		if (get_ipaddr(host) != auth_server) {
584 			memset(host, 0, len);
585 			continue;
586 		}
587 		while (isspace((unsigned char)*secret))
588 			++secret;
589 		if (*secret)
590 			break;
591 	}
592 	if (host) {
593 		strlcpy(auth_secret, secret, sizeof(auth_secret));
594 		memset(host, 0, len);
595 	}
596 	fclose(servfd);
597 }
598 
599 void
600 servtimeout(int signo)
601 {
602 
603 	timedout = 1;
604 }
605 
606 /*
607  * Parse a challenge received from the server
608  */
609 void
610 parse_challenge(auth_hdr_t *authhdr, char *state, char *challenge)
611 {
612 	int length;
613 	int attribute, attribute_len;
614 	u_char *ptr;
615 
616 	ptr = authhdr->data;
617 	length = ntohs(authhdr->length) - AUTH_HDR_LEN;
618 
619 	*state = 0;
620 
621 	while (length > 0) {
622 		attribute = *ptr++;
623 		attribute_len = *ptr++;
624 		length -= attribute_len;
625 		attribute_len -= 2;
626 
627 		switch (attribute) {
628 		case PW_PORT_MESSAGE:
629 			if (challenge) {
630 				memcpy(challenge, ptr, attribute_len);
631 				challenge[attribute_len] = '\0';
632 			} else
633 				printf("%.*s", attribute_len, ptr);
634 			break;
635 		case PW_STATE:
636 			memcpy(state, ptr, attribute_len);
637 			state[attribute_len] = '\0';
638 			break;
639 		}
640 		ptr += attribute_len;
641 	}
642 }
643