diff --git a/branches/vpnc-nortel/Makefile b/branches/vpnc-nortel/Makefile index 3ed4700..1fc914e 100644 --- a/branches/vpnc-nortel/Makefile +++ b/branches/vpnc-nortel/Makefile @@ -51,7 +51,7 @@ CRYPTO_CFLAGS = -DOPENSSL_GPL_VIOLATION -DCRYPTO_OPENSSL CRYPTO_SRCS = crypto-openssl.c endif -SRCS = sysdep.c vpnc-debug.c isakmp-pkt.c tunip.c config.c dh.c math_group.c supp.c decrypt-utils.c crypto.c $(CRYPTO_SRCS) +SRCS = sysdep.c vpnc-debug.c isakmp-pkt.c tunip.c config.c dh.c math_group.c supp.c decrypt-utils.c crypto.c $(CRYPTO_SRCS) kernel_ipsec.c BINS = vpnc cisco-decrypt test-crypto OBJS = $(addsuffix .o,$(basename $(SRCS))) CRYPTO_OBJS = $(addsuffix .o,$(basename $(CRYPTO_SRCS))) diff --git a/branches/vpnc-nortel/config.c b/branches/vpnc-nortel/config.c index f213cd4..196cb94 100644 --- a/branches/vpnc-nortel/config.c +++ b/branches/vpnc-nortel/config.c @@ -35,12 +35,14 @@ #include "vpnc.h" #include "supp.h" #include "decrypt-utils.h" +#include "kernel_ipsec.h" const char *config[LAST_CONFIG]; int opt_debug = 0; int opt_nd; int opt_1des, opt_no_encryption, opt_auth_mode; +int opt_kernel_ipsec; enum natt_mode_enum opt_natt_mode; enum vendor_enum opt_vendor; enum if_mode_enum opt_if_mode; @@ -476,6 +478,13 @@ static const struct config_names_s { "Target network in dotted decimal or CIDR notation\n", config_def_target_network }, { + CONFIG_KERNEL_IPSEC, 0, 1, + "--kernel-ipsec", + "kernel ipsec", + NULL, + "Use kernel ipsec implementation", + NULL + }, { 0, 0, 0, NULL, NULL, NULL, NULL, NULL } }; @@ -716,6 +725,7 @@ void do_config(int argc, char **argv) opt_debug = (config[CONFIG_DEBUG]) ? atoi(config[CONFIG_DEBUG]) : 0; opt_nd = (config[CONFIG_ND]) ? 1 : 0; opt_1des = (config[CONFIG_ENABLE_1DES]) ? 1 : 0; + opt_kernel_ipsec = (config[CONFIG_KERNEL_IPSEC]) ? 1 : 0; if (!strcmp(config[CONFIG_VENDOR], "cisco")) { opt_vendor = VENDOR_CISCO; @@ -914,8 +924,8 @@ void do_config(int argc, char **argv) case CONFIG_IPSEC_SECRET: case CONFIG_XAUTH_PIN: case CONFIG_XAUTH_PASSWORD: - s = strdup(getpass("")); - break; +// s = strdup(getpass("")); +// break; case CONFIG_IPSEC_GATEWAY: case CONFIG_IPSEC_ID: case CONFIG_XAUTH_USERNAME: @@ -960,5 +970,8 @@ void do_config(int argc, char **argv) if (get_dh_group_ike()->ike_sa_id == 0) error(1, 0, "IKE DH Group must not be nopfs\n"); + if (opt_kernel_ipsec && kernel_ipsec_init() == -1) + exit(1); + return; } diff --git a/branches/vpnc-nortel/config.h b/branches/vpnc-nortel/config.h index 795c3ae..5228571 100644 --- a/branches/vpnc-nortel/config.h +++ b/branches/vpnc-nortel/config.h @@ -60,6 +60,7 @@ enum config_enum { CONFIG_AUTH_MODE, CONFIG_CA_FILE, CONFIG_CA_DIR, + CONFIG_KERNEL_IPSEC, LAST_CONFIG }; @@ -107,6 +108,7 @@ extern enum vendor_enum opt_vendor; extern int opt_debug; extern int opt_nd; extern int opt_1des, opt_no_encryption, opt_auth_mode; +extern int opt_kernel_ipsec; extern enum natt_mode_enum opt_natt_mode; extern enum if_mode_enum opt_if_mode; extern uint16_t opt_udpencapport; diff --git a/branches/vpnc-nortel/kernel_ipsec.c b/branches/vpnc-nortel/kernel_ipsec.c new file mode 100644 index 0000000..5afc6fd --- /dev/null +++ b/branches/vpnc-nortel/kernel_ipsec.c @@ -0,0 +1,980 @@ +/* Kernel IPSEC support via PF_KEY + Copyright (C) 2004 Mattias Nissler + Copyright (C) 2009 Antonio Borneo + + 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. + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#if defined(__linux__) +#include +#include +#else +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "sysdep.h" +#include "vpnc.h" +#include "kernel_ipsec.h" + +static int pfkey_sock = -1; +static uint32_t pfkey_seq = 1; +static pid_t pfkey_pid; + +/* buffer size intially allocated for a message (should be enough for most messages) */ +#define SAMB_BASE_MSG_SIZE 256 + +/* private struct and macros, don't use directly */ +struct _samb_msghead { + struct _samb_msginfo { + /* pointer to the current extension */ + char *ext; + /* current allocated size (without info struct) */ + size_t size; + } info; + struct sadb_msg msg; +}; + +#define _samb_msg_to_mb(m) \ + ((struct _samb_msghead *) (((char *) (m)) - sizeof(struct _samb_msginfo))) + +#define _samb_align(x, a) ((((x) + ((a) - 1))/(a)) * (a)) + +/* setting up a sockaddr_in (reserved fields and port zeroed, correct lenght + * field) for sending to the kernel */ +static void setup_sockaddr_in(struct sockaddr_in *sain, struct in_addr *addr) +{ + memset(sain, 0, sizeof(struct sockaddr_in)); + sain->sin_family = AF_INET; +#ifdef HAVE_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + sain->sin_port = htons(0); + memcpy(&(sain->sin_addr), addr, sizeof(struct in_addr)); +} + +/* ensures a message buffer can store size more bytes */ +static void +_samb_ensure_size(struct sadb_msg **msg, size_t size) +{ + struct _samb_msghead *m = _samb_msg_to_mb(*msg); + + if ((m->msg.sadb_msg_len << 3) + size > m->info.size) { + int growbytes = (size > SAMB_BASE_MSG_SIZE) ? size : SAMB_BASE_MSG_SIZE; + size_t ext_offset = (char *) m->info.ext - (char *) m; + struct _samb_msghead *np = realloc(m, m->info.size + sizeof(struct _samb_msginfo) + growbytes); + + if (np == NULL) { + free(m); + *msg = NULL; + } else { + /* adjust the ext pointer. maybe we got a new allocation */ + np->info.ext = (char *) np + ext_offset; + np->info.size += growbytes; + *msg = &(np->msg); + } + } +} + +static unsigned int _samb_ext_size_map[] = { + 0, /* SADB_EXT_RESERVED */ + sizeof(struct sadb_sa), /* SADB_EXT_SA */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_CURRENT */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_HARD */ + sizeof(struct sadb_lifetime), /* SADB_EXT_LIFETIME_SOFT */ + sizeof(struct sadb_address), /* SADB_EXT_ADDRESS_SRC */ + sizeof(struct sadb_address), /* SADB_EXT_ADDRESS_DST */ + sizeof(struct sadb_address), /* SADB_EXT_ADDRESS_PROXY */ + sizeof(struct sadb_key), /* SADB_EXT_KEY_AUTH */ + sizeof(struct sadb_key), /* SADB_EXT_KEY_ENCRYPT */ + sizeof(struct sadb_ident), /* SADB_EXT_IDENTITY_SRC */ + sizeof(struct sadb_ident), /* SADB_EXT_IDENTITY_DST */ + sizeof(struct sadb_sens), /* SADB_EXT_SENSITIVITY */ + sizeof(struct sadb_prop), /* SADB_EXT_PROPOSAL */ + sizeof(struct sadb_supported), /* SADB_EXT_SUPPORTED_AUTH */ + sizeof(struct sadb_supported), /* SADB_EXT_SUPPORTED_ENCRYPT */ + sizeof(struct sadb_spirange), /* SADB_EXT_SPIRANGE */ +#ifdef SADB_X_SPDADD + sizeof(struct sadb_x_kmprivate), /* SADB_X_EXT_KMPRIVATE */ + sizeof(struct sadb_x_policy), /* SADB_X_EXT_POLICY */ + sizeof(struct sadb_x_sa2), /* SADB_X_EXT_SA2 */ +#endif + 0 +}; + +/* public macros and functions for message buffer access */ + +/* allocate a new message */ +static struct sadb_msg * +samb_alloc(size_t size) +{ + struct _samb_msghead *head = malloc(size + sizeof(struct _samb_msginfo)); + if (head != NULL) { + head->info.size = SAMB_BASE_MSG_SIZE - sizeof(struct _samb_msginfo); + head->info.ext = (char *) head + sizeof(struct _samb_msghead); + head->msg.sadb_msg_len = sizeof(struct sadb_msg) >> 3; + head->msg.sadb_msg_version = PF_KEY_V2; + head->msg.sadb_msg_errno = 0; + head->msg.sadb_msg_reserved = 0; + head->msg.sadb_msg_pid = pfkey_pid; + return &(head->msg); + } + + return NULL; +} + +/* free a message buffer */ +static void +samb_free(struct sadb_msg *msg) +{ + struct _samb_msghead *head = _samb_msg_to_mb(msg); + + /* zero out the memory for security (it might contain keys) */ + memset(msg, 0, head->info.size); + free(head); +} + +/* appends a new extension to message m. sa_type is the type constant (SADB_*). a pointer to xsize + * bytes of extra space (after the extension, as required by some extension types) is stored in xp. + */ +static struct sadb_ext * +samb_append_ext_x(struct sadb_msg **m, uint8_t ext_type, unsigned int xsize, char **xp) +{ + size_t real_size = _samb_align(_samb_ext_size_map[ext_type] + xsize, 8); + _samb_ensure_size(m, real_size); + struct sadb_ext *ext = NULL; + + if (*m != NULL) { + struct _samb_msghead *mh = _samb_msg_to_mb(*m); + mh->msg.sadb_msg_len += real_size >> 3; + ext = (struct sadb_ext *) mh->info.ext; + memset(ext, 0, real_size); + ext->sadb_ext_len = real_size >> 3; + ext->sadb_ext_type = ext_type; + if (xp != NULL) + *xp = (char *) ext + _samb_ext_size_map[ext_type]; + mh->info.ext += real_size; + } + + return ext; +} + +#define samb_append_ext(m, ext_type) \ + samb_append_ext_x(m, ext_type, 0, NULL) + +/* gets the next extension of type satype in message m. used when reading a message. returns NULL if + * no such extension is available. + */ +static struct sadb_ext * +samb_was_next_ext(struct sadb_msg *m, uint8_t ext_type) +{ + struct _samb_msghead *mh = _samb_msg_to_mb(m); + struct sadb_ext *ne = NULL; + + char *end = ((char *) (m)) + ((m)->sadb_msg_len << 3); + /* check wether extension pointer and extension size are ok */ + if (mh->info.ext + sizeof(struct sadb_ext) < end + && mh->info.ext + (((struct sadb_ext *) (mh->info.ext))->sadb_ext_len << 3) < end + && ((struct sadb_ext *) mh->info.ext)->sadb_ext_type == ext_type) { + ne = (struct sadb_ext *) mh->info.ext; + mh->info.ext += ne->sadb_ext_len << 3; + } + + return ne; +} + +static struct sadb_ext * +samb_next_ext(struct sadb_msg *m) +{ + struct _samb_msghead *mh = _samb_msg_to_mb(m); + struct sadb_ext *ne = NULL; + char *end = ((char *) (m)) + ((m)->sadb_msg_len << 3); + + /* check wether extension pointer and extension size are ok */ + if (mh->info.ext + sizeof(struct sadb_ext) <= end + && mh->info.ext + (((struct sadb_ext *) (mh->info.ext))->sadb_ext_len << 3) <= end) { + ne = (struct sadb_ext *) mh->info.ext; + mh->info.ext += ne->sadb_ext_len << 3; + } + return ne; +} + +static int +samb_append_address(struct sadb_msg **msg, struct sockaddr *addr, uint8_t prefixlen, uint16_t type) +{ + struct sadb_address *sa_addr; + char *addr_space; + + /* append extension */ + sa_addr = (struct sadb_address *) samb_append_ext_x(msg, type, sizeof(struct sockaddr), &addr_space); + if (sa_addr == NULL) + return -1; + + /* write data into extension */ + sa_addr->sadb_address_proto = IPSEC_PROTO_ANY; + sa_addr->sadb_address_prefixlen = prefixlen; + sa_addr->sadb_address_reserved = 0; +#ifdef HAVE_SA_LEN + addr->sa_len = sizeof(struct sockaddr); +#endif + memcpy(addr_space, addr, sizeof(struct sockaddr)); + + return 0; +} + +static int +samb_append_key(struct sadb_msg **msg, const unsigned char *key, unsigned int key_bits, + uint16_t type) +{ + struct sadb_key *sa_key; + char *key_space; + + /* append extension */ + sa_key = (struct sadb_key *) samb_append_ext_x(msg, type, (key_bits + 7) / 8, &key_space); + if (sa_key == NULL) + return -1; + + /* write data into extension */ + sa_key->sadb_key_bits = key_bits; + sa_key->sadb_key_reserved = 0; + memcpy(key_space, key, (key_bits + 7) / 8); + + return 0; +} + +static int +samb_receive(struct sadb_msg *msg) +{ + struct _samb_msghead *head = _samb_msg_to_mb(msg); + int err; + + /* receive data */ + err = read(pfkey_sock, msg, head->info.size); + if (err == -1) + return -1; + + /* sanity check */ + if (err < (int) sizeof(struct sadb_msg)) { + fputs("bogus PF_KEY packet from kernel", stderr); + return -1; + } + + return 0; +} + +static int +samb_flush() +{ + struct sadb_msg *msg; + + /* allocacte a message buffer */ + msg = samb_alloc(SAMB_BASE_MSG_SIZE); + if (msg == NULL) + return -1; + + while (samb_receive(msg) == 0) { + /* look at the message */ + if ((pid_t) msg->sadb_msg_pid == pfkey_pid) { + /* should not happen */ + fprintf(stderr, "Kernel answer for message (seq #%d) while flushing\n", msg->sadb_msg_seq); + } + } + + if (errno == EAGAIN) { + /* no more messages */ + return 0; + } + + perror("flushing socket"); + return -1; +} + +static int +samb_send(struct sadb_msg *msg, struct sadb_msg **answer, int free) +{ + int err; + uint32_t seq = msg->sadb_msg_seq = pfkey_seq++; + struct sadb_msg *ans; + int len = msg->sadb_msg_len << 3; + + /* flush waiting incoming messages, they are responses to other processes messages, not + * interesting for us + */ + if (samb_flush() == -1) { + if (free) + samb_free(msg); + return -1; + } + + /* deliver the packet */ + err = write(pfkey_sock, msg, len); + if (err != len) { + if (free) + samb_free(msg); + + if (err == -1) + perror("error writing PF_KEY socket"); + else + fputs("error writing PF_KEY socket", stderr); + + return -1; + } + + if (free) + samb_free(msg); + + /* allocate answer buffer. well, a big one ;-) */ +#define ANSWER_MSG_LEN 4096 + ans = samb_alloc(ANSWER_MSG_LEN); + if (ans == NULL) { + fputs("no memory for answer packet", stderr); + return -1; + } + + /* fetch an answer */ + while (1) { + if (samb_receive(ans) == -1) { + perror("error reading from PF_KEY socket"); + samb_free(ans); + return -1; + } + + /* see if it is the answer */ + if ((pid_t) ans->sadb_msg_pid == pfkey_pid && ans->sadb_msg_seq == seq) { + if (answer != NULL) + *answer = ans; + return 0; + } + + /* if not, loop */ + } + + /* never reached */ + samb_free(ans); + return -1; +} + +struct algo_map { + unsigned int my_id; + unsigned int sadb_id; + const char *sadb_name; +}; + +/* mapping of auth algorithms */ +static struct algo_map auth_algo_map[] = { + { GCRY_MD_MD5, SADB_AALG_MD5HMAC, "hmac-md5" }, + { GCRY_MD_SHA1, SADB_AALG_SHA1HMAC, "hmac-sha1" }, + { 0, 0, NULL } +}; + +/* mapping of encryption algorithms */ +static struct algo_map encrypt_algo_map[] = { + { GCRY_CIPHER_DES, SADB_EALG_DESCBC, "des-cbc" }, + { GCRY_CIPHER_3DES, SADB_EALG_3DESCBC, "3des-cbc" }, +#ifdef __APPLE__ + { GCRY_CIPHER_AES128, SADB_X_EALG_AES, "aes-ctr" }, + { GCRY_CIPHER_AES192, SADB_X_EALG_AES, "aes-ctr" }, + { GCRY_CIPHER_AES256, SADB_X_EALG_AES, "aes-ctr" }, +#endif + { 0, 0, NULL} +}; + +static struct algo_map * +map_algo(struct algo_map map[], unsigned int ext_algo) +{ + int i; + + for (i = 0; map[i].my_id != 0; i++) { + if (map[i].my_id == ext_algo) + return &map[i]; + } + + /* 0 is no algo in PF_KEY */ + return NULL; +}; + +/* we keep a list of the sas we have added so we can delete them on shutdown */ +static struct added_sa { + struct added_sa *next; + uint32_t spi; + struct sockaddr *local; + struct sockaddr *remote; +} *added_sas = NULL; + +static struct added_sa * +make_sa_record(uint32_t spi, struct sockaddr *local, struct sockaddr *remote) +{ + struct added_sa *asa; + + /* allocate memory. one big chunk is easier */ + asa = malloc(sizeof(struct added_sa) + 2*sizeof(struct sockaddr)); + if (asa == NULL) + return NULL; + + asa->local = (struct sockaddr *) ((char *) asa + sizeof(struct added_sa)); + asa->remote = (struct sockaddr *) ((char *) asa->local + sizeof(struct sockaddr)); + + asa->spi = spi; + asa->next = added_sas; + memcpy(asa->local, local, sizeof(struct sockaddr)); + memcpy(asa->remote, remote, sizeof(struct sockaddr)); + + return asa; +} + +#ifdef SADB_X_SPDADD + +/* the same for security policy entries */ +static struct added_sp { + struct added_sp *next; + uint32_t id; +} *added_sps = NULL; + +static int +add_sp_entry(struct sockaddr_in *local, uint8_t lpl, struct sockaddr_in *remote, uint8_t rpl, + struct sockaddr_in *tunlocal, struct sockaddr_in *tunremote, uint8_t dir) +{ + struct sadb_msg *msg, *answer; + struct sadb_x_policy *policy; + struct sadb_x_ipsecrequest *ipsr; + struct sockaddr_in *tunsrc, *tundst, localaddr, remoteaddr; + struct added_sp *asp; + + /* build the message */ + msg = samb_alloc(SAMB_BASE_MSG_SIZE); + if (msg == NULL) + return -1; + + msg->sadb_msg_type = SADB_X_SPDADD; + msg->sadb_msg_satype = SADB_SATYPE_UNSPEC; + + /* spdadd 10.0.11.41/32[21] 10.0.11.33/32[any] any + * -P out ipsec esp/tunnel/192.168.0.1-192.168.1.2/require ; */ + if (0) { + char src_addr[16], dst_addr[16]; + + strcpy(src_addr, inet_ntoa(local->sin_addr)); + strcpy(dst_addr, inet_ntoa(remote->sin_addr)); + printf("spdadd %s[any] %s[any] any\n", src_addr, dst_addr); + printf(" -P %s ipsec\n", (dir == IPSEC_DIR_INBOUND) ? "in" : "out"); + strcpy(src_addr, inet_ntoa(tunlocal->sin_addr)); + strcpy(dst_addr, inet_ntoa(tunremote->sin_addr)); + printf(" esp/tunnel/%s-%s/require ;\n\n", src_addr, dst_addr); + } + + /* add policy extension */ + policy = (struct sadb_x_policy *) samb_append_ext_x(&msg, SADB_X_EXT_POLICY, + sizeof(struct sadb_x_ipsecrequest) + 2 * sizeof(struct sockaddr_in), (char **) (void *) &ipsr); + + if (msg == NULL) + return -1; + + /* fill policy. we want to have packets encrypted using IPSEC */ + policy->sadb_x_policy_type = IPSEC_POLICY_IPSEC; + policy->sadb_x_policy_dir = dir; + policy->sadb_x_policy_reserved = 0; + policy->sadb_x_policy_id = 0; + /*policy->sadb_x_policy_reserved2 = 0;*/ + + /* fill ipsecrequest structure. note that the len field is in bytes, not in 64 bit words, + * including the following two sockaddr structs (describing the tunnel endpoints) */ + ipsr->sadb_x_ipsecrequest_len = sizeof(struct sadb_x_ipsecrequest) + + 2 * sizeof(struct sockaddr_in); + /* we want to use esp, tunnel mode, required */ + ipsr->sadb_x_ipsecrequest_proto = IPPROTO_ESP; + ipsr->sadb_x_ipsecrequest_mode = IPSEC_MODE_TUNNEL; + ipsr->sadb_x_ipsecrequest_level = IPSEC_LEVEL_REQUIRE; + ipsr->sadb_x_ipsecrequest_reqid = 0; + + /* setup the sockaddr_in structs describing the tunnel endpoints */ + tunsrc = (struct sockaddr_in *) ((char *) ipsr + sizeof(struct sadb_x_ipsecrequest)); + setup_sockaddr_in(tunsrc, &(tunlocal->sin_addr)); + + tundst = (struct sockaddr_in *) ((char *) tunsrc + sizeof(struct sockaddr_in)); + setup_sockaddr_in(tundst, &(tunremote->sin_addr)); + + /* append address extensions describing the source and destination addresses of the packets that + * are to be secured */ + setup_sockaddr_in(&localaddr, &(local->sin_addr)); + + if (samb_append_address(&msg, (struct sockaddr *) &localaddr, lpl, SADB_EXT_ADDRESS_SRC) == -1) { + printf("AB: return error at line %d\n", __LINE__); + return -1; + } + + setup_sockaddr_in(&remoteaddr, &(remote->sin_addr)); + if (samb_append_address(&msg, (struct sockaddr *) &remoteaddr, rpl, SADB_EXT_ADDRESS_DST) == -1) { + printf("AB: return error at line %d\n", __LINE__); + return -1; + } + + asp = malloc(sizeof(struct added_sp)); + if (asp == NULL) { + samb_free(msg); + printf("AB: return error at line %d\n", __LINE__); + return -1; + } + + /* ready, send it */ + if (samb_send(msg, &answer, 1) == -1) { + free(asp); + printf("AB: return error at line %d\n", __LINE__); + return -1; + } + + if (answer->sadb_msg_errno != 0) { + errno = answer->sadb_msg_errno; + perror("PF_KEY returned error [1]"); + samb_free(answer); + free(asp); + return -1; + } + + /* save the policy id for deletion of the SP later and hook the SP record into our chain */ + do { + policy = (struct sadb_x_policy *) samb_next_ext(answer); + /* printf("search policy, found %d\n", policy->sadb_x_policy_exttype); */ + if (policy == NULL) { + samb_free(answer); + printf("AB: return error at line %d\n", __LINE__); + free(asp); + return -1; + } + } while (policy->sadb_x_policy_exttype != SADB_X_EXT_POLICY); +#if 0 + policy = (struct sadb_x_policy *) samb_was_next_ext(answer, SADB_X_EXT_POLICY); + if (policy == NULL) { + samb_free(answer); + printf("AB: return error at line %d\n", __LINE__); + free(asp); + return -1; + } +#endif + asp->next = added_sps; + asp->id = policy->sadb_x_policy_id; + added_sps = asp; + + samb_free(answer); + return 0; +} + +static int +remove_sp_entry(uint32_t id) +{ + struct sadb_msg *msg, *answer; + struct sadb_x_policy *policy; + + /* build the message */ + msg = samb_alloc(SAMB_BASE_MSG_SIZE); + if (msg == NULL) + return -1; + + msg->sadb_msg_type = SADB_X_SPDDELETE2; + msg->sadb_msg_satype = SADB_SATYPE_UNSPEC; + + /* add policy extension */ + policy = (struct sadb_x_policy *) samb_append_ext(&msg, SADB_X_EXT_POLICY); + if (msg == NULL) + return -1; + + /* fill policy. we only need the id field, zero everything else */ + policy->sadb_x_policy_type = 0; + policy->sadb_x_policy_dir = 0; + policy->sadb_x_policy_reserved = 0; + policy->sadb_x_policy_id = id; + /*policy->sadb_x_policy_reserved2 = 0;*/ + + /* ready, send it */ + if (samb_send(msg, &answer, 1) == -1) + return -1; + + if (answer->sadb_msg_errno != 0) { + errno = answer->sadb_msg_errno; + perror("PF_KEY returned error [2]"); + samb_free(answer); + return -1; + } + + samb_free(answer); + return 0; +} + +static void +flush_our_sps() +{ + while (added_sps != NULL) { + struct added_sp *temp = added_sps; + added_sps = added_sps->next; + /* ignore failure */ + remove_sp_entry(temp->id); + free(temp); + } +} +#endif + +static int +add_sa(struct sa_block *s, struct ike_sa *sadesc, struct sockaddr *local, struct sockaddr *peer, + uint8_t msg_type) +{ + struct sadb_msg *msg, *answer; + struct sadb_sa *sa; + struct added_sa *asa = NULL; + + /* build the message */ + msg = samb_alloc(SAMB_BASE_MSG_SIZE); + if (msg == NULL) + return -1; + + msg->sadb_msg_type = msg_type; + msg->sadb_msg_satype = SADB_SATYPE_ESP; + + /* add 10.0.11.41 10.0.11.33 esp 0x10001 + * -E des-cbc 0x3ffe05014819ffff + * -A hmac-md5 "authentication!!" ; */ + if (0) { + char src_addr[16], dst_addr[16]; + unsigned int i; + strcpy(src_addr, inet_ntoa(((struct sockaddr_in *)local)->sin_addr)); + strcpy(dst_addr, inet_ntoa(((struct sockaddr_in *)peer)->sin_addr)); + printf("add -4 %s %s esp 0x%08x\n", src_addr, dst_addr, ntohl(sadesc->spi)); + printf(" -m tunnel\n"); + printf(" -E %s 0x", map_algo(encrypt_algo_map, s->ipsec.cry_algo)->sadb_name); + for (i = 0 ; i < s->ipsec.key_len ; i++) + printf("%02x", *(sadesc->key_cry + i)); + printf("\n -A %s 0x", map_algo(auth_algo_map, s->ipsec.md_algo)->sadb_name); + for (i = 0 ; i < s->ipsec.md_len ; i++) + printf("%02x", *(sadesc->key_md + i)); + printf(" ;\n\n"); + } + + /* add sa extension */ + sa = (struct sadb_sa *) samb_append_ext(&msg, SADB_EXT_SA); + if (msg == NULL) + return -1; + + sa->sadb_sa_spi = sadesc->spi; /* htonl(sadesc->spi); */ + sa->sadb_sa_replay = 0; + sa->sadb_sa_state = SADB_SASTATE_MATURE; + sa->sadb_sa_auth = map_algo(auth_algo_map, s->ipsec.md_algo)->sadb_id; + sa->sadb_sa_encrypt = map_algo(encrypt_algo_map, s->ipsec.cry_algo)->sadb_id; + sa->sadb_sa_flags = 0; + + if (sa->sadb_sa_auth == 0 || sa->sadb_sa_encrypt == 0) { + /* algorithm not supported */ + samb_free(msg); + return -1; + } + + { /* AB: */ + struct sadb_x_sa2 *xsa2; + xsa2 = (struct sadb_x_sa2 *)samb_append_ext(&msg, SADB_X_EXT_SA2); + if (msg == NULL) + return -1; + xsa2->sadb_x_sa2_mode = IPSEC_MODE_TUNNEL; + xsa2->sadb_x_sa2_sequence = 0; + xsa2->sadb_x_sa2_reqid = 0; + } + + /* add source and destination addresses */ + if (samb_append_address(&msg, local, 32, SADB_EXT_ADDRESS_SRC) == -1) + return -1; + + if (samb_append_address(&msg, peer, 32, SADB_EXT_ADDRESS_DST) == -1) + return -1; + + /* add keys */ + if (samb_append_key(&msg, sadesc->key_md, s->ipsec.md_len << 3, + SADB_EXT_KEY_AUTH) == -1) + return -1; + + if (samb_append_key(&msg, sadesc->key_cry, s->ipsec.key_len << 3, + SADB_EXT_KEY_ENCRYPT) == -1) + return -1; + + if (msg_type == SADB_ADD) { + asa = make_sa_record(sadesc->spi, local, peer); + if (asa == NULL) { + samb_free(msg); + return -1; + } + } + + /* ready, send it */ + if (samb_send(msg, &answer, 1) == -1) { + if (asa != NULL) + free(asa); + return -1; + } + + if (answer->sadb_msg_errno != 0) { + errno = answer->sadb_msg_errno; + perror("PF_KEY returned error [3]"); + samb_free(answer); + if (asa != NULL) + free(asa); + return -1; + } + + if (asa != NULL) + added_sas = asa; + samb_free(answer); + + return 0; +} + +static int +remove_sa(uint32_t spi, struct sockaddr *src, struct sockaddr *dst) +{ + struct sadb_msg *msg, *answer; + struct sadb_sa *sa; + + /* build the message */ + msg = samb_alloc(SAMB_BASE_MSG_SIZE); + if (msg == NULL) + return -1; + + msg->sadb_msg_type = SADB_DELETE; + msg->sadb_msg_satype = SADB_SATYPE_ESP; + + /* add sa extension */ + sa = (struct sadb_sa *) samb_append_ext(&msg, SADB_EXT_SA); + if (msg == NULL) + return -1; + + sa->sadb_sa_spi = spi; /* htonl(spi); */ + /* zero out the other fields */ + sa->sadb_sa_replay = 0; + sa->sadb_sa_state = 0; + sa->sadb_sa_auth = 0; + sa->sadb_sa_encrypt = 0; + sa->sadb_sa_flags = 0; + + /* add source and destination addresses */ + if (samb_append_address(&msg, src, 32, SADB_EXT_ADDRESS_SRC) == -1) + return -1; + + if (samb_append_address(&msg, dst, 32, SADB_EXT_ADDRESS_DST) == -1) + return -1; + + /* ready, send it */ + if (samb_send(msg, &answer, 1) == -1) + return -1; + + if (answer->sadb_msg_errno != 0) { + errno = answer->sadb_msg_errno; + perror("PF_KEY returned error [4]"); + samb_free(answer); + return -1; + } + + samb_free(answer); + return 0; +} + +static void +flush_our_sas() +{ + while (added_sas != NULL) { + struct added_sa *temp = added_sas; + added_sas = added_sas->next; + /* ignore result, as there is nothing we can do */ + remove_sa(temp->spi, temp->local, temp->remote); + free(temp); + } +} + +int +kernel_ipsec_init() +{ + int flags; + + /* already opened? */ + if (pfkey_sock >= 0) + return 0; + + pfkey_pid = getpid(); + + /* open the PF_KEY socket */ + pfkey_sock = socket(PF_KEY, SOCK_RAW, PF_KEY_V2); + if (pfkey_sock == -1) { + perror("Error opening PF_KEY socket"); + return -1; + } + + /* make the socket non-blocking */ + flags = fcntl(pfkey_sock, F_GETFL, 0); + if (flags == -1) { + perror("F_GETFL on PF_KEY socket"); + return -1; + } + + flags |= O_NONBLOCK; + + flags = fcntl(pfkey_sock, F_SETFL, flags); + if (flags == -1) { + perror("F_SETFL on PF_KEY socket"); + return -1; + } + + return 0; +} + +void +kernel_ipsec_shutdown() +{ + if (pfkey_sock >= 0) { + /* delete all security policy entries and security associations we have made */ + flush_our_sps(); + flush_our_sas(); + + /* close the socket */ + close(pfkey_sock); + pfkey_sock = -1; + } + + pfkey_pid = (pid_t) 0; +} + +int +kernel_ipsec_get_spi(uint32_t *spi, struct sockaddr *src, struct sockaddr *dst) +{ + struct sadb_msg *msg, *answer; + struct sadb_spirange *spirange; + struct sadb_sa *sa; + struct added_sa *asa; + + /* build SADB_GETSPI message */ + msg = samb_alloc(SAMB_BASE_MSG_SIZE); + if (msg == NULL) + return -1; + + msg->sadb_msg_type = SADB_GETSPI; + msg->sadb_msg_satype = SADB_SATYPE_ESP; + + /* add source and destination addresses */ + if (samb_append_address(&msg, src, 32, SADB_EXT_ADDRESS_SRC) == -1) + return -1; + + if (samb_append_address(&msg, dst, 32, SADB_EXT_ADDRESS_DST) == -1) + return -1; + + /* add spi range extension */ + spirange = (struct sadb_spirange *) samb_append_ext(&msg, SADB_EXT_SPIRANGE); + if (msg == NULL) + return -1; + + /* Linux does not accept [min,max]=[0,0xffffffff] */ + spirange->sadb_spirange_min = 256; + spirange->sadb_spirange_max = 0xffffffff; + spirange->sadb_spirange_reserved = 0; + + /* allocate sa record */ + asa = make_sa_record(0, src, dst); + if (asa == NULL) { + samb_free(msg); + return -1; + } + + /* ready, send it */ + if (samb_send(msg, &answer, 1) == -1) { + free(asa); + return -1; + } + + if (answer->sadb_msg_errno != 0) { + errno = answer->sadb_msg_errno; + perror("PF_KEY returned error [5]"); + samb_free(answer); + free(asa); + return -1; + } + + /* get and store the spi */ + sa = (struct sadb_sa *) samb_was_next_ext(answer, SADB_EXT_SA); + if (sa == NULL) { + samb_free(answer); + free(asa); + return -1; + } + + *spi = sa->sadb_sa_spi; /* ntohl(sa->sadb_sa_spi); */ + samb_free(answer); + + /* update the record */ + asa->spi = *spi; + added_sas = asa; + + return 0; +} + +int +kernel_ipsec_setup(struct sa_block *s) +{ + struct sockaddr_in local, remote, anyaddr, vpnaddr; + struct in_addr ipaddrany; + int r; + /* struct ike_sa *local_sa = &s->ipsec.rx; */ + /* struct ike_sa *remote_sa= &s->ipsec.tx; */ + + ipaddrany.s_addr = INADDR_ANY; + setup_sockaddr_in(&anyaddr, &ipaddrany); + setup_sockaddr_in(&local, &s->src /*&(remote_sa->source.sin_addr)*/); + setup_sockaddr_in(&remote, &s->dst /*&(remote_sa->dest.sin_addr)*/); + setup_sockaddr_in(&vpnaddr, &s->our_address /*&(local_sa->dest)*/); + +#if 0 + /* pass the security associations to the kernel */ + if (add_sa(s, &s->ipsec.rx, (struct sockaddr *) &remote, (struct sockaddr *) &local, SADB_UPDATE) == -1 + || add_sa(s, &s->ipsec.tx, (struct sockaddr *) &local, (struct sockaddr *) &remote, SADB_ADD) == -1 +#ifdef SADB_X_SPDADD + /* add two entries: one for each direction */ + || add_sp_entry(&vpnaddr, 32, &anyaddr, 0, &local, &remote, IPSEC_DIR_OUTBOUND) == -1 + || add_sp_entry(&anyaddr, 0, &vpnaddr, 32, &remote, &local, IPSEC_DIR_INBOUND) == -1 +#endif + ) + return -1; +#endif + r = 0; + if (add_sa(s, &s->ipsec.rx, (struct sockaddr *) &remote, (struct sockaddr *) &local, SADB_UPDATE) == -1) + r = 1; + if (r == 0 && add_sa(s, &s->ipsec.tx, (struct sockaddr *) &local, (struct sockaddr *) &remote, SADB_ADD) == -1) + r = 2; +#ifdef SADB_X_SPDADD + if (r == 0 && add_sp_entry(&vpnaddr, 32, &anyaddr, 0, &local, &remote, IPSEC_DIR_OUTBOUND) == -1) + r = 3; + if (r == 0 && add_sp_entry(&anyaddr, 0, &vpnaddr, 32, &remote, &local, IPSEC_DIR_INBOUND) == -1) + r = 4; +#endif + if (r) { + printf("AB: kernel_ipsec_setup() error %d\n", r); + return -1; + } + return 0; +} + diff --git a/branches/vpnc-nortel/kernel_ipsec.h b/branches/vpnc-nortel/kernel_ipsec.h new file mode 100644 index 0000000..5e07548 --- /dev/null +++ b/branches/vpnc-nortel/kernel_ipsec.h @@ -0,0 +1,28 @@ +/* IPSec VPN client compatible with Cisco equipment. + Copyright (C) 2009 Antonio Borneo + + 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. + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + $Id: $ +*/ + +#ifndef __KERNEL_IPSEC_H__ +#define __KERNEL_IPSEC_H__ + +int kernel_ipsec_init(); +void kernel_ipsec_shutdown(); +int kernel_ipsec_get_spi(uint32_t *spi, struct sockaddr *src, struct sockaddr *dst); +int kernel_ipsec_setup(struct sa_block *s); +#endif diff --git a/branches/vpnc-nortel/sysdep.h b/branches/vpnc-nortel/sysdep.h index a5cb7e3..9c3a32a 100644 --- a/branches/vpnc-nortel/sysdep.h +++ b/branches/vpnc-nortel/sysdep.h @@ -51,6 +51,8 @@ int tun_get_hwaddr(int fd, char *dev, uint8_t *hwaddr); /***************************************************************************/ #if defined(__NetBSD__) #define HAVE_SA_LEN 1 +#define HAVE_SIN_LEN 1 + #define HAVE_VASPRINTF 1 #define HAVE_ASPRINTF 1 @@ -62,6 +64,7 @@ int tun_get_hwaddr(int fd, char *dev, uint8_t *hwaddr); /***************************************************************************/ #if defined(__OpenBSD__) #define HAVE_SA_LEN 1 +#define HAVE_SIN_LEN 1 #define NEED_IPLEN_FIX 1 #define NEW_TUN 1 @@ -75,11 +78,13 @@ int tun_get_hwaddr(int fd, char *dev, uint8_t *hwaddr); /***************************************************************************/ #if defined(__FreeBSD_kernel__) #define HAVE_SA_LEN 1 +#define HAVE_SIN_LEN 1 #endif /***************************************************************************/ #if defined(__FreeBSD__) #define HAVE_SA_LEN 1 +#define HAVE_SIN_LEN 1 #define HAVE_VASPRINTF 1 #define HAVE_ASPRINTF 1 @@ -91,6 +96,7 @@ int tun_get_hwaddr(int fd, char *dev, uint8_t *hwaddr); /***************************************************************************/ #if defined(__DragonFly__) #define HAVE_SA_LEN 1 +#define HAVE_SIN_LEN 1 #define HAVE_VASPRINTF 1 #define HAVE_ASPRINTF 1 @@ -102,6 +108,7 @@ int tun_get_hwaddr(int fd, char *dev, uint8_t *hwaddr); /***************************************************************************/ #if defined(__APPLE__) #define HAVE_SA_LEN 1 +#define HAVE_SIN_LEN 1 #define NEED_IPLEN_FIX 1 #define HAVE_VASPRINTF 1 diff --git a/branches/vpnc-nortel/tunip.c b/branches/vpnc-nortel/tunip.c index b111616..50b662f 100644 --- a/branches/vpnc-nortel/tunip.c +++ b/branches/vpnc-nortel/tunip.c @@ -89,6 +89,7 @@ #include "vpnc.h" #include "tunip.h" +#include "kernel_ipsec.h" #ifndef MAX #define MAX(a,b) ((a)>(b)?(a):(b)) @@ -803,8 +804,10 @@ static void vpnc_main_loop(struct sa_block *s) nfds = MAX(nfds, s->tun_fd +1); #endif - FD_SET(s->esp_fd, &rfds); - nfds = MAX(nfds, s->esp_fd +1); + if (!opt_kernel_ipsec) { + FD_SET(s->esp_fd, &rfds); + nfds = MAX(nfds, s->esp_fd +1); + } if (s->ike_fd != s->esp_fd) { FD_SET(s->ike_fd, &rfds); @@ -894,11 +897,13 @@ static void vpnc_main_loop(struct sa_block *s) #if !defined(__CYGWIN__) if (FD_ISSET(s->tun_fd, &refds)) { + if (opt_kernel_ipsec) + syslog(LOG_WARNING, "packet on the tunnel interface while in kernel IPSEC mode. check your security policy!\n"); process_tun(s); } #endif - if (FD_ISSET(s->esp_fd, &refds) ) { + if (!opt_kernel_ipsec && FD_ISSET(s->esp_fd, &refds) ) { process_socket(s); } @@ -946,6 +951,7 @@ static void vpnc_main_loop(struct sa_block *s) } + kernel_ipsec_shutdown(); switch (do_kill) { case -2: syslog(LOG_NOTICE, "connection terminated by dead peer detection"); @@ -1058,6 +1064,11 @@ void vpnc_doit(struct sa_block *s) } else { printf("VPNC started in foreground...\n"); } + if (opt_kernel_ipsec) + if (kernel_ipsec_setup(s) == -1) { + printf("error in kernel_ipsec_setup()\n"); + exit(1); + } openlog("vpnc", LOG_PID | LOG_PERROR, LOG_DAEMON); write_pidfile(pidfile); diff --git a/branches/vpnc-nortel/vpnc.c b/branches/vpnc-nortel/vpnc.c index 906319e..5d0cbd4 100644 --- a/branches/vpnc-nortel/vpnc.c +++ b/branches/vpnc-nortel/vpnc.c @@ -50,6 +50,7 @@ #include "vpnc.h" #include "tunip.h" #include "supp.h" +#include "kernel_ipsec.h" #if defined(__CYGWIN__) GCRY_THREAD_OPTION_PTHREAD_IMPL; @@ -3037,7 +3038,30 @@ static void do_phase2(struct sa_block *s) DEBUGTOP(2, printf("do_phase2: S7.1 QM_packet1\n")); - gcry_create_nonce((uint8_t *) & s->ipsec.rx.spi, sizeof(s->ipsec.rx.spi)); + if (!opt_kernel_ipsec) { + gcry_create_nonce((uint8_t *) & s->ipsec.rx.spi, sizeof(s->ipsec.rx.spi)); + } else { + struct sockaddr_in saddr, daddr; + char local_addr[16], remote_addr[16]; + + memset(&saddr, 0, sizeof(struct sockaddr_in)); + saddr.sin_family = AF_INET; + saddr.sin_port = 0; + saddr.sin_addr = s->src; + + memset(&daddr, 0, sizeof(struct sockaddr_in)); + daddr.sin_family = AF_INET; + daddr.sin_port = 0; + daddr.sin_addr = s->dst; + + if (kernel_ipsec_get_spi(&s->ipsec.rx.spi, (struct sockaddr *) &daddr, (struct sockaddr *) &saddr) == -1) + exit(1); + + strcpy(local_addr, inet_ntoa(saddr.sin_addr)); + strcpy(remote_addr, inet_ntoa(daddr.sin_addr)); + + DEBUG(2, printf("get_spi: %s -> %s, spi %#08x\n", remote_addr, local_addr, s->ipsec.rx.spi)); + } rp = make_our_sa_ipsec_nortel(s, transform, proposal->u.p.number); /* FIXME: LEAK: allocated memory never freed */ gcry_create_nonce((uint8_t *) nonce, sizeof(nonce)); rp->next = new_isakmp_data_payload(ISAKMP_PAYLOAD_NONCE, nonce, sizeof(nonce)); @@ -3385,6 +3409,8 @@ static void do_phase2_qm(struct sa_block *s) s->ipsec.natt_active_mode = NATT_ACTIVE_CISCO_UDP; /* AB: change it */ } else if (s->ipsec.encap_mode != IPSEC_ENCAP_TUNNEL) { s->esp_fd = s->ike_fd; + } else if (opt_kernel_ipsec) { + s->esp_fd = -1; } else { #ifdef IP_HDRINCL int hincl = 1; @@ -3779,8 +3805,10 @@ int main(int argc, char **argv) s->ike.src_port = atoi(config[CONFIG_LOCAL_PORT]); s->ike.dst_port = ISAKMP_PORT; s->ike_fd = make_socket(s, s->ike.src_port, s->ike.dst_port); - DEBUGTOP(2, printf("S3 setup_tunnel\n")); - setup_tunnel(s); + if (1 || !opt_kernel_ipsec) { + DEBUGTOP(2, printf("S3 setup_tunnel\n")); + setup_tunnel(s); + } do_load_balance = 0; do { @@ -3809,8 +3837,10 @@ int main(int argc, char **argv) } } while (do_load_balance); DEBUGTOP(2, printf("S7 setup_link (phase 2 + main_loop)\n")); - DEBUGTOP(2, printf("S7.0 run interface setup script\n")); - config_tunnel(s); + if (1 || !opt_kernel_ipsec) { + DEBUGTOP(2, printf("S7.0 run interface setup script\n")); + config_tunnel(s); + } do_phase2_qm(s); DEBUGTOP(2, printf("S7.9 main loop (receive and transmit ipsec packets)\n")); vpnc_doit(s); @@ -3820,8 +3850,10 @@ int main(int argc, char **argv) send_delete_isakmp(s); /* Cleanup routing */ - DEBUGTOP(2, printf("S8 close_tunnel\n")); - close_tunnel(s); + if (1 || !opt_kernel_ipsec) { + DEBUGTOP(2, printf("S8 close_tunnel\n")); + close_tunnel(s); + } /* Free resources */ DEBUGTOP(2, printf("S9 cleanup\n"));