sql pool prototype
[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.release ISNULL OR p.timeout ISNULL "
99 " OR (l.release < (p.timeout + l.acquire))) "
100 "ORDER BY l.acquire LIMIT 1", DB_TEXT, name,
101 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id),
102 DB_INT, DB_BLOB);
103 if (e)
104 {
105 if (e->enumerate(e, &lease, &address))
106 {
107 /* found one, set the lease to active */
108 if (this->db->execute(this->db, NULL,
109 "UPDATE leases SET release = NULL WHERE id = ?",
110 DB_INT, lease) > 0)
111 {
112 ip = ip_from_chunk(address);
113 DBG1(DBG_CFG, "reassigning address from valid lease "
114 "from pool %s", name);
115 }
116 }
117 e->destroy(e);
118 }
119 this->mutex->unlock(this->mutex);
120 return ip;
121 }
122
123 /**
124 * Create a new lease entry for client
125 */
126 static host_t* create_lease(private_sql_attribute_t *this,
127 char *name, identification_t *id)
128 {
129 enumerator_t *e;
130 chunk_t address;
131 host_t *ip = NULL;
132 int pool, identity = 0;
133 bool new = FALSE;
134
135 /* we currently do not use database transactions. While this would be
136 * the clean way, there is no real advantage, but some disadvantages:
137 * - we would require InnoDB for mysql, as MyISAM does not support trans.
138 * - the mysql plugin uses connection pooling, and we would need a
139 * mechanism to lock transactions to a single connection.
140 */
141 this->mutex->lock(this->mutex);
142
143 /* find an address which has outdated leases only */
144 e = this->db->query(this->db,
145 "SELECT pool, address FROM leases "
146 "JOIN pools ON leases.pool = pools.id "
147 "WHERE name = ? "
148 "GROUP BY address HAVING release NOTNULL "
149 "AND MAX(release) < ? + pools.timeout LIMIT 1",
150 DB_TEXT, name, DB_UINT, time(NULL),
151 DB_INT, DB_BLOB);
152
153 if (!e || !e->enumerate(e, &pool, &address))
154 {
155 DESTROY_IF(e);
156 /* no outdated lease found, acquire new address */
157 e = this->db->query(this->db,
158 "SELECT id, next FROM pools WHERE name = ? AND next <= end",
159 DB_TEXT, name,
160 DB_INT, DB_BLOB);
161 if (!e || !e->enumerate(e, &pool, &address))
162 {
163 /* pool seems full */
164 DESTROY_IF(e);
165 this->mutex->unlock(this->mutex);
166 return NULL;
167 }
168 new = TRUE;
169 }
170 address = chunk_clonea(address);
171 e->destroy(e);
172
173 /* look for peer identity in the identities table */
174 e = this->db->query(this->db,
175 "SELECT id FROM identities WHERE type = ? AND data = ?",
176 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id),
177 DB_INT);
178 if (!e || !e->enumerate(e, &identity))
179 {
180 DESTROY_IF(e);
181 /* not found, insert new one */
182 this->db->execute(this->db, &identity,
183 "INSERT INTO identities (type, data) VALUES (?, ?)",
184 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id));
185 }
186 else
187 {
188 e->destroy(e);
189 }
190 /* if we have an identity, insert a new lease */
191 if (identity)
192 {
193 if (this->db->execute(this->db, NULL,
194 "INSERT INTO leases (pool, address, identity, acquire) "
195 "VALUES (?, ?, ?, ?)",
196 DB_INT, pool, DB_BLOB, address, DB_INT, identity,
197 DB_UINT, time(NULL)) > 0)
198 {
199 ip = ip_from_chunk(address);
200 if (new)
201 { /* update next address, as we have consumed one */
202 increment_chunk(address);
203 this->db->execute(this->db, NULL,
204 "UPDATE pools set next = ? WHERE id = ?",
205 DB_BLOB, address, DB_INT, pool);
206 DBG1(DBG_CFG, "assigning lease with new address "
207 "from pool %s", name);
208 }
209 else
210 {
211 DBG1(DBG_CFG, "reassigning address from expired lease "
212 "from pool %s", name);
213 }
214 }
215 }
216 this->mutex->unlock(this->mutex);
217 return ip;
218 }
219
220 /**
221 * Implementation of attribute_provider_t.acquire_address
222 */
223 static host_t* acquire_address(private_sql_attribute_t *this,
224 char *name, identification_t *id,
225 auth_info_t *auth, host_t *requested)
226 {
227 host_t *ip;
228
229 ip = get_lease(this, name, id);
230 if (!ip)
231 {
232 ip = create_lease(this, name, id);
233 }
234 return ip;
235 }
236
237 /**
238 * Implementation of attribute_provider_t.release_address
239 */
240 static bool release_address(private_sql_attribute_t *this,
241 char *name, host_t *address)
242 {
243 if (this->db->execute(this->db, NULL,
244 "UPDATE leases SET release = ? WHERE "
245 "pool IN (SELECT id FROM pools WHERE name = ?) AND "
246 "address = ? AND release ISNULL",
247 DB_UINT, time(NULL),
248 DB_TEXT, name, DB_BLOB, address->get_address(address)) > 0)
249 {
250 return TRUE;
251 }
252 return FALSE;
253 }
254
255 /**
256 * Implementation of sql_attribute_t.destroy
257 */
258 static void destroy(private_sql_attribute_t *this)
259 {
260 this->mutex->destroy(this->mutex);
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
271 this->public.provider.acquire_address = (host_t*(*)(attribute_provider_t *this, char*, identification_t *,auth_info_t *, host_t *))acquire_address;
272 this->public.provider.release_address = (bool(*)(attribute_provider_t *this, char*,host_t *))release_address;
273 this->public.destroy = (void(*)(sql_attribute_t*))destroy;
274
275 this->db = db;
276 this->mutex = mutex_create(MUTEX_DEFAULT);
277
278 return &this->public;
279 }
280