]>
Commit | Line | Data |
---|---|---|
c434b2a4 MW |
1 | /* |
2 | * Copyright (C) 2013 Martin Willi | |
19ef2aec TB |
3 | * |
4 | * Copyright (C) secunet Security Networks AG | |
c434b2a4 MW |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
14 | * for more details. | |
15 | */ | |
16 | ||
17 | #include "eap_radius_xauth.h" | |
18 | #include "eap_radius_plugin.h" | |
19 | #include "eap_radius.h" | |
3a399574 | 20 | #include "eap_radius_forward.h" |
c434b2a4 MW |
21 | |
22 | #include <daemon.h> | |
23 | #include <radius_client.h> | |
d787ada8 | 24 | #include <collections/array.h> |
c434b2a4 MW |
25 | |
26 | ||
27 | typedef struct private_eap_radius_xauth_t private_eap_radius_xauth_t; | |
d787ada8 MW |
28 | typedef struct xauth_round_t xauth_round_t; |
29 | ||
30 | /** | |
31 | * Configuration for an XAuth authentication exchange | |
32 | */ | |
33 | struct xauth_round_t { | |
34 | /** XAuth message type to send */ | |
35 | configuration_attribute_type_t type; | |
36 | /** Message to present to user */ | |
37 | char *message; | |
38 | }; | |
c434b2a4 MW |
39 | |
40 | /** | |
41 | * Private data of an eap_radius_xauth_t object. | |
42 | */ | |
43 | struct private_eap_radius_xauth_t { | |
44 | ||
45 | /** | |
46 | * Public interface. | |
47 | */ | |
48 | eap_radius_xauth_t public; | |
49 | ||
50 | /** | |
51 | * ID of the server | |
52 | */ | |
53 | identification_t *server; | |
54 | ||
55 | /** | |
56 | * ID of the peer | |
57 | */ | |
58 | identification_t *peer; | |
59 | ||
60 | /** | |
61 | * RADIUS connection | |
62 | */ | |
63 | radius_client_t *client; | |
d787ada8 MW |
64 | |
65 | /** | |
66 | * XAuth authentication rounds, as xauth_round_t | |
67 | */ | |
68 | array_t *rounds; | |
69 | ||
70 | /** | |
71 | * XAuth round currently in progress | |
72 | */ | |
73 | xauth_round_t round; | |
74 | ||
75 | /** | |
2db6d5b8 | 76 | * Concatenated password of all rounds |
d787ada8 MW |
77 | */ |
78 | chunk_t pass; | |
c434b2a4 MW |
79 | }; |
80 | ||
d787ada8 MW |
81 | /** |
82 | * Fetch next XAuth round, add attributes to CP payload | |
83 | */ | |
84 | static bool build_round(private_eap_radius_xauth_t *this, cp_payload_t *cp) | |
85 | { | |
86 | if (!array_remove(this->rounds, ARRAY_HEAD, &this->round)) | |
87 | { | |
88 | return FALSE; | |
89 | } | |
90 | cp->add_attribute(cp, configuration_attribute_create_chunk( | |
3ecfc83c | 91 | PLV1_CONFIGURATION_ATTRIBUTE, this->round.type, chunk_empty)); |
d787ada8 MW |
92 | |
93 | if (this->round.message && strlen(this->round.message)) | |
94 | { | |
95 | cp->add_attribute(cp, configuration_attribute_create_chunk( | |
3ecfc83c | 96 | PLV1_CONFIGURATION_ATTRIBUTE, XAUTH_MESSAGE, |
d787ada8 MW |
97 | chunk_from_str(this->round.message))); |
98 | } | |
99 | return TRUE; | |
100 | } | |
101 | ||
c434b2a4 MW |
102 | METHOD(xauth_method_t, initiate, status_t, |
103 | private_eap_radius_xauth_t *this, cp_payload_t **out) | |
104 | { | |
105 | cp_payload_t *cp; | |
106 | ||
3ecfc83c | 107 | cp = cp_payload_create_type(PLV1_CONFIGURATION, CFG_REQUEST); |
d787ada8 | 108 | /* first message always comes with username */ |
c434b2a4 | 109 | cp->add_attribute(cp, configuration_attribute_create_chunk( |
3ecfc83c | 110 | PLV1_CONFIGURATION_ATTRIBUTE, XAUTH_USER_NAME, chunk_empty)); |
d787ada8 MW |
111 | |
112 | if (build_round(this, cp)) | |
113 | { | |
114 | *out = cp; | |
115 | return NEED_MORE; | |
116 | } | |
117 | cp->destroy(cp); | |
118 | return FAILED; | |
c434b2a4 MW |
119 | } |
120 | ||
121 | /** | |
122 | * Verify a password using RADIUS User-Name/User-Password attributes | |
123 | */ | |
d787ada8 | 124 | static status_t verify_radius(private_eap_radius_xauth_t *this) |
c434b2a4 MW |
125 | { |
126 | radius_message_t *request, *response; | |
127 | status_t status = FAILED; | |
128 | ||
129 | request = radius_message_create(RMC_ACCESS_REQUEST); | |
130 | request->add(request, RAT_USER_NAME, this->peer->get_encoding(this->peer)); | |
d787ada8 | 131 | request->add(request, RAT_USER_PASSWORD, this->pass); |
c434b2a4 MW |
132 | |
133 | eap_radius_build_attributes(request); | |
3a399574 | 134 | eap_radius_forward_from_ike(request); |
c434b2a4 MW |
135 | |
136 | response = this->client->request(this->client, request); | |
137 | if (response) | |
138 | { | |
3a399574 | 139 | eap_radius_forward_to_ike(response); |
c434b2a4 MW |
140 | switch (response->get_code(response)) |
141 | { | |
142 | case RMC_ACCESS_ACCEPT: | |
143 | eap_radius_process_attributes(response); | |
144 | status = SUCCESS; | |
145 | break; | |
146 | case RMC_ACCESS_CHALLENGE: | |
147 | DBG1(DBG_IKE, "RADIUS Access-Challenge not supported"); | |
148 | /* FALL */ | |
149 | case RMC_ACCESS_REJECT: | |
150 | default: | |
151 | DBG1(DBG_IKE, "RADIUS authentication of '%Y' failed", | |
152 | this->peer); | |
153 | break; | |
154 | } | |
155 | response->destroy(response); | |
156 | } | |
157 | else | |
158 | { | |
159 | eap_radius_handle_timeout(NULL); | |
160 | } | |
161 | request->destroy(request); | |
162 | return status; | |
163 | } | |
164 | ||
165 | METHOD(xauth_method_t, process, status_t, | |
166 | private_eap_radius_xauth_t *this, cp_payload_t *in, cp_payload_t **out) | |
167 | { | |
168 | configuration_attribute_t *attr; | |
169 | enumerator_t *enumerator; | |
170 | identification_t *id; | |
d787ada8 | 171 | cp_payload_t *cp; |
c434b2a4 MW |
172 | chunk_t user = chunk_empty, pass = chunk_empty; |
173 | ||
174 | enumerator = in->create_attribute_enumerator(in); | |
175 | while (enumerator->enumerate(enumerator, &attr)) | |
176 | { | |
d787ada8 | 177 | if (attr->get_type(attr) == XAUTH_USER_NAME) |
c434b2a4 | 178 | { |
d787ada8 MW |
179 | user = attr->get_chunk(attr); |
180 | } | |
181 | else if (attr->get_type(attr) == this->round.type) | |
182 | { | |
183 | pass = attr->get_chunk(attr); | |
184 | /* trim password to any null termination. As User-Password | |
185 | * uses null padding, we can't have any null in it, and some | |
186 | * clients actually send null terminated strings (Android). */ | |
187 | pass.len = strnlen(pass.ptr, pass.len); | |
c434b2a4 MW |
188 | } |
189 | } | |
190 | enumerator->destroy(enumerator); | |
191 | ||
d787ada8 | 192 | if (!pass.ptr) |
c434b2a4 | 193 | { |
d787ada8 MW |
194 | DBG1(DBG_IKE, "peer did not respond to our XAuth %N request", |
195 | configuration_attribute_type_names, this->round.type); | |
c434b2a4 MW |
196 | return FAILED; |
197 | } | |
d787ada8 | 198 | this->pass = chunk_cat("mc", this->pass, pass); |
c434b2a4 MW |
199 | if (user.len) |
200 | { | |
201 | id = identification_create_from_data(user); | |
202 | if (!id) | |
203 | { | |
204 | DBG1(DBG_IKE, "failed to parse provided XAuth username"); | |
205 | return FAILED; | |
206 | } | |
207 | this->peer->destroy(this->peer); | |
208 | this->peer = id; | |
209 | } | |
d787ada8 MW |
210 | |
211 | if (array_count(this->rounds) == 0) | |
212 | { | |
213 | return verify_radius(this); | |
214 | } | |
3ecfc83c | 215 | cp = cp_payload_create_type(PLV1_CONFIGURATION, CFG_REQUEST); |
d787ada8 MW |
216 | if (build_round(this, cp)) |
217 | { | |
218 | *out = cp; | |
219 | return NEED_MORE; | |
220 | } | |
221 | cp->destroy(cp); | |
222 | return FAILED; | |
c434b2a4 MW |
223 | } |
224 | ||
225 | METHOD(xauth_method_t, get_identity, identification_t*, | |
226 | private_eap_radius_xauth_t *this) | |
227 | { | |
228 | return this->peer; | |
229 | } | |
230 | ||
d787ada8 MW |
231 | /** |
232 | * Parse XAuth round configuration | |
233 | */ | |
234 | static bool parse_rounds(private_eap_radius_xauth_t *this, char *profile) | |
235 | { | |
236 | struct { | |
237 | char *str; | |
238 | configuration_attribute_type_t type; | |
239 | } map[] = { | |
240 | { "password", XAUTH_USER_PASSWORD, }, | |
241 | { "passcode", XAUTH_PASSCODE, }, | |
242 | { "nextpin", XAUTH_NEXT_PIN, }, | |
243 | { "answer", XAUTH_ANSWER, }, | |
244 | }; | |
245 | enumerator_t *enumerator; | |
246 | char *type, *message; | |
247 | xauth_round_t round; | |
248 | int i; | |
249 | ||
250 | if (!profile || strlen(profile) == 0) | |
251 | { | |
252 | /* no config, fallback to password */ | |
253 | round.type = XAUTH_USER_PASSWORD; | |
254 | round.message = NULL; | |
255 | array_insert(this->rounds, ARRAY_TAIL, &round); | |
256 | return TRUE; | |
257 | } | |
258 | ||
259 | enumerator = lib->settings->create_key_value_enumerator(lib->settings, | |
d223fe80 | 260 | "%s.plugins.eap-radius.xauth.%s", lib->ns, profile); |
d787ada8 MW |
261 | while (enumerator->enumerate(enumerator, &type, &message)) |
262 | { | |
263 | bool invalid = TRUE; | |
264 | ||
265 | for (i = 0; i < countof(map); i++) | |
266 | { | |
267 | if (strcaseeq(map[i].str, type)) | |
268 | { | |
269 | round.type = map[i].type; | |
270 | round.message = message; | |
271 | array_insert(this->rounds, ARRAY_TAIL, &round); | |
272 | invalid = FALSE; | |
273 | break; | |
274 | } | |
275 | } | |
276 | if (invalid) | |
277 | { | |
278 | DBG1(DBG_CFG, "invalid XAuth round type: '%s'", type); | |
279 | enumerator->destroy(enumerator); | |
280 | return FALSE; | |
281 | } | |
282 | } | |
283 | enumerator->destroy(enumerator); | |
284 | ||
285 | if (array_count(this->rounds) == 0) | |
286 | { | |
287 | DBG1(DBG_CFG, "XAuth configuration profile '%s' invalid", profile); | |
288 | return FALSE; | |
289 | } | |
290 | return TRUE; | |
291 | } | |
292 | ||
c434b2a4 MW |
293 | METHOD(xauth_method_t, destroy, void, |
294 | private_eap_radius_xauth_t *this) | |
295 | { | |
296 | DESTROY_IF(this->client); | |
d787ada8 MW |
297 | chunk_clear(&this->pass); |
298 | array_destroy(this->rounds); | |
c434b2a4 MW |
299 | this->server->destroy(this->server); |
300 | this->peer->destroy(this->peer); | |
301 | free(this); | |
302 | } | |
303 | ||
304 | /* | |
305 | * Described in header. | |
306 | */ | |
307 | eap_radius_xauth_t *eap_radius_xauth_create_server(identification_t *server, | |
510ecf61 MW |
308 | identification_t *peer, |
309 | char *profile) | |
c434b2a4 MW |
310 | { |
311 | private_eap_radius_xauth_t *this; | |
312 | ||
313 | INIT(this, | |
314 | .public = { | |
315 | .xauth_method = { | |
316 | .initiate = _initiate, | |
317 | .process = _process, | |
318 | .get_identity = _get_identity, | |
319 | .destroy = _destroy, | |
320 | }, | |
321 | }, | |
322 | .server = server->clone(server), | |
323 | .peer = peer->clone(peer), | |
324 | .client = eap_radius_create_client(), | |
d787ada8 | 325 | .rounds = array_create(sizeof(xauth_round_t), 0), |
c434b2a4 MW |
326 | ); |
327 | ||
d787ada8 MW |
328 | if (!parse_rounds(this, profile)) |
329 | { | |
330 | destroy(this); | |
331 | return NULL; | |
332 | } | |
c434b2a4 MW |
333 | if (!this->client) |
334 | { | |
335 | destroy(this); | |
336 | return NULL; | |
337 | } | |
338 | return &this->public; | |
339 | } |