Verify received RADIUS DAE requests
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_dae.c
1 /*
2 * Copyright (C) 2012 Martin Willi
3 * Copyright (C) 2012 revosec AG
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 "eap_radius_dae.h"
17
18 #include "radius_message.h"
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <unistd.h>
24 #include <errno.h>
25
26 #include <daemon.h>
27 #include <threading/thread.h>
28 #include <processing/jobs/callback_job.h>
29
30 #define RADIUS_DAE_PORT 3799
31
32 typedef struct private_eap_radius_dae_t private_eap_radius_dae_t;
33
34 /**
35 * Private data of an eap_radius_dae_t object.
36 */
37 struct private_eap_radius_dae_t {
38
39 /**
40 * Public eap_radius_dae_t interface.
41 */
42 eap_radius_dae_t public;
43
44 /**
45 * RADIUS session state
46 */
47 eap_radius_accounting_t *accounting;
48
49 /**
50 * Socket to listen on authorization extension port
51 */
52 int fd;
53
54 /**
55 * Listen job
56 */
57 callback_job_t *job;
58
59 /**
60 * RADIUS shared secret for DAE exchanges
61 */
62 chunk_t secret;
63
64 /**
65 * MD5 hasher
66 */
67 hasher_t *hasher;
68
69 /**
70 * HMAC MD5 signer, with secret set
71 */
72 signer_t *signer;
73 };
74
75 /**
76 * Receive RADIUS DAE requests
77 */
78 static job_requeue_t receive(private_eap_radius_dae_t *this)
79 {
80 struct sockaddr_storage addr;
81 socklen_t addr_len = sizeof(addr);
82 radius_message_t *request;
83 char buf[2048];
84 ssize_t len;
85 bool oldstate;
86
87 oldstate = thread_cancelability(TRUE);
88 len = recvfrom(this->fd, buf, sizeof(buf), 0,
89 (struct sockaddr*)&addr, &addr_len);
90 thread_cancelability(oldstate);
91
92 if (len > 0)
93 {
94 request = radius_message_parse(chunk_create(buf, len));
95 if (request)
96 {
97 if (request->verify(request, NULL, this->secret,
98 this->hasher, this->signer))
99 {
100 switch (request->get_code(request))
101 {
102 case RMC_DISCONNECT_REQUEST:
103 /* TODO */
104 case RMC_COA_REQUEST:
105 /* TODO */
106 default:
107 DBG1(DBG_CFG, "ignoring unsupported RADIUS DAE %N "
108 "message", radius_message_code_names,
109 request->get_code(request));
110 break;
111 }
112 }
113 request->destroy(request);
114 }
115 else
116 {
117 DBG1(DBG_NET, "ignoring invalid RADIUS DAE request");
118 }
119 }
120 else
121 {
122 DBG1(DBG_NET, "receving RADIUS DAE request failed: %s", strerror(errno));
123 }
124 return JOB_REQUEUE_DIRECT;
125 }
126
127 /**
128 * Open DAE socket
129 */
130 static bool open_socket(private_eap_radius_dae_t *this)
131 {
132 host_t *host;
133
134 this->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
135 if (this->fd == -1)
136 {
137 DBG1(DBG_CFG, "unable to open RADIUS DAE socket: %s", strerror(errno));
138 return FALSE;
139 }
140
141 host = host_create_from_string(
142 lib->settings->get_str(lib->settings,
143 "charon.plugins.eap-radius.dae.listen", "0.0.0.0"),
144 lib->settings->get_int(lib->settings,
145 "charon.plugins.eap-radius.dae.port", RADIUS_DAE_PORT));
146 if (!host)
147 {
148 DBG1(DBG_CFG, "invalid RADIUS DAE listen address");
149 return FALSE;
150 }
151
152 if (bind(this->fd, host->get_sockaddr(host),
153 *host->get_sockaddr_len(host)) == -1)
154 {
155 DBG1(DBG_CFG, "unable to bind RADIUS DAE socket: %s", strerror(errno));
156 host->destroy(host);
157 return FALSE;
158 }
159 host->destroy(host);
160 return TRUE;
161 }
162
163 METHOD(eap_radius_dae_t, destroy, void,
164 private_eap_radius_dae_t *this)
165 {
166 if (this->job)
167 {
168 this->job->cancel(this->job);
169 }
170 if (this->fd != -1)
171 {
172 close(this->fd);
173 }
174 DESTROY_IF(this->signer);
175 DESTROY_IF(this->hasher);
176 free(this);
177 }
178
179 /**
180 * See header
181 */
182 eap_radius_dae_t *eap_radius_dae_create(eap_radius_accounting_t *accounting)
183 {
184 private_eap_radius_dae_t *this;
185
186 INIT(this,
187 .public = {
188 .destroy = _destroy,
189 },
190 .accounting = accounting,
191 .fd = -1,
192 .secret = {
193 .ptr = lib->settings->get_str(lib->settings,
194 "charon.plugins.eap-radius.dae.secret", NULL),
195 },
196 .hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
197 .signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
198 );
199
200 if (!this->hasher || !this->signer)
201 {
202 destroy(this);
203 return NULL;
204 }
205 if (!this->secret.ptr)
206 {
207 DBG1(DBG_CFG, "missing RADIUS DAE secret, disabled");
208 destroy(this);
209 return NULL;
210 }
211 this->secret.len = strlen(this->secret.ptr);
212 this->signer->set_key(this->signer, this->secret);
213
214 if (!open_socket(this))
215 {
216 destroy(this);
217 return NULL;
218 }
219
220 this->job = callback_job_create_with_prio((callback_job_cb_t)receive,
221 this, NULL, NULL, JOB_PRIO_CRITICAL);
222 lib->processor->queue_job(lib->processor, (job_t*)this->job);
223
224 return &this->public;
225 }