1a67096cb39b89f59f3d60919eeb1d551eca85fc
[strongswan.git] / src / libcharon / plugins / eap_radius / radius_client.c
1 /*
2 * Copyright (C) 2009 Martin Willi
3 * 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 "radius_client.h"
17
18 #include "eap_radius_plugin.h"
19 #include "radius_server.h"
20
21 #include <unistd.h>
22 #include <errno.h>
23
24 #include <debug.h>
25 #include <utils/host.h>
26 #include <utils/linked_list.h>
27 #include <threading/condvar.h>
28 #include <threading/mutex.h>
29
30 typedef struct private_radius_client_t private_radius_client_t;
31
32 /**
33 * Private data of an radius_client_t object.
34 */
35 struct private_radius_client_t {
36
37 /**
38 * Public radius_client_t interface.
39 */
40 radius_client_t public;
41
42 /**
43 * Selected RADIUS server
44 */
45 radius_server_t *server;
46
47 /**
48 * RADIUS servers State attribute
49 */
50 chunk_t state;
51
52 /**
53 * EAP MSK, from MPPE keys
54 */
55 chunk_t msk;
56 };
57
58 /**
59 * Save the state attribute to include in further request
60 */
61 static void save_state(private_radius_client_t *this, radius_message_t *msg)
62 {
63 enumerator_t *enumerator;
64 int type;
65 chunk_t data;
66
67 enumerator = msg->create_enumerator(msg);
68 while (enumerator->enumerate(enumerator, &type, &data))
69 {
70 if (type == RAT_STATE)
71 {
72 free(this->state.ptr);
73 this->state = chunk_clone(data);
74 enumerator->destroy(enumerator);
75 return;
76 }
77 }
78 enumerator->destroy(enumerator);
79 /* no state attribute found, remove state */
80 chunk_free(&this->state);
81 }
82
83 METHOD(radius_client_t, request, radius_message_t*,
84 private_radius_client_t *this, radius_message_t *req)
85 {
86 char virtual[] = {0x00,0x00,0x00,0x05};
87 radius_socket_t *socket;
88 radius_message_t *res;
89
90 /* we add the "Virtual" NAS-Port-Type, as we SHOULD include one */
91 req->add(req, RAT_NAS_PORT_TYPE, chunk_create(virtual, sizeof(virtual)));
92 /* add our NAS-Identifier */
93 req->add(req, RAT_NAS_IDENTIFIER,
94 this->server->get_nas_identifier(this->server));
95 /* add State attribute, if server sent one */
96 if (this->state.ptr)
97 {
98 req->add(req, RAT_STATE, this->state);
99 }
100 socket = this->server->get_socket(this->server);
101 DBG1(DBG_CFG, "sending RADIUS %N to server '%s'", radius_message_code_names,
102 req->get_code(req), this->server->get_name(this->server));
103 res = socket->request(socket, req);
104 if (res)
105 {
106 DBG1(DBG_CFG, "received RADIUS %N from server '%s'",
107 radius_message_code_names, res->get_code(res),
108 this->server->get_name(this->server));
109 save_state(this, res);
110 if (res->get_code(res) == RMC_ACCESS_ACCEPT)
111 {
112 chunk_clear(&this->msk);
113 this->msk = socket->decrypt_msk(socket, req, res);
114 }
115 this->server->put_socket(this->server, socket, TRUE);
116 return res;
117 }
118 this->server->put_socket(this->server, socket, FALSE);
119 return NULL;
120 }
121
122 METHOD(radius_client_t, get_msk, chunk_t,
123 private_radius_client_t *this)
124 {
125 return this->msk;
126 }
127
128 METHOD(radius_client_t, destroy, void,
129 private_radius_client_t *this)
130 {
131 this->server->destroy(this->server);
132 chunk_clear(&this->msk);
133 free(this->state.ptr);
134 free(this);
135 }
136
137 /**
138 * See header
139 */
140 radius_client_t *radius_client_create()
141 {
142 private_radius_client_t *this;
143 enumerator_t *enumerator;
144 radius_server_t *server;
145 int current, best = -1;
146
147 INIT(this,
148 .public = {
149 .request = _request,
150 .get_msk = _get_msk,
151 .destroy = _destroy,
152 },
153 );
154
155 enumerator = eap_radius_create_server_enumerator();
156 while (enumerator->enumerate(enumerator, &server))
157 {
158 current = server->get_preference(server);
159 if (current > best ||
160 /* for two with equal preference, 50-50 chance */
161 (current == best && random() % 2 == 0))
162 {
163 DBG2(DBG_CFG, "RADIUS server '%s' is candidate: %d",
164 server->get_name(server), current);
165 best = current;
166 DESTROY_IF(this->server);
167 this->server = server->get_ref(server);
168 }
169 else
170 {
171 DBG2(DBG_CFG, "RADIUS server '%s' skipped: %d",
172 server->get_name(server), current);
173 }
174 }
175 enumerator->destroy(enumerator);
176
177 if (!this->server)
178 {
179 free(this);
180 return NULL;
181 }
182
183 return &this->public;
184 }
185