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