experimental and untested reimplementation of sql based IP pool
[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
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 /**
41 * read a host_t address from the addresses table
42 */
43 static host_t *host_from_chunk(chunk_t chunk)
44 {
45 switch (chunk.len)
46 {
47 case 4:
48 return host_create_from_chunk(AF_INET, chunk, 0);
49 case 16:
50 return host_create_from_chunk(AF_INET6, chunk, 0);
51 default:
52 return NULL;
53 }
54 }
55
56 /**
57 * lookup/insert an identity
58 */
59 static u_int get_identity(private_sql_attribute_t *this, identification_t *id)
60 {
61 enumerator_t *e;
62 u_int row;
63
64 /* look for peer identity in the identities table */
65 e = this->db->query(this->db,
66 "SELECT id FROM identities WHERE type = ? AND data = ?",
67 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id),
68 DB_UINT);
69
70 if (e && e->enumerate(e, &row))
71 {
72 e->destroy(e);
73 return row;
74 }
75 DESTROY_IF(e);
76 /* not found, insert new one */
77 if (this->db->execute(this->db, &row,
78 "INSERT INTO identities (type, data) VALUES (?, ?)",
79 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id)) == 1)
80 {
81 return row;
82 }
83 return 0;
84 }
85
86 /**
87 * Lookup pool by name
88 */
89 static u_int get_pool(private_sql_attribute_t *this, char *name, u_int *timeout)
90 {
91 enumerator_t *e;
92 u_int pool;
93
94 e = this->db->query(this->db, "SELECT id, timeout FROM pools WHERE name = ?",
95 DB_TEXT, name, DB_UINT, DB_UINT);
96 if (e && e->enumerate(e, &pool, timeout))
97 {
98 e->destroy(e);
99 return pool;
100 }
101 DBG1(DBG_CFG, "ip pool '%s' not found");
102 return 0;
103 }
104
105 /**
106 * Lookup a lease
107 */
108 static host_t *get_address(private_sql_attribute_t *this, char *name,
109 u_int pool, u_int timeout, u_int identity)
110 {
111 enumerator_t *e;
112 u_int id;
113 chunk_t address;
114 host_t *host;
115 time_t now = time(NULL);
116
117 /* We check for leases for that identity first and for other expired
118 * leases afterwards. We select an address as a candidate, but double
119 * check if it is still valid in the update. This allows us to work
120 * without locking. */
121
122 /* check for an existing lease for that identity */
123 while (TRUE)
124 {
125 e = this->db->query(this->db,
126 "SELECT id, address FROM addresses "
127 "WHERE pool = ? AND identity = ? AND released != 0 LIMIT 1",
128 DB_UINT, pool, DB_UINT, identity, DB_UINT, DB_BLOB);
129 if (!e || !e->enumerate(e, &id, &address))
130 {
131 DESTROY_IF(e);
132 break;
133 }
134 address = chunk_clonea(address);
135 e->destroy(e);
136 if (this->db->execute(this->db, NULL,
137 "UPDATE addresses SET acquired = ?, released = 0 "
138 "WHERE id = ? AND identity = ? AND released != 0",
139 DB_UINT, now, DB_UINT, id, DB_UINT, identity) > 0)
140 {
141 host = host_from_chunk(address);
142 if (host)
143 {
144 DBG1(DBG_CFG, "acquired existing lease "
145 "for address %H in pool '%s'", host, name);
146 return host;
147 }
148 }
149 }
150
151 /* check for an expired lease */
152 while (TRUE)
153 {
154 e = this->db->query(this->db,
155 "SELECT id, address FROM addresses "
156 "WHERE pool = ? AND released != 0 AND released < ? LIMIT 1",
157 DB_UINT, pool, DB_UINT, now - timeout, DB_UINT, DB_BLOB);
158 if (!e || !e->enumerate(e, &id, &address))
159 {
160 DESTROY_IF(e);
161 break;
162 }
163 address = chunk_clonea(address);
164 e->destroy(e);
165
166 if (this->db->execute(this->db, NULL,
167 "UPDATE addresses SET "
168 "acquired = ?, released = 0, identity = ? "
169 "WHERE id = ? AND released != 0 AND released < ?",
170 DB_UINT, now, DB_UINT, identity,
171 DB_UINT, id, DB_UINT, now - timeout) > 0)
172 {
173 host = host_from_chunk(address);
174 if (host)
175 {
176 DBG1(DBG_CFG, "acquired new lease "
177 "for address %H in pool '%s'", host, name);
178 return host;
179 }
180 }
181 }
182 DBG1(DBG_CFG, "no available address found in pool '%s'", name);
183 return 0;
184 }
185
186 /**
187 * Implementation of attribute_provider_t.acquire_address
188 */
189 static host_t* acquire_address(private_sql_attribute_t *this,
190 char *name, identification_t *id,
191 auth_info_t *auth, host_t *requested)
192 {
193 enumerator_t *enumerator;
194 u_int pool, timeout, identity;
195 host_t *address = NULL;
196
197 identity = get_identity(this, id);
198 if (identity)
199 {
200 enumerator = enumerator_create_token(name, ",", " ");
201 while (enumerator->enumerate(enumerator, &name))
202 {
203 pool = get_pool(this, name, &timeout);
204 if (pool)
205 {
206 address = get_address(this, name, pool, timeout, identity);
207 if (address)
208 {
209 break;
210 }
211 }
212 }
213 enumerator->destroy(enumerator);
214 }
215 return address;
216 }
217
218 /**
219 * Implementation of attribute_provider_t.release_address
220 */
221 static bool release_address(private_sql_attribute_t *this,
222 char *name, host_t *address)
223 {
224 enumerator_t *enumerator;
225 bool found = FALSE;
226 time_t now = time(NULL);
227
228 enumerator = enumerator_create_token(name, ",", " ");
229 while (enumerator->enumerate(enumerator, &name))
230 {
231 u_int pool, timeout;
232
233 pool = get_pool(this, name, &timeout);
234 if (pool)
235 {
236 if (this->db->execute(this->db, NULL,
237 "INSERT INTO leases (address, identity, acquired, released)"
238 " SELECT id, identity, acquired, ? FROM addresses "
239 " WHERE pool = ? AND address = ?",
240 DB_UINT, now, DB_UINT, pool,
241 DB_BLOB, address->get_address(address)) > 0 &&
242 this->db->execute(this->db, NULL,
243 "UPDATE addresses SET released = ? WHERE "
244 "pool = ? AND address = ?", DB_UINT, time(NULL),
245 DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0)
246 {
247 found = TRUE;
248 break;
249 }
250 }
251 }
252 enumerator->destroy(enumerator);
253 return found;
254 }
255
256 /**
257 * Implementation of sql_attribute_t.destroy
258 */
259 static void destroy(private_sql_attribute_t *this)
260 {
261 free(this);
262 }
263
264 /*
265 * see header file
266 */
267 sql_attribute_t *sql_attribute_create(database_t *db)
268 {
269 private_sql_attribute_t *this = malloc_thing(private_sql_attribute_t);
270 time_t now = time(NULL);
271
272 this->public.provider.acquire_address = (host_t*(*)(attribute_provider_t *this, char*, identification_t *,auth_info_t *, host_t *))acquire_address;
273 this->public.provider.release_address = (bool(*)(attribute_provider_t *this, char*,host_t *))release_address;
274 this->public.destroy = (void(*)(sql_attribute_t*))destroy;
275
276 this->db = db;
277
278 /* close any "online" leases in the case we crashed */
279 this->db->execute(this->db, NULL,
280 "INSERT INTO leases (address, identity, acquired, released)"
281 " SELECT id, identity, acquired, ? FROM addresses "
282 " WHERE released = 0", DB_UINT, now);
283 this->db->execute(this->db, NULL,
284 "UPDATE addresses SET released = ? WHERE released = 0",
285 DB_UINT, now);
286 return &this->public;
287 }
288