sql plugin supports a list of pools to fall back, specified by e.g. rightsourceip...
[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 * $Id$
16 */
17
18 #include "sql_attribute.h"
19
20 #include <daemon.h>
21 #include <utils/mutex.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 * mutex to simulate transactions
42 */
43 mutex_t *mutex;
44 };
45
46 /**
47 * convert a address blob to an ip of the correct family
48 */
49 static host_t *ip_from_chunk(chunk_t address)
50 {
51 switch (address.len)
52 {
53 case 4:
54 return host_create_from_chunk(AF_INET, address, 0);
55 case 16:
56 return host_create_from_chunk(AF_INET6, address, 0);
57 default:
58 return NULL;
59 }
60 }
61
62 /**
63 * increment a chunk, as it would reprensent a network order integer
64 */
65 static void increment_chunk(chunk_t chunk)
66 {
67 int i;
68
69 for (i = chunk.len - 1; i >= 0; i--)
70 {
71 if (++chunk.ptr[i] != 0)
72 {
73 return;
74 }
75 }
76 }
77
78 /**
79 * Lookup if we have an existing lease
80 */
81 static host_t* get_lease(private_sql_attribute_t *this,
82 char *name, identification_t *id)
83 {
84 enumerator_t *e;
85 chunk_t address;
86 host_t *ip = NULL;
87 int lease;
88
89 /* transaction simulation, see create_lease() */
90 this->mutex->lock(this->mutex);
91
92 /* select a lease for "id" which still valid */
93 e = this->db->query(this->db,
94 "SELECT l.id, l.address FROM leases AS l "
95 "JOIN pools AS p ON l.pool = p.id "
96 "JOIN identities AS i ON l.identity = i.id "
97 "WHERE p.name = ? AND i.type = ? AND i.data = ? "
98 "AND (l.released IS NULL OR p.timeout = 0 "
99 " OR (l.released >= (? - p.timeout))) "
100 "ORDER BY l.acquired LIMIT 1", DB_TEXT, name,
101 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id),
102 DB_UINT, time(NULL),
103 DB_UINT, DB_BLOB);
104 if (e)
105 {
106 if (e->enumerate(e, &lease, &address))
107 {
108 /* found one, set the lease to active */
109 if (this->db->execute(this->db, NULL,
110 "UPDATE leases SET released = NULL WHERE id = ?",
111 DB_UINT, lease) > 0)
112 {
113 ip = ip_from_chunk(address);
114 DBG1(DBG_CFG, "reassigning address from valid lease "
115 "from pool '%s'", name);
116 }
117 }
118 e->destroy(e);
119 }
120 this->mutex->unlock(this->mutex);
121 return ip;
122 }
123
124 /**
125 * Create a new lease entry for client
126 */
127 static host_t* create_lease(private_sql_attribute_t *this,
128 char *name, identification_t *id)
129 {
130 enumerator_t *e;
131 chunk_t address;
132 host_t *ip = NULL;
133 u_int pool, identity = 0, released, timeout;
134 bool new = FALSE;
135
136 /* we currently do not use database transactions. While this would be
137 * the clean way, there is no real advantage, but some disadvantages:
138 * - we would require InnoDB for mysql, as MyISAM does not support trans.
139 * - the mysql plugin uses connection pooling, and we would need a
140 * mechanism to lock transactions to a single connection.
141 */
142 this->mutex->lock(this->mutex);
143
144 /* find an address which has outdated leases only. The HAVING clause filters
145 * out leases which are active (released = NULL) or not expired */
146 e = this->db->query(this->db,
147 "SELECT pool, address, released, timeout FROM leases "
148 "JOIN pools ON leases.pool = pools.id "
149 "WHERE name = ? and timeout > 0 "
150 "GROUP BY address HAVING COUNT(released) = COUNT(*) "
151 "AND MAX(released) < (? - timeout) LIMIT 1",
152 DB_TEXT, name, DB_UINT, time(NULL),
153 DB_UINT, DB_BLOB, DB_UINT, DB_UINT);
154
155 if (!e || !e->enumerate(e, &pool, &address, &released, &timeout))
156 {
157 DESTROY_IF(e);
158 /* no outdated lease found, acquire new address */
159 e = this->db->query(this->db,
160 "SELECT id, next FROM pools WHERE name = ? AND next <= end",
161 DB_TEXT, name,
162 DB_UINT, DB_BLOB);
163 if (!e || !e->enumerate(e, &pool, &address))
164 {
165 /* pool seems full */
166 DESTROY_IF(e);
167 this->mutex->unlock(this->mutex);
168 return NULL;
169 }
170 new = TRUE;
171 }
172 address = chunk_clonea(address);
173 e->destroy(e);
174
175 /* look for peer identity in the identities table */
176 e = this->db->query(this->db,
177 "SELECT id FROM identities WHERE type = ? AND data = ?",
178 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id),
179 DB_UINT);
180 if (!e || !e->enumerate(e, &identity))
181 {
182 DESTROY_IF(e);
183 /* not found, insert new one */
184 this->db->execute(this->db, &identity,
185 "INSERT INTO identities (type, data) VALUES (?, ?)",
186 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id));
187 }
188 else
189 {
190 e->destroy(e);
191 }
192 /* if we have an identity, insert a new lease */
193 if (identity)
194 {
195 if (this->db->execute(this->db, NULL,
196 "INSERT INTO leases (pool, address, identity, acquired) "
197 "VALUES (?, ?, ?, ?)",
198 DB_UINT, pool, DB_BLOB, address, DB_UINT, identity,
199 DB_UINT, time(NULL)) > 0)
200 {
201 ip = ip_from_chunk(address);
202 if (new)
203 { /* update next address, as we have consumed one */
204 increment_chunk(address);
205 this->db->execute(this->db, NULL,
206 "UPDATE pools SET next = ? WHERE id = ?",
207 DB_BLOB, address, DB_UINT, pool);
208 DBG1(DBG_CFG, "assigning lease with new address "
209 "from pool '%s'", name);
210 }
211 else
212 {
213 DBG1(DBG_CFG, "reassigning address from expired lease "
214 "from pool '%s'", name);
215 }
216 }
217 }
218 this->mutex->unlock(this->mutex);
219 return ip;
220 }
221
222 /**
223 * Implementation of attribute_provider_t.acquire_address
224 */
225 static host_t* acquire_address(private_sql_attribute_t *this,
226 char *name, identification_t *id,
227 auth_info_t *auth, host_t *requested)
228 {
229 enumerator_t *enumerator;
230 host_t *ip = NULL;
231
232 enumerator = enumerator_create_token(name, ",", " ");
233 while (enumerator->enumerate(enumerator, &name))
234 {
235 ip = get_lease(this, name, id);
236 if (ip)
237 {
238 break;
239 }
240 ip = create_lease(this, name, id);
241 if (ip)
242 {
243 break;
244 }
245 }
246 enumerator->destroy(enumerator);
247 return ip;
248 }
249
250 /**
251 * Implementation of attribute_provider_t.release_address
252 */
253 static bool release_address(private_sql_attribute_t *this,
254 char *name, host_t *address)
255 {
256 enumerator_t *enumerator;
257
258 enumerator = enumerator_create_token(name, ",", " ");
259 while (enumerator->enumerate(enumerator, &name))
260 {
261 if (this->db->execute(this->db, NULL,
262 "UPDATE leases SET released = ? WHERE "
263 "pool IN (SELECT id FROM pools WHERE name = ?) AND "
264 "address = ? AND released IS NULL",
265 DB_UINT, time(NULL),
266 DB_TEXT, name, DB_BLOB, address->get_address(address)) > 0)
267 {
268 enumerator->destroy(enumerator);
269 return TRUE;
270 }
271 }
272 enumerator->destroy(enumerator);
273 return FALSE;
274 }
275
276 /**
277 * Implementation of sql_attribute_t.destroy
278 */
279 static void destroy(private_sql_attribute_t *this)
280 {
281 this->mutex->destroy(this->mutex);
282 free(this);
283 }
284
285 /*
286 * see header file
287 */
288 sql_attribute_t *sql_attribute_create(database_t *db)
289 {
290 private_sql_attribute_t *this = malloc_thing(private_sql_attribute_t);
291
292 this->public.provider.acquire_address = (host_t*(*)(attribute_provider_t *this, char*, identification_t *,auth_info_t *, host_t *))acquire_address;
293 this->public.provider.release_address = (bool(*)(attribute_provider_t *this, char*,host_t *))release_address;
294 this->public.destroy = (void(*)(sql_attribute_t*))destroy;
295
296 this->db = db;
297 this->mutex = mutex_create(MUTEX_DEFAULT);
298
299 /* close any "online" leases in the case we crashed */
300 this->db->execute(this->db, NULL,
301 "UPDATE leases SET released = ? WHERE released IS NULL",
302 DB_UINT, time(NULL));
303
304 return &this->public;
305 }
306