convert comma-separated RDNs into slash-separated OpenSSL --subject format
[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 replace
319 * ", " by "/ " in order 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 id = identification_create_from_string(value);
344 }
345 if (!id)
346 {
347 fprintf(stderr, "invalid 'id' in filter string.\n");
348 exit(-1);
349 }
350 break;
351 case FIL_ADDR:
352 if (value)
353 {
354 addr = host_create_from_string(value, 0);
355 }
356 if (!addr)
357 {
358 fprintf(stderr, "invalid 'addr' in filter string.\n");
359 exit(-1);
360 }
361 break;
362 case FIL_TSTAMP:
363 if (value)
364 {
365 tstamp = atoi(value);
366 }
367 if (tstamp == 0)
368 {
369 online = TRUE;
370 }
371 break;
372 case FIL_STATE:
373 if (value)
374 {
375 if (streq(value, "online"))
376 {
377 online = TRUE;
378 }
379 else if (streq(value, "valid"))
380 {
381 valid = TRUE;
382 }
383 else if (streq(value, "expired"))
384 {
385 expired = TRUE;
386 }
387 else
388 {
389 fprintf(stderr, "invalid 'state' in filter string.\n");
390 exit(-1);
391 }
392 }
393 break;
394 default:
395 fprintf(stderr, "invalid filter string.\n");
396 exit(-1);
397 break;
398 }
399 }
400 query = db->query(db,
401 "SELECT name, address, identities.type, "
402 "identities.data, acquired, released, timeout "
403 "FROM leases JOIN pools ON leases.pool = pools.id "
404 "JOIN identities ON leases.identity = identities.id "
405 "WHERE (? OR name = ?) "
406 "AND (? OR (identities.type = ? AND identities.data = ?)) "
407 "AND (? OR address = ?) "
408 "AND (? OR (? >= acquired AND (? <= released OR released IS NULL))) "
409 "AND (? OR released IS NULL) "
410 "AND (? OR released > ? - timeout) "
411 "AND (? OR released < ? - timeout)",
412 DB_INT, pool == NULL, DB_TEXT, pool,
413 DB_INT, id == NULL,
414 DB_INT, id ? id->get_type(id) : 0,
415 DB_BLOB, id ? id->get_encoding(id) : chunk_empty,
416 DB_INT, addr == NULL,
417 DB_BLOB, addr ? addr->get_address(addr) : chunk_empty,
418 DB_INT, tstamp == 0, DB_UINT, tstamp, DB_UINT, tstamp,
419 DB_INT, !online,
420 DB_INT, !valid, DB_INT, time(NULL),
421 DB_INT, !expired, DB_INT, time(NULL),
422 DB_TEXT, DB_BLOB, DB_INT, DB_BLOB, DB_UINT, DB_UINT, DB_UINT);
423 /* id and addr leak but we can't destroy them until query is destroyed. */
424 return query;
425 }
426
427 /**
428 * ipsec pool --leases - show lease information of a pool
429 */
430 static void leases(char *filter, bool utc)
431 {
432 enumerator_t *query;
433 chunk_t address_chunk, identity_chunk;
434 int identity_type;
435 char *name;
436 u_int acquired, released, timeout;
437 host_t *address;
438 identification_t *identity;
439 bool found = FALSE;
440
441 query = create_lease_query(filter);
442 if (!query)
443 {
444 fprintf(stderr, "querying leases failed.\n");
445 exit(-1);
446 }
447 while (query->enumerate(query, &name, &address_chunk, &identity_type,
448 &identity_chunk, &acquired, &released, &timeout))
449 {
450 if (!found)
451 {
452 int len = utc ? 25 : 21;
453
454 found = TRUE;
455 printf("%-8s %-15s %-7s %-*s %-*s %s\n",
456 "name", "address", "status", len, "start", len, "end", "identity");
457 }
458 address = host_create_from_blob(address_chunk);
459 identity = identification_create_from_encoding(identity_type, identity_chunk);
460
461 printf("%-8s %-15H ", name, address);
462 if (released == 0)
463 {
464 printf("%-7s ", "online");
465 }
466 else if (timeout == 0)
467 {
468 printf("%-7s ", "static");
469 }
470 else if (released >= time(NULL) - timeout)
471 {
472 printf("%-7s ", "valid");
473 }
474 else
475 {
476 printf("%-7s ", "expired");
477 }
478
479 printf(" %#T ", &acquired, utc);
480 if (released)
481 {
482 printf("%#T ", &released, utc);
483 }
484 else
485 {
486 printf(" ");
487 if (utc)
488 {
489 printf(" ");
490 }
491 }
492 printf("%D\n", identity);
493 DESTROY_IF(address);
494 identity->destroy(identity);
495 }
496 query->destroy(query);
497 if (!found)
498 {
499 fprintf(stderr, "no matching leases found.\n");
500 exit(-1);
501 }
502 exit(0);
503 }
504
505 /**
506 * ipsec pool --purge - delete expired leases
507 */
508 static void purge(char *name)
509 {
510 enumerator_t *query;
511 u_int id, timeout, purged = 0;
512
513 query = db->query(db, "SELECT id, timeout FROM pools WHERE name = ?",
514 DB_TEXT, name, DB_UINT, DB_UINT);
515 if (!query)
516 {
517 fprintf(stderr, "purging pool failed.\n");
518 exit(-1);
519 }
520 /* we have to keep one lease if we purge. It wouldn't be reallocateable
521 * as we move on the "next" address for speedy allocation */
522 if (query->enumerate(query, &id, &timeout))
523 {
524 timeout = time(NULL) - timeout;
525 purged = db->execute(db, NULL,
526 "DELETE FROM leases WHERE pool = ? "
527 "AND released IS NOT NULL AND released < ? AND id NOT IN ("
528 " SELECT id FROM leases "
529 " WHERE released IS NOT NULL and released < ? "
530 " GROUP BY address)",
531 DB_UINT, id, DB_UINT, timeout, DB_UINT, timeout);
532 }
533 query->destroy(query);
534 fprintf(stderr, "purged %d leases in pool '%s'.\n", purged, name);
535 exit(0);
536 }
537
538 /**
539 * atexit handler to close db on shutdown
540 */
541 static void cleanup(void)
542 {
543 db->destroy(db);
544 DESTROY_IF(start);
545 DESTROY_IF(end);
546 }
547
548 /**
549 * Logging hook for library logs, using stderr output
550 */
551 static void dbg_stderr(int level, char *fmt, ...)
552 {
553 va_list args;
554
555 if (level <= 1)
556 {
557 va_start(args, fmt);
558 vfprintf(stderr, fmt, args);
559 fprintf(stderr, "\n");
560 va_end(args);
561 }
562 }
563
564 int main(int argc, char *argv[])
565 {
566 char *uri, *name = "", *filter = "";
567 int timeout = 0;
568 bool utc = FALSE;
569 enum {
570 OP_USAGE,
571 OP_STATUS,
572 OP_ADD,
573 OP_DEL,
574 OP_RESIZE,
575 OP_LEASES,
576 OP_PURGE,
577 } operation = OP_USAGE;
578
579 dbg = dbg_stderr;
580 library_init(STRONGSWAN_CONF);
581 atexit(library_deinit);
582 lib->plugins->load(lib->plugins, IPSEC_PLUGINDIR,
583 lib->settings->get_str(lib->settings, "pool.load", PLUGINS));
584
585 uri = lib->settings->get_str(lib->settings, "charon.plugins.sql.database", NULL);
586 if (!uri)
587 {
588 fprintf(stderr, "database URI charon.plugins.sql.database not set.\n");
589 exit(-1);
590 }
591 db = lib->db->create(lib->db, uri);
592 if (!db)
593 {
594 fprintf(stderr, "opening database failed.\n");
595 exit(-1);
596 }
597 atexit(cleanup);
598
599 while (TRUE)
600 {
601 int c;
602
603 struct option long_opts[] = {
604 { "help", no_argument, NULL, 'h' },
605
606 { "utc", no_argument, NULL, 'u' },
607 { "status", no_argument, NULL, 'w' },
608 { "add", required_argument, NULL, 'a' },
609 { "del", required_argument, NULL, 'd' },
610 { "resize", required_argument, NULL, 'r' },
611 { "leases", no_argument, NULL, 'l' },
612 { "purge", required_argument, NULL, 'p' },
613
614 { "start", required_argument, NULL, 's' },
615 { "end", required_argument, NULL, 'e' },
616 { "timeout", required_argument, NULL, 't' },
617 { "filter", required_argument, NULL, 'f' },
618 { 0,0,0,0 }
619 };
620
621 c = getopt_long(argc, argv, "", long_opts, NULL);
622 switch (c)
623 {
624 case EOF:
625 break;
626 case 'h':
627 break;
628 case 'w':
629 operation = OP_STATUS;
630 break;
631 case 'u':
632 utc = TRUE;
633 continue;
634 case 'a':
635 operation = OP_ADD;
636 name = optarg;
637 continue;
638 case 'd':
639 operation = OP_DEL;
640 name = optarg;
641 continue;
642 case 'r':
643 operation = OP_RESIZE;
644 name = optarg;
645 continue;
646 case 'l':
647 operation = OP_LEASES;
648 continue;
649 case 'p':
650 operation = OP_PURGE;
651 name = optarg;
652 continue;
653 case 's':
654 start = host_create_from_string(optarg, 0);
655 if (start == NULL)
656 {
657 fprintf(stderr, "invalid start address: '%s'.\n", optarg);
658 operation = OP_USAGE;
659 break;
660 }
661 continue;
662 case 'e':
663 end = host_create_from_string(optarg, 0);
664 if (end == NULL)
665 {
666 fprintf(stderr, "invalid end address: '%s'.\n", optarg);
667 operation = OP_USAGE;
668 break;
669 }
670 continue;
671 case 't':
672 timeout = atoi(optarg);
673 if (timeout == 0 && strcmp(optarg, "0") != 0)
674 {
675 fprintf(stderr, "invalid timeout '%s'.\n", optarg);
676 operation = OP_USAGE;
677 break;
678 }
679 continue;
680 case 'f':
681 filter = optarg;
682 continue;
683 default:
684 operation = OP_USAGE;
685 break;
686 }
687 break;
688 }
689
690 switch (operation)
691 {
692 case OP_USAGE:
693 usage();
694 break;
695 case OP_STATUS:
696 status();
697 break;
698 case OP_ADD:
699 if (start == NULL || end == NULL)
700 {
701 fprintf(stderr, "missing arguments.\n");
702 usage();
703 }
704 add(name, start, end, timeout);
705 break;
706 case OP_DEL:
707 del(name);
708 break;
709 case OP_RESIZE:
710 if (end == NULL)
711 {
712 fprintf(stderr, "missing arguments.\n");
713 usage();
714 }
715 resize(name, end);
716 break;
717 case OP_LEASES:
718 leases(filter, utc);
719 break;
720 case OP_PURGE:
721 purge(name);
722 break;
723 }
724 exit(0);
725 }
726