/* $OpenBSD: ipsecctl.c,v 1.85 2023/03/07 17:43:59 guenther Exp $ */ /* * Copyright (c) 2004, 2005 Hans-Joerg Hoexer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipsecctl.h" #include "pfkey.h" int ipsecctl_rules(char *, int); FILE *ipsecctl_fopen(const char *, const char *); int ipsecctl_commit(int, struct ipsecctl *); int ipsecctl_add_rule(struct ipsecctl *, struct ipsec_rule *); void ipsecctl_free_rule(struct ipsec_rule *); int ipsecctl_merge_rules(struct ipsec_rule *, struct ipsec_rule *); int ipsecctl_cmp_ident(struct ipsec_rule *, struct ipsec_rule *); int ipsecctl_rule_matchsrc(struct ipsec_rule *, struct ipsec_addr_wrap *); int ipsecctl_rule_matchdst(struct ipsec_rule *, struct ipsec_addr_wrap *); void ipsecctl_print_addr(struct ipsec_addr_wrap *); void ipsecctl_print_proto(u_int8_t); void ipsecctl_print_port(u_int16_t, const char *); void ipsecctl_print_key(struct ipsec_key *); void ipsecctl_print_flow(struct ipsec_rule *, int); void ipsecctl_print_sa(struct ipsec_rule *, int); void ipsecctl_print_sabundle(struct ipsec_rule *, int); int ipsecctl_flush(int); void ipsecctl_get_rules(struct ipsecctl *); void ipsecctl_print_title(char *); void ipsecctl_show_flows(int); void ipsecctl_show_sas(int); int ipsecctl_monitor(int); void usage(void); const char *ipsecctl_lookup_option(char *, const char **); static int unmask(struct ipsec_addr *); int sacompare(const void *, const void *); const char *showopt; char *isakmpd_fifo = "/var/run/isakmpd.fifo"; int first_title = 1; static const char *showopt_list[] = { "flow", "sa", "all", NULL }; static const char *direction[] = {"?", "in", "out"}; static const char *flowtype[] = {"?", "use", "acquire", "require", "deny", "bypass", "dontacq"}; static const char *satype[] = {"?", "esp", "ah", "ipcomp", "tcpmd5", "ipip"}; static const char *tmode[] = {"?", "transport", "tunnel"}; static const char *auth[] = {"?", "psk", "rsa"}; struct sad { struct sadb_msg *sad_msg; u_int32_t sad_spi; }; int sacompare(const void *va, const void *vb) { const struct sad *a = va, *b = vb; if (a->sad_spi < b->sad_spi) return (-1); if (a->sad_spi > b->sad_spi) return (1); return (0); } int ipsecctl_rules(char *filename, int opts) { struct ipsecctl ipsec; struct ipsec_rule *rp; int action, error = 0; bzero(&ipsec, sizeof(ipsec)); ipsec.opts = opts; TAILQ_INIT(&ipsec.rule_queue); TAILQ_INIT(&ipsec.bundle_queue); if (parse_rules(filename, &ipsec) < 0) { warnx("Syntax error in config file: ipsec rules not loaded"); error = 1; } else { if (opts & IPSECCTL_OPT_DELETE) action = ACTION_DELETE; else action = ACTION_ADD; if ((opts & IPSECCTL_OPT_NOACTION) == 0) error = ipsecctl_commit(action, &ipsec); } /* This also frees the rules in ipsec.bundle_queue. */ while ((rp = TAILQ_FIRST(&ipsec.rule_queue))) { TAILQ_REMOVE(&ipsec.rule_queue, rp, rule_entry); ipsecctl_free_rule(rp); } return (error); } FILE * ipsecctl_fopen(const char *name, const char *mode) { struct stat st; FILE *fp; fp = fopen(name, mode); if (fp == NULL) return (NULL); if (fstat(fileno(fp), &st)) { fclose(fp); return (NULL); } if (S_ISDIR(st.st_mode)) { fclose(fp); errno = EISDIR; return (NULL); } return (fp); } int ipsecctl_commit(int action, struct ipsecctl *ipsec) { struct ipsec_rule *rp; int ret = 0; if (pfkey_init() == -1) errx(1, "ipsecctl_commit: failed to open PF_KEY socket"); TAILQ_FOREACH(rp, &ipsec->rule_queue, rule_entry) { if (rp->type & RULE_IKE) { if (ike_ipsec_establish(action, rp, isakmpd_fifo) == -1) { warnx("failed to %s ike rule %d", action == ACTION_DELETE ? "delete" : "add", rp->nr); ret = 2; } } else { if (pfkey_ipsec_establish(action, rp) == -1) { warnx("failed to %s rule %d", action == ACTION_DELETE ? "delete" : "add", rp->nr); ret = 2; } } } return (ret); } int ipsecctl_add_rule(struct ipsecctl *ipsec, struct ipsec_rule *r) { TAILQ_INSERT_TAIL(&ipsec->rule_queue, r, rule_entry); if ((ipsec->opts & IPSECCTL_OPT_VERBOSE) && !(ipsec->opts & IPSECCTL_OPT_SHOW)) ipsecctl_print_rule(r, ipsec->opts); return (0); } void ipsecctl_free_rule(struct ipsec_rule *rp) { if (rp->src) { free(rp->src->name); free(rp->src); } if (rp->dst) { free(rp->dst->name); free(rp->dst); } if (rp->dst2) { free(rp->dst2->name); free(rp->dst2); } if (rp->local) { free(rp->local->name); free(rp->local); } if (rp->peer) { free(rp->peer->name); free(rp->peer); } if (rp->auth) { free(rp->auth->srcid); free(rp->auth->dstid); free(rp->auth); } if (rp->ikeauth) { free(rp->ikeauth->string); free(rp->ikeauth); } free(rp->xfs); free(rp->p1xfs); free(rp->p2xfs); free(rp->p1life); free(rp->p2life); if (rp->authkey) { free(rp->authkey->data); free(rp->authkey); } if (rp->enckey) { free(rp->enckey->data); free(rp->enckey); } free(rp->p1name); free(rp->p2name); free(rp->p2lid); free(rp->p2nid); free(rp->p2rid); free(rp); } /* * Merge two flow rules if they match. * * Return 0 if ``from'' has been merged into ``to'', -1 otherwise. */ int ipsecctl_merge_rules(struct ipsec_rule *to, struct ipsec_rule *from) { int match = 0; assert((to->type & RULE_FLOW) && (from->type & RULE_FLOW)); if ((to->satype != from->satype) || (to->direction != from->direction) || (to->sport != from->sport) || (to->dport != from->dport) || (to->proto != from->proto)) return (-1); if (to->local != NULL || from->local != NULL) { if ((to->local == NULL) || (from->local == NULL) || memcmp(to->local, from->local, sizeof(*to->local))) return (-1); } if (to->peer != NULL || from->peer != NULL) { if ((to->peer == NULL) || (from->peer == NULL) || memcmp(to->peer, from->peer, sizeof(*to->peer))) return (-1); } if (ipsecctl_cmp_ident(to, from)) return (-1); if (ipsecctl_rule_matchsrc(to, from->src)) { free(from->src->name); free(from->src); from->src = NULL; match = 1; } if (ipsecctl_rule_matchdst(to, from->dst)) { free(from->dst->name); free(from->dst); from->dst = NULL; match = 1; } if (!match) return (-1); TAILQ_INSERT_TAIL(&to->collapsed_rules, from, bundle_entry); return (0); } /* * Return 0 if ``r1'' and ``r2'' IDENTITY match, -1 otherwise. */ int ipsecctl_cmp_ident(struct ipsec_rule *r1, struct ipsec_rule *r2) { if ((r1->auth == NULL) && (r2->auth == NULL)) return (0) ; if ((r1->auth == NULL) || (r2->auth == NULL)) return (-1); if (r1->auth->type != r2->auth->type) return (-1); if (r1->auth->srcid != NULL) { if (r2->auth->srcid == NULL) return (-1); if (strcmp(r1->auth->srcid, r2->auth->srcid)) return (-1); } if (r1->auth->dstid) { if (r2->auth->dstid == NULL) return (-1); if (strcmp(r1->auth->dstid, r2->auth->dstid)) return (-1); } return (0); } /* * Return 0 if ``r'' or its merged entries contain ``src'', -1 otherwise. */ int ipsecctl_rule_matchsrc(struct ipsec_rule *r, struct ipsec_addr_wrap *src) { struct ipsec_rule *r2; if (memcmp(r->src, src, sizeof(*r->src)) == 0) return (-1); TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) { if (r2->src == NULL) continue; if (memcmp(r2->src, src, sizeof(*r->src)) == 0) return (-1); } return (0); } /* * Return 0 if ``r'' or its merged entries contain ``dst'', -1 otherwise. */ int ipsecctl_rule_matchdst(struct ipsec_rule *r, struct ipsec_addr_wrap *dst) { struct ipsec_rule *r2; if (memcmp(r->dst, dst, sizeof(*r->dst)) == 0) return (-1); TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) { if (r2->dst == NULL) continue; if (memcmp(r2->dst, dst, sizeof(*r->dst)) == 0) return (-1); } return (0); } void ipsecctl_print_addr(struct ipsec_addr_wrap *ipa) { int bits; char buf[NI_MAXHOST]; if (ipa == NULL) { printf("?"); return; } if (inet_ntop(ipa->af, &ipa->address, buf, sizeof(buf)) == NULL) printf("?"); else printf("%s", buf); bits = unmask(&ipa->mask); if (bits != (ipa->af == AF_INET ? 32 : 128)) printf("/%d", bits); } void ipsecctl_print_proto(u_int8_t proto) { struct protoent *p; if ((p = getprotobynumber(proto)) != NULL) printf("%s", p->p_name); else printf("%u", proto); } void ipsecctl_print_port(u_int16_t port, const char *proto) { struct servent *s; if ((s = getservbyport(port, proto)) != NULL) printf("%s", s->s_name); else printf("%u", ntohs(port)); } void ipsecctl_print_key(struct ipsec_key *key) { int i; for (i = 0; i < (int)key->len; i++) printf("%02x", key->data[i]); } void ipsecctl_print_flow(struct ipsec_rule *r, int opts) { struct ipsec_rule *r2; printf("flow %s %s", satype[r->satype], direction[r->direction]); if (r->proto) { printf(" proto "); ipsecctl_print_proto(r->proto); } printf(" from "); if (opts & IPSECCTL_OPT_COLLAPSE) { printf("{ "); ipsecctl_print_addr(r->src); TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) { if (r2->src == NULL) continue; printf(", "); ipsecctl_print_addr(r2->src); } printf(" }"); } else ipsecctl_print_addr(r->src); if (r->sport) { printf(" port "); ipsecctl_print_port(r->sport, r->proto == IPPROTO_TCP ? "tcp" : "udp"); } printf(" to "); if (opts & IPSECCTL_OPT_COLLAPSE) { printf("{ "); ipsecctl_print_addr(r->dst); TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) { if (r2->dst == NULL) continue; printf(", "); ipsecctl_print_addr(r2->dst); } printf(" }"); } else ipsecctl_print_addr(r->dst); if (r->dport) { printf(" port "); ipsecctl_print_port(r->dport, r->proto == IPPROTO_TCP ? "tcp" : "udp"); } if (r->local) { printf(" local "); ipsecctl_print_addr(r->local); } if (r->peer) { printf(" peer "); ipsecctl_print_addr(r->peer); } if (r->auth) { if (r->auth->srcid) printf(" srcid %s", r->auth->srcid); if (r->auth->dstid) printf(" dstid %s", r->auth->dstid); if (r->auth->type > 0) printf(" %s", auth[r->auth->type]); } printf(" type %s", flowtype[r->flowtype]); printf("\n"); } void ipsecctl_print_sa(struct ipsec_rule *r, int opts) { printf("%s ", satype[r->satype]); /* tunnel/transport is only meaningful for esp/ah/ipcomp */ if (r->satype != IPSEC_TCPMD5 && r->satype != IPSEC_IPIP) printf("%s ", tmode[r->tmode]); printf("from "); ipsecctl_print_addr(r->src); printf(" to "); ipsecctl_print_addr(r->dst); printf(" spi 0x%08x", r->spi); if (r->satype != IPSEC_TCPMD5) { if (r->xfs && r->xfs->authxf) printf(" auth %s", r->xfs->authxf->name); if (r->xfs && r->xfs->encxf) printf(" enc %s", r->xfs->encxf->name); if (r->xfs && r->xfs->compxf) printf(" comp %s", r->xfs->compxf->name); } if (r->authkey && (opts & IPSECCTL_OPT_SHOWKEY)) { if (r->satype == IPSEC_TCPMD5) printf(" "); else printf(" \\\n\t"); printf("authkey 0x"); ipsecctl_print_key(r->authkey); } if (r->enckey && (opts & IPSECCTL_OPT_SHOWKEY)) { if (r->satype == IPSEC_TCPMD5) printf(" "); else printf(" \\\n\t"); printf("enckey 0x"); ipsecctl_print_key(r->enckey); } printf("\n"); } void ipsecctl_print_sabundle(struct ipsec_rule *r, int opts) { printf("[bundle %s to ", satype[r->proto]); ipsecctl_print_addr(r->dst); printf(" spi 0x%08x with %s to ", r->spi, satype[r->proto2]); ipsecctl_print_addr(r->dst2); printf(" spi 0x%08x", r->spi2); printf("]\n"); } void ipsecctl_print_rule(struct ipsec_rule *r, int opts) { struct ipsec_rule *r2; if (opts & IPSECCTL_OPT_VERBOSE2) { printf("@%d", r->nr); if (opts & IPSECCTL_OPT_COLLAPSE) { TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) { printf(",%d", r2->nr); } } printf(" "); } if (r->type & RULE_FLOW) ipsecctl_print_flow(r, opts); if (r->type & RULE_SA) ipsecctl_print_sa(r, opts); if (r->type & RULE_IKE) ike_print_config(r, opts); if (r->type & RULE_BUNDLE) ipsecctl_print_sabundle(r, opts); } int ipsecctl_flush(int opts) { if (opts & IPSECCTL_OPT_NOACTION) return (0); if (pfkey_init() == -1) errx(1, "ipsecctl_flush: failed to open PF_KEY socket"); if (pfkey_ipsec_flush() == -1) errx(1, "ipsecctl_flush: failed to flush"); return (0); } void ipsecctl_get_rules(struct ipsecctl *ipsec) { struct sadb_msg *msg; struct ipsec_rule *rule, *last = NULL; int mib[4]; size_t need; char *buf, *lim, *next; mib[0] = CTL_NET; mib[1] = PF_KEY; mib[2] = PF_KEY_V2; mib[3] = NET_KEY_SPD_DUMP; if (sysctl(mib, 4, NULL, &need, NULL, 0) == -1) err(1, "ipsecctl_get_rules: sysctl"); if (need == 0) return; if ((buf = malloc(need)) == NULL) err(1, "ipsecctl_get_rules: malloc"); if (sysctl(mib, 4, buf, &need, NULL, 0) == -1) err(1, "ipsecctl_get_rules: sysctl"); lim = buf + need; for (next = buf; next < lim; next += msg->sadb_msg_len * PFKEYV2_CHUNK) { msg = (struct sadb_msg *)next; if (msg->sadb_msg_len == 0) break; rule = calloc(1, sizeof(struct ipsec_rule)); if (rule == NULL) err(1, "ipsecctl_get_rules: calloc"); rule->nr = ipsec->rule_nr++; rule->type |= RULE_FLOW; TAILQ_INIT(&rule->collapsed_rules); if (pfkey_parse(msg, rule)) errx(1, "ipsecctl_get_rules: " "failed to parse PF_KEY message"); /* * Try to collapse ``rule'' with the last enqueued rule. * * Note that comparing only the last entry works only if * the dump is sorted. */ if ((ipsec->opts & IPSECCTL_OPT_COLLAPSE) && (last != NULL) && (ipsecctl_merge_rules(last, rule) == 0)) continue; ipsecctl_add_rule(ipsec, rule); last = rule; } free(buf); } void ipsecctl_print_title(char *title) { if (!first_title) printf("\n"); first_title = 0; printf("%s\n", title); } void ipsecctl_show_flows(int opts) { struct ipsecctl ipsec; struct ipsec_rule *rp; bzero(&ipsec, sizeof(ipsec)); ipsec.opts = opts; TAILQ_INIT(&ipsec.rule_queue); ipsecctl_get_rules(&ipsec); if (opts & IPSECCTL_OPT_SHOWALL) ipsecctl_print_title("FLOWS:"); if (TAILQ_FIRST(&ipsec.rule_queue) == 0) { if (opts & IPSECCTL_OPT_SHOWALL) printf("No flows\n"); return; } while ((rp = TAILQ_FIRST(&ipsec.rule_queue))) { TAILQ_REMOVE(&ipsec.rule_queue, rp, rule_entry); ipsecctl_print_rule(rp, ipsec.opts); free(rp->src->name); free(rp->src); free(rp->dst->name); free(rp->dst); if (rp->local) { free(rp->local->name); free(rp->local); } if (rp->peer) { free(rp->peer->name); free(rp->peer); } if (rp->auth) { free(rp->auth->srcid); free(rp->auth->dstid); free(rp->auth); } free(rp); } } void ipsecctl_show_sas(int opts) { struct sadb_msg *msg; struct sad *sad; int mib[5], sacount, i; size_t need = 0; char *buf, *lim, *next; mib[0] = CTL_NET; mib[1] = PF_KEY; mib[2] = PF_KEY_V2; mib[3] = NET_KEY_SADB_DUMP; mib[4] = SADB_SATYPE_UNSPEC; if (opts & IPSECCTL_OPT_SHOWALL) ipsecctl_print_title("SAD:"); /* When the SAD is empty we get ENOENT, no need to err(). */ if (sysctl(mib, 5, NULL, &need, NULL, 0) == -1 && errno != ENOENT) err(1, "ipsecctl_show_sas: sysctl"); if (need == 0) { if (opts & IPSECCTL_OPT_SHOWALL) printf("No entries\n"); return; } if ((buf = malloc(need)) == NULL) err(1, "ipsecctl_show_sas: malloc"); if (sysctl(mib, 5, buf, &need, NULL, 0) == -1) err(1, "ipsecctl_show_sas: sysctl"); sacount = 0; lim = buf + need; for (next = buf; next < lim; next += msg->sadb_msg_len * PFKEYV2_CHUNK) { msg = (struct sadb_msg *)next; if (msg->sadb_msg_len == 0) break; sacount++; } if ((sad = calloc(sacount, sizeof(*sad))) == NULL) err(1, "ipsecctl_show_sas: calloc"); i = 0; for (next = buf; next < lim; next += msg->sadb_msg_len * PFKEYV2_CHUNK) { msg = (struct sadb_msg *)next; if (msg->sadb_msg_len == 0) break; sad[i].sad_spi = pfkey_get_spi(msg); sad[i].sad_msg = msg; i++; } qsort(sad, sacount, sizeof(*sad), sacompare); for (i = 0; i < sacount; i++) pfkey_print_sa(sad[i].sad_msg, opts); free(sad); free(buf); } int ipsecctl_monitor(int opts) { return (pfkey_monitor(opts)); } __dead void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-cdFkmnv] [-D macro=value] [-f file]" " [-i fifo] [-s modifier]\n", __progname); exit(1); } const char * ipsecctl_lookup_option(char *cmd, const char **list) { if (cmd != NULL && *cmd) for (; *list; list++) if (!strncmp(cmd, *list, strlen(cmd))) return (*list); return (NULL); } int main(int argc, char *argv[]) { int error = 0; int ch; int opts = 0; char *rulesopt = NULL; if (argc < 2) usage(); while ((ch = getopt(argc, argv, "cD:df:Fi:kmnvs:")) != -1) { switch (ch) { case 'c': opts |= IPSECCTL_OPT_COLLAPSE; break; case 'D': if (cmdline_symset(optarg) < 0) warnx("could not parse macro definition %s", optarg); break; case 'd': opts |= IPSECCTL_OPT_DELETE; break; case 'f': rulesopt = optarg; break; case 'F': opts |= IPSECCTL_OPT_FLUSH; break; case 'i': isakmpd_fifo = optarg; break; case 'k': opts |= IPSECCTL_OPT_SHOWKEY; break; case 'm': opts |= IPSECCTL_OPT_MONITOR; break; case 'n': opts |= IPSECCTL_OPT_NOACTION; break; case 'v': if (opts & IPSECCTL_OPT_VERBOSE) opts |= IPSECCTL_OPT_VERBOSE2; opts |= IPSECCTL_OPT_VERBOSE; break; case 's': showopt = ipsecctl_lookup_option(optarg, showopt_list); if (showopt == NULL) { warnx("Unknown show modifier '%s'", optarg); usage(); /* NOTREACHED */ } opts |= IPSECCTL_OPT_SHOW; break; default: usage(); /* NOTREACHED */ } } if (argc != optind) { warnx("unknown command line argument: %s ...", argv[optind]); usage(); /* NOTREACHED */ } if (opts & IPSECCTL_OPT_FLUSH) if (ipsecctl_flush(opts)) error = 1; if (rulesopt != NULL) if (ipsecctl_rules(rulesopt, opts)) error = 1; if (showopt != NULL) { switch (*showopt) { case 'f': ipsecctl_show_flows(opts); break; case 's': ipsecctl_show_sas(opts); break; case 'a': opts |= IPSECCTL_OPT_SHOWALL; ipsecctl_show_flows(opts); ipsecctl_show_sas(opts); } } if (opts & IPSECCTL_OPT_MONITOR) if (ipsecctl_monitor(opts)) error = 1; exit(error); } static int unmask(struct ipsec_addr *ipa) { int i = 31, j = 0, b = 0; u_int32_t tmp; while (j < 4 && ipa->addr32[j] == 0xffffffff) { b += 32; j++; } if (j < 4) { tmp = ntohl(ipa->addr32[j]); for (i = 31; tmp & (1 << i); --i) b++; } return (b); }