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