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