Pass full pool list to release_address
[strongswan.git] / src / libhydra / plugins / attr_sql / sql_attribute.c
1 /*
2 * Copyright (C) 2008 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 <time.h>
17
18 #include <debug.h>
19 #include <library.h>
20
21 #include "sql_attribute.h"
22
23 typedef struct private_sql_attribute_t private_sql_attribute_t;
24
25 /**
26 * private data of sql_attribute
27 */
28 struct private_sql_attribute_t {
29
30 /**
31 * public functions
32 */
33 sql_attribute_t public;
34
35 /**
36 * database connection
37 */
38 database_t *db;
39
40 /**
41 * whether to record lease history in lease table
42 */
43 bool history;
44 };
45
46 /**
47 * lookup/insert an identity
48 */
49 static u_int get_identity(private_sql_attribute_t *this, identification_t *id)
50 {
51 enumerator_t *e;
52 u_int row;
53
54 /* look for peer identity in the identities table */
55 e = this->db->query(this->db,
56 "SELECT id FROM identities WHERE type = ? AND data = ?",
57 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id),
58 DB_UINT);
59
60 if (e && e->enumerate(e, &row))
61 {
62 e->destroy(e);
63 return row;
64 }
65 DESTROY_IF(e);
66 /* not found, insert new one */
67 if (this->db->execute(this->db, &row,
68 "INSERT INTO identities (type, data) VALUES (?, ?)",
69 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id)) == 1)
70 {
71 return row;
72 }
73 return 0;
74 }
75
76 /**
77 * Lookup an attribute pool by name
78 */
79 static u_int get_attr_pool(private_sql_attribute_t *this, char *name)
80 {
81 enumerator_t *e;
82 u_int row = 0;
83
84 e = this->db->query(this->db,
85 "SELECT id FROM attribute_pools WHERE name = ?",
86 DB_TEXT, name, DB_UINT);
87 if (e)
88 {
89 e->enumerate(e, &row);
90 }
91 DESTROY_IF(e);
92
93 return row;
94 }
95
96 /**
97 * Lookup pool by name
98 */
99 static u_int get_pool(private_sql_attribute_t *this, char *name, u_int *timeout)
100 {
101 enumerator_t *e;
102 u_int pool;
103
104 e = this->db->query(this->db, "SELECT id, timeout FROM pools WHERE name = ?",
105 DB_TEXT, name, DB_UINT, DB_UINT);
106 if (e && e->enumerate(e, &pool, timeout))
107 {
108 e->destroy(e);
109 return pool;
110 }
111 DESTROY_IF(e);
112 return 0;
113 }
114
115 /**
116 * Look up an existing lease
117 */
118 static host_t* check_lease(private_sql_attribute_t *this, char *name,
119 u_int pool, u_int identity)
120 {
121 while (TRUE)
122 {
123 u_int id;
124 chunk_t address;
125 enumerator_t *e;
126 time_t now = time(NULL);
127
128 e = this->db->query(this->db,
129 "SELECT id, address FROM addresses "
130 "WHERE pool = ? AND identity = ? AND released != 0 LIMIT 1",
131 DB_UINT, pool, DB_UINT, identity, DB_UINT, DB_BLOB);
132 if (!e || !e->enumerate(e, &id, &address))
133 {
134 DESTROY_IF(e);
135 break;
136 }
137 address = chunk_clonea(address);
138 e->destroy(e);
139
140 if (this->db->execute(this->db, NULL,
141 "UPDATE addresses SET acquired = ?, released = 0 "
142 "WHERE id = ? AND identity = ? AND released != 0",
143 DB_UINT, now, DB_UINT, id, DB_UINT, identity) > 0)
144 {
145 host_t *host;
146
147 host = host_create_from_chunk(AF_UNSPEC, address, 0);
148 if (host)
149 {
150 DBG1(DBG_CFG, "acquired existing lease for address %H in"
151 " pool '%s'", host, name);
152 return host;
153 }
154 }
155 }
156 return NULL;
157 }
158
159 /**
160 * We check for unallocated addresses or expired leases. First we select an
161 * address as a candidate, but double check later on if it is still available
162 * during the update operation. This allows us to work without locking.
163 */
164 static host_t* get_lease(private_sql_attribute_t *this, char *name,
165 u_int pool, u_int timeout, u_int identity)
166 {
167 while (TRUE)
168 {
169 u_int id;
170 chunk_t address;
171 enumerator_t *e;
172 time_t now = time(NULL);
173 int hits;
174
175 if (timeout)
176 {
177 /* check for an expired lease */
178 e = this->db->query(this->db,
179 "SELECT id, address FROM addresses "
180 "WHERE pool = ? AND released != 0 AND released < ? LIMIT 1",
181 DB_UINT, pool, DB_UINT, now - timeout, DB_UINT, DB_BLOB);
182 }
183 else
184 {
185 /* with static leases, check for an unallocated address */
186 e = this->db->query(this->db,
187 "SELECT id, address FROM addresses "
188 "WHERE pool = ? AND identity = 0 LIMIT 1",
189 DB_UINT, pool, DB_UINT, DB_BLOB);
190
191 }
192
193 if (!e || !e->enumerate(e, &id, &address))
194 {
195 DESTROY_IF(e);
196 break;
197 }
198 address = chunk_clonea(address);
199 e->destroy(e);
200
201 if (timeout)
202 {
203 hits = this->db->execute(this->db, NULL,
204 "UPDATE addresses SET "
205 "acquired = ?, released = 0, identity = ? "
206 "WHERE id = ? AND released != 0 AND released < ?",
207 DB_UINT, now, DB_UINT, identity,
208 DB_UINT, id, DB_UINT, now - timeout);
209 }
210 else
211 {
212 hits = this->db->execute(this->db, NULL,
213 "UPDATE addresses SET "
214 "acquired = ?, released = 0, identity = ? "
215 "WHERE id = ? AND identity = 0",
216 DB_UINT, now, DB_UINT, identity, DB_UINT, id);
217 }
218 if (hits > 0)
219 {
220 host_t *host;
221
222 host = host_create_from_chunk(AF_UNSPEC, address, 0);
223 if (host)
224 {
225 DBG1(DBG_CFG, "acquired new lease for address %H in pool '%s'",
226 host, name);
227 return host;
228 }
229 }
230 }
231 DBG1(DBG_CFG, "no available address found in pool '%s'", name);
232 return NULL;
233 }
234
235 METHOD(attribute_provider_t, acquire_address, host_t*,
236 private_sql_attribute_t *this, linked_list_t *pools, identification_t *id,
237 host_t *requested)
238 {
239 enumerator_t *enumerator;
240 host_t *address = NULL;
241 u_int identity, pool, timeout;
242 char *name;
243
244 identity = get_identity(this, id);
245 if (identity)
246 {
247 /* check for an existing lease in all pools */
248 enumerator = pools->create_enumerator(pools);
249 while (enumerator->enumerate(enumerator, &name))
250 {
251 pool = get_pool(this, name, &timeout);
252 if (pool)
253 {
254 address = check_lease(this, name, pool, identity);
255 if (address)
256 {
257 break;
258 }
259 }
260 }
261 enumerator->destroy(enumerator);
262
263 if (!address)
264 {
265 /* get an unallocated address or expired lease */
266 enumerator = pools->create_enumerator(pools);
267 while (enumerator->enumerate(enumerator, &name))
268 {
269 pool = get_pool(this, name, &timeout);
270 if (pool)
271 {
272 address = get_lease(this, name, pool, timeout, identity);
273 if (address)
274 {
275 break;
276 }
277 }
278 }
279 enumerator->destroy(enumerator);
280 }
281 }
282 return address;
283 }
284
285 METHOD(attribute_provider_t, release_address, bool,
286 private_sql_attribute_t *this, linked_list_t *pools, host_t *address,
287 identification_t *id)
288 {
289 enumerator_t *enumerator;
290 u_int pool, timeout;
291 time_t now = time(NULL);
292 bool found = FALSE;
293 char *name;
294
295 enumerator = pools->create_enumerator(pools);
296 while (enumerator->enumerate(enumerator, &name))
297 {
298 pool = get_pool(this, name, &timeout);
299 if (!pool)
300 {
301 continue;
302 }
303 if (this->db->execute(this->db, NULL,
304 "UPDATE addresses SET released = ? WHERE "
305 "pool = ? AND address = ?", DB_UINT, time(NULL),
306 DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0)
307 {
308 if (this->history)
309 {
310 this->db->execute(this->db, NULL,
311 "INSERT INTO leases (address, identity, acquired, released)"
312 " SELECT id, identity, acquired, ? FROM addresses "
313 " WHERE pool = ? AND address = ?",
314 DB_UINT, now, DB_UINT, pool,
315 DB_BLOB, address->get_address(address));
316 }
317 found = TRUE;
318 break;
319 }
320 }
321 enumerator->destroy(enumerator);
322
323 return found;
324 }
325
326 METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*,
327 private_sql_attribute_t *this, linked_list_t *pools, identification_t *id,
328 linked_list_t *vips)
329 {
330 enumerator_t *attr_enumerator = NULL;
331
332 if (vips->get_count(vips))
333 {
334 enumerator_t *pool_enumerator;
335 u_int count;
336 char *name;
337
338 this->db->execute(this->db, NULL, "BEGIN EXCLUSIVE TRANSACTION");
339
340 /* in a first step check for attributes that match name and id */
341 if (id)
342 {
343 u_int identity = get_identity(this, id);
344
345 pool_enumerator = pools->create_enumerator(pools);
346 while (pool_enumerator->enumerate(pool_enumerator, &name))
347 {
348 u_int attr_pool = get_attr_pool(this, name);
349 if (!attr_pool)
350 {
351 continue;
352 }
353
354 attr_enumerator = this->db->query(this->db,
355 "SELECT count(*) FROM attributes "
356 "WHERE pool = ? AND identity = ?",
357 DB_UINT, attr_pool, DB_UINT, identity, DB_UINT);
358
359 if (attr_enumerator &&
360 attr_enumerator->enumerate(attr_enumerator, &count) &&
361 count != 0)
362 {
363 attr_enumerator->destroy(attr_enumerator);
364 attr_enumerator = this->db->query(this->db,
365 "SELECT type, value FROM attributes "
366 "WHERE pool = ? AND identity = ?", DB_UINT,
367 attr_pool, DB_UINT, identity, DB_INT, DB_BLOB);
368 break;
369 }
370 DESTROY_IF(attr_enumerator);
371 attr_enumerator = NULL;
372 }
373 pool_enumerator->destroy(pool_enumerator);
374 }
375
376 /* in a second step check for attributes that match name */
377 if (!attr_enumerator)
378 {
379 pool_enumerator = pools->create_enumerator(pools);
380 while (pool_enumerator->enumerate(pool_enumerator, &name))
381 {
382 u_int attr_pool = get_attr_pool(this, name);
383 if (!attr_pool)
384 {
385 continue;
386 }
387
388 attr_enumerator = this->db->query(this->db,
389 "SELECT count(*) FROM attributes "
390 "WHERE pool = ? AND identity = 0",
391 DB_UINT, attr_pool, DB_UINT);
392
393 if (attr_enumerator &&
394 attr_enumerator->enumerate(attr_enumerator, &count) &&
395 count != 0)
396 {
397 attr_enumerator->destroy(attr_enumerator);
398 attr_enumerator = this->db->query(this->db,
399 "SELECT type, value FROM attributes "
400 "WHERE pool = ? AND identity = 0",
401 DB_UINT, attr_pool, DB_INT, DB_BLOB);
402 break;
403 }
404 DESTROY_IF(attr_enumerator);
405 attr_enumerator = NULL;
406 }
407 pool_enumerator->destroy(pool_enumerator);
408 }
409
410 this->db->execute(this->db, NULL, "END TRANSACTION");
411
412 /* lastly try to find global attributes */
413 if (!attr_enumerator)
414 {
415 attr_enumerator = this->db->query(this->db,
416 "SELECT type, value FROM attributes "
417 "WHERE pool = 0 AND identity = 0",
418 DB_INT, DB_BLOB);
419 }
420 }
421
422 return (attr_enumerator ? attr_enumerator : enumerator_create_empty());
423 }
424
425 METHOD(sql_attribute_t, destroy, void,
426 private_sql_attribute_t *this)
427 {
428 free(this);
429 }
430
431 /*
432 * see header file
433 */
434 sql_attribute_t *sql_attribute_create(database_t *db)
435 {
436 private_sql_attribute_t *this;
437 time_t now = time(NULL);
438
439 INIT(this,
440 .public = {
441 .provider = {
442 .acquire_address = _acquire_address,
443 .release_address = _release_address,
444 .create_attribute_enumerator = _create_attribute_enumerator,
445 },
446 .destroy = _destroy,
447 },
448 .db = db,
449 .history = lib->settings->get_bool(lib->settings,
450 "libhydra.plugins.attr-sql.lease_history", TRUE),
451 );
452
453 /* close any "online" leases in the case we crashed */
454 if (this->history)
455 {
456 this->db->execute(this->db, NULL,
457 "INSERT INTO leases (address, identity, acquired, released)"
458 " SELECT id, identity, acquired, ? FROM addresses "
459 " WHERE released = 0", DB_UINT, now);
460 }
461 this->db->execute(this->db, NULL,
462 "UPDATE addresses SET released = ? WHERE released = 0",
463 DB_UINT, now);
464 return &this->public;
465 }
466