xref: /openbsd/usr.sbin/npppd/npppd/radius_req.c (revision 891d7ab6)
1 /* $OpenBSD: radius_req.c,v 1.3 2010/07/02 21:20:57 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+.c and event(3).
30  * @author	Yasuoka Masahiko
31  * $Id: radius_req.c,v 1.3 2010/07/02 21:20:57 yasuoka Exp $
32  */
33 #include <sys/types.h>
34 #include <sys/param.h>
35 #include <sys/time.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 #include <unistd.h>
39 #include <stdio.h>
40 #include <syslog.h>
41 #include <stdlib.h>
42 #include <radius+.h>
43 #include <radiusconst.h>
44 #include <debugutil.h>
45 #include <time.h>
46 #include <event.h>
47 #include <string.h>
48 
49 #include "radius_req.h"
50 
51 struct overlapped {
52 	struct event ev_sock;
53 	int socket;
54 	int ntry;
55 	int timeout;
56 	struct sockaddr_storage ss;
57 	void *context;
58 	radius_response *response_fn;
59 	char secret[MAX_RADIUS_SECRET];
60 	RADIUS_PACKET *pkt;
61 };
62 
63 static int   radius_request0 (struct overlapped *);
64 static void  radius_request_io_event (int, short, void *);
65 static int select_srcaddr(struct sockaddr const *, struct sockaddr *, socklen_t *);
66 
67 #ifdef	RADIUS_REQ_DEBUG
68 #define	RADIUS_REQ_ASSERT(cond)					\
69 	if (!(cond)) {						\
70 	    fprintf(stderr,					\
71 		"\nASSERT(" #cond ") failed on %s() at %s:%d.\n"\
72 		, __func__, __FILE__, __LINE__);		\
73 	    abort(); 						\
74 	}
75 #else
76 #define	RADIUS_REQ_ASSERT(cond)
77 #endif
78 
79 /**
80  * Send RADIUS request message.  The pkt(RADIUS packet) will be released
81  * by this implementation.
82  */
83 void
84 radius_request(RADIUS_REQUEST_CTX ctx, RADIUS_PACKET *pkt)
85 {
86 	struct overlapped *lap;
87 
88 	RADIUS_REQ_ASSERT(pkt != NULL);
89 	RADIUS_REQ_ASSERT(ctx != NULL);
90 	lap = ctx;
91 	lap->pkt = pkt;
92 	if (radius_request0(lap) != 0) {
93 		if (lap->response_fn != NULL)
94 			lap->response_fn(lap->context, NULL,
95 			    RADIUS_REQUST_ERROR);
96 	}
97 }
98 
99 /**
100  * Prepare NAS-IP-Address or NAS-IPv6-Address.  If
101  * setting->server[setting->curr_server].sock is not initialized, address
102  * will be selected automatically.
103  */
104 int
105 radius_prepare_nas_address(radius_req_setting *setting,
106     RADIUS_PACKET *pkt)
107 {
108 	int af;
109 	struct sockaddr_in *sin4;
110 	struct sockaddr_in6 *sin6;
111 	socklen_t socklen;
112 
113 	/* See RFC 2765, 3162 */
114 	RADIUS_REQ_ASSERT(setting != NULL);
115 
116 	af = setting->server[setting->curr_server].peer.sin6.sin6_family;
117 	RADIUS_REQ_ASSERT(af == AF_INET6 || af == AF_INET);
118 
119 	sin4 = &setting->server[setting->curr_server].sock.sin4;
120 	sin6 = &setting->server[setting->curr_server].sock.sin6;
121 
122 	switch (af) {
123 	case AF_INET:
124 		socklen = sizeof(*sin4);
125 		if (sin4->sin_addr.s_addr == INADDR_ANY) {
126 			if (select_srcaddr((struct sockaddr const *)
127 			    &setting->server[setting->curr_server].peer,
128 			    (struct sockaddr *)sin4, &socklen) != 0) {
129 				RADIUS_REQ_ASSERT("NOTREACHED" == NULL);
130 				goto fail;
131 			}
132 		}
133 		if (radius_put_ipv4_attr(pkt, RADIUS_TYPE_NAS_IP_ADDRESS,
134 		    sin4->sin_addr) != 0)
135 			goto fail;
136 		break;
137 	case AF_INET6:
138 		socklen = sizeof(*sin6);
139 		if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
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_raw_attr(pkt, RADIUS_TYPE_NAS_IPV6_ADDRESS,
148 		    sin6->sin6_addr.s6_addr, sizeof(sin6->sin6_addr.s6_addr))
149 		    != 0)
150 			goto fail;
151 		break;
152 	}
153 
154 	return 0;
155 fail:
156 	return 1;
157 }
158 
159 /**
160  * Prepare sending RADIUS request.  This implementation will call back to
161  * notice that it receives the response or it fails for timeouts to the
162  * The context that is set as 'pctx' and response packet that is given
163  * by the callback function will be released by this implementation internally.
164  * @param setting	Setting for RADIUS server or request.
165  * @param context	Context for the caller.
166  * @param pctx		Pointer to the space for context of RADIUS request
167  *			(RADIUS_REQUEST_CTX).  This will be used for canceling.
168  *			NULL can be specified when you don't need.
169  * @param response_fn	Specify callback function as a pointer. The function
170  *			will be called when it receives a response or when
171  *			request fails for timeouts.
172  * @param timeout	response timeout in second.
173  */
174 int
175 radius_prepare(radius_req_setting *setting, void *context,
176     RADIUS_REQUEST_CTX *pctx, radius_response response_fn, int timeout)
177 {
178 	int sock;
179 	struct overlapped *lap;
180 	struct sockaddr_in6 *sin6;
181 
182 	RADIUS_REQ_ASSERT(setting != NULL);
183 	lap = NULL;
184 
185 	if (setting->server[setting->curr_server].enabled == 0)
186 		return 1;
187 	if ((lap = malloc(sizeof(struct overlapped))) == NULL) {
188 		log_printf(LOG_ERR, "malloc() failed in %s: %m", __func__);
189 		goto fail;
190 	}
191 	sin6 = &setting->server[setting->curr_server].peer.sin6;
192 	if ((sock = socket(sin6->sin6_family, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
193 		log_printf(LOG_ERR, "socket() failed in %s: %m", __func__);
194 		goto fail;
195 	}
196 	memset(lap, 0, sizeof(struct overlapped));
197 	memcpy(&lap->ss, &setting->server[setting->curr_server].peer,
198 	    setting->server[setting->curr_server].peer.sin6.sin6_len);
199 
200 	lap->socket = sock;
201 	lap->timeout = MIN(setting->timeout, timeout);
202 	lap->ntry = timeout / lap->timeout;
203 	lap->context = context;
204 	lap->response_fn = response_fn;
205 	memcpy(lap->secret, setting->server[setting->curr_server].secret,
206 	    sizeof(lap->secret));
207 	if (pctx != NULL)
208 		*pctx = lap;
209 
210 	return 0;
211 fail:
212 	if (lap != NULL)
213 		free(lap);
214 
215 	return 1;
216 }
217 
218 /**
219  * Cancel the RADIUS request.
220  * @param	The context received by {@link radius_request()}
221  */
222 void
223 radius_cancel_request(RADIUS_REQUEST_CTX ctx)
224 {
225 	struct overlapped *lap;
226 
227 	lap = ctx;
228 	if (lap->socket >= 0) {
229 		event_del(&lap->ev_sock);
230 		close(lap->socket);
231 		lap->socket = -1;
232 	}
233 	if (lap->pkt != NULL) {
234 		radius_delete_packet(lap->pkt);
235 		lap->pkt = NULL;
236 	}
237 	memset(lap->secret, 0x41, sizeof(lap->secret));
238 
239 	free(lap);
240 }
241 
242 /** Return the shared secret for RADIUS server that is used by this context.  */
243 const char *
244 radius_get_server_secret(RADIUS_REQUEST_CTX ctx)
245 {
246 	struct overlapped *lap;
247 
248 	lap = ctx;
249 	RADIUS_REQ_ASSERT(lap != NULL);
250 
251 	return lap->secret;
252 }
253 
254 /** Return the address of RADIUS server that is used by this context.  */
255 struct sockaddr *
256 radius_get_server_address(RADIUS_REQUEST_CTX ctx)
257 {
258 	struct overlapped *lap;
259 
260 	lap = ctx;
261 	RADIUS_REQ_ASSERT(lap != NULL);
262 
263 	return (struct sockaddr *)&lap->ss;
264 }
265 
266 static int
267 radius_request0(struct overlapped *lap)
268 {
269 	struct timeval tv0;
270 
271 	RADIUS_REQ_ASSERT(lap->ntry > 0);
272 
273 	lap->ntry--;
274 	if (radius_sendto(lap->socket, lap->pkt, 0, (struct sockaddr *)
275 	    &lap->ss, lap->ss.ss_len) != 0)
276 		return 1;
277 	tv0.tv_usec = 0;
278 	tv0.tv_sec = lap->timeout;
279 
280 	event_set(&lap->ev_sock, lap->socket, EV_READ,
281 	    radius_request_io_event, lap);
282 	event_add(&lap->ev_sock, &tv0);
283 
284 	return 0;
285 }
286 
287 static void
288 radius_request_io_event(int fd, short evmask, void *context)
289 {
290 	struct overlapped *lap;
291 	struct sockaddr_storage ss;
292 	int flags;
293 	socklen_t len;
294 	RADIUS_PACKET *respkt;
295 
296 	RADIUS_REQ_ASSERT(context != NULL);
297 
298 	if ((evmask & EV_READ) != 0) {
299 		lap = context;
300 		flags = 0;
301 
302 		RADIUS_REQ_ASSERT(lap->socket >= 0);
303 		if (lap->socket < 0)
304 			return;
305 		RADIUS_REQ_ASSERT(lap->pkt != NULL);
306 
307 		memset(&ss, 0, sizeof(ss));
308 		len = sizeof(ss);
309 		if ((respkt = radius_recvfrom(lap->socket, 0,
310 		    (struct sockaddr *)&ss, &len)) == NULL) {
311 			log_printf(LOG_ERR, "recvfrom() failed in %s: %m",
312 			    __func__);
313 			flags |= RADIUS_REQUST_ERROR;
314 		} else if (lap->secret[0] == '\0') {
315 			flags |= RADIUS_REQUST_CHECK_AUTHENTICTOR_NO_CHECK;
316 		} else {
317 			radius_set_request_packet(respkt, lap->pkt);
318 			if (!radius_check_response_authenticator(respkt,
319 			    lap->secret))
320 				flags |= RADIUS_REQUST_CHECK_AUTHENTICTOR_OK;
321 		}
322 
323 		if (lap->response_fn != NULL)
324 			lap->response_fn(lap->context, respkt, flags);
325 
326 		if (respkt != NULL)
327 			radius_delete_packet(respkt);
328 		radius_cancel_request(lap);
329 	} else if ((evmask & EV_TIMEOUT) != 0) {
330 		lap = context;
331 		if (lap->ntry > 0) {
332 			if (radius_request0(lap) != 0) {
333 				if (lap->response_fn != NULL)
334 					lap->response_fn(lap->context, NULL,
335 					    RADIUS_REQUST_ERROR);
336 				radius_cancel_request(lap);
337 			}
338 			return;
339 		}
340 		if (lap->response_fn != NULL)
341 			lap->response_fn(lap->context, NULL,
342 			    RADIUS_REQUST_TIMEOUT);
343 		radius_cancel_request(lap);
344 	}
345 }
346 
347 static int
348 select_srcaddr(struct sockaddr const *dst, struct sockaddr *src,
349     socklen_t *srclen)
350 {
351 	int sock;
352 
353 	sock = -1;
354 	if ((sock = socket(dst->sa_family, SOCK_DGRAM, IPPROTO_UDP)) < 0)
355 		goto fail;
356 	if (connect(sock, dst, dst->sa_len) != 0)
357 		goto fail;
358 	if (getsockname(sock, src, srclen) != 0)
359 		goto fail;
360 
361 	close(sock);
362 
363 	return 0;
364 fail:
365 	if (sock >= 0)
366 		close(sock);
367 
368 	return 1;
369 }
370