717d8fe595c379da05207dd129ee613acda94c88
[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))) "
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_UINT, time(NULL),
103 DB_INT, 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 release = NULL WHERE id = ?",
111 DB_INT, 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 int pool, identity = 0;
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 */
145 e = this->db->query(this->db,
146 "SELECT pool, address FROM leases "
147 "JOIN pools ON leases.pool = pools.id "
148 "WHERE name = ? "
149 "GROUP BY address HAVING release NOTNULL "
150 "AND MAX(release) < ? + pools.timeout LIMIT 1",
151 DB_TEXT, name, DB_UINT, time(NULL),
152 DB_INT, DB_BLOB);
153
154 if (!e || !e->enumerate(e, &pool, &address))
155 {
156 DESTROY_IF(e);
157 /* no outdated lease found, acquire new address */
158 e = this->db->query(this->db,
159 "SELECT id, next FROM pools WHERE name = ? AND next <= end",
160 DB_TEXT, name,
161 DB_INT, DB_BLOB);
162 if (!e || !e->enumerate(e, &pool, &address))
163 {
164 /* pool seems full */
165 DESTROY_IF(e);
166 this->mutex->unlock(this->mutex);
167 return NULL;
168 }
169 new = TRUE;
170 }
171 address = chunk_clonea(address);
172 e->destroy(e);
173
174 /* look for peer identity in the identities table */
175 e = this->db->query(this->db,
176 "SELECT id FROM identities WHERE type = ? AND data = ?",
177 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id),
178 DB_INT);
179 if (!e || !e->enumerate(e, &identity))
180 {
181 DESTROY_IF(e);
182 /* not found, insert new one */
183 this->db->execute(this->db, &identity,
184 "INSERT INTO identities (type, data) VALUES (?, ?)",
185 DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id));
186 }
187 else
188 {
189 e->destroy(e);
190 }
191 /* if we have an identity, insert a new lease */
192 if (identity)
193 {
194 if (this->db->execute(this->db, NULL,
195 "INSERT INTO leases (pool, address, identity, acquire) "
196 "VALUES (?, ?, ?, ?)",
197 DB_INT, pool, DB_BLOB, address, DB_INT, identity,
198 DB_UINT, time(NULL)) > 0)
199 {
200 ip = ip_from_chunk(address);
201 if (new)
202 { /* update next address, as we have consumed one */
203 increment_chunk(address);
204 this->db->execute(this->db, NULL,
205 "UPDATE pools set next = ? WHERE id = ?",
206 DB_BLOB, address, DB_INT, pool);
207 DBG1(DBG_CFG, "assigning lease with new address "
208 "from pool %s", name);
209 }
210 else
211 {
212 DBG1(DBG_CFG, "reassigning address from expired lease "
213 "from pool %s", name);
214 }
215 }
216 }
217 this->mutex->unlock(this->mutex);
218 return ip;
219 }
220
221 /**
222 * Implementation of attribute_provider_t.acquire_address
223 */
224 static host_t* acquire_address(private_sql_attribute_t *this,
225 char *name, identification_t *id,
226 auth_info_t *auth, host_t *requested)
227 {
228 host_t *ip;
229
230 ip = get_lease(this, name, id);
231 if (!ip)
232 {
233 ip = create_lease(this, name, id);
234 }
235 return ip;
236 }
237
238 /**
239 * Implementation of attribute_provider_t.release_address
240 */
241 static bool release_address(private_sql_attribute_t *this,
242 char *name, host_t *address)
243 {
244 if (this->db->execute(this->db, NULL,
245 "UPDATE leases SET release = ? WHERE "
246 "pool IN (SELECT id FROM pools WHERE name = ?) AND "
247 "address = ? AND release ISNULL",
248 DB_UINT, time(NULL),
249 DB_TEXT, name, DB_BLOB, address->get_address(address)) > 0)
250 {
251 return TRUE;
252 }
253 return FALSE;
254 }
255
256 /**
257 * Implementation of sql_attribute_t.destroy
258 */
259 static void destroy(private_sql_attribute_t *this)
260 {
261 this->mutex->destroy(this->mutex);
262 free(this);
263 }
264
265 /*
266 * see header file
267 */
268 sql_attribute_t *sql_attribute_create(database_t *db)
269 {
270 private_sql_attribute_t *this = malloc_thing(private_sql_attribute_t);
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 this->mutex = mutex_create(MUTEX_DEFAULT);
278
279 return &this->public;
280 }
281