summaryrefslogtreecommitdiff
path: root/programs/pluto/foodgroups.c
diff options
context:
space:
mode:
Diffstat (limited to 'programs/pluto/foodgroups.c')
-rw-r--r--programs/pluto/foodgroups.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/programs/pluto/foodgroups.c b/programs/pluto/foodgroups.c
new file mode 100644
index 000000000..52e32f0fb
--- /dev/null
+++ b/programs/pluto/foodgroups.c
@@ -0,0 +1,462 @@
+/* Implement policy groups-style control files (aka "foodgroups")
+ * Copyright (C) 2002 D. Hugh Redelmeier.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * RCSID $Id: foodgroups.c,v 1.2 2004/04/01 18:28:32 as Exp $
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/queue.h>
+
+#include <freeswan.h>
+
+#include "constants.h"
+#include "defs.h"
+#include "connections.h"
+#include "foodgroups.h"
+#include "kernel.h"
+#include "lex.h"
+#include "log.h"
+#include "whack.h"
+
+
+/* Food group config files are found in directory fg_path */
+
+#ifndef POLICYGROUPSDIR
+#define POLICYGROUPSDIR "/etc/ipsec.d/policies"
+#endif
+
+const char *policygroups_dir = POLICYGROUPSDIR;
+
+static char *fg_path = NULL;
+static size_t fg_path_space = 0;
+
+
+/* Groups is a list of connections that are policy groups.
+ * The list is updated as group connections are added and deleted.
+ */
+
+struct fg_groups {
+ struct fg_groups *next;
+ struct connection *connection;
+};
+
+static struct fg_groups *groups = NULL;
+
+
+/* Targets is a list of pairs: subnet and its policy group.
+ * This list is bulk-updated on whack --listen and
+ * incrementally updated when group connections are deleted.
+ *
+ * It is ordered by source subnet, and if those are equal, then target subnet.
+ * A subnet is compared by comparing the network, and if those are equal,
+ * comparing the mask.
+ */
+
+struct fg_targets {
+ struct fg_targets *next;
+ struct fg_groups *group;
+ ip_subnet subnet;
+ char *name; /* name of instance of group conn */
+};
+
+static struct fg_targets *targets = NULL;
+
+struct fg_targets *new_targets;
+
+/* ipcmp compares the two ip_address values a and b.
+ * It returns -1, 0, or +1 if a is, respectively,
+ * less than, equal to, or greater than b.
+ */
+static int
+ipcmp(ip_address *a, ip_address *b)
+{
+ if (addrtypeof(a) != addrtypeof(b))
+ {
+ return addrtypeof(a) < addrtypeof(b)? -1 : 1;
+ }
+ else if (sameaddr(a, b))
+ {
+ return 0;
+ }
+ else
+ {
+ const struct sockaddr *sa = sockaddrof(a)
+ , *sb = sockaddrof(b);
+
+ passert(addrtypeof(a) == AF_INET); /* not yet implemented IPv6 version :-( */
+ return (ntohl(((const struct sockaddr_in *)sa)->sin_addr.s_addr)
+ < ntohl(((const struct sockaddr_in *)sb)->sin_addr.s_addr))
+ ? -1 : 1;
+ }
+}
+
+/* subnetcmp compares the two ip_subnet values a and b.
+ * It returns -1, 0, or +1 if a is, respectively,
+ * less than, equal to, or greater than b.
+ */
+static int
+subnetcmp(const ip_subnet *a, const ip_subnet *b)
+{
+ ip_address neta, maska, netb, maskb;
+ int r;
+
+ networkof(a, &neta);
+ maskof(a, &maska);
+ networkof(b, &netb);
+ maskof(b, &maskb);
+ r = ipcmp(&neta, &netb);
+ if (r == 0)
+ r = ipcmp(&maska, &maskb);
+ return r;
+}
+
+static void
+read_foodgroup(struct fg_groups *g)
+{
+ const char *fgn = g->connection->name;
+ const ip_subnet *lsn = &g->connection->spd.this.client;
+ size_t plen = strlen(policygroups_dir) + 1 + strlen(fgn) + 1;
+ struct file_lex_position flp_space;
+
+ if (plen > fg_path_space)
+ {
+ pfreeany(fg_path);
+ fg_path_space = plen + 10;
+ fg_path = alloc_bytes(fg_path_space, "policy group path");
+ }
+ snprintf(fg_path, fg_path_space, "%s/%s", policygroups_dir, fgn);
+ if (!lexopen(&flp_space, fg_path, TRUE))
+ {
+ DBG(DBG_CONTROL, DBG_log("no group file \"%s\"", fg_path));
+ }
+ else
+ {
+ plog("loading group \"%s\"", fg_path);
+ for (;;)
+ {
+ switch (flp->bdry)
+ {
+ case B_none:
+ {
+ /* !!! this test is not sufficient for distinguishing address families.
+ * We need a notation to specify that a FQDN is to be resolved to IPv6.
+ */
+ const struct af_info *afi = strchr(tok, ':') == NULL
+ ? &af_inet4_info: &af_inet6_info;
+ ip_subnet sn;
+ err_t ugh;
+
+ if (strchr(tok, '/') == NULL)
+ {
+ /* no /, so treat as /32 or V6 equivalent */
+ ip_address t;
+
+ ugh = ttoaddr(tok, 0, afi->af, &t);
+ if (ugh == NULL)
+ ugh = addrtosubnet(&t, &sn);
+ }
+ else
+ {
+ ugh = ttosubnet(tok, 0, afi->af, &sn);
+ }
+
+ if (ugh != NULL)
+ {
+ loglog(RC_LOG_SERIOUS, "\"%s\" line %d: %s \"%s\""
+ , flp->filename, flp->lino, ugh, tok);
+ }
+ else if (afi->af != AF_INET)
+ {
+ loglog(RC_LOG_SERIOUS
+ , "\"%s\" line %d: unsupported Address Family \"%s\""
+ , flp->filename, flp->lino, tok);
+ }
+ else
+ {
+ /* Find where new entry ought to go in new_targets. */
+ struct fg_targets **pp;
+ int r;
+
+ for (pp = &new_targets; ; pp = &(*pp)->next)
+ {
+ if (*pp == NULL)
+ {
+ r = -1; /* end of list is infinite */
+ break;
+ }
+ r = subnetcmp(lsn, &(*pp)->group->connection->spd.this.client);
+ if (r == 0)
+ r = subnetcmp(&sn, &(*pp)->subnet);
+ if (r <= 0)
+ break;
+ }
+
+ if (r == 0)
+ {
+ char source[SUBNETTOT_BUF];
+
+ subnettot(lsn, 0, source, sizeof(source));
+ loglog(RC_LOG_SERIOUS
+ , "\"%s\" line %d: subnet \"%s\", source %s, already \"%s\""
+ , flp->filename
+ , flp->lino
+ , tok
+ , source
+ , (*pp)->group->connection->name);
+ }
+ else
+ {
+ struct fg_targets *f = alloc_thing(struct fg_targets, "fg_target");
+
+ f->next = *pp;
+ f->group = g;
+ f->subnet = sn;
+ f->name = NULL;
+ *pp = f;
+ }
+ }
+ }
+ (void)shift(); /* next */
+ continue;
+
+ case B_record:
+ flp->bdry = B_none; /* eat the Record Boundary */
+ (void)shift(); /* get real first token */
+ continue;
+
+ case B_file:
+ break; /* done */
+ }
+ break; /* if we reach here, out of loop */
+ }
+ lexclose();
+ }
+}
+
+static void
+free_targets(void)
+{
+ while (targets != NULL)
+ {
+ struct fg_targets *t = targets;
+
+ targets = t->next;
+ pfreeany(t->name);
+ pfree(t);
+ }
+}
+
+void
+load_groups(void)
+{
+ passert(new_targets == NULL);
+
+ /* for each group, add config file targets into new_targets */
+ {
+ struct fg_groups *g;
+
+ for (g = groups; g != NULL; g = g->next)
+ if (oriented(*g->connection))
+ read_foodgroup(g);
+ }
+
+ /* dump new_targets */
+ DBG(DBG_CONTROL,
+ {
+ struct fg_targets *t;
+
+ for (t = new_targets; t != NULL; t = t->next)
+ {
+ char asource[SUBNETTOT_BUF];
+ char atarget[SUBNETTOT_BUF];
+
+ subnettot(&t->group->connection->spd.this.client
+ , 0, asource, sizeof(asource));
+ subnettot(&t->subnet, 0, atarget, sizeof(atarget));
+ DBG_log("%s->%s %s"
+ , asource, atarget
+ , t->group->connection->name);
+ }
+ });
+
+ /* determine and deal with differences between targets and new_targets.
+ * structured like a merge.
+ */
+ {
+ struct fg_targets *op = targets
+ , *np = new_targets;
+
+ while (op != NULL && np != NULL)
+ {
+ int r = subnetcmp(&op->group->connection->spd.this.client
+ , &np->group->connection->spd.this.client);
+
+ if (r == 0)
+ r = subnetcmp(&op->subnet, &np->subnet);
+
+ if (r == 0 && op->group == np->group)
+ {
+ /* unchanged -- steal name & skip over */
+ np->name = op->name;
+ op->name = NULL;
+ op = op->next;
+ np = np->next;
+ }
+ else
+ {
+ /* note: following cases overlap! */
+ if (r <= 0)
+ {
+ remove_group_instance(op->group->connection, op->name);
+ op = op->next;
+ }
+ if (r >= 0)
+ {
+ np->name = add_group_instance(np->group->connection, &np->subnet);
+ np = np->next;
+ }
+ }
+ }
+ for (; op != NULL; op = op->next)
+ remove_group_instance(op->group->connection, op->name);
+ for (; np != NULL; np = np->next)
+ np->name = add_group_instance(np->group->connection, &np->subnet);
+
+ /* update: new_targets replaces targets */
+ free_targets();
+ targets = new_targets;
+ new_targets = NULL;
+ }
+}
+
+
+void
+add_group(struct connection *c)
+{
+ struct fg_groups *g = alloc_thing(struct fg_groups, "policy group");
+
+ g->next = groups;
+ groups = g;
+
+ g->connection = c;
+}
+
+static struct fg_groups *
+find_group(const struct connection *c)
+{
+ struct fg_groups *g;
+
+ for (g = groups; g != NULL && g->connection != c; g = g->next)
+ ;
+ return g;
+}
+
+void
+route_group(struct connection *c)
+{
+ /* it makes no sense to route a connection that is ISAKMP-only */
+ if (!NEVER_NEGOTIATE(c->policy) && !HAS_IPSEC_POLICY(c->policy))
+ {
+ loglog(RC_ROUTE, "cannot route an ISAKMP-only group connection");
+ }
+ else
+ {
+ struct fg_groups *g = find_group(c);
+ struct fg_targets *t;
+
+ passert(g != NULL);
+ g->connection->policy |= POLICY_GROUTED;
+ for (t = targets; t != NULL; t = t->next)
+ {
+ if (t->group == g)
+ {
+ struct connection *ci = con_by_name(t->name, FALSE);
+
+ if (ci != NULL)
+ {
+ set_cur_connection(ci);
+ if (!trap_connection(ci))
+ whack_log(RC_ROUTE, "could not route");
+ set_cur_connection(c);
+ }
+ }
+ }
+ }
+}
+
+void
+unroute_group(struct connection *c)
+{
+ struct fg_groups *g = find_group(c);
+ struct fg_targets *t;
+
+ passert(g != NULL);
+ g->connection->policy &= ~POLICY_GROUTED;
+ for (t = targets; t != NULL; t = t->next)
+ {
+ if (t->group == g)
+ {
+ struct connection *ci = con_by_name(t->name, FALSE);
+
+ if (ci != NULL)
+ {
+ set_cur_connection(ci);
+ unroute_connection(ci);
+ set_cur_connection(c);
+ }
+ }
+ }
+}
+
+void
+delete_group(const struct connection *c)
+{
+ struct fg_groups *g;
+
+ /* find and remove from groups */
+ {
+ struct fg_groups **pp;
+
+ for (pp = &groups; (g = *pp)->connection != c; pp = &(*pp)->next)
+ ;
+
+ *pp = g->next;
+ }
+
+ /* find and remove from targets */
+ {
+ struct fg_targets **pp;
+
+ for (pp = &targets; *pp != NULL; )
+ {
+ struct fg_targets *t = *pp;
+
+ if (t->group == g)
+ {
+ *pp = t->next;
+ remove_group_instance(t->group->connection, t->name);
+ pfree(t);
+ /* pp is ready for next iteration */
+ }
+ else
+ {
+ pp = &t->next;
+ }
+ }
+ }
+
+ pfree(g);
+}