Migrated sql_attribute to INIT/METHOD macros
[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 * wheter 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, char *names, identification_t *id,
237 host_t *requested)
238 {
239 host_t *address = NULL;
240 u_int identity, pool, timeout;
241
242 identity = get_identity(this, id);
243 if (identity)
244 {
245 /* check for a single pool first (no concatenation and enumeration) */
246 if (strchr(names, ',') == NULL)
247 {
248 pool = get_pool(this, names, &timeout);
249 if (pool)
250 {
251 /* check for an existing lease */
252 address = check_lease(this, names, pool, identity);
253 if (address == NULL)
254 {
255 /* get an unallocated address or expired lease */
256 address = get_lease(this, names, pool, timeout, identity);
257 }
258 }
259 }
260 else
261 {
262 enumerator_t *enumerator;
263 char *name;
264
265 /* in a first step check for an existing lease over all pools */
266 enumerator = enumerator_create_token(names, ",", " ");
267 while (enumerator->enumerate(enumerator, &name))
268 {
269 pool = get_pool(this, name, &timeout);
270 if (pool)
271 {
272 address = check_lease(this, name, pool, identity);
273 if (address)
274 {
275 enumerator->destroy(enumerator);
276 return address;
277 }
278 }
279 }
280 enumerator->destroy(enumerator);
281
282 /* in a second step get an unallocated address or expired lease */
283 enumerator = enumerator_create_token(names, ",", " ");
284 while (enumerator->enumerate(enumerator, &name))
285 {
286 pool = get_pool(this, name, &timeout);
287 if (pool)
288 {
289 address = get_lease(this, name, pool, timeout, identity);
290 if (address)
291 {
292 break;
293 }
294 }
295 }
296 enumerator->destroy(enumerator);
297 }
298 }
299 return address;
300 }
301
302 METHOD(attribute_provider_t, release_address, bool,
303 private_sql_attribute_t *this, char *name, host_t *address,
304 identification_t *id)
305 {
306 enumerator_t *enumerator;
307 bool found = FALSE;
308 time_t now = time(NULL);
309
310 enumerator = enumerator_create_token(name, ",", " ");
311 while (enumerator->enumerate(enumerator, &name))
312 {
313 u_int pool, timeout;
314
315 pool = get_pool(this, name, &timeout);
316 if (pool)
317 {
318 if (this->history)
319 {
320 this->db->execute(this->db, NULL,
321 "INSERT INTO leases (address, identity, acquired, released)"
322 " SELECT id, identity, acquired, ? FROM addresses "
323 " WHERE pool = ? AND address = ?",
324 DB_UINT, now, DB_UINT, pool,
325 DB_BLOB, address->get_address(address));
326 }
327 if (this->db->execute(this->db, NULL,
328 "UPDATE addresses SET released = ? WHERE "
329 "pool = ? AND address = ?", DB_UINT, time(NULL),
330 DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0)
331 {
332 found = TRUE;
333 break;
334 }
335 }
336 }
337 enumerator->destroy(enumerator);
338 return found;
339 }
340
341 METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*,
342 private_sql_attribute_t *this, char *names, identification_t *id,
343 host_t *vip)
344 {
345 enumerator_t *attr_enumerator = NULL;
346
347 if (vip)
348 {
349 enumerator_t *names_enumerator;
350 u_int count;
351 char *name;
352
353 this->db->execute(this->db, NULL, "BEGIN EXCLUSIVE TRANSACTION");
354
355 /* in a first step check for attributes that match name and id */
356 if (id)
357 {
358 u_int identity = get_identity(this, id);
359
360 names_enumerator = enumerator_create_token(names, ",", " ");
361 while (names_enumerator->enumerate(names_enumerator, &name))
362 {
363 u_int attr_pool = get_attr_pool(this, name);
364 if (!attr_pool)
365 {
366 continue;
367 }
368
369 attr_enumerator = this->db->query(this->db,
370 "SELECT count(*) FROM attributes "
371 "WHERE pool = ? AND identity = ?",
372 DB_UINT, attr_pool, DB_UINT, identity, DB_UINT);
373
374 if (attr_enumerator &&
375 attr_enumerator->enumerate(attr_enumerator, &count) &&
376 count != 0)
377 {
378 attr_enumerator->destroy(attr_enumerator);
379 attr_enumerator = this->db->query(this->db,
380 "SELECT type, value FROM attributes "
381 "WHERE pool = ? AND identity = ?", DB_UINT,
382 attr_pool, DB_UINT, identity, DB_INT, DB_BLOB);
383 break;
384 }
385 DESTROY_IF(attr_enumerator);
386 attr_enumerator = NULL;
387 }
388 names_enumerator->destroy(names_enumerator);
389 }
390
391 /* in a second step check for attributes that match name */
392 if (!attr_enumerator)
393 {
394 names_enumerator = enumerator_create_token(names, ",", " ");
395 while (names_enumerator->enumerate(names_enumerator, &name))
396 {
397 u_int attr_pool = get_attr_pool(this, name);
398 if (!attr_pool)
399 {
400 continue;
401 }
402
403 attr_enumerator = this->db->query(this->db,
404 "SELECT count(*) FROM attributes "
405 "WHERE pool = ? AND identity = 0",
406 DB_UINT, attr_pool, DB_UINT);
407
408 if (attr_enumerator &&
409 attr_enumerator->enumerate(attr_enumerator, &count) &&
410 count != 0)
411 {
412 attr_enumerator->destroy(attr_enumerator);
413 attr_enumerator = this->db->query(this->db,
414 "SELECT type, value FROM attributes "
415 "WHERE pool = ? AND identity = 0",
416 DB_UINT, attr_pool, DB_INT, DB_BLOB);
417 break;
418 }
419 DESTROY_IF(attr_enumerator);
420 attr_enumerator = NULL;
421 }
422 names_enumerator->destroy(names_enumerator);
423 }
424
425 this->db->execute(this->db, NULL, "END TRANSACTION");
426
427 /* lastly try to find global attributes */
428 if (!attr_enumerator)
429 {
430 attr_enumerator = this->db->query(this->db,
431 "SELECT type, value FROM attributes "
432 "WHERE pool = 0 AND identity = 0",
433 DB_INT, DB_BLOB);
434 }
435 }
436
437 return (attr_enumerator ? attr_enumerator : enumerator_create_empty());
438 }
439
440 METHOD(sql_attribute_t, destroy, void,
441 private_sql_attribute_t *this)
442 {
443 free(this);
444 }
445
446 /*
447 * see header file
448 */
449 sql_attribute_t *sql_attribute_create(database_t *db)
450 {
451 private_sql_attribute_t *this;
452 time_t now = time(NULL);
453
454 INIT(this,
455 .public = {
456 .provider = {
457 .acquire_address = _acquire_address,
458 .release_address = _release_address,
459 .create_attribute_enumerator = _create_attribute_enumerator,
460 },
461 .destroy = _destroy,
462 },
463 .db = db,
464 .history = lib->settings->get_bool(lib->settings,
465 "libhydra.plugins.attr-sql.lease_history", TRUE),
466 );
467
468 /* close any "online" leases in the case we crashed */
469 if (this->history)
470 {
471 this->db->execute(this->db, NULL,
472 "INSERT INTO leases (address, identity, acquired, released)"
473 " SELECT id, identity, acquired, ? FROM addresses "
474 " WHERE released = 0", DB_UINT, now);
475 }
476 this->db->execute(this->db, NULL,
477 "UPDATE addresses SET released = ? WHERE released = 0",
478 DB_UINT, now);
479 return &this->public;
480 }
481