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