created libradius shared by eap-radius and tnc-pdp plugins
[strongswan.git] / src / libcharon / plugins / tnc_pdp / tnc_pdp.c
1 /*
2 * Copyright (C) 2010 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "tnc_pdp.h"
17
18 #include <errno.h>
19 #include <unistd.h>
20
21 #include <radius_message.h>
22
23 #include <daemon.h>
24 #include <debug.h>
25 #include <threading/thread.h>
26 #include <processing/jobs/callback_job.h>
27
28 typedef struct private_tnc_pdp_t private_tnc_pdp_t;
29
30 /**
31 * Maximum size of a RADIUS IP packet
32 */
33 #define MAX_PACKET 4096
34
35 /**
36 * private data of tnc_pdp_t
37 */
38 struct private_tnc_pdp_t {
39
40 /**
41 * implements tnc_pdp_t interface
42 */
43 tnc_pdp_t public;
44
45 /**
46 * IPv4 RADIUS socket
47 */
48 int ipv4;
49
50 /**
51 * IPv6 RADIUS socket
52 */
53 int ipv6;
54
55 /**
56 * Callback job dispatching commands
57 */
58 callback_job_t *job;
59
60 };
61
62
63 /**
64 * Open IPv4 or IPv6 UDP RADIUS socket
65 */
66 static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
67 {
68 int on = TRUE;
69 struct sockaddr_storage addr;
70 socklen_t addrlen;
71 int skt;
72
73 memset(&addr, 0, sizeof(addr));
74 addr.ss_family = family;
75
76 /* precalculate constants depending on address family */
77 switch (family)
78 {
79 case AF_INET:
80 {
81 struct sockaddr_in *sin = (struct sockaddr_in *)&addr;
82
83 htoun32(&sin->sin_addr.s_addr, INADDR_ANY);
84 htoun16(&sin->sin_port, port);
85 addrlen = sizeof(struct sockaddr_in);
86 break;
87 }
88 case AF_INET6:
89 {
90 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr;
91
92 memcpy(&sin6->sin6_addr, &in6addr_any, sizeof(in6addr_any));
93 htoun16(&sin6->sin6_port, port);
94 addrlen = sizeof(struct sockaddr_in6);
95 break;
96 }
97 default:
98 return 0;
99 }
100
101 /* open the socket */
102 skt = socket(family, SOCK_DGRAM, IPPROTO_UDP);
103 if (skt < 0)
104 {
105 DBG1(DBG_NET, "opening RADIUS socket failed: %s", strerror(errno));
106 return 0;
107 }
108 if (setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
109 {
110 DBG1(DBG_NET, "unable to set SO_REUSEADDR on socket: %s", strerror(errno));
111 close(skt);
112 return 0;
113 }
114
115 /* bind the socket */
116 if (bind(skt, (struct sockaddr *)&addr, addrlen) < 0)
117 {
118 DBG1(DBG_NET, "unable to bind RADIUS socket: %s", strerror(errno));
119 close(skt);
120 return 0;
121 }
122
123 return skt;
124 }
125
126 /**
127 * Process packets received on the RADIUS socket
128 */
129 static job_requeue_t receive(private_tnc_pdp_t *this)
130 {
131 while (TRUE)
132 {
133 radius_message_t *request;
134 char buffer[MAX_PACKET];
135 int max_fd = 0, selected = 0, bytes_read = 0;
136 fd_set rfds;
137 bool oldstate;
138 host_t *source;
139 struct msghdr msg;
140 struct iovec iov;
141 union {
142 struct sockaddr_in in4;
143 struct sockaddr_in6 in6;
144 } src;
145
146 FD_ZERO(&rfds);
147
148 if (this->ipv4)
149 {
150 FD_SET(this->ipv4, &rfds);
151 }
152 if (this->ipv6)
153 {
154 FD_SET(this->ipv6, &rfds);
155 }
156 max_fd = max(this->ipv4, this->ipv6);
157
158 DBG2(DBG_NET, "waiting for data on RADIUS sockets");
159 oldstate = thread_cancelability(TRUE);
160 if (select(max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
161 {
162 thread_cancelability(oldstate);
163 continue;
164 }
165 thread_cancelability(oldstate);
166
167 if (FD_ISSET(this->ipv4, &rfds))
168 {
169 selected = this->ipv4;
170 }
171 else if (FD_ISSET(this->ipv6, &rfds))
172 {
173 selected = this->ipv6;
174 }
175 else
176 {
177 /* oops, shouldn't happen */
178 continue;
179 }
180
181 /* read received packet */
182 msg.msg_name = &src;
183 msg.msg_namelen = sizeof(src);
184 iov.iov_base = buffer;
185 iov.iov_len = MAX_PACKET;
186 msg.msg_iov = &iov;
187 msg.msg_iovlen = 1;
188 msg.msg_flags = 0;
189
190 bytes_read = recvmsg(selected, &msg, 0);
191 if (bytes_read < 0)
192 {
193 DBG1(DBG_NET, "error reading RADIUS socket: %s", strerror(errno));
194 continue;
195 }
196 if (msg.msg_flags & MSG_TRUNC)
197 {
198 DBG1(DBG_NET, "receive buffer too small, RADIUS packet discarded");
199 continue;
200 }
201 source = host_create_from_sockaddr((sockaddr_t*)&src);
202 DBG2(DBG_NET, "received RADIUS packet from %#H", source);
203 DBG3(DBG_NET, "%b", buffer, bytes_read);
204 request = radius_message_parse_response(chunk_create(buffer, bytes_read));
205 if (request)
206 {
207 DBG2(DBG_NET, "received valid RADIUS message");
208 request->destroy(request);
209 }
210 else
211 {
212 DBG1(DBG_NET, "received invalid RADIUS message, ignored");
213 }
214 source->destroy(source);
215 }
216 return JOB_REQUEUE_FAIR;
217 }
218
219 METHOD(tnc_pdp_t, destroy, void,
220 private_tnc_pdp_t *this)
221 {
222 this->job->cancel(this->job);
223 if (this->ipv4)
224 {
225 close(this->ipv4);
226 }
227 if (this->ipv6)
228 {
229 close(this->ipv6);
230 }
231 free(this);
232 }
233
234 /*
235 * see header file
236 */
237 tnc_pdp_t *tnc_pdp_create(u_int16_t port)
238 {
239 private_tnc_pdp_t *this;
240
241 INIT(this,
242 .public = {
243 .destroy = _destroy,
244 },
245 .ipv4 = open_socket(this, AF_INET, port),
246 .ipv6 = open_socket(this, AF_INET6, port),
247 );
248
249 if (!this->ipv4 && !this->ipv6)
250 {
251 DBG1(DBG_NET, "couldd not create any RADIUS sockets");
252 destroy(this);
253 return NULL;
254 }
255 if (!this->ipv4)
256 {
257 DBG1(DBG_NET, "could not open IPv4 RADIUS socket, IPv4 disabled");
258 }
259 if (!this->ipv6)
260 {
261 DBG1(DBG_NET, "could not open IPv6 RADIUS socket, IPv6 disabled");
262 }
263
264 this->job = callback_job_create_with_prio((callback_job_cb_t)receive,
265 this, NULL, NULL, JOB_PRIO_CRITICAL);
266 lib->processor->queue_job(lib->processor, (job_t*)this->job);
267
268 return &this->public;
269 }
270