xref: /openbsd/usr.sbin/npppd/npppd/radius_req.c (revision 006a08d9)
1 /*	$OpenBSD: radius_req.c,v 1.12 2024/02/26 08:47:28 yasuoka Exp $ */
2 
3 /*-
4  * Copyright (c) 2009 Internet Initiative Japan Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 /**@file
29  * This file provides functions for RADIUS request using radius(3) and event(3).
30  * @author	Yasuoka Masahiko
31  * $Id: radius_req.c,v 1.12 2024/02/26 08:47:28 yasuoka Exp $
32  */
33 #include <sys/types.h>
34 #include <sys/time.h>
35 #include <sys/socket.h>
36 #include <netinet/in.h>
37 #include <unistd.h>
38 #include <stdio.h>
39 #include <syslog.h>
40 #include <stdlib.h>
41 #include <debugutil.h>
42 #include <time.h>
43 #include <event.h>
44 #include <string.h>
45 #include <errno.h>
46 
47 #include "radius_req.h"
48 #include <radius.h>
49 
50 #ifndef nitems
51 #define	nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
52 #endif
53 
54 struct overlapped {
55 	struct event		 ev_sock;
56 	int			 socket;
57 	int			 ntry;
58 	int			 max_tries;
59 	int			 failovers;
60 	int			 acct_delay_time;
61 	int			 response_fn_calling;
62 	struct sockaddr_storage	 ss;
63 	struct timespec		 req_time;
64 	void			*context;
65 	radius_response		*response_fn;
66 	char			 secret[MAX_RADIUS_SECRET];
67 	RADIUS_PACKET		*pkt;
68 	radius_req_setting	*setting;
69 };
70 
71 static int   radius_request0(struct overlapped *);
72 static int   radius_prepare_socket(struct overlapped *);
73 static void  radius_request_io_event (int, short, void *);
74 static void  radius_on_response(RADIUS_REQUEST_CTX, RADIUS_PACKET *, int, int);
75 static int   select_srcaddr(struct sockaddr const *, struct sockaddr *, socklen_t *);
76 static void  radius_req_setting_ref(radius_req_setting *);
77 static void  radius_req_setting_unref(radius_req_setting *);
78 
79 #ifdef	RADIUS_REQ_DEBUG
80 #define RADIUS_REQ_DBG(x)	log_printf x
81 #define	RADIUS_REQ_ASSERT(cond)					\
82 	if (!(cond)) {						\
83 	    fprintf(stderr,					\
84 		"\nASSERT(" #cond ") failed on %s() at %s:%d.\n"\
85 		, __func__, __FILE__, __LINE__);		\
86 	    abort(); 						\
87 	}
88 #else
89 #define	RADIUS_REQ_ASSERT(cond)
90 #define RADIUS_REQ_DBG(x)
91 #endif
92 
93 /**
94  * Send RADIUS request message.  The pkt(RADIUS packet) will be released
95  * by this implementation.
96  */
97 void
radius_request(RADIUS_REQUEST_CTX ctx,RADIUS_PACKET * pkt)98 radius_request(RADIUS_REQUEST_CTX ctx, RADIUS_PACKET *pkt)
99 {
100 	uint32_t ival;
101 	struct overlapped *lap;
102 
103 	RADIUS_REQ_ASSERT(pkt != NULL);
104 	RADIUS_REQ_ASSERT(ctx != NULL);
105 	lap = ctx;
106 	lap->pkt = pkt;
107 	if (radius_get_uint32_attr(pkt, RADIUS_TYPE_ACCT_DELAY_TIME, &ival)
108 	    == 0)
109 		lap->acct_delay_time = 1;
110 	radius_request0(lap);
111 }
112 
113 /**
114  * Prepare NAS-IP-Address or NAS-IPv6-Address.  If
115  * setting->server[setting->curr_server].sock is not initialized, address
116  * will be selected automatically.
117  */
118 int
radius_prepare_nas_address(radius_req_setting * setting,RADIUS_PACKET * pkt)119 radius_prepare_nas_address(radius_req_setting *setting,
120     RADIUS_PACKET *pkt)
121 {
122 	int af;
123 	struct sockaddr_in *sin4;
124 	struct sockaddr_in6 *sin6;
125 	socklen_t socklen;
126 
127 	/* See RFC 2765, 3162 */
128 	RADIUS_REQ_ASSERT(setting != NULL);
129 
130 	af = setting->server[setting->curr_server].peer.sin6.sin6_family;
131 	RADIUS_REQ_ASSERT(af == AF_INET6 || af == AF_INET);
132 
133 	sin4 = &setting->server[setting->curr_server].sock.sin4;
134 	sin6 = &setting->server[setting->curr_server].sock.sin6;
135 
136 	switch (af) {
137 	case AF_INET:
138 		socklen = sizeof(*sin4);
139 		if (sin4->sin_addr.s_addr == INADDR_ANY) {
140 			if (select_srcaddr((struct sockaddr const *)
141 			    &setting->server[setting->curr_server].peer,
142 			    (struct sockaddr *)sin4, &socklen) != 0) {
143 				RADIUS_REQ_ASSERT("NOTREACHED" == NULL);
144 				goto fail;
145 			}
146 		}
147 		if (radius_put_ipv4_attr(pkt, RADIUS_TYPE_NAS_IP_ADDRESS,
148 		    sin4->sin_addr) != 0)
149 			goto fail;
150 		break;
151 	case AF_INET6:
152 		socklen = sizeof(*sin6);
153 		if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
154 			if (select_srcaddr((struct sockaddr const *)
155 			    &setting->server[setting->curr_server].peer,
156 			    (struct sockaddr *)sin4, &socklen) != 0) {
157 				RADIUS_REQ_ASSERT("NOTREACHED" == NULL);
158 				goto fail;
159 			}
160 		}
161 		if (radius_put_raw_attr(pkt, RADIUS_TYPE_NAS_IPV6_ADDRESS,
162 		    sin6->sin6_addr.s6_addr, sizeof(sin6->sin6_addr.s6_addr))
163 		    != 0)
164 			goto fail;
165 		break;
166 	}
167 
168 	return 0;
169 fail:
170 	return 1;
171 }
172 
173 
174 /** Checks whether the request can fail over to another server */
175 int
radius_request_can_failover(RADIUS_REQUEST_CTX ctx)176 radius_request_can_failover(RADIUS_REQUEST_CTX ctx)
177 {
178 	struct overlapped *lap;
179 	radius_req_setting *setting;
180 
181 	lap = ctx;
182 	setting = lap->setting;
183 
184 	if (lap->failovers >= setting->max_failovers)
185 		return 0;
186 	if (memcmp(&lap->ss, &setting->server[setting->curr_server].peer,
187 	    setting->server[setting->curr_server].peer.sin6.sin6_len) == 0)
188 		/* flagged server doesn't differ from the last server. */
189 		return 0;
190 
191 	return 1;
192 }
193 
194 /** Send RADIUS request failing over to another server. */
195 int
radius_request_failover(RADIUS_REQUEST_CTX ctx)196 radius_request_failover(RADIUS_REQUEST_CTX ctx)
197 {
198 	struct overlapped *lap;
199 
200 	lap = ctx;
201 	RADIUS_REQ_ASSERT(lap != NULL);
202 	RADIUS_REQ_ASSERT(lap->socket >= 0)
203 
204 	if (!radius_request_can_failover(lap))
205 		return -1;
206 
207 	if (radius_prepare_socket(lap) != 0)
208 		return -1;
209 
210 	if (radius_request0(lap) != 0)
211 		return -1;
212 
213 	lap->failovers++;
214 
215 	return 0;
216 }
217 
218 static int
radius_prepare_socket(struct overlapped * lap)219 radius_prepare_socket(struct overlapped *lap)
220 {
221 	int sock;
222 	radius_req_setting *setting;
223 	struct sockaddr *sa;
224 
225 	setting = lap->setting;
226 	if (lap->socket >= 0)
227 		close(lap->socket);
228 	lap->socket = -1;
229 
230 	sa = (struct sockaddr *)&setting->server[setting->curr_server].peer;
231 
232 	if ((sock = socket(sa->sa_family, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
233 		log_printf(LOG_ERR, "socket() failed in %s: %m", __func__);
234 		return -1;
235 	}
236 	if (connect(sock, sa, sa->sa_len) != 0) {
237 		log_printf(LOG_ERR, "connect() failed in %s: %m", __func__);
238 		close(sock);
239 		return -1;
240 	}
241 	memcpy(&lap->ss, sa, sa->sa_len);
242 	lap->socket = sock;
243 	memcpy(lap->secret, setting->server[setting->curr_server].secret,
244 	    sizeof(lap->secret));
245 	lap->ntry = lap->max_tries;
246 
247 	return 0;
248 }
249 
250 /**
251  * Prepare sending RADIUS request.  This implementation will call back to
252  * notice that it receives the response or it fails for timeouts to the
253  * The context that is set as 'pctx' and response packet that is given
254  * by the callback function will be released by this implementation internally.
255  * @param setting	Setting for RADIUS server or request.
256  * @param context	Context for the caller.
257  * @param pctx		Pointer to the space for context of RADIUS request
258  *			(RADIUS_REQUEST_CTX).  This will be used for canceling.
259  *			NULL can be specified when you don't need.
260  * @param response_fn	Specify callback function as a pointer. The function
261  *			will be called when it receives a response or when
262  *			request fails for timeouts.
263  * @param timeout	response timeout in second.
264  */
265 int
radius_prepare(radius_req_setting * setting,void * context,RADIUS_REQUEST_CTX * pctx,radius_response response_fn)266 radius_prepare(radius_req_setting *setting, void *context,
267     RADIUS_REQUEST_CTX *pctx, radius_response response_fn)
268 {
269 	struct overlapped *lap;
270 
271 	RADIUS_REQ_ASSERT(setting != NULL);
272 	lap = NULL;
273 
274 	if (setting->server[setting->curr_server].enabled == 0)
275 		return 1;
276 	if ((lap = calloc(1, sizeof(struct overlapped))) == NULL) {
277 		log_printf(LOG_ERR, "calloc() failed in %s: %m", __func__);
278 		goto fail;
279 	}
280 	lap->context = context;
281 	lap->response_fn = response_fn;
282 	lap->socket = -1;
283 	lap->setting = setting;
284 
285 	lap->max_tries = setting->max_tries;
286 	if (lap->max_tries <= 0)
287 		lap->max_tries = 3;	/* default max tries */
288 
289 	if (radius_prepare_socket(lap) != 0)
290 		goto fail;
291 
292 	if (pctx != NULL)
293 		*pctx = lap;
294 
295 	radius_req_setting_ref(setting);
296 
297 	return 0;
298 fail:
299 	free(lap);
300 
301 	return 1;
302 }
303 
304 /**
305  * Cancel the RADIUS request.
306  * @param	The context received by {@link radius_request()}
307  */
308 void
radius_cancel_request(RADIUS_REQUEST_CTX ctx)309 radius_cancel_request(RADIUS_REQUEST_CTX ctx)
310 {
311 	struct overlapped	*lap = ctx;
312 
313 	/*
314 	 * Don't call this function from the callback function.
315 	 * The context will be freed after the callback function is called.
316 	 */
317 	RADIUS_REQ_ASSERT(lap->response_fn_calling == 0);
318 	if (lap->response_fn_calling != 0)
319 		return;
320 
321 	if (lap->socket >= 0) {
322 		event_del(&lap->ev_sock);
323 		close(lap->socket);
324 		lap->socket = -1;
325 	}
326 	if (lap->pkt != NULL) {
327 		radius_delete_packet(lap->pkt);
328 		lap->pkt = NULL;
329 	}
330 	radius_req_setting_unref(lap->setting);
331 
332 	memset(lap->secret, 0x41, sizeof(lap->secret));
333 
334 	free(lap);
335 }
336 
337 /** Return the shared secret for RADIUS server that is used by this context.  */
338 const char *
radius_get_server_secret(RADIUS_REQUEST_CTX ctx)339 radius_get_server_secret(RADIUS_REQUEST_CTX ctx)
340 {
341 	struct overlapped *lap;
342 
343 	lap = ctx;
344 	RADIUS_REQ_ASSERT(lap != NULL);
345 
346 	return lap->secret;
347 }
348 
349 /** Return the address of RADIUS server that is used by this context.  */
350 struct sockaddr *
radius_get_server_address(RADIUS_REQUEST_CTX ctx)351 radius_get_server_address(RADIUS_REQUEST_CTX ctx)
352 {
353 	struct overlapped *lap;
354 
355 	lap = ctx;
356 	RADIUS_REQ_ASSERT(lap != NULL);
357 
358 	return (struct sockaddr *)&lap->ss;
359 }
360 
361 static int
radius_request0(struct overlapped * lap)362 radius_request0(struct overlapped *lap)
363 {
364 	struct timeval tv0;
365 
366 	RADIUS_REQ_ASSERT(lap->ntry > 0);
367 
368 	if (lap->acct_delay_time != 0) {
369 		struct timespec curr, delta;
370 
371 		if (clock_gettime(CLOCK_MONOTONIC, &curr) != 0) {
372 			log_printf(LOG_CRIT,
373 			    "clock_gettime(CLOCK_MONOTONIC,) failed: %m");
374 			RADIUS_REQ_ASSERT(0);
375 		}
376 		if (!timespecisset(&lap->req_time))
377 			lap->req_time = curr;
378 		else {
379 			timespecsub(&curr, &lap->req_time, &delta);
380 			if (radius_set_uint32_attr(lap->pkt,
381 			    RADIUS_TYPE_ACCT_DELAY_TIME, delta.tv_sec) == 0)
382 				radius_update_id(lap->pkt);
383 		}
384 	}
385 	if (radius_get_code(lap->pkt) == RADIUS_CODE_ACCOUNTING_REQUEST)
386 		radius_set_accounting_request_authenticator(lap->pkt,
387 		    radius_get_server_secret(lap));
388 	else
389 		radius_put_message_authenticator(lap->pkt,
390 		    radius_get_server_secret(lap));
391 
392 	lap->ntry--;
393 	if (radius_send(lap->socket, lap->pkt, 0) != 0) {
394 		log_printf(LOG_ERR, "sendto() failed in %s: %m",
395 		    __func__);
396 		radius_on_response(lap, NULL, RADIUS_REQUEST_ERROR, 1);
397 		return 1;
398 	}
399 	tv0.tv_usec = 0;
400 	tv0.tv_sec = lap->setting->timeout;
401 
402 	event_set(&lap->ev_sock, lap->socket, EV_READ | EV_PERSIST,
403 	    radius_request_io_event, lap);
404 	event_add(&lap->ev_sock, &tv0);
405 
406 	return 0;
407 }
408 
409 static void
radius_request_io_event(int fd,short evmask,void * context)410 radius_request_io_event(int fd, short evmask, void *context)
411 {
412 	struct overlapped *lap;
413 	struct sockaddr_storage ss;
414 	int flags;
415 	socklen_t len;
416 	RADIUS_PACKET *respkt;
417 
418 	RADIUS_REQ_ASSERT(context != NULL);
419 
420 	lap = context;
421 	respkt = NULL;
422 	flags = 0;
423 	if ((evmask & EV_READ) != 0) {
424 		RADIUS_REQ_ASSERT(lap->socket >= 0);
425 		if (lap->socket < 0)
426 			return;
427 		RADIUS_REQ_ASSERT(lap->pkt != NULL);
428 		memset(&ss, 0, sizeof(ss));
429 		len = sizeof(ss);
430 		if ((respkt = radius_recv(lap->socket, 0)) == NULL) {
431 			RADIUS_REQ_DBG((LOG_DEBUG,
432 			    "radius_recv() on %s(): %m", __func__));
433 			/*
434 			 * Ignore error by icmp.  Wait a response from the
435 			 * server anyway, it may eventually become ready.
436 			 */
437 			switch (errno) {
438 			case EHOSTDOWN: case EHOSTUNREACH: case ECONNREFUSED:
439 				return;	/* sleep the rest of timeout time */
440 			}
441 			flags |= RADIUS_REQUEST_ERROR;
442 		} else if (lap->secret[0] == '\0') {
443 			flags |= RADIUS_REQUEST_CHECK_AUTHENTICATOR_NO_CHECK
444 			    | RADIUS_REQUEST_CHECK_MSG_AUTHENTICATOR_NO_CHECK;
445 		} else {
446 			radius_set_request_packet(respkt, lap->pkt);
447 			if (!radius_check_response_authenticator(respkt,
448 			    lap->secret))
449 				flags |= RADIUS_REQUEST_CHECK_AUTHENTICATOR_OK;
450 			if (!radius_has_attr(respkt, RADIUS_TYPE_MESSAGE_AUTHENTICATOR))
451 				flags |= RADIUS_REQUEST_CHECK_NO_MSG_AUTHENTICATOR;
452 			else if (radius_check_message_authenticator(respkt, lap->secret) == 0)
453 				flags |= RADIUS_REQUEST_CHECK_MSG_AUTHENTICATOR_OK;
454 		}
455 		radius_on_response(lap, respkt, flags, 0);
456 		radius_delete_packet(respkt);
457 	} else if ((evmask & EV_TIMEOUT) != 0) {
458 		if (lap->ntry > 0) {
459 			RADIUS_REQ_DBG((LOG_DEBUG,
460 			    "%s() timed out retry", __func__));
461 			radius_request0(lap);
462 			return;
463 		}
464 		RADIUS_REQ_DBG((LOG_DEBUG, "%s() timed out", __func__));
465 		flags |= RADIUS_REQUEST_TIMEOUT;
466 		radius_on_response(lap, NULL, flags, 1);
467 	}
468 }
469 
470 static void
radius_on_response(RADIUS_REQUEST_CTX ctx,RADIUS_PACKET * pkt,int flags,int server_failure)471 radius_on_response(RADIUS_REQUEST_CTX ctx, RADIUS_PACKET *pkt, int flags,
472     int server_failure)
473 {
474 	struct overlapped *lap;
475 	int failovers;
476 
477 	lap = ctx;
478 	if (server_failure) {
479 		int i, n;
480 		struct sockaddr *sa_curr;
481 
482 		sa_curr = (struct sockaddr *)&lap->setting->server[
483 		    lap->setting->curr_server].peer;
484 		if (sa_curr->sa_len == lap->ss.ss_len &&
485 		    memcmp(sa_curr, &lap->ss, sa_curr->sa_len) == 0) {
486 			/*
487 			 * The server on failure is flagged as the current.
488 			 * change the current
489 			 */
490 			for (i = 1; i < nitems(lap->setting->server); i++) {
491 				n = (lap->setting->curr_server + i) %
492 				    nitems(lap->setting->server);
493 				if (lap->setting->server[n].enabled) {
494 					lap->setting->curr_server = n;
495 					break;
496 				}
497 			}
498 		}
499 	}
500 
501 	failovers = lap->failovers;
502 	if (lap->response_fn != NULL) {
503 		lap->response_fn_calling++;
504 		lap->response_fn(lap->context, pkt, flags, ctx);
505 		lap->response_fn_calling--;
506 	}
507 	if (failovers == lap->failovers)
508 		radius_cancel_request(lap);
509 }
510 
511 static int
select_srcaddr(struct sockaddr const * dst,struct sockaddr * src,socklen_t * srclen)512 select_srcaddr(struct sockaddr const *dst, struct sockaddr *src,
513     socklen_t *srclen)
514 {
515 	int sock;
516 
517 	sock = -1;
518 	if ((sock = socket(dst->sa_family, SOCK_DGRAM, IPPROTO_UDP)) < 0)
519 		goto fail;
520 	if (connect(sock, dst, dst->sa_len) != 0)
521 		goto fail;
522 	if (getsockname(sock, src, srclen) != 0)
523 		goto fail;
524 
525 	close(sock);
526 
527 	return 0;
528 fail:
529 	if (sock >= 0)
530 		close(sock);
531 
532 	return 1;
533 }
534 
535 radius_req_setting *
radius_req_setting_create(void)536 radius_req_setting_create(void)
537 {
538 	return calloc(1, sizeof(radius_req_setting));
539 }
540 
541 int
radius_req_setting_has_server(radius_req_setting * setting)542 radius_req_setting_has_server(radius_req_setting *setting)
543 {
544 	return setting->server[setting->curr_server].enabled;
545 }
546 
547 void
radius_req_setting_destroy(radius_req_setting * setting)548 radius_req_setting_destroy(radius_req_setting *setting)
549 {
550 	setting->destroyed = 1;
551 
552 	if (setting->refcnt == 0)
553 		free(setting);
554 }
555 
556 static void
radius_req_setting_ref(radius_req_setting * setting)557 radius_req_setting_ref(radius_req_setting *setting)
558 {
559 	setting->refcnt++;
560 }
561 
562 static void
radius_req_setting_unref(radius_req_setting * setting)563 radius_req_setting_unref(radius_req_setting *setting)
564 {
565 	setting->refcnt--;
566 	if (setting->destroyed)
567 		radius_req_setting_destroy(setting);
568 }
569