8055be71c66e90169767cf52b290b528d746c49e
[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, char *name, 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 pool = get_pool(this, name, &timeout);
246 if (pool)
247 {
248 /* check for an existing lease */
249 address = check_lease(this, name, pool, identity);
250 if (address == NULL)
251 {
252 /* get an unallocated address or expired lease */
253 address = get_lease(this, name, pool, timeout, identity);
254 }
255 }
256 }
257 return address;
258 }
259
260 METHOD(attribute_provider_t, release_address, bool,
261 private_sql_attribute_t *this, char *name, host_t *address,
262 identification_t *id)
263 {
264 u_int pool, timeout;
265 time_t now = time(NULL);
266
267 pool = get_pool(this, name, &timeout);
268 if (pool)
269 {
270 if (this->history)
271 {
272 this->db->execute(this->db, NULL,
273 "INSERT INTO leases (address, identity, acquired, released)"
274 " SELECT id, identity, acquired, ? FROM addresses "
275 " WHERE pool = ? AND address = ?",
276 DB_UINT, now, DB_UINT, pool,
277 DB_BLOB, address->get_address(address));
278 }
279 if (this->db->execute(this->db, NULL,
280 "UPDATE addresses SET released = ? WHERE "
281 "pool = ? AND address = ?", DB_UINT, time(NULL),
282 DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0)
283 {
284 return TRUE;
285 }
286 }
287 return FALSE;
288 }
289
290 METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*,
291 private_sql_attribute_t *this, linked_list_t *pools, identification_t *id,
292 linked_list_t *vips)
293 {
294 enumerator_t *attr_enumerator = NULL;
295
296 if (vips->get_count(vips))
297 {
298 enumerator_t *pool_enumerator;
299 u_int count;
300 char *name;
301
302 this->db->execute(this->db, NULL, "BEGIN EXCLUSIVE TRANSACTION");
303
304 /* in a first step check for attributes that match name and id */
305 if (id)
306 {
307 u_int identity = get_identity(this, id);
308
309 pool_enumerator = pools->create_enumerator(pools);
310 while (pool_enumerator->enumerate(pool_enumerator, &name))
311 {
312 u_int attr_pool = get_attr_pool(this, name);
313 if (!attr_pool)
314 {
315 continue;
316 }
317
318 attr_enumerator = this->db->query(this->db,
319 "SELECT count(*) FROM attributes "
320 "WHERE pool = ? AND identity = ?",
321 DB_UINT, attr_pool, DB_UINT, identity, DB_UINT);
322
323 if (attr_enumerator &&
324 attr_enumerator->enumerate(attr_enumerator, &count) &&
325 count != 0)
326 {
327 attr_enumerator->destroy(attr_enumerator);
328 attr_enumerator = this->db->query(this->db,
329 "SELECT type, value FROM attributes "
330 "WHERE pool = ? AND identity = ?", DB_UINT,
331 attr_pool, DB_UINT, identity, DB_INT, DB_BLOB);
332 break;
333 }
334 DESTROY_IF(attr_enumerator);
335 attr_enumerator = NULL;
336 }
337 pool_enumerator->destroy(pool_enumerator);
338 }
339
340 /* in a second step check for attributes that match name */
341 if (!attr_enumerator)
342 {
343 pool_enumerator = pools->create_enumerator(pools);
344 while (pool_enumerator->enumerate(pool_enumerator, &name))
345 {
346 u_int attr_pool = get_attr_pool(this, name);
347 if (!attr_pool)
348 {
349 continue;
350 }
351
352 attr_enumerator = this->db->query(this->db,
353 "SELECT count(*) FROM attributes "
354 "WHERE pool = ? AND identity = 0",
355 DB_UINT, attr_pool, DB_UINT);
356
357 if (attr_enumerator &&
358 attr_enumerator->enumerate(attr_enumerator, &count) &&
359 count != 0)
360 {
361 attr_enumerator->destroy(attr_enumerator);
362 attr_enumerator = this->db->query(this->db,
363 "SELECT type, value FROM attributes "
364 "WHERE pool = ? AND identity = 0",
365 DB_UINT, attr_pool, DB_INT, DB_BLOB);
366 break;
367 }
368 DESTROY_IF(attr_enumerator);
369 attr_enumerator = NULL;
370 }
371 pool_enumerator->destroy(pool_enumerator);
372 }
373
374 this->db->execute(this->db, NULL, "END TRANSACTION");
375
376 /* lastly try to find global attributes */
377 if (!attr_enumerator)
378 {
379 attr_enumerator = this->db->query(this->db,
380 "SELECT type, value FROM attributes "
381 "WHERE pool = 0 AND identity = 0",
382 DB_INT, DB_BLOB);
383 }
384 }
385
386 return (attr_enumerator ? attr_enumerator : enumerator_create_empty());
387 }
388
389 METHOD(sql_attribute_t, destroy, void,
390 private_sql_attribute_t *this)
391 {
392 free(this);
393 }
394
395 /*
396 * see header file
397 */
398 sql_attribute_t *sql_attribute_create(database_t *db)
399 {
400 private_sql_attribute_t *this;
401 time_t now = time(NULL);
402
403 INIT(this,
404 .public = {
405 .provider = {
406 .acquire_address = _acquire_address,
407 .release_address = _release_address,
408 .create_attribute_enumerator = _create_attribute_enumerator,
409 },
410 .destroy = _destroy,
411 },
412 .db = db,
413 .history = lib->settings->get_bool(lib->settings,
414 "libhydra.plugins.attr-sql.lease_history", TRUE),
415 );
416
417 /* close any "online" leases in the case we crashed */
418 if (this->history)
419 {
420 this->db->execute(this->db, NULL,
421 "INSERT INTO leases (address, identity, acquired, released)"
422 " SELECT id, identity, acquired, ? FROM addresses "
423 " WHERE released = 0", DB_UINT, now);
424 }
425 this->db->execute(this->db, NULL,
426 "UPDATE addresses SET released = ? WHERE released = 0",
427 DB_UINT, now);
428 return &this->public;
429 }
430