/* routines that interface with the kernel's IPsec mechanism * Copyright (C) 1997 Angelos D. Keromytis. * Copyright (C) 1998, 1999 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 . * * 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: kernel.c,v 1.86 2000/06/21 18:24:33 dhr Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KLIPS # include # include # include u_int32_t pfkey_seq = 0; #endif /* KLIPS */ #include "constants.h" #include "defs.h" #include "rnd.h" #include "id.h" #include "connections.h" /* needs id.h */ #include "state.h" #include "kernel.h" #include "log.h" #include "server.h" #include "whack.h" /* for RC_LOG_SERIOUS */ bool no_klips = FALSE; /* don't actually use KLIPS */ void pfkey_handle(void) { char buffer[4096]; struct sadb_ext *extensions[SADB_EXT_MAX + 1]; ssize_t len = read(pfkeyfd, buffer, sizeof(buffer)); DBG(DBG_CONTROL, DBG_log(BLANK_FORMAT); DBG_log("pfkey_handle: read %d octets of pfkey message", len)); if (len == -1) { log_errno((e, "read() failed in pfkey_handle()")); #if 0 /* this may be a dumb idea */ close(pfkeyfd); pfkeyfd = NULL_FD; #endif return; } /* Parse with default extension parsers */ if (pfkey_msg_parse((struct sadb_msg *)buffer, NULL, extensions, EXT_BITS_OUT)) { DBG(DBG_CONTROL, DBG_log("pfkey_handle:" " insane test message received from below, \"don't panic\".")); } else { DBG(DBG_CONTROL, DBG_log("pfkey_handle:" " sane message parsed but not processed (yet) from below.")); } /* Need to add pluto's own extension * processors and message processing here */ } /* Generate Unique SPI numbers. * * The specs say that the number must not be less than 0x100. * XXX This should be replaced by a call to the kernel when * XXX we get an API. * The returned SPI is in network byte order. * We use a random number as the initial SPI so that there is * a good chance that different Pluto instances will choose * different SPIs. This is good for two reasons. * - the keying material for the initiator and responder only * differs if the SPIs differ. * - if Pluto is restarted, it would otherwise recycle the SPI * numbers and confuse everything. When the kernel generates * SPIs, this will no longer matter. */ ipsec_spi_t get_ipsec_spi(ipsec_spi_t avoid) { static ipsec_spi_t spi = 0; /* host order, so not returned directly! */ spi++; while (spi < IPSEC_DOI_SPI_OUR_MIN || spi == ntohl(avoid)) get_rnd_bytes((u_char *)&spi, sizeof(spi)); DBG(DBG_CONTROL, { ipsec_spi_t spi_net = htonl(spi); DBG_dump("generate SPI:", (u_char *)&spi_net, sizeof(spi_net)); }); return htonl(spi); } /* invoke the updown script to do the routing and firewall commands required * * The user-specified updown script is run. Parameters are fed to it in * the form of environment variables. All such environment variables * have names starting with "PLUTO_". * * The operation to be performed is specified by PLUTO_VERB. This * verb has a suffix "-host" if the client on this end is just the * host; otherwise the suffix is "-client". * * "prepare-host" and "prepare_client" are used to delete a route * that may exist (due to forces outside of Pluto). It is used to * prepare for pluto creating a route. * * "route-host" and "route-client" are used to install a route. * Since routing is based only on destination, the PLUTO_MY_CLIENT_* * values are probably of no use (using them may signify a bug). * * "unroute-host" and "unroute-client" are used to delete a route. * Since routing is based only on destination, the PLUTO_MY_CLIENT_* * values are probably of no use (using them may signify a bug). * * "up-host" and "up-client" are run when an eroute is added (not replaced). * They are useful for adjusting a firewall: usually for adding a rule * to let processed packets flow between clients. Note that only * one eroute may exist for a pair of client subnets but inbound * IPsec SAs may persist without an eroute. * * "down-host" and "down-client" are run when an eroute is deleted. * They are useful for adjusting a firewall. */ #ifndef DEFAULT_UPDOWN # define DEFAULT_UPDOWN "ipsec _updown" #endif static bool do_command(struct connection *c, const char *verb) { char cmd[1024]; const char *verb_suffix = self_client(c->this)? "-host" : "-client"; /* form the command string */ { char me_str[SOCKADDR_STRING_SIZE], myclientnet_str[SOCKADDR_STRING_SIZE], myclientmask_str[SOCKADDR_STRING_SIZE], peer_str[SOCKADDR_STRING_SIZE], nexthop_str[SOCKADDR_STRING_SIZE], peerclientnet_str[SOCKADDR_STRING_SIZE], peerclientmask_str[SOCKADDR_STRING_SIZE]; if (-1 == snprintf(cmd, sizeof(cmd), "2>&1 " /* capture stderr along with stdout */ "PLUTO_VERSION='1.0' " /* change VERSION when interface space changes */ "PLUTO_VERB='%s%s' " "PLUTO_CONNECTION='%s' " "PLUTO_NEXT_HOP='%s' " "PLUTO_INTERFACE='%s' " "PLUTO_ME='%s' " "PLUTO_MY_CLIENT_NET='%s' " "PLUTO_MY_CLIENT_MASK='%s' " "PLUTO_PEER='%s' " "PLUTO_PEER_CLIENT_NET='%s' " "PLUTO_PEER_CLIENT_MASK='%s' " "%s" /* actual script */ , verb, verb_suffix , c->name , strcpy(nexthop_str, inet_ntoa(c->this.host_nexthop)) , c->interface->vname , strcpy(me_str, inet_ntoa(c->this.host_addr)) , strcpy(myclientnet_str, inet_ntoa(c->this.client_net)) , strcpy(myclientmask_str, inet_ntoa(c->this.client_mask)) , strcpy(peer_str, inet_ntoa(c->that.host_addr)) , strcpy(peerclientnet_str, inet_ntoa(c->that.client_net)) , strcpy(peerclientmask_str, inet_ntoa(c->that.client_mask)) , c->this.updown == NULL? DEFAULT_UPDOWN : c->this.updown)) { loglog(RC_LOG_SERIOUS, "%s%s command too long!", verb, verb_suffix); return FALSE; } } DBG(DBG_CONTROL, DBG_log("executing %s%s: %s" , verb, verb_suffix, cmd)); #ifdef KLIPS if (!no_klips) { /* invoke the script, catching stderr and stdout */ FILE *f = popen(cmd, "r"); if (f == NULL) { loglog(RC_LOG_SERIOUS, "unable to popen %s%s command", verb, verb_suffix); return FALSE; } /* log any output */ for (;;) { char resp[256]; if (fgets(resp, sizeof(resp), f) == NULL) { if (ferror(f)) { log_errno((e, "fgets failed on output of %s%s command" , verb, verb_suffix)); return FALSE; } else { passert(feof(f)); break; } } else { char *e = resp + strlen(resp); if (e > resp && e[-1] == '\n') e[-1] = '\0'; /* trim trailing '\n' */ log("%s%s output: %s", verb, verb_suffix, resp); } } /* report on and react to return code */ { int r = pclose(f); if (r == -1) { log_errno((e, "pclose failed for %s%s command" , verb, verb_suffix)); return FALSE; } else if (WIFEXITED(r)) { if (WEXITSTATUS(r) != 0) { loglog(RC_LOG_SERIOUS, "%s%s command exited with status %d" , verb, verb_suffix, WEXITSTATUS(r)); return FALSE; } } else if (WIFSIGNALED(r)) { loglog(RC_LOG_SERIOUS, "%s%s command exited with signal %d" , verb, verb_suffix, WTERMSIG(r)); return FALSE; } else { loglog(RC_LOG_SERIOUS, "%s%s command exited with unknown status %d" , verb, verb_suffix, r); return FALSE; } } } #endif /* KLIPS */ return TRUE; } bool route_connection(struct connection *c, bool doit) { if (c->routed) { /* already done */ } else if (!no_klips && c->this.host_port != IKE_UDP_PORT && inside_client(c->that.host_addr, c->that)) { loglog(RC_LOG_SERIOUS, "cannot install route: peer is within its client"); } else if (!doit) { /* just testing -- we expect to be able to do it */ return TRUE; } else { struct connection *d = route_owner(c, FALSE); passert(c->eroute_owner == SOS_NOBODY); if (d == NULL) { /* nobody's got it: we can try to install our route */ (void) do_command(c, "prepare"); /* just in case; ignore failure */ if (do_command(c, "route")) c->routed = TRUE; } else if (same_ip(d->this.host_nexthop, c->this.host_nexthop) && d->interface == c->interface) { /* The other connection has the same nexthop and interface, * so we're done. */ c->routed = TRUE; } else if (d->eroute_owner == SOS_NOBODY) { /* The other connection has the route, and it conflicts * (the nexthop or interface differs), but that connection * isn't using the route. We'll steal it! There might be * other connections using the same route, but none of * them is using it either (otherwise route_owner() * would have returned one that did). * * A feature of LINUX allows us to install the new route * before deleting the old if the nexthops differ. * This reduces the "window of vulnerability" when packets * might flow in the clear. * * c->routed must be set last so that route_owner() * doesn't find it. */ bool preadd = !same_ip(d->this.host_nexthop, c->this.host_nexthop); if (!preadd || do_command(c, "route")) { /* one unroute for all */ if (do_command(d, "unroute")) { do { passert(d->eroute_owner == SOS_NOBODY); d->routed = FALSE; d = route_owner(c, FALSE); } while (d != NULL); if (preadd || do_command(c, "route")) c->routed = TRUE; } } } else { loglog(RC_LOG_SERIOUS, "cannot install route: connection \"%s\" already has it" , d->name); } } return c->routed; } void unroute_connection(struct connection *c) { /* only unroute if no other connection shares it */ if (c->routed) { c->routed = FALSE; if (route_owner(c, FALSE) == NULL && !do_command(c, "unroute")) c->routed = TRUE; /* undo on failure */ } } static void set_text_said(char text_said[SATOA_BUF], ip_address dst, ipsec_spi_t spi, int proto) { struct sa_id said; said.dst = dst; said.spi = spi; said.proto = proto; satoa(said, 0, text_said, SATOA_BUF); } static bool pfkey_build(int error , const char *description , const char *text_said , struct sadb_ext *extensions[SADB_EXT_MAX + 1]) { if (error == 0) { return TRUE; } else { loglog(RC_LOG_SERIOUS, "building of %s %s failed, code %d" , description, text_said, error); pfkey_extensions_free(extensions); return FALSE; } } static bool finish_pfkey_msg(struct sadb_ext *extensions[SADB_EXT_MAX + 1] , const char *description , const char *text_said) { struct sadb_msg *pfkey_msg; int error = pfkey_msg_build(&pfkey_msg, extensions, EXT_BITS_IN); if (error != 0) { loglog(RC_LOG_SERIOUS, "building of pfkey_msg %s %s failed, code %d" , description, text_said, error); pfkey_extensions_free(extensions); pfkey_msg_free(&pfkey_msg); return FALSE; } { size_t len = pfkey_msg->sadb_msg_len * IPSEC_PFKEYv2_ALIGN; DBG_cond_dump(DBG_KLIPS, description, (void *) pfkey_msg, len); if (!no_klips) { ssize_t r = write(pfkeyfd, pfkey_msg, len); if (r != (ssize_t)len) { log_errno((e, "pfkey write() of %s %s failed" , description, text_said)); pfkey_extensions_free(extensions); pfkey_msg_free(&pfkey_msg); return FALSE; } } pfkey_extensions_free(extensions); pfkey_msg_free(&pfkey_msg); } return TRUE; } #ifdef KLIPS /* Setup an IPsec route entry. Code taken from addrt.c. * We are only dealing with outbound SAs. * op is one of the following KLIPS operators: */ #define ERO_REPLACE_FLAG 0x100 /* out of band */ #define ERO_DELETE SADB_X_DELFLOW #define ERO_ADD SADB_X_ADDFLOW #define ERO_REPLACE (SADB_X_ADDFLOW | ERO_REPLACE_FLAG) static bool do_eroute(struct state *st, unsigned op, const char *opname UNUSED) { struct connection *c = st->st_connection; struct sadb_ext *extensions[SADB_EXT_MAX + 1]; struct sockaddr_in s_ska, d_ska, sflow_ska, dflow_ska, smask_ska, dmask_ska; char text_said[SATOA_BUF]; unsigned int inner_proto, inner_satype; ipsec_spi_t inner_spi; /* figure out the SPI and protocol (in two forms) * for the innermost transformation. */ if (st->st_ah.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL || st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) { inner_spi = st->st_tunnel_out_spi; inner_proto = SA_IPIP; inner_satype = SADB_X_SATYPE_IPIP; } else if (st->st_esp.present) { inner_spi = st->st_esp.attrs.spi; inner_proto = SA_ESP; inner_satype = SADB_SATYPE_ESP; } else if (st->st_ah.present) { inner_spi = st->st_ah.attrs.spi; inner_proto = SA_AH; inner_satype = SADB_SATYPE_AH; } else { passert(FALSE); /* no transform at all! */ } set_text_said(text_said, c->that.host_addr, inner_spi, inner_proto); mksin(s_ska, c->this.host_addr.s_addr, 0); /* KLIPS doesn't care */ mksin(sflow_ska, c->this.client_net.s_addr, 0); mksin(smask_ska, c->this.client_mask.s_addr, 0); mksin(d_ska, c->that.host_addr.s_addr, 0); mksin(dflow_ska, c->that.client_net.s_addr, 0); mksin(dmask_ska, c->that.client_mask.s_addr, 0); DBG(DBG_CONTROL, { char mybuf[SOCKADDR_STRING_SIZE*2]; char peerbuf[SOCKADDR_STRING_SIZE*2]; subnettoa(c->this.client_net, c->this.client_mask, 0, mybuf, sizeof(mybuf)); subnettoa(c->that.client_net, c->that.client_mask, 0, peerbuf, sizeof(peerbuf)); DBG_log("%s eroute %s to %s via %s" , opname, mybuf, peerbuf, text_said); }); pfkey_extensions_init(extensions); return pfkey_build(pfkey_msg_hdr_build(&extensions[0] , op & ~ERO_REPLACE_FLAG, inner_satype, 0, ++pfkey_seq, getpid()) , "pfkey_msg_hdr flow", text_said, extensions) && (op == ERO_DELETE || (pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] , SADB_EXT_SA , inner_spi /* in network order */ , 0, 0, 0, 0, op == ERO_REPLACE? SADB_X_SAFLAGS_REPLACEFLOW : 0) , "pfkey_sa add flow", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_SRC] , SADB_EXT_ADDRESS_SRC, 0, 0, (struct sockaddr*)&s_ska) , "pfkey_addr_s add flow", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_DST] , SADB_EXT_ADDRESS_DST, 0, 0, (struct sockaddr*)&d_ska) , "pfkey_addr_d add flow", text_said, extensions))) && pfkey_build(pfkey_address_build(&extensions[SADB_X_EXT_ADDRESS_SRC_FLOW] , SADB_X_EXT_ADDRESS_SRC_FLOW, 0, 0, (struct sockaddr*)&sflow_ska) , "pfkey_addr_sflow", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_X_EXT_ADDRESS_DST_FLOW] , SADB_X_EXT_ADDRESS_DST_FLOW, 0, 0, (struct sockaddr*)&dflow_ska) , "pfkey_addr_dflow", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_X_EXT_ADDRESS_SRC_MASK] , SADB_X_EXT_ADDRESS_SRC_MASK, 0, 0, (struct sockaddr*)&smask_ska) , "pfkey_addr_smask", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_X_EXT_ADDRESS_DST_MASK] , SADB_X_EXT_ADDRESS_DST_MASK, 0, 0, (struct sockaddr*)&dmask_ska) , "pfkey_addr_dmask", text_said, extensions) && finish_pfkey_msg(extensions, "flow", text_said); } static bool del_spi(ipsec_spi_t spi, int proto, struct in_addr src, struct in_addr dest) { struct sadb_ext *extensions[SADB_EXT_MAX + 1]; struct sockaddr_in pfkey_address_s_ska; struct sockaddr_in pfkey_address_d_ska; char text_said[SATOA_BUF]; pfkey_extensions_init(extensions); set_text_said(text_said, dest, spi, proto); mksin(pfkey_address_s_ska, src.s_addr, 0); mksin(pfkey_address_d_ska, dest.s_addr, 0); DBG(DBG_KLIPS, DBG_log("delete %s", text_said)); return pfkey_build(pfkey_msg_hdr_build(&extensions[0], SADB_DELETE , proto2satype(proto), 0, ++pfkey_seq, getpid()) , "pfkey_msg_hdr delete SA", text_said, extensions) && pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] , SADB_EXT_SA , spi /* in host order */ , 0, SADB_SASTATE_MATURE, 0, 0, 0) , "pfkey_sa delete SA", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_SRC] , SADB_EXT_ADDRESS_SRC, 0, 0, (struct sockaddr*)&pfkey_address_s_ska) , "pfkey_addr_s delete SA", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_DST] , SADB_EXT_ADDRESS_DST, 0, 0, (struct sockaddr*)&pfkey_address_d_ska) , "pfkey_addr_d delete SA", text_said, extensions) && finish_pfkey_msg(extensions, "Delete SA", text_said); } /* Setup a pair of SAs. Code taken from setsa.c and spigrp.c, in * ipsec-0.5. */ /* A netlink header defines EM_MAXRELSPIS, the max number of groupings. * Is there a PFKEY equivalent? */ #ifndef EM_MAXRELSPIS # define EM_MAXRELSPIS 4 /* AH ESP IPIP IPCOMP */ #endif static bool setup_half_ipsec_sa(struct state *st, bool inbound) { /* Build an inbound or outbound SA */ struct connection *c = st->st_connection; struct in_addr src = inbound? c->that.host_addr : c->this.host_addr, dst = inbound? c->this.host_addr : c->that.host_addr; /* SPIs, saved for spigrouping or undoing, if necessary */ struct sa_id said[EM_MAXRELSPIS], *said_next = said; struct sadb_ext *extensions[SADB_EXT_MAX + 1]; struct sockaddr_in src_sin, dst_sin; char text_said[SATOA_BUF]; mksin(src_sin, src.s_addr, 0); mksin(dst_sin, dst.s_addr, 0); /* set up AH SA, if any */ if (st->st_ah.present) { ipsec_spi_t ah_spi = inbound? st->st_ah.our_spi : st->st_ah.attrs.spi; u_char *ah_dst_keymat = inbound? st->st_ah.our_keymat : st->st_ah.peer_keymat; unsigned char authalg; switch (st->st_ah.attrs.auth) { case AUTH_ALGORITHM_HMAC_MD5: authalg = SADB_AALG_MD5HMAC; break; case AUTH_ALGORITHM_HMAC_SHA1: authalg = SADB_AALG_SHA1HMAC; break; case AUTH_ALGORITHM_KPDK: case AUTH_ALGORITHM_DES_MAC: default: loglog(RC_LOG_SERIOUS, "%s not implemented yet" , enum_show(&auth_alg_names, st->st_ah.attrs.auth)); goto fail; } pfkey_extensions_init(extensions); set_text_said(text_said, dst, ah_spi, SA_AH); if (!(pfkey_build(pfkey_msg_hdr_build(&extensions[0], SADB_ADD , SADB_SATYPE_AH, 0, ++pfkey_seq, getpid()) , "pfkey_msg_hdr Add AH SA", text_said, extensions) && pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] , SADB_EXT_SA , ah_spi /* in network order */ , 32, SADB_SASTATE_MATURE, authalg, 0, 0) , "pfkey_sa Add AH SA", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_SRC] , SADB_EXT_ADDRESS_SRC, 0, 0 , (struct sockaddr *)&src_sin) , "pfkey_addr_s Add AH SA", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_DST] , SADB_EXT_ADDRESS_DST, 0, 0 , (struct sockaddr *)&dst_sin) , "pfkey_addr_d Add AH SA", text_said, extensions) && pfkey_build(pfkey_key_build(&extensions[SADB_EXT_KEY_AUTH] , SADB_EXT_KEY_AUTH, st->st_ah.keymat_len * IPSEC_PFKEYv2_ALIGN , ah_dst_keymat) , "pfkey_key_a Add AH SA", text_said, extensions) && finish_pfkey_msg(extensions, "Add AH SA", text_said))) goto fail; said_next->dst = dst; said_next->spi = ah_spi; said_next->proto = SA_AH; said_next++; } /* set up ESP SA, if any */ if (st->st_esp.present) { ipsec_spi_t esp_spi = inbound? st->st_esp.our_spi : st->st_esp.attrs.spi; u_char *esp_dst_keymat = inbound? st->st_esp.our_keymat : st->st_esp.peer_keymat; struct esp_info { u_int8_t transid; /* negotiated ESP transform */ u_int16_t auth; /* negotiated AUTH */ size_t enckeylen; /* keylength for ESP transform */ size_t authkeylen; /* keylength for AUTH */ u_int8_t encryptalg; u_int8_t authalg; }; const struct esp_info *ei; static const struct esp_info esp_info[] = { { ESP_NULL, AUTH_ALGORITHM_HMAC_MD5, 0, HMAC_MD5_KEY_LEN, SADB_EALG_NULL, SADB_AALG_MD5HMAC }, { ESP_NULL, AUTH_ALGORITHM_HMAC_SHA1, 0, HMAC_SHA1_KEY_LEN, SADB_EALG_NULL, SADB_AALG_SHA1HMAC }, { ESP_DES, AUTH_ALGORITHM_NONE, DES_CBC_BLOCK_SIZE, 0, SADB_EALG_DESCBC, SADB_AALG_NONE }, { ESP_DES, AUTH_ALGORITHM_HMAC_MD5, DES_CBC_BLOCK_SIZE, HMAC_MD5_KEY_LEN, SADB_EALG_DESCBC, SADB_AALG_MD5HMAC }, { ESP_DES, AUTH_ALGORITHM_HMAC_SHA1, DES_CBC_BLOCK_SIZE, HMAC_SHA1_KEY_LEN, SADB_EALG_DESCBC, SADB_AALG_SHA1HMAC }, { ESP_3DES, AUTH_ALGORITHM_NONE, DES_CBC_BLOCK_SIZE * 3, 0, SADB_EALG_3DESCBC, SADB_AALG_NONE }, { ESP_3DES, AUTH_ALGORITHM_HMAC_MD5, DES_CBC_BLOCK_SIZE * 3, HMAC_MD5_KEY_LEN, SADB_EALG_3DESCBC, SADB_AALG_MD5HMAC }, { ESP_3DES, AUTH_ALGORITHM_HMAC_SHA1, DES_CBC_BLOCK_SIZE * 3, HMAC_SHA1_KEY_LEN, SADB_EALG_3DESCBC, SADB_AALG_SHA1HMAC }, }; for (ei = esp_info; ; ei++) { if (ei == &esp_info[elemsof(esp_info)]) { /* note: enum_show may use a static buffer, so two * calls in one printf would be a mistake. * enum_name does the same job, without a static buffer, * assuming the name will be found. */ loglog(RC_LOG_SERIOUS, "ESP transform %s / auth %s not implemented yet", enum_name(&esp_transformid_names, st->st_esp.attrs.transid), enum_name(&auth_alg_names, st->st_esp.attrs.auth)); goto fail; } if (st->st_esp.attrs.transid == ei->transid && st->st_esp.attrs.auth == ei->auth) break; } /* divide up keying material */ passert(st->st_esp.keymat_len == ei->enckeylen + ei->authkeylen); pfkey_extensions_init(extensions); set_text_said(text_said, dst, esp_spi, SA_ESP); if (!(pfkey_build(pfkey_msg_hdr_build(&extensions[0], SADB_ADD , SADB_SATYPE_ESP, 0, ++pfkey_seq, getpid()) , "pfkey_msg_hdr Add ESP SA", text_said, extensions) && pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] , SADB_EXT_SA , esp_spi /* in network order */ , 32, SADB_SASTATE_MATURE, ei->authalg, ei->encryptalg, 0) , "pfkey_sa Add ESP SA", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_SRC] , SADB_EXT_ADDRESS_SRC, 0, 0 , (struct sockaddr*)&src_sin) , "pfkey_addr_s Add ESP SA", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_DST] , SADB_EXT_ADDRESS_DST, 0, 0 , (struct sockaddr*)&dst_sin) , "pfkey_addr_d Add ESP SA", text_said, extensions) && (ei->authkeylen == 0 || pfkey_build(pfkey_key_build(&extensions[SADB_EXT_KEY_AUTH] , SADB_EXT_KEY_AUTH, ei->authkeylen * IPSEC_PFKEYv2_ALIGN , esp_dst_keymat + ei->enckeylen) , "pfkey_key_a Add ESP SA", text_said, extensions)) && (ei->enckeylen == 0 || pfkey_build(pfkey_key_build(&extensions[SADB_EXT_KEY_ENCRYPT] , SADB_EXT_KEY_ENCRYPT, ei->enckeylen * IPSEC_PFKEYv2_ALIGN , esp_dst_keymat) , "pfkey_key_a Add ESP SA", text_said, extensions)) && finish_pfkey_msg(extensions, "Add ESP SA", text_said))) goto fail; said_next->dst = dst; said_next->spi = esp_spi; said_next->proto = SA_ESP; said_next++; } /* If we are tunnelling, set up IP in IP pseudo SA */ if (st->st_ah.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL || st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL) { /* XXX hack alert -- we SHOULD NOT HAVE TO HAVE A DIFFERENT SPI * XXX FOR IP-in-IP ENCAPSULATION! */ ipsec_spi_t ipip_spi; /* Allocate an SPI for the tunnel. * Since our peer will never see this, * and it comes from its own number space, * it is purely a local implementation wart. */ { static ipsec_spi_t last_tunnel_spi = 0x100; ipip_spi = htonl(++last_tunnel_spi); if (inbound) st->st_tunnel_in_spi = ipip_spi; else st->st_tunnel_out_spi = ipip_spi; } pfkey_extensions_init(extensions); set_text_said(text_said , c->that.host_addr, ipip_spi, SA_IPIP); if (!(pfkey_build(pfkey_msg_hdr_build(&extensions[0] , SADB_ADD, SADB_X_SATYPE_IPIP, 0, ++pfkey_seq, getpid()) , "pfkey_msg_hdr Add outgoing IPIP SA", text_said, extensions) && pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] , SADB_EXT_SA , ipip_spi /* in network order */ , 0, SADB_SASTATE_MATURE , 0 , 0 , 0) , "pfkey_sa Add outgoing IPIP SA", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_SRC] , SADB_EXT_ADDRESS_SRC, 0, 0 , (struct sockaddr*)&src_sin) , "pfkey_addr_s Add outgoing IPIP SA", text_said, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_DST] , SADB_EXT_ADDRESS_DST, 0, 0 , (struct sockaddr*)&dst_sin) , "pfkey_addr_d Add outgoing IPIP SA", text_said, extensions) && finish_pfkey_msg(extensions, "Add outgoing IPIP SA", text_said))) goto fail; said_next->dst = dst; said_next->spi = ipip_spi; said_next->proto = SA_IPIP; said_next++; } /* If there are multiple SPIs, group them. */ if (said_next > &said[1]) { struct sa_id *s; /* group SAs, two at a time, inner to outer (backwards in said[]) * The grouping is by pairs. So if said[] contains ah esp ipip, * the grouping would be ipip:esp, esp:ah. */ for (s = said_next-1; s != said; ) { struct sadb_ext *extensions[SADB_EXT_MAX + 1]; struct sockaddr_in ska0, ska1; char text_said0[SATOA_BUF], text_said1[SATOA_BUF]; s--; /* group s[1] and s[0], in that order */ pfkey_extensions_init(extensions); set_text_said(text_said0, s[0].dst, s[0].spi, s[0].proto); mksin(ska0, s[0].dst.s_addr, 0); set_text_said(text_said1, s[1].dst, s[1].spi, s[1].proto); mksin(ska1, s[1].dst.s_addr, 0); DBG(DBG_KLIPS, DBG_log("grouping %s and %s", text_said1, text_said0)); if (!(pfkey_build(pfkey_msg_hdr_build(&extensions[0] , SADB_X_GRPSA , proto2satype(s[1].proto) , 0, ++pfkey_seq, getpid()) , "pfkey_msg_hdr group", text_said1, extensions) && pfkey_build(pfkey_sa_build(&extensions[SADB_EXT_SA] , SADB_EXT_SA , s[1].spi /* in network order */ , 0, 0, 0, 0, 0) , "pfkey_sa group", text_said1, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_EXT_ADDRESS_DST] , SADB_EXT_ADDRESS_DST, 0, 0, (struct sockaddr*)&ska1) , "pfkey_addr_d group", text_said1, extensions) && pfkey_build(pfkey_x_satype_build(&extensions[SADB_X_EXT_SATYPE2] , proto2satype(s[0].proto)) , "pfkey_satype group", text_said0, extensions) && pfkey_build(pfkey_sa_build(&extensions[SADB_X_EXT_SA2] , SADB_X_EXT_SA2 , s[0].spi /* in network order */ , 0, 0, 0, 0, 0) , "pfkey_sa2 group", text_said0, extensions) && pfkey_build(pfkey_address_build(&extensions[SADB_X_EXT_ADDRESS_DST2] , SADB_X_EXT_ADDRESS_DST2, 0, 0, (struct sockaddr*)&ska0) , "pfkey_addr_d2 group", text_said0, extensions) && finish_pfkey_msg(extensions, "group", text_said1))) goto fail; } /* could update said, but it will not be used */ } return TRUE; fail: { /* undo the done SPIs */ while (said_next-- != said) (void) del_spi(said_next->spi, said_next->proto , src, said_next->dst); return FALSE; } } /* teardown_ipsec_sa is a canibalized version of setup_ipsec_sa */ static bool teardown_half_ipsec_sa(struct state *st, bool inbound) { /* We need to delete AH, ESP, and IP in IP SPIs. * But if there is more than one, they have been grouped * so deleting any one will do. So we just delete the * first one found. It may or may not be the only one. */ struct connection *c = st->st_connection; struct ipsec_proto_info *f; /* first SA found */ unsigned proto; if (st->st_ah.present) { f = &st->st_ah; proto = SA_AH; } else if (st->st_esp.present) { f = &st->st_esp; proto = SA_ESP; } else { passert(FALSE); /* neither AH nor ESP in outbound SA bundle! */ } return inbound ? del_spi(f->our_spi, proto, c->that.host_addr, c->this.host_addr) : del_spi(f->attrs.spi, proto, c->this.host_addr, c->that.host_addr); } /* demand that we can eroute */ static bool could_eroute(struct connection *c, struct connection *ero) { if (ero != NULL && ero != c && ero->eroute_owner != SOS_NOBODY) { loglog(RC_LOG_SERIOUS, "cannot install eroute -- it is in use for \"%s\"", ero->name); return FALSE; /* another connection already using the eroute */ } return TRUE; } #endif /* KLIPS */ /* Note: install_inbound_ipsec_sa is only used by the Responder. * The Responder will subsequently use install_ipsec_sa for the outbound. * The Initiator uses install_ipsec_sa to install both at once. */ bool install_inbound_ipsec_sa(struct state *st) { struct connection *const c = st->st_connection; /* If our peer has a fixed-address client, check if we already * have a route for that client that conflicts. We will take this * as proof that that route and the connections using it are * obsolete and should be eliminated. Interestingly, this is * the only case in which we can tell that a connection is obsolete. */ if (!self_client(c->that)) { for (;;) { struct connection *o = route_owner(c, FALSE); if (o == NULL) break; /* nobody has a route */ if (same_ip(o->this.host_addr, c->this.host_addr) && same_ip(o->that.host_addr, c->that.host_addr)) break; /* existing route is compatible */ loglog(RC_LOG_SERIOUS, "route to peer's client conflicts with \"%s\" %s; unorienting old connection to free the route" , o->name, inet_ntoa(o->that.host_addr)); unorient_connection(o); } } #ifdef KLIPS /* check that we will be able to eroute */ if (!could_eroute(c, route_owner(c, TRUE))) return FALSE; /* Check that we will be able to route outgoing packets * through ipsecN interface. Actually, this only checks that * our peer isn't inside his own subnet or if she is, * that we're using UDP 500 so that IKE messages will not * be processed. */ if (!route_connection(c, FALSE)) return FALSE; /* (attempt to) actually set up the SAs */ return setup_half_ipsec_sa(st, TRUE); #else /* !KLIPS */ DBG(DBG_CONTROL, DBG_log("install_inbound_ipsec_sa()")); return TRUE; #endif /* !KLIPS */ } bool install_ipsec_sa(struct state *st, bool inbound_also) { #ifdef KLIPS bool res; struct connection *c = st->st_connection, *ero = route_owner(c, TRUE); /* who, if anyone, owns our eroute? */ if (!could_eroute(c, ero)) return FALSE; /* route outgoing packets through ipsecN interface. * Note that we don't bother to undo this on failure. */ if (!route_connection(c, TRUE)) return FALSE; /* (attempt to) actually set up the SAs */ res = (!inbound_also || setup_half_ipsec_sa(st, TRUE)) && setup_half_ipsec_sa(st, FALSE); /* no other connection has this eroute, but * this connection might have it for another SA. * This is expected when rekeying. * If another SA has it, we replace the eroute. */ if (res) { /* succeed OR tear SA down again */ if (c->eroute_owner == SOS_NOBODY) { /* new eroute: we also need to run "up" command */ res = do_eroute(st, ERO_ADD, "add"); if (res) { /* succeed OR tear eroute down again */ res = do_command(st->st_connection, "up"); if (!res) (void) do_eroute(st, ERO_DELETE, "delete"); } } else { /* old eroute being replaced -- no command required */ res = do_eroute(st, ERO_REPLACE, "replace"); } if (res) { c->eroute_owner = st->st_serialno; } else { (void) teardown_half_ipsec_sa(st, FALSE); if (inbound_also) (void) teardown_half_ipsec_sa(st, TRUE); } } return res; #else /* !KLIPS */ DBG(DBG_CONTROL, DBG_log("install_ipsec_sa() %s" , inbound_also? "inbound and oubound" : "outbound only")); return route_connection(st->st_connection, TRUE); #endif /* !KLIPS */ } bool delete_ipsec_sa(struct state *st, bool inbound_only) { #ifdef KLIPS struct connection *c = st->st_connection; bool own_eroute = c->eroute_owner == st->st_serialno; bool res = TRUE; if (inbound_only) { res = teardown_half_ipsec_sa(st, TRUE); } else { if (own_eroute) { res = do_command(st->st_connection, "down") && do_eroute(st, ERO_DELETE, "delete"); if (res) c->eroute_owner = SOS_NOBODY; } if (res) res = teardown_half_ipsec_sa(st, FALSE) && teardown_half_ipsec_sa(st, TRUE); } return res; #else /* !KLIPS */ DBG(DBG_CONTROL, DBG_log("if I knew how, I'd do_eroute() and teardown_ipsec_sa()")); return TRUE; #endif /* !KLIPS */ }