510c769c694804a7f98757f934173fe5bb1bf5ea
[strongswan.git] / src / charon / plugins / sql / pool.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 #define _GNU_SOURCE
19 #include <getopt.h>
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <time.h>
23
24 #include <debug.h>
25 #include <library.h>
26 #include <utils/host.h>
27
28 /**
29 * global database handle
30 */
31 database_t *db;
32
33 /**
34 * --start/--end addresses of various subcommands
35 */
36 host_t *start = NULL, *end = NULL;
37
38 /**
39 * create a host from a blob
40 */
41 static host_t *host_create_from_blob(chunk_t blob)
42 {
43 return host_create_from_chunk(blob.len == 4 ? AF_INET : AF_INET6, blob, 0);
44 }
45
46 /**
47 * calculate the size of a pool using start and end address chunk
48 */
49 static u_int get_pool_size(chunk_t start, chunk_t end)
50 {
51 u_int *start_ptr, *end_ptr;
52
53 if (start.len < sizeof(u_int) || end.len < sizeof(u_int))
54 {
55 return 0;
56 }
57 start_ptr = (u_int*)(start.ptr + start.len - sizeof(u_int));
58 end_ptr = (u_int*)(end.ptr + end.len - sizeof(u_int));
59 return ntohl(*end_ptr) - ntohl(*start_ptr) + 1;
60 }
61
62 /**
63 * print usage info
64 */
65 static void usage(void)
66 {
67 printf("\
68 Usage:\n\
69 ipsec pool --status|--add|--del|--resize|--purge [options]\n\
70 \n\
71 ipsec pool --status\n\
72 Show a list of installed pools with statistics.\n\
73 \n\
74 ipsec pool --add <name> --start <start> --end <end> [--timeout <timeout>]\n\
75 Add a new pool to the database.\n\
76 name: Name of the pool, as used in ipsec.conf rightsourceip=%%name\n\
77 start: Start address of the pool\n\
78 end: End address of the pool\n\
79 timeout: Lease time in hours, 0 for static leases\n\
80 \n\
81 ipsec pool --del <name>\n\
82 Delete a pool from the database.\n\
83 name: Name of the pool to delete\n\
84 \n\
85 ipsec pool --resize <name> --end <end>\n\
86 Grow or shrink an existing pool.\n\
87 name: Name of the pool to resize\n\
88 end: New end address for the pool\n\
89 \n\
90 ipsec pool --leases <name> [--filter <filter>] [--utc]\n\
91 Show lease information using filters:\n\
92 name: Name of the pool to show leases from\n\
93 filter: Filter string containing comma separated key=value filters,\n\
94 e.g. id=alice@strongswan.org,addr=1.1.1.1\n\
95 pool: name of the pool\n\
96 id: assigned identity of the lease\n\
97 addr: lease IP address\n\
98 tstamp: UNIX timestamp when lease was valid, as integer\n\
99 status: status of the lease: online|valid|expired\n\
100 utc: Show times in UTC instead of local time\n\
101 \n\
102 ipsec pool --purge <name>\n\
103 Delete expired leases of a pool:\n\
104 name: Name of the pool to purge\n\
105 \n");
106 exit(0);
107 }
108
109 /**
110 * ipsec pool --status - show pool overview
111 */
112 static void status(void)
113 {
114 enumerator_t *pool, *lease;
115 bool found = FALSE;
116
117 pool = db->query(db, "SELECT id, name, start, end, timeout FROM pools",
118 DB_INT, DB_TEXT, DB_BLOB, DB_BLOB, DB_UINT);
119 if (pool)
120 {
121 char *name;
122 chunk_t start_chunk, end_chunk;
123 host_t *start, *end;
124 u_int id, timeout, online = 0, used = 0, size = 0;
125
126 while (pool->enumerate(pool, &id, &name,
127 &start_chunk, &end_chunk, &timeout))
128 {
129 if (!found)
130 {
131 printf("%8s %15s %15s %8s %6s %11s %11s\n",
132 "name", "start", "end", "timeout", "size", "online", "leases");
133 found = TRUE;
134 }
135
136 start = host_create_from_blob(start_chunk);
137 end = host_create_from_blob(end_chunk);
138 size = get_pool_size(start_chunk, end_chunk);
139 printf("%8s %15H %15H ", name, start, end);
140 if (timeout)
141 {
142 printf("%7dh ", timeout/3600);
143 }
144 else
145 {
146 printf("%8s ", "static");
147 }
148 printf("%6d ", size);
149 /* get number of online hosts */
150 lease = db->query(db, "SELECT COUNT(*) FROM leases "
151 "WHERE pool = ? AND released IS NULL",
152 DB_UINT, id, DB_INT);
153 if (lease)
154 {
155 lease->enumerate(lease, &online);
156 lease->destroy(lease);
157 }
158 printf("%5d (%2d%%) ", online, online*100/size);
159 /* get number of online or valid lieases */
160 lease = db->query(db, "SELECT COUNT(*) FROM leases JOIN pools "
161 "ON leases.pool = pools.id "
162 "WHERE pools.id = ? "
163 "AND (released IS NULL OR released > ? - timeout) ",
164 DB_UINT, id, DB_UINT, time(NULL), DB_UINT);
165 if (lease)
166 {
167 lease->enumerate(lease, &used);
168 lease->destroy(lease);
169 }
170 printf("%5d (%2d%%) ", used, used*100/size);
171
172 printf("\n");
173 DESTROY_IF(start);
174 DESTROY_IF(end);
175 }
176 pool->destroy(pool);
177 }
178 if (!found)
179 {
180 printf("no pools found.\n");
181 }
182 exit(0);
183 }
184
185 /**
186 * ipsec pool --add - add a new pool
187 */
188 static void add(char *name, host_t *start, host_t *end, int timeout)
189 {
190 chunk_t start_addr, end_addr;
191
192 start_addr = start->get_address(start);
193 end_addr = end->get_address(end);
194
195 if (start_addr.len != end_addr.len ||
196 memcmp(start_addr.ptr, end_addr.ptr, start_addr.len) > 0)
197 {
198 fprintf(stderr, "invalid start/end pair specified.\n");
199 exit(-1);
200 }
201 if (db->execute(db, NULL,
202 "INSERT INTO pools (name, start, end, next, timeout) "
203 "VALUES (?, ?, ?, ?, ?)",
204 DB_TEXT, name, DB_BLOB, start_addr,
205 DB_BLOB, end_addr, DB_BLOB, start_addr,
206 DB_INT, timeout*3600) != 1)
207 {
208 fprintf(stderr, "creating pool failed.\n");
209 exit(-1);
210 }
211 exit(0);
212 }
213
214 /**
215 * ipsec pool --del - delete a pool
216 */
217 static void del(char *name)
218 {
219 enumerator_t *query;
220 u_int id;
221 bool found = FALSE;
222
223 query = db->query(db, "SELECT id FROM pools WHERE name = ?",
224 DB_TEXT, name, DB_UINT);
225 if (!query)
226 {
227 fprintf(stderr, "deleting pool failed.\n");
228 exit(-1);
229 }
230 while (query->enumerate(query, &id))
231 {
232 found = TRUE;
233 if (db->execute(db, NULL,
234 "DELETE FROM pools WHERE id = ?", DB_UINT, id) != 1 ||
235 db->execute(db, NULL,
236 "DELETE FROM leases WHERE pool = ?", DB_UINT, id) < 0)
237 {
238 fprintf(stderr, "deleting pool failed.\n");
239 query->destroy(query);
240 exit(-1);
241 }
242 }
243 query->destroy(query);
244 if (!found)
245 {
246 fprintf(stderr, "pool '%s' not found.\n", name);
247 exit(-1);
248 }
249 exit(0);
250 }
251
252 /**
253 * ipsec pool --resize - resize a pool
254 */
255 static void resize(char *name, host_t *end)
256 {
257 enumerator_t *query;
258 chunk_t next_addr, end_addr;
259
260 end_addr = end->get_address(end);
261
262 query = db->query(db, "SELECT next FROM pools WHERE name = ?",
263 DB_TEXT, name, DB_BLOB);
264 if (!query || !query->enumerate(query, &next_addr))
265 {
266 DESTROY_IF(query);
267 fprintf(stderr, "resizing pool failed.\n");
268 exit(-1);
269 }
270 if (next_addr.len != end_addr.len ||
271 memcmp(end_addr.ptr, next_addr.ptr, end_addr.len) < 0)
272 {
273 end = host_create_from_blob(next_addr);
274 fprintf(stderr, "pool addresses up to %H in use, resizing failed.\n", end);
275 end->destroy(end);
276 query->destroy(query);
277 exit(-1);
278 }
279 query->destroy(query);
280
281 if (db->execute(db, NULL,
282 "UPDATE pools SET end = ? WHERE name = ?",
283 DB_BLOB, end_addr, DB_TEXT, name) <= 0)
284 {
285 fprintf(stderr, "pool '%s' not found.\n", name);
286 exit(-1);
287 }
288 exit(0);
289 }
290
291 /**
292 * create the lease query using the filter string
293 */
294 static enumerator_t *create_lease_query(char *filter)
295 {
296 enumerator_t *query;
297 identification_t *id = NULL;
298 host_t *addr = NULL;
299 u_int tstamp = 0;
300 bool online = FALSE, valid = FALSE, expired = FALSE;
301 char *value, *pos, *pool = NULL;
302 enum {
303 FIL_POOL = 0,
304 FIL_ID,
305 FIL_ADDR,
306 FIL_TSTAMP,
307 FIL_STATE,
308 };
309 char *const token[] = {
310 [FIL_POOL] = "pool",
311 [FIL_ID] = "id",
312 [FIL_ADDR] = "addr",
313 [FIL_TSTAMP] = "tstamp",
314 [FIL_STATE] = "status",
315 NULL
316 };
317
318 /* if the filter string contains a distinguished name as a ID, we filter
319 * out ", " and replace them by "; " to not confuse the getsubopt parser */
320 pos = filter;
321 while ((pos = strchr(pos, ',')))
322 {
323 if (pos[1] == ' ')
324 {
325 pos[0] = ';';
326 }
327 pos++;
328 }
329
330 while (filter && *filter != '\0')
331 {
332 switch (getsubopt(&filter, token, &value))
333 {
334 case FIL_POOL:
335 if (value)
336 {
337 pool = value;
338 }
339 break;
340 case FIL_ID:
341 if (value)
342 {
343 /* now re-replace the "; " by ", " */
344 pos = value;
345 while ((pos = strchr(pos, ';')))
346 {
347 if (pos[1] == ' ')
348 {
349 pos[0] = ',';
350 }
351 pos++;
352 }
353 id = identification_create_from_string(value);
354 }
355 if (!id)
356 {
357 fprintf(stderr, "invalid 'id' in filter string.\n");
358 exit(-1);
359 }
360 break;
361 case FIL_ADDR:
362 if (value)
363 {
364 addr = host_create_from_string(value, 0);
365 }
366 if (!addr)
367 {
368 fprintf(stderr, "invalid 'addr' in filter string.\n");
369 exit(-1);
370 }
371 break;
372 case FIL_TSTAMP:
373 if (value)
374 {
375 tstamp = atoi(value);
376 }
377 if (tstamp == 0)
378 {
379 online = TRUE;
380 }
381 break;
382 case FIL_STATE:
383 if (value)
384 {
385 if (streq(value, "online"))
386 {
387 online = TRUE;
388 }
389 else if (streq(value, "valid"))
390 {
391 valid = TRUE;
392 }
393 else if (streq(value, "expired"))
394 {
395 expired = TRUE;
396 }
397 else
398 {
399 fprintf(stderr, "invalid 'state' in filter string.\n");
400 exit(-1);
401 }
402 }
403 break;
404 default:
405 fprintf(stderr, "invalid filter string.\n");
406 exit(-1);
407 break;
408 }
409 }
410 query = db->query(db,
411 "SELECT name, address, identities.type, "
412 "identities.data, acquired, released, timeout "
413 "FROM leases JOIN pools ON leases.pool = pools.id "
414 "JOIN identities ON leases.identity = identities.id "
415 "WHERE (? OR name = ?) "
416 "AND (? OR (identities.type = ? AND identities.data = ?)) "
417 "AND (? OR address = ?) "
418 "AND (? OR (? >= acquired AND (? <= released OR released IS NULL))) "
419 "AND (? OR released IS NULL) "
420 "AND (? OR released > ? - timeout) "
421 "AND (? OR released < ? - timeout)",
422 DB_INT, pool == NULL, DB_TEXT, pool,
423 DB_INT, id == NULL,
424 DB_INT, id ? id->get_type(id) : 0,
425 DB_BLOB, id ? id->get_encoding(id) : chunk_empty,
426 DB_INT, addr == NULL,
427 DB_BLOB, addr ? addr->get_address(addr) : chunk_empty,
428 DB_INT, tstamp == 0, DB_UINT, tstamp, DB_UINT, tstamp,
429 DB_INT, !online,
430 DB_INT, !valid, DB_INT, time(NULL),
431 DB_INT, !expired, DB_INT, time(NULL),
432 DB_TEXT, DB_BLOB, DB_INT, DB_BLOB, DB_UINT, DB_UINT, DB_UINT);
433 /* id and addr leak but we can't destroy them until query is destroyed. */
434 return query;
435 }
436
437 /**
438 * ipsec pool --leases - show lease information of a pool
439 */
440 static void leases(char *filter, bool utc)
441 {
442 enumerator_t *query;
443 chunk_t address_chunk, identity_chunk;
444 int identity_type;
445 char *name;
446 u_int acquired, released, timeout;
447 host_t *address;
448 identification_t *identity;
449 bool found = FALSE;
450
451 query = create_lease_query(filter);
452 if (!query)
453 {
454 fprintf(stderr, "querying leases failed.\n");
455 exit(-1);
456 }
457 while (query->enumerate(query, &name, &address_chunk, &identity_type,
458 &identity_chunk, &acquired, &released, &timeout))
459 {
460 if (!found)
461 {
462 int len = utc ? 25 : 21;
463
464 found = TRUE;
465 printf("%-8s %-15s %-7s %-*s %-*s %s\n",
466 "name", "address", "status", len, "start", len, "end", "identity");
467 }
468 address = host_create_from_blob(address_chunk);
469 identity = identification_create_from_encoding(identity_type, identity_chunk);
470
471 printf("%-8s %-15H ", name, address);
472 if (released == 0)
473 {
474 printf("%-7s ", "online");
475 }
476 else if (timeout == 0)
477 {
478 printf("%-7s ", "static");
479 }
480 else if (released >= time(NULL) - timeout)
481 {
482 printf("%-7s ", "valid");
483 }
484 else
485 {
486 printf("%-7s ", "expired");
487 }
488
489 printf(" %#T ", &acquired, utc);
490 if (released)
491 {
492 printf("%#T ", &released, utc);
493 }
494 else
495 {
496 printf(" ");
497 if (utc)
498 {
499 printf(" ");
500 }
501 }
502 printf("%D\n", identity);
503 DESTROY_IF(address);
504 identity->destroy(identity);
505 }
506 query->destroy(query);
507 if (!found)
508 {
509 fprintf(stderr, "no matching leases found.\n");
510 exit(-1);
511 }
512 exit(0);
513 }
514
515 /**
516 * ipsec pool --purge - delete expired leases
517 */
518 static void purge(char *name)
519 {
520 enumerator_t *query;
521 u_int id, timeout, purged = 0;
522
523 query = db->query(db, "SELECT id, timeout FROM pools WHERE name = ?",
524 DB_TEXT, name, DB_UINT, DB_UINT);
525 if (!query)
526 {
527 fprintf(stderr, "purging pool failed.\n");
528 exit(-1);
529 }
530 /* we have to keep one lease if we purge. It wouldn't be reallocateable
531 * as we move on the "next" address for speedy allocation */
532 if (query->enumerate(query, &id, &timeout))
533 {
534 timeout = time(NULL) - timeout;
535 purged = db->execute(db, NULL,
536 "DELETE FROM leases WHERE pool = ? "
537 "AND released IS NOT NULL AND released < ? AND id NOT IN ("
538 " SELECT id FROM leases "
539 " WHERE released IS NOT NULL and released < ? "
540 " GROUP BY address)",
541 DB_UINT, id, DB_UINT, timeout, DB_UINT, timeout);
542 }
543 query->destroy(query);
544 fprintf(stderr, "purged %d leases in pool '%s'.\n", purged, name);
545 exit(0);
546 }
547
548 /**
549 * atexit handler to close db on shutdown
550 */
551 static void cleanup(void)
552 {
553 db->destroy(db);
554 DESTROY_IF(start);
555 DESTROY_IF(end);
556 }
557
558 /**
559 * Logging hook for library logs, using stderr output
560 */
561 static void dbg_stderr(int level, char *fmt, ...)
562 {
563 va_list args;
564
565 if (level <= 1)
566 {
567 va_start(args, fmt);
568 vfprintf(stderr, fmt, args);
569 fprintf(stderr, "\n");
570 va_end(args);
571 }
572 }
573
574 int main(int argc, char *argv[])
575 {
576 char *uri, *name = "", *filter = "";
577 int timeout = 0;
578 bool utc = FALSE;
579 enum {
580 OP_USAGE,
581 OP_STATUS,
582 OP_ADD,
583 OP_DEL,
584 OP_RESIZE,
585 OP_LEASES,
586 OP_PURGE,
587 } operation = OP_USAGE;
588
589 dbg = dbg_stderr;
590 library_init(STRONGSWAN_CONF);
591 atexit(library_deinit);
592 lib->plugins->load(lib->plugins, IPSEC_PLUGINDIR,
593 lib->settings->get_str(lib->settings, "pool.load", PLUGINS));
594
595 uri = lib->settings->get_str(lib->settings, "charon.plugins.sql.database", NULL);
596 if (!uri)
597 {
598 fprintf(stderr, "database URI charon.plugins.sql.database not set.\n");
599 exit(-1);
600 }
601 db = lib->db->create(lib->db, uri);
602 if (!db)
603 {
604 fprintf(stderr, "opening database failed.\n");
605 exit(-1);
606 }
607 atexit(cleanup);
608
609 while (TRUE)
610 {
611 int c;
612
613 struct option long_opts[] = {
614 { "help", no_argument, NULL, 'h' },
615
616 { "utc", no_argument, NULL, 'u' },
617 { "status", no_argument, NULL, 'w' },
618 { "add", required_argument, NULL, 'a' },
619 { "del", required_argument, NULL, 'd' },
620 { "resize", required_argument, NULL, 'r' },
621 { "leases", no_argument, NULL, 'l' },
622 { "purge", required_argument, NULL, 'p' },
623
624 { "start", required_argument, NULL, 's' },
625 { "end", required_argument, NULL, 'e' },
626 { "timeout", required_argument, NULL, 't' },
627 { "filter", required_argument, NULL, 'f' },
628 { 0,0,0,0 }
629 };
630
631 c = getopt_long(argc, argv, "", long_opts, NULL);
632 switch (c)
633 {
634 case EOF:
635 break;
636 case 'h':
637 break;
638 case 'w':
639 operation = OP_STATUS;
640 break;
641 case 'u':
642 utc = TRUE;
643 continue;
644 case 'a':
645 operation = OP_ADD;
646 name = optarg;
647 continue;
648 case 'd':
649 operation = OP_DEL;
650 name = optarg;
651 continue;
652 case 'r':
653 operation = OP_RESIZE;
654 name = optarg;
655 continue;
656 case 'l':
657 operation = OP_LEASES;
658 continue;
659 case 'p':
660 operation = OP_PURGE;
661 name = optarg;
662 continue;
663 case 's':
664 start = host_create_from_string(optarg, 0);
665 if (start == NULL)
666 {
667 fprintf(stderr, "invalid start address: '%s'.\n", optarg);
668 operation = OP_USAGE;
669 break;
670 }
671 continue;
672 case 'e':
673 end = host_create_from_string(optarg, 0);
674 if (end == NULL)
675 {
676 fprintf(stderr, "invalid end address: '%s'.\n", optarg);
677 operation = OP_USAGE;
678 break;
679 }
680 continue;
681 case 't':
682 timeout = atoi(optarg);
683 if (timeout == 0 && strcmp(optarg, "0") != 0)
684 {
685 fprintf(stderr, "invalid timeout '%s'.\n", optarg);
686 operation = OP_USAGE;
687 break;
688 }
689 continue;
690 case 'f':
691 filter = optarg;
692 continue;
693 default:
694 operation = OP_USAGE;
695 break;
696 }
697 break;
698 }
699
700 switch (operation)
701 {
702 case OP_USAGE:
703 usage();
704 break;
705 case OP_STATUS:
706 status();
707 break;
708 case OP_ADD:
709 if (start == NULL || end == NULL)
710 {
711 fprintf(stderr, "missing arguments.\n");
712 usage();
713 }
714 add(name, start, end, timeout);
715 break;
716 case OP_DEL:
717 del(name);
718 break;
719 case OP_RESIZE:
720 if (end == NULL)
721 {
722 fprintf(stderr, "missing arguments.\n");
723 usage();
724 }
725 resize(name, end);
726 break;
727 case OP_LEASES:
728 leases(filter, utc);
729 break;
730 case OP_PURGE:
731 purge(name);
732 break;
733 }
734 exit(0);
735 }
736