check for an existing lease over all assigned pools first
[strongswan.git] / src / charon / plugins / 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 "sql_attribute.h"
17
18 #include <time.h>
19
20 #include <daemon.h>
21
22 typedef struct private_sql_attribute_t private_sql_attribute_t;
23
24 /**
25 * private data of sql_attribute
26 */
27 struct private_sql_attribute_t {
28
29 /**
30 * public functions
31 */
32 sql_attribute_t public;
33
34 /**
35 * database connection
36 */
37 database_t *db;
38
39 /**
40 * wheter to record lease history in lease table
41 */
42 bool history;
43 };
44
45 /**
46 * lookup/insert an identity
47 */
48 static u_int get_identity(private_sql_attribute_t *this, identification_t *id)
49 {
50 enumerator_t *e;
51 u_int row;
52
53 /* look for peer identity in the identities table */
54 e = this->db->query(this->db,
55 "SELECT id FROM identities WHERE type = ? AND data = ?",
56 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id),
57 DB_UINT);
58
59 if (e && e->enumerate(e, &row))
60 {
61 e->destroy(e);
62 return row;
63 }
64 DESTROY_IF(e);
65 /* not found, insert new one */
66 if (this->db->execute(this->db, &row,
67 "INSERT INTO identities (type, data) VALUES (?, ?)",
68 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id)) == 1)
69 {
70 return row;
71 }
72 return 0;
73 }
74
75 /**
76 * Lookup pool by name
77 */
78 static u_int get_pool(private_sql_attribute_t *this, char *name, u_int *timeout)
79 {
80 enumerator_t *e;
81 u_int pool;
82
83 e = this->db->query(this->db, "SELECT id, timeout FROM pools WHERE name = ?",
84 DB_TEXT, name, DB_UINT, DB_UINT);
85 if (e && e->enumerate(e, &pool, timeout))
86 {
87 e->destroy(e);
88 return pool;
89 }
90 DESTROY_IF(e);
91 return 0;
92 }
93
94 /**
95 * Look up an existing lease
96 */
97 static host_t* check_lease(private_sql_attribute_t *this, char *name,
98 u_int pool, u_int identity)
99 {
100 while (TRUE)
101 {
102 u_int id;
103 chunk_t address;
104 enumerator_t *e;
105 time_t now = time(NULL);
106
107 e = this->db->query(this->db,
108 "SELECT id, address FROM addresses "
109 "WHERE pool = ? AND identity = ? AND released != 0 LIMIT 1",
110 DB_UINT, pool, DB_UINT, identity, DB_UINT, DB_BLOB);
111 if (!e || !e->enumerate(e, &id, &address))
112 {
113 DESTROY_IF(e);
114 break;
115 }
116 address = chunk_clonea(address);
117 e->destroy(e);
118
119 if (this->db->execute(this->db, NULL,
120 "UPDATE addresses SET acquired = ?, released = 0 "
121 "WHERE id = ? AND identity = ? AND released != 0",
122 DB_UINT, now, DB_UINT, id, DB_UINT, identity) > 0)
123 {
124 host_t *host;
125
126 host = host_create_from_chunk(AF_UNSPEC, address, 0);
127 if (host)
128 {
129 DBG1(DBG_CFG, "acquired existing lease "
130 "for address %H in pool '%s'", host, name);
131 return host;
132 }
133 }
134 }
135 return NULL;
136 }
137
138 /**
139 * We check for unallocated addresses or expired leases. First we select an
140 * address as a candidate, but double check later on if it is still available
141 * during the update operation. This allows us to work without locking.
142 */
143 static host_t* get_lease(private_sql_attribute_t *this, char *name,
144 u_int pool, u_int timeout, u_int identity)
145 {
146 while (TRUE)
147 {
148 u_int id;
149 chunk_t address;
150 enumerator_t *e;
151 time_t now = time(NULL);
152 int hits;
153
154 if (timeout)
155 {
156 /* check for an expired lease */
157 e = this->db->query(this->db,
158 "SELECT id, address FROM addresses "
159 "WHERE pool = ? AND released != 0 AND released < ? LIMIT 1",
160 DB_UINT, pool, DB_UINT, now - timeout, DB_UINT, DB_BLOB);
161 }
162 else
163 {
164 /* with static leases, check for an unallocated address */
165 e = this->db->query(this->db,
166 "SELECT id, address FROM addresses "
167 "WHERE pool = ? AND identity = 0 LIMIT 1",
168 DB_UINT, pool, DB_UINT, DB_BLOB);
169
170 }
171
172 if (!e || !e->enumerate(e, &id, &address))
173 {
174 DESTROY_IF(e);
175 break;
176 }
177 address = chunk_clonea(address);
178 e->destroy(e);
179
180 if (timeout)
181 {
182 hits = this->db->execute(this->db, NULL,
183 "UPDATE addresses SET "
184 "acquired = ?, released = 0, identity = ? "
185 "WHERE id = ? AND released != 0 AND released < ?",
186 DB_UINT, now, DB_UINT, identity,
187 DB_UINT, id, DB_UINT, now - timeout);
188 }
189 else
190 {
191 hits = this->db->execute(this->db, NULL,
192 "UPDATE addresses SET "
193 "acquired = ?, released = 0, identity = ? "
194 "WHERE id = ? AND identity = 0",
195 DB_UINT, now, DB_UINT, identity, DB_UINT, id);
196 }
197 if (hits > 0)
198 {
199 host_t *host;
200
201 host = host_create_from_chunk(AF_UNSPEC, address, 0);
202 if (host)
203 {
204 DBG1(DBG_CFG, "acquired new lease "
205 "for address %H in pool '%s'", host, name);
206 return host;
207 }
208 }
209 }
210 DBG1(DBG_CFG, "no available address found in pool '%s'", name);
211 return NULL;
212 }
213
214 /**
215 * Implementation of attribute_provider_t.acquire_address
216 */
217 static host_t* acquire_address(private_sql_attribute_t *this,
218 char *names, identification_t *id,
219 host_t *requested)
220 {
221 host_t *address = NULL;
222 u_int identity, pool, timeout;
223
224 identity = get_identity(this, id);
225 if (identity)
226 {
227 char *name;
228 enumerator_t *enumerator;
229
230 /* in a first step check for an existing lease over all pools */
231 enumerator = enumerator_create_token(names, ",", " ");
232 while (enumerator->enumerate(enumerator, &name))
233 {
234 pool = get_pool(this, name, &timeout);
235 if (pool)
236 {
237 address = check_lease(this, name, pool, identity);
238 if (address)
239 {
240 enumerator->destroy(enumerator);
241 return address;
242 }
243 }
244 }
245 enumerator->destroy(enumerator);
246
247 /* in a second step get an unallocated address or expired lease */
248 enumerator = enumerator_create_token(names, ",", " ");
249 while (enumerator->enumerate(enumerator, &name))
250 {
251 pool = get_pool(this, name, &timeout);
252 if (pool)
253 {
254 address = get_lease(this, name, pool, timeout, identity);
255 if (address)
256 {
257 break;
258 }
259 }
260 }
261 enumerator->destroy(enumerator);
262 }
263 return address;
264 }
265
266 /**
267 * Implementation of attribute_provider_t.release_address
268 */
269 static bool release_address(private_sql_attribute_t *this,
270 char *name, host_t *address, identification_t *id)
271 {
272 enumerator_t *enumerator;
273 bool found = FALSE;
274 time_t now = time(NULL);
275
276 enumerator = enumerator_create_token(name, ",", " ");
277 while (enumerator->enumerate(enumerator, &name))
278 {
279 u_int pool, timeout;
280
281 pool = get_pool(this, name, &timeout);
282 if (pool)
283 {
284 if (this->history)
285 {
286 this->db->execute(this->db, NULL,
287 "INSERT INTO leases (address, identity, acquired, released)"
288 " SELECT id, identity, acquired, ? FROM addresses "
289 " WHERE pool = ? AND address = ?",
290 DB_UINT, now, DB_UINT, pool,
291 DB_BLOB, address->get_address(address));
292 }
293 if (this->db->execute(this->db, NULL,
294 "UPDATE addresses SET released = ? WHERE "
295 "pool = ? AND address = ?", DB_UINT, time(NULL),
296 DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0)
297 {
298 found = TRUE;
299 break;
300 }
301 }
302 }
303 enumerator->destroy(enumerator);
304 return found;
305 }
306
307 /**
308 * Implementation of sql_attribute_t.destroy
309 */
310 static void destroy(private_sql_attribute_t *this)
311 {
312 free(this);
313 }
314
315 /*
316 * see header file
317 */
318 sql_attribute_t *sql_attribute_create(database_t *db)
319 {
320 private_sql_attribute_t *this = malloc_thing(private_sql_attribute_t);
321 time_t now = time(NULL);
322
323 this->public.provider.acquire_address = (host_t*(*)(attribute_provider_t *this, char*, identification_t *, host_t *))acquire_address;
324 this->public.provider.release_address = (bool(*)(attribute_provider_t *this, char*,host_t *, identification_t*))release_address;
325 this->public.provider.create_attribute_enumerator = (enumerator_t*(*)(attribute_provider_t*, identification_t *id))enumerator_create_empty;
326 this->public.destroy = (void(*)(sql_attribute_t*))destroy;
327
328 this->db = db;
329 this->history = lib->settings->get_bool(lib->settings,
330 "charon.plugins.sql.lease_history", TRUE);
331
332 /* close any "online" leases in the case we crashed */
333 if (this->history)
334 {
335 this->db->execute(this->db, NULL,
336 "INSERT INTO leases (address, identity, acquired, released)"
337 " SELECT id, identity, acquired, ? FROM addresses "
338 " WHERE released = 0", DB_UINT, now);
339 }
340 this->db->execute(this->db, NULL,
341 "UPDATE addresses SET released = ? WHERE released = 0",
342 DB_UINT, now);
343 return &this->public;
344 }
345