Pass the full list of pools to acquire_address, enumerate in providers
[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, char *name, host_t *address,
287 identification_t *id)
288 {
289 u_int pool, timeout;
290 time_t now = time(NULL);
291
292 pool = get_pool(this, name, &timeout);
293 if (pool)
294 {
295 if (this->history)
296 {
297 this->db->execute(this->db, NULL,
298 "INSERT INTO leases (address, identity, acquired, released)"
299 " SELECT id, identity, acquired, ? FROM addresses "
300 " WHERE pool = ? AND address = ?",
301 DB_UINT, now, DB_UINT, pool,
302 DB_BLOB, address->get_address(address));
303 }
304 if (this->db->execute(this->db, NULL,
305 "UPDATE addresses SET released = ? WHERE "
306 "pool = ? AND address = ?", DB_UINT, time(NULL),
307 DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0)
308 {
309 return TRUE;
310 }
311 }
312 return FALSE;
313 }
314
315 METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*,
316 private_sql_attribute_t *this, linked_list_t *pools, identification_t *id,
317 linked_list_t *vips)
318 {
319 enumerator_t *attr_enumerator = NULL;
320
321 if (vips->get_count(vips))
322 {
323 enumerator_t *pool_enumerator;
324 u_int count;
325 char *name;
326
327 this->db->execute(this->db, NULL, "BEGIN EXCLUSIVE TRANSACTION");
328
329 /* in a first step check for attributes that match name and id */
330 if (id)
331 {
332 u_int identity = get_identity(this, id);
333
334 pool_enumerator = pools->create_enumerator(pools);
335 while (pool_enumerator->enumerate(pool_enumerator, &name))
336 {
337 u_int attr_pool = get_attr_pool(this, name);
338 if (!attr_pool)
339 {
340 continue;
341 }
342
343 attr_enumerator = this->db->query(this->db,
344 "SELECT count(*) FROM attributes "
345 "WHERE pool = ? AND identity = ?",
346 DB_UINT, attr_pool, DB_UINT, identity, DB_UINT);
347
348 if (attr_enumerator &&
349 attr_enumerator->enumerate(attr_enumerator, &count) &&
350 count != 0)
351 {
352 attr_enumerator->destroy(attr_enumerator);
353 attr_enumerator = this->db->query(this->db,
354 "SELECT type, value FROM attributes "
355 "WHERE pool = ? AND identity = ?", DB_UINT,
356 attr_pool, DB_UINT, identity, DB_INT, DB_BLOB);
357 break;
358 }
359 DESTROY_IF(attr_enumerator);
360 attr_enumerator = NULL;
361 }
362 pool_enumerator->destroy(pool_enumerator);
363 }
364
365 /* in a second step check for attributes that match name */
366 if (!attr_enumerator)
367 {
368 pool_enumerator = pools->create_enumerator(pools);
369 while (pool_enumerator->enumerate(pool_enumerator, &name))
370 {
371 u_int attr_pool = get_attr_pool(this, name);
372 if (!attr_pool)
373 {
374 continue;
375 }
376
377 attr_enumerator = this->db->query(this->db,
378 "SELECT count(*) FROM attributes "
379 "WHERE pool = ? AND identity = 0",
380 DB_UINT, attr_pool, DB_UINT);
381
382 if (attr_enumerator &&
383 attr_enumerator->enumerate(attr_enumerator, &count) &&
384 count != 0)
385 {
386 attr_enumerator->destroy(attr_enumerator);
387 attr_enumerator = this->db->query(this->db,
388 "SELECT type, value FROM attributes "
389 "WHERE pool = ? AND identity = 0",
390 DB_UINT, attr_pool, DB_INT, DB_BLOB);
391 break;
392 }
393 DESTROY_IF(attr_enumerator);
394 attr_enumerator = NULL;
395 }
396 pool_enumerator->destroy(pool_enumerator);
397 }
398
399 this->db->execute(this->db, NULL, "END TRANSACTION");
400
401 /* lastly try to find global attributes */
402 if (!attr_enumerator)
403 {
404 attr_enumerator = this->db->query(this->db,
405 "SELECT type, value FROM attributes "
406 "WHERE pool = 0 AND identity = 0",
407 DB_INT, DB_BLOB);
408 }
409 }
410
411 return (attr_enumerator ? attr_enumerator : enumerator_create_empty());
412 }
413
414 METHOD(sql_attribute_t, destroy, void,
415 private_sql_attribute_t *this)
416 {
417 free(this);
418 }
419
420 /*
421 * see header file
422 */
423 sql_attribute_t *sql_attribute_create(database_t *db)
424 {
425 private_sql_attribute_t *this;
426 time_t now = time(NULL);
427
428 INIT(this,
429 .public = {
430 .provider = {
431 .acquire_address = _acquire_address,
432 .release_address = _release_address,
433 .create_attribute_enumerator = _create_attribute_enumerator,
434 },
435 .destroy = _destroy,
436 },
437 .db = db,
438 .history = lib->settings->get_bool(lib->settings,
439 "libhydra.plugins.attr-sql.lease_history", TRUE),
440 );
441
442 /* close any "online" leases in the case we crashed */
443 if (this->history)
444 {
445 this->db->execute(this->db, NULL,
446 "INSERT INTO leases (address, identity, acquired, released)"
447 " SELECT id, identity, acquired, ? FROM addresses "
448 " WHERE released = 0", DB_UINT, now);
449 }
450 this->db->execute(this->db, NULL,
451 "UPDATE addresses SET released = ? WHERE released = 0",
452 DB_UINT, now);
453 return &this->public;
454 }
455