Use large enough buffers for IPv6 addresses in Calling-Station-Id
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_accounting.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_accounting.h"
17
18 #include <time.h>
19
20 #include "radius_message.h"
21 #include "radius_client.h"
22 #include <daemon.h>
23 #include <utils/hashtable.h>
24 #include <threading/mutex.h>
25
26 typedef struct private_eap_radius_accounting_t private_eap_radius_accounting_t;
27
28 /**
29 * Private data of an eap_radius_accounting_t object.
30 */
31 struct private_eap_radius_accounting_t {
32
33 /**
34 * Public eap_radius_accounting_t interface.
35 */
36 eap_radius_accounting_t public;
37
38 /**
39 * Hashtable with sessions, IKE_SA unique id => entry_t
40 */
41 hashtable_t *sessions;
42
43 /**
44 * Mutex to lock sessions
45 */
46 mutex_t *mutex;
47
48 /**
49 * Session ID prefix
50 */
51 u_int32_t prefix;
52 };
53
54 /**
55 * Hashtable entry with usage stats
56 */
57 typedef struct {
58 /** RADIUS accounting session ID */
59 char sid[16];
60 /** number of octets sent */
61 u_int64_t sent;
62 /** number of octets received */
63 u_int64_t received;
64 /** session creation time */
65 time_t created;
66 } entry_t;
67
68 /**
69 * Accounting message status types
70 */
71 typedef enum {
72 ACCT_STATUS_START = 1,
73 ACCT_STATUS_STOP = 2,
74 ACCT_STATUS_INTERIM_UPDATE = 3,
75 ACCT_STATUS_ACCOUNTING_ON = 7,
76 ACCT_STATUS_ACCOUNTING_OFF = 8,
77 } radius_acct_status_t;
78
79 /**
80 * Hashtable hash function
81 */
82 static u_int hash(uintptr_t key)
83 {
84 return key;
85 }
86
87 /**
88 * Hashtable equals function
89 */
90 static bool equals(uintptr_t a, uintptr_t b)
91 {
92 return a == b;
93 }
94
95 /**
96 * Update usage counter when a CHILD_SA rekeys/goes down
97 */
98 static void update_usage(private_eap_radius_accounting_t *this,
99 ike_sa_t *ike_sa, child_sa_t *child_sa)
100 {
101 u_int64_t sent, received;
102 entry_t *entry;
103
104 child_sa->get_usestats(child_sa, FALSE, NULL, &sent);
105 child_sa->get_usestats(child_sa, TRUE, NULL, &received);
106
107 this->mutex->lock(this->mutex);
108 entry = this->sessions->get(this->sessions,
109 (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa));
110 if (entry)
111 {
112 entry->sent += sent;
113 entry->received += received;
114 }
115 this->mutex->unlock(this->mutex);
116 }
117
118 /**
119 * Send a RADIUS message, wait for response
120 */
121 static bool send_message(private_eap_radius_accounting_t *this,
122 radius_message_t *request)
123 {
124 radius_message_t *response;
125 radius_client_t *client;
126 bool ack = FALSE;
127
128 client = radius_client_create();
129 if (client)
130 {
131 response = client->request(client, request);
132 if (response)
133 {
134 ack = response->get_code(response) == RMC_ACCOUNTING_RESPONSE;
135 response->destroy(response);
136 }
137 client->destroy(client);
138 }
139 return ack;
140 }
141
142 /**
143 * Send an accounting start message
144 */
145 static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
146 {
147 char buf[64];
148 radius_message_t *message;
149 host_t *vip;
150 entry_t *entry;
151 u_int32_t id, value;
152
153 id = ike_sa->get_unique_id(ike_sa);
154 INIT(entry,
155 .created = time_monotonic(NULL),
156 );
157 snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, id);
158
159 message = radius_message_create_request(RMC_ACCOUNTING_REQUEST);
160 value = htonl(ACCT_STATUS_START);
161 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
162 message->add(message, RAT_ACCT_SESSION_ID,
163 chunk_create(entry->sid, strlen(entry->sid)));
164 snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa));
165 message->add(message, RAT_USER_NAME, chunk_create(buf, strlen(buf)));
166 snprintf(buf, sizeof(buf), "%H", ike_sa->get_other_host(ike_sa));
167 message->add(message, RAT_CALLING_STATION_ID, chunk_create(buf, strlen(buf)));
168 vip = ike_sa->get_virtual_ip(ike_sa, FALSE);
169 if (vip)
170 {
171 message->add(message, RAT_FRAMED_IP_ADDRESS, vip->get_address(vip));
172 }
173 if (send_message(this, message))
174 {
175 this->mutex->lock(this->mutex);
176 entry = this->sessions->put(this->sessions, (void*)(uintptr_t)id, entry);
177 this->mutex->unlock(this->mutex);
178 free(entry);
179 }
180 message->destroy(message);
181 }
182
183 /**
184 * Send an account stop message
185 */
186 static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
187 {
188 radius_message_t *message;
189 entry_t *entry;
190 u_int32_t id, value;
191 host_t *vip;
192 char buf[64];
193
194 id = ike_sa->get_unique_id(ike_sa);
195 this->mutex->lock(this->mutex);
196 entry = this->sessions->remove(this->sessions, (void*)(uintptr_t)id);
197 this->mutex->unlock(this->mutex);
198 if (entry)
199 {
200 message = radius_message_create_request(RMC_ACCOUNTING_REQUEST);
201 value = htonl(ACCT_STATUS_STOP);
202 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
203 message->add(message, RAT_ACCT_SESSION_ID,
204 chunk_create(entry->sid, strlen(entry->sid)));
205 snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa));
206 message->add(message, RAT_USER_NAME, chunk_create(buf, strlen(buf)));
207 snprintf(buf, sizeof(buf), "%H", ike_sa->get_other_host(ike_sa));
208 message->add(message, RAT_CALLING_STATION_ID,
209 chunk_create(buf, strlen(buf)));
210 vip = ike_sa->get_virtual_ip(ike_sa, FALSE);
211 if (vip)
212 {
213 message->add(message, RAT_FRAMED_IP_ADDRESS, vip->get_address(vip));
214 }
215 value = htonl(entry->sent);
216 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
217 value = htonl(entry->sent >> 32);
218 if (value)
219 {
220 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
221 chunk_from_thing(value));
222 }
223 value = htonl(entry->received);
224 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
225 value = htonl(entry->received >> 32);
226 if (value)
227 {
228 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
229 chunk_from_thing(value));
230 }
231 value = htonl(time_monotonic(NULL) - entry->created);
232 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
233
234 send_message(this, message);
235 message->destroy(message);
236 free(entry);
237 }
238 }
239
240 METHOD(listener_t, ike_updown, bool,
241 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
242 {
243 if (!up)
244 {
245 enumerator_t *enumerator;
246 child_sa_t *child_sa;
247
248 /* update usage for all children just before sending stop */
249 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
250 while (enumerator->enumerate(enumerator, &child_sa))
251 {
252 update_usage(this, ike_sa, child_sa);
253 }
254 enumerator->destroy(enumerator);
255
256 send_stop(this, ike_sa);
257 }
258 return TRUE;
259 }
260
261 METHOD(listener_t, message_hook, bool,
262 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
263 message_t *message, bool incoming)
264 {
265 /* start accounting here, virtual IP now is set */
266 if (ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
267 message->get_exchange_type(message) == IKE_AUTH &&
268 !incoming && !message->get_request(message))
269 {
270 send_start(this, ike_sa);
271 }
272 return TRUE;
273 }
274
275 METHOD(listener_t, child_rekey, bool,
276 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
277 child_sa_t *old, child_sa_t *new)
278 {
279 update_usage(this, ike_sa, old);
280
281 return TRUE;
282 }
283
284 METHOD(listener_t, child_updown, bool,
285 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
286 child_sa_t *child_sa, bool up)
287 {
288 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
289 {
290 update_usage(this, ike_sa, child_sa);
291 }
292 return TRUE;
293 }
294
295 METHOD(eap_radius_accounting_t, destroy, void,
296 private_eap_radius_accounting_t *this)
297 {
298 this->mutex->destroy(this->mutex);
299 this->sessions->destroy(this->sessions);
300 free(this);
301 }
302
303 /**
304 * See header
305 */
306 eap_radius_accounting_t *eap_radius_accounting_create()
307 {
308 private_eap_radius_accounting_t *this;
309
310 INIT(this,
311 .public = {
312 .listener = {
313 .ike_updown = _ike_updown,
314 .message = _message_hook,
315 .child_updown = _child_updown,
316 .child_rekey = _child_rekey,
317 },
318 .destroy = _destroy,
319 },
320 /* use system time as Session ID prefix */
321 .prefix = (u_int32_t)time(NULL),
322 .sessions = hashtable_create((hashtable_hash_t)hash,
323 (hashtable_equals_t)equals, 32),
324 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
325 );
326
327 return &this->public;
328 }