fa728eab641b4ba98821379fee05ad1652820cf9
[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 * Lookup a lease
96 */
97 static host_t *get_address(private_sql_attribute_t *this, char *name,
98 u_int pool, u_int timeout, u_int identity)
99 {
100 enumerator_t *e;
101 u_int id;
102 chunk_t address;
103 host_t *host;
104 time_t now = time(NULL);
105
106 /* We check for leases for that identity first and for other expired
107 * leases afterwards. We select an address as a candidate, but double
108 * check if it is still valid in the update. This allows us to work
109 * without locking. */
110
111 /* check for an existing lease for that identity */
112 while (TRUE)
113 {
114 e = this->db->query(this->db,
115 "SELECT id, address FROM addresses "
116 "WHERE pool = ? AND identity = ? AND released != 0 LIMIT 1",
117 DB_UINT, pool, DB_UINT, identity, DB_UINT, DB_BLOB);
118 if (!e || !e->enumerate(e, &id, &address))
119 {
120 DESTROY_IF(e);
121 break;
122 }
123 address = chunk_clonea(address);
124 e->destroy(e);
125 if (this->db->execute(this->db, NULL,
126 "UPDATE addresses SET acquired = ?, released = 0 "
127 "WHERE id = ? AND identity = ? AND released != 0",
128 DB_UINT, now, DB_UINT, id, DB_UINT, identity) > 0)
129 {
130 host = host_create_from_chunk(AF_UNSPEC, address, 0);
131 if (host)
132 {
133 DBG1(DBG_CFG, "acquired existing lease "
134 "for address %H in pool '%s'", host, name);
135 return host;
136 }
137 }
138 }
139
140 /* check for an available address */
141 while (TRUE)
142 {
143 int hits;
144
145 if (timeout)
146 {
147 /* check for an expired lease */
148 e = this->db->query(this->db,
149 "SELECT id, address FROM addresses "
150 "WHERE pool = ? AND released != 0 AND released < ? LIMIT 1",
151 DB_UINT, pool, DB_UINT, now - timeout, DB_UINT, DB_BLOB);
152 }
153 else
154 {
155 /* with static leases, check for an unallocated address */
156 e = this->db->query(this->db,
157 "SELECT id, address FROM addresses "
158 "WHERE pool = ? AND identity = 0 LIMIT 1",
159 DB_UINT, pool, DB_UINT, DB_BLOB);
160
161 }
162
163 if (!e || !e->enumerate(e, &id, &address))
164 {
165 DESTROY_IF(e);
166 break;
167 }
168 address = chunk_clonea(address);
169 e->destroy(e);
170
171 if (timeout)
172 {
173 hits = this->db->execute(this->db, NULL,
174 "UPDATE addresses SET "
175 "acquired = ?, released = 0, identity = ? "
176 "WHERE id = ? AND released != 0 AND released < ?",
177 DB_UINT, now, DB_UINT, identity,
178 DB_UINT, id, DB_UINT, now - timeout);
179 }
180 else
181 {
182 hits = this->db->execute(this->db, NULL,
183 "UPDATE addresses SET "
184 "acquired = ?, released = 0, identity = ? "
185 "WHERE id = ? AND identity = 0",
186 DB_UINT, now, DB_UINT, identity, DB_UINT, id);
187 }
188 if (hits > 0)
189 {
190 host = host_create_from_chunk(AF_UNSPEC, address, 0);
191 if (host)
192 {
193 DBG1(DBG_CFG, "acquired new lease "
194 "for address %H in pool '%s'", host, name);
195 return host;
196 }
197 }
198 }
199 DBG1(DBG_CFG, "no available address found in pool '%s'", name);
200 return 0;
201 }
202
203 /**
204 * Implementation of attribute_provider_t.acquire_address
205 */
206 static host_t* acquire_address(private_sql_attribute_t *this,
207 char *name, identification_t *id,
208 host_t *requested)
209 {
210 enumerator_t *enumerator;
211 u_int pool, timeout, identity;
212 host_t *address = NULL;
213
214 identity = get_identity(this, id);
215 if (identity)
216 {
217 enumerator = enumerator_create_token(name, ",", " ");
218 while (enumerator->enumerate(enumerator, &name))
219 {
220 pool = get_pool(this, name, &timeout);
221 if (pool)
222 {
223 address = get_address(this, name, pool, timeout, identity);
224 if (address)
225 {
226 break;
227 }
228 }
229 }
230 enumerator->destroy(enumerator);
231 }
232 return address;
233 }
234
235 /**
236 * Implementation of attribute_provider_t.release_address
237 */
238 static bool release_address(private_sql_attribute_t *this,
239 char *name, host_t *address, identification_t *id)
240 {
241 enumerator_t *enumerator;
242 bool found = FALSE;
243 time_t now = time(NULL);
244
245 enumerator = enumerator_create_token(name, ",", " ");
246 while (enumerator->enumerate(enumerator, &name))
247 {
248 u_int pool, timeout;
249
250 pool = get_pool(this, name, &timeout);
251 if (pool)
252 {
253 if (this->history)
254 {
255 this->db->execute(this->db, NULL,
256 "INSERT INTO leases (address, identity, acquired, released)"
257 " SELECT id, identity, acquired, ? FROM addresses "
258 " WHERE pool = ? AND address = ?",
259 DB_UINT, now, DB_UINT, pool,
260 DB_BLOB, address->get_address(address));
261 }
262 if (this->db->execute(this->db, NULL,
263 "UPDATE addresses SET released = ? WHERE "
264 "pool = ? AND address = ?", DB_UINT, time(NULL),
265 DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0)
266 {
267 found = TRUE;
268 break;
269 }
270 }
271 }
272 enumerator->destroy(enumerator);
273 return found;
274 }
275
276 /**
277 * Implementation of sql_attribute_t.destroy
278 */
279 static void destroy(private_sql_attribute_t *this)
280 {
281 free(this);
282 }
283
284 /*
285 * see header file
286 */
287 sql_attribute_t *sql_attribute_create(database_t *db)
288 {
289 private_sql_attribute_t *this = malloc_thing(private_sql_attribute_t);
290 time_t now = time(NULL);
291
292 this->public.provider.acquire_address = (host_t*(*)(attribute_provider_t *this, char*, identification_t *, host_t *))acquire_address;
293 this->public.provider.release_address = (bool(*)(attribute_provider_t *this, char*,host_t *, identification_t*))release_address;
294 this->public.provider.create_attribute_enumerator = (enumerator_t*(*)(attribute_provider_t*, identification_t *id))enumerator_create_empty;
295 this->public.destroy = (void(*)(sql_attribute_t*))destroy;
296
297 this->db = db;
298 this->history = lib->settings->get_bool(lib->settings,
299 "charon.plugins.sql.lease_history", TRUE);
300
301 /* close any "online" leases in the case we crashed */
302 if (this->history)
303 {
304 this->db->execute(this->db, NULL,
305 "INSERT INTO leases (address, identity, acquired, released)"
306 " SELECT id, identity, acquired, ? FROM addresses "
307 " WHERE released = 0", DB_UINT, now);
308 }
309 this->db->execute(this->db, NULL,
310 "UPDATE addresses SET released = ? WHERE released = 0",
311 DB_UINT, now);
312 return &this->public;
313 }
314