Ignore SQL-based IP address pools if their address family does not match
[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 <utils/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 and address family
98 */
99 static u_int get_pool(private_sql_attribute_t *this, char *name, int family,
100 u_int *timeout)
101 {
102 enumerator_t *e;
103 chunk_t start;
104 u_int pool;
105
106 e = this->db->query(this->db,
107 "SELECT id, start, timeout FROM pools WHERE name = ?",
108 DB_TEXT, name, DB_UINT, DB_BLOB, DB_UINT);
109 if (e && e->enumerate(e, &pool, &start, timeout))
110 {
111 if ((family == AF_INET && start.len == 4) ||
112 (family == AF_INET6 && start.len == 16))
113 {
114 e->destroy(e);
115 return pool;
116 }
117 }
118 DESTROY_IF(e);
119 return 0;
120 }
121
122 /**
123 * Look up an existing lease
124 */
125 static host_t* check_lease(private_sql_attribute_t *this, char *name,
126 u_int pool, u_int identity)
127 {
128 while (TRUE)
129 {
130 u_int id;
131 chunk_t address;
132 enumerator_t *e;
133 time_t now = time(NULL);
134
135 e = this->db->query(this->db,
136 "SELECT id, address FROM addresses "
137 "WHERE pool = ? AND identity = ? AND released != 0 LIMIT 1",
138 DB_UINT, pool, DB_UINT, identity, DB_UINT, DB_BLOB);
139 if (!e || !e->enumerate(e, &id, &address))
140 {
141 DESTROY_IF(e);
142 break;
143 }
144 address = chunk_clonea(address);
145 e->destroy(e);
146
147 if (this->db->execute(this->db, NULL,
148 "UPDATE addresses SET acquired = ?, released = 0 "
149 "WHERE id = ? AND identity = ? AND released != 0",
150 DB_UINT, now, DB_UINT, id, DB_UINT, identity) > 0)
151 {
152 host_t *host;
153
154 host = host_create_from_chunk(AF_UNSPEC, address, 0);
155 if (host)
156 {
157 DBG1(DBG_CFG, "acquired existing lease for address %H in"
158 " pool '%s'", host, name);
159 return host;
160 }
161 }
162 }
163 return NULL;
164 }
165
166 /**
167 * We check for unallocated addresses or expired leases. First we select an
168 * address as a candidate, but double check later on if it is still available
169 * during the update operation. This allows us to work without locking.
170 */
171 static host_t* get_lease(private_sql_attribute_t *this, char *name,
172 u_int pool, u_int timeout, u_int identity)
173 {
174 while (TRUE)
175 {
176 u_int id;
177 chunk_t address;
178 enumerator_t *e;
179 time_t now = time(NULL);
180 int hits;
181
182 if (timeout)
183 {
184 /* check for an expired lease */
185 e = this->db->query(this->db,
186 "SELECT id, address FROM addresses "
187 "WHERE pool = ? AND released != 0 AND released < ? LIMIT 1",
188 DB_UINT, pool, DB_UINT, now - timeout, DB_UINT, DB_BLOB);
189 }
190 else
191 {
192 /* with static leases, check for an unallocated address */
193 e = this->db->query(this->db,
194 "SELECT id, address FROM addresses "
195 "WHERE pool = ? AND identity = 0 LIMIT 1",
196 DB_UINT, pool, DB_UINT, DB_BLOB);
197
198 }
199
200 if (!e || !e->enumerate(e, &id, &address))
201 {
202 DESTROY_IF(e);
203 break;
204 }
205 address = chunk_clonea(address);
206 e->destroy(e);
207
208 if (timeout)
209 {
210 hits = this->db->execute(this->db, NULL,
211 "UPDATE addresses SET "
212 "acquired = ?, released = 0, identity = ? "
213 "WHERE id = ? AND released != 0 AND released < ?",
214 DB_UINT, now, DB_UINT, identity,
215 DB_UINT, id, DB_UINT, now - timeout);
216 }
217 else
218 {
219 hits = this->db->execute(this->db, NULL,
220 "UPDATE addresses SET "
221 "acquired = ?, released = 0, identity = ? "
222 "WHERE id = ? AND identity = 0",
223 DB_UINT, now, DB_UINT, identity, DB_UINT, id);
224 }
225 if (hits > 0)
226 {
227 host_t *host;
228
229 host = host_create_from_chunk(AF_UNSPEC, address, 0);
230 if (host)
231 {
232 DBG1(DBG_CFG, "acquired new lease for address %H in pool '%s'",
233 host, name);
234 return host;
235 }
236 }
237 }
238 DBG1(DBG_CFG, "no available address found in pool '%s'", name);
239 return NULL;
240 }
241
242 METHOD(attribute_provider_t, acquire_address, host_t*,
243 private_sql_attribute_t *this, linked_list_t *pools, identification_t *id,
244 host_t *requested)
245 {
246 enumerator_t *enumerator;
247 host_t *address = NULL;
248 u_int identity, pool, timeout;
249 char *name;
250 int family;
251
252 identity = get_identity(this, id);
253 if (identity)
254 {
255 family = requested->get_family(requested);
256 /* check for an existing lease in all pools */
257 enumerator = pools->create_enumerator(pools);
258 while (enumerator->enumerate(enumerator, &name))
259 {
260 pool = get_pool(this, name, family, &timeout);
261 if (pool)
262 {
263 address = check_lease(this, name, pool, identity);
264 if (address)
265 {
266 break;
267 }
268 }
269 }
270 enumerator->destroy(enumerator);
271
272 if (!address)
273 {
274 /* get an unallocated address or expired lease */
275 enumerator = pools->create_enumerator(pools);
276 while (enumerator->enumerate(enumerator, &name))
277 {
278 pool = get_pool(this, name, family, &timeout);
279 if (pool)
280 {
281 address = get_lease(this, name, pool, timeout, identity);
282 if (address)
283 {
284 break;
285 }
286 }
287 }
288 enumerator->destroy(enumerator);
289 }
290 }
291 return address;
292 }
293
294 METHOD(attribute_provider_t, release_address, bool,
295 private_sql_attribute_t *this, linked_list_t *pools, host_t *address,
296 identification_t *id)
297 {
298 enumerator_t *enumerator;
299 u_int pool, timeout;
300 time_t now = time(NULL);
301 bool found = FALSE;
302 char *name;
303 int family;
304
305 family = address->get_family(address);
306 enumerator = pools->create_enumerator(pools);
307 while (enumerator->enumerate(enumerator, &name))
308 {
309 pool = get_pool(this, name, family, &timeout);
310 if (!pool)
311 {
312 continue;
313 }
314 if (this->db->execute(this->db, NULL,
315 "UPDATE addresses SET released = ? WHERE "
316 "pool = ? AND address = ?", DB_UINT, time(NULL),
317 DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0)
318 {
319 if (this->history)
320 {
321 this->db->execute(this->db, NULL,
322 "INSERT INTO leases (address, identity, acquired, released)"
323 " SELECT id, identity, acquired, ? FROM addresses "
324 " WHERE pool = ? AND address = ?",
325 DB_UINT, now, DB_UINT, pool,
326 DB_BLOB, address->get_address(address));
327 }
328 found = TRUE;
329 break;
330 }
331 }
332 enumerator->destroy(enumerator);
333
334 return found;
335 }
336
337 METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*,
338 private_sql_attribute_t *this, linked_list_t *pools, identification_t *id,
339 linked_list_t *vips)
340 {
341 enumerator_t *attr_enumerator = NULL;
342
343 if (vips->get_count(vips))
344 {
345 enumerator_t *pool_enumerator;
346 u_int count;
347 char *name;
348
349 this->db->execute(this->db, NULL, "BEGIN EXCLUSIVE TRANSACTION");
350
351 /* in a first step check for attributes that match name and id */
352 if (id)
353 {
354 u_int identity = get_identity(this, id);
355
356 pool_enumerator = pools->create_enumerator(pools);
357 while (pool_enumerator->enumerate(pool_enumerator, &name))
358 {
359 u_int attr_pool = get_attr_pool(this, name);
360 if (!attr_pool)
361 {
362 continue;
363 }
364
365 attr_enumerator = this->db->query(this->db,
366 "SELECT count(*) FROM attributes "
367 "WHERE pool = ? AND identity = ?",
368 DB_UINT, attr_pool, DB_UINT, identity, DB_UINT);
369
370 if (attr_enumerator &&
371 attr_enumerator->enumerate(attr_enumerator, &count) &&
372 count != 0)
373 {
374 attr_enumerator->destroy(attr_enumerator);
375 attr_enumerator = this->db->query(this->db,
376 "SELECT type, value FROM attributes "
377 "WHERE pool = ? AND identity = ?", DB_UINT,
378 attr_pool, DB_UINT, identity, DB_INT, DB_BLOB);
379 break;
380 }
381 DESTROY_IF(attr_enumerator);
382 attr_enumerator = NULL;
383 }
384 pool_enumerator->destroy(pool_enumerator);
385 }
386
387 /* in a second step check for attributes that match name */
388 if (!attr_enumerator)
389 {
390 pool_enumerator = pools->create_enumerator(pools);
391 while (pool_enumerator->enumerate(pool_enumerator, &name))
392 {
393 u_int attr_pool = get_attr_pool(this, name);
394 if (!attr_pool)
395 {
396 continue;
397 }
398
399 attr_enumerator = this->db->query(this->db,
400 "SELECT count(*) FROM attributes "
401 "WHERE pool = ? AND identity = 0",
402 DB_UINT, attr_pool, DB_UINT);
403
404 if (attr_enumerator &&
405 attr_enumerator->enumerate(attr_enumerator, &count) &&
406 count != 0)
407 {
408 attr_enumerator->destroy(attr_enumerator);
409 attr_enumerator = this->db->query(this->db,
410 "SELECT type, value FROM attributes "
411 "WHERE pool = ? AND identity = 0",
412 DB_UINT, attr_pool, DB_INT, DB_BLOB);
413 break;
414 }
415 DESTROY_IF(attr_enumerator);
416 attr_enumerator = NULL;
417 }
418 pool_enumerator->destroy(pool_enumerator);
419 }
420
421 this->db->execute(this->db, NULL, "END TRANSACTION");
422
423 /* lastly try to find global attributes */
424 if (!attr_enumerator)
425 {
426 attr_enumerator = this->db->query(this->db,
427 "SELECT type, value FROM attributes "
428 "WHERE pool = 0 AND identity = 0",
429 DB_INT, DB_BLOB);
430 }
431 }
432
433 return (attr_enumerator ? attr_enumerator : enumerator_create_empty());
434 }
435
436 METHOD(sql_attribute_t, destroy, void,
437 private_sql_attribute_t *this)
438 {
439 free(this);
440 }
441
442 /*
443 * see header file
444 */
445 sql_attribute_t *sql_attribute_create(database_t *db)
446 {
447 private_sql_attribute_t *this;
448 time_t now = time(NULL);
449
450 INIT(this,
451 .public = {
452 .provider = {
453 .acquire_address = _acquire_address,
454 .release_address = _release_address,
455 .create_attribute_enumerator = _create_attribute_enumerator,
456 },
457 .destroy = _destroy,
458 },
459 .db = db,
460 .history = lib->settings->get_bool(lib->settings,
461 "libhydra.plugins.attr-sql.lease_history", TRUE),
462 );
463
464 /* close any "online" leases in the case we crashed */
465 if (this->history)
466 {
467 this->db->execute(this->db, NULL,
468 "INSERT INTO leases (address, identity, acquired, released)"
469 " SELECT id, identity, acquired, ? FROM addresses "
470 " WHERE released = 0", DB_UINT, now);
471 }
472 this->db->execute(this->db, NULL,
473 "UPDATE addresses SET released = ? WHERE released = 0",
474 DB_UINT, now);
475 return &this->public;
476 }
477