summaryrefslogtreecommitdiff
path: root/sbin/isakmpd/nat_traversal.c
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/isakmpd/nat_traversal.c')
-rw-r--r--sbin/isakmpd/nat_traversal.c384
1 files changed, 384 insertions, 0 deletions
diff --git a/sbin/isakmpd/nat_traversal.c b/sbin/isakmpd/nat_traversal.c
new file mode 100644
index 00000000000..83713df201f
--- /dev/null
+++ b/sbin/isakmpd/nat_traversal.c
@@ -0,0 +1,384 @@
+/* $OpenBSD: nat_traversal.c,v 1.1 2004/06/20 15:24:05 ho Exp $ */
+
+/*
+ * Copyright (c) 2004 Håkan Olsson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include "sysdep.h"
+
+#include "exchange.h"
+#include "hash.h"
+#include "ipsec.h"
+#include "isakmp_fld.h"
+#include "isakmp_num.h"
+#include "ipsec_num.h"
+#include "hash.h"
+#include "log.h"
+#include "message.h"
+#include "nat_traversal.h"
+#include "prf.h"
+#include "sa.h"
+#include "transport.h"
+#include "util.h"
+
+/*
+ * XXX According to draft-ietf-ipsec-nat-t-ike-07.txt, the NAT-T
+ * capability of the other peer is determined by a particular vendor ID
+ * sent as the first message. This vendor ID string is supposed to be a
+ * MD5 hash of "RFC XXXX", where XXXX is the future RFC number.
+ *
+ * These seem to be the "well" known variants of this string in use by
+ * products today.
+ */
+static const char *isakmp_nat_t_cap_text[] = {
+ "draft-ietf-ipsec-nat-t-ike-00", /* V1 (XXX: may be obsolete) */
+ "draft-ietf-ipsec-nat-t-ike-02\n", /* V2 */
+ "draft-ietf-ipsec-nat-t-ike-03", /* V3 */
+#ifdef notyet
+ "RFC XXXX",
+#endif
+};
+
+/* The MD5 hashes of the above strings is put in this array. */
+static char **nat_t_hashes;
+static size_t nat_t_hashsize;
+
+static int nat_t_setup_hashes(void);
+static int nat_t_add_vendor_payload(struct message *, char *);
+static int nat_t_add_nat_d(struct message *, struct sockaddr *);
+static int nat_t_match_nat_d_payload(struct message *, struct sockaddr *);
+
+void
+nat_t_init(void)
+{
+ nat_t_hashes = (char **)NULL;
+}
+
+/* Generate the NAT-T capability marker hashes. Executed only once. */
+static int
+nat_t_setup_hashes(void)
+{
+ struct hash *hash;
+ int n = sizeof isakmp_nat_t_cap_text / sizeof isakmp_nat_t_cap_text[0];
+ int i;
+
+ /* The draft says to use MD5. */
+ hash = hash_get(HASH_MD5);
+ if (!hash) {
+ /* Should never happen. */
+ log_print("nat_t_setup_hashes: "
+ "could not find MD5 hash structure!");
+ return -1;
+ }
+ nat_t_hashsize = hash->hashsize;
+
+ /* Allocate one more than is necessary, i.e NULL terminated. */
+ nat_t_hashes = (char **)calloc((size_t)(n + 1), sizeof(char *));
+ if (!nat_t_hashes) {
+ log_error("nat_t_setup_hashes: calloc (%lu,%lu) failed",
+ (unsigned long)n, (unsigned long)sizeof(char *));
+ return -1;
+ }
+
+ /* Populate with hashes. */
+ for (i = 0; i < n; i++) {
+ nat_t_hashes[i] = (char *)malloc(nat_t_hashsize);
+ if (!nat_t_hashes[i]) {
+ log_error("nat_t_setup_hashes: malloc (%lu) failed",
+ (unsigned long)nat_t_hashsize);
+ goto errout;
+ }
+
+ hash->Init(hash->ctx);
+ hash->Update(hash->ctx,
+ (unsigned char *)isakmp_nat_t_cap_text[i],
+ strlen(isakmp_nat_t_cap_text[i]));
+ hash->Final(nat_t_hashes[i], hash->ctx);
+
+ LOG_DBG((LOG_EXCHANGE, 50, "nat_t_setup_hashes: "
+ "MD5(\"%s\") (%d bytes)", isakmp_nat_t_cap_text[i],
+ nat_t_hashsize));
+ LOG_DBG_BUF((LOG_EXCHANGE, 50, "nat_t_setup_hashes",
+ nat_t_hashes[i], nat_t_hashsize));
+ }
+
+ return 0;
+
+ errout:
+ for (i = 0; i < n; i++)
+ if (nat_t_hashes[i])
+ free(nat_t_hashes[i]);
+ free(nat_t_hashes);
+ nat_t_hashes = NULL;
+ return -1;
+}
+
+/* Add one NAT-T VENDOR payload. */
+static int
+nat_t_add_vendor_payload(struct message *msg, char *hash)
+{
+ size_t buflen = nat_t_hashsize + ISAKMP_GEN_SZ;
+ u_int8_t *buf;
+
+ buf = malloc(buflen);
+ if (!buf) {
+ log_error("nat_t_add_vendor_payload: malloc (%lu) failed",
+ (unsigned long)buflen);
+ return -1;
+ }
+
+ SET_ISAKMP_GEN_LENGTH(buf, buflen);
+ memcpy(buf + ISAKMP_VENDOR_ID_OFF, hash, nat_t_hashsize);
+ if (message_add_payload(msg, ISAKMP_PAYLOAD_VENDOR, buf, buflen, 1)) {
+ free(buf);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Add the NAT-T capability markers (VENDOR payloads). */
+int
+nat_t_add_vendor_payloads(struct message *msg)
+{
+ int i = 0;
+
+ if (!nat_t_hashes)
+ if (nat_t_setup_hashes())
+ return 0; /* XXX should this be an error? */
+
+ while (nat_t_hashes[i])
+ if (nat_t_add_vendor_payload(msg, nat_t_hashes[i++]))
+ return -1;
+
+ return 0;
+}
+
+/*
+ * Check an incoming message for NAT-T capability markers.
+ */
+void
+nat_t_check_vendor_payload(struct message *msg, struct payload *p)
+{
+ u_int8_t *pbuf = p->p;
+ size_t vlen;
+ int i = 0;
+
+ /* Already checked? */
+ if (p->flags & PL_MARK ||
+ msg->exchange->flags & EXCHANGE_FLAG_NAT_T_CAP_PEER)
+ return;
+
+ if (!nat_t_hashes)
+ if (nat_t_setup_hashes())
+ return;
+
+ vlen = GET_ISAKMP_GEN_LENGTH(pbuf) - ISAKMP_GEN_SZ;
+ if (vlen != nat_t_hashsize) {
+ LOG_DBG((LOG_EXCHANGE, 50, "nat_t_check_vendor_payload: "
+ "bad size %d != %d", vlen, nat_t_hashsize));
+ return;
+ }
+
+ while (nat_t_hashes[i])
+ if (memcmp(nat_t_hashes[i++], pbuf + ISAKMP_GEN_SZ,
+ vlen) == 0) {
+ /* This peer is NAT-T capable. */
+ msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_CAP_PEER;
+ LOG_DBG((LOG_EXCHANGE, 10,
+ "nat_t_check_vendor_payload: "
+ "NAT-T capable peer detected"));
+ p->flags |= PL_MARK;
+ return;
+ }
+
+ return;
+}
+
+/* Generate the NAT-D payload hash : HASH(CKY-I | CKY-R | IP | Port). */
+static u_int8_t *
+nat_t_generate_nat_d_hash(struct message *msg, struct sockaddr *sa,
+ size_t *hashlen)
+{
+ struct ipsec_exch *ie = (struct ipsec_exch *)msg->exchange->data;
+ struct hash *hash;
+ struct prf *prf;
+ u_int8_t *res;
+ in_port_t port;
+ int prf_type = PRF_HMAC; /* XXX */
+
+ hash = hash_get(ie->hash->type);
+ if (hash == NULL) {
+ log_print ("nat_t_generate_nat_d_hash: no hash");
+ return NULL;
+ }
+
+ prf = prf_alloc(prf_type, hash->type, msg->exchange->cookies,
+ ISAKMP_HDR_COOKIES_LEN);
+ if(!prf) {
+ log_print("nat_t_generate_nat_d_hash: prf_alloc failed");
+ return NULL;
+ }
+
+ *hashlen = prf->blocksize;
+ res = (u_int8_t *)malloc((unsigned long)*hashlen);
+ if (!res) {
+ log_print("nat_t_generate_nat_d_hash: malloc (%lu) failed",
+ (unsigned long)*hashlen);
+ prf_free(prf);
+ *hashlen = 0;
+ return NULL;
+ }
+
+ port = sockaddr_port(sa);
+ memset(res, 0, *hashlen);
+
+ prf->Update(prf->prfctx, sockaddr_addrdata(sa), sockaddr_addrlen(sa));
+ prf->Update(prf->prfctx, (unsigned char *)&port, sizeof port);
+ prf->Final(res, prf->prfctx);
+ prf_free (prf);
+
+ return res;
+}
+
+/* Add a NAT-D payload to our message. */
+static int
+nat_t_add_nat_d(struct message *msg, struct sockaddr *sa)
+{
+ u_int8_t *hbuf, *buf;
+ size_t hbuflen, buflen;
+
+ hbuf = nat_t_generate_nat_d_hash(msg, sa, &hbuflen);
+ if (!hbuf) {
+ log_print("nat_t_add_nat_d: NAT-D hash gen failed");
+ return -1;
+ }
+
+ buflen = ISAKMP_NAT_D_DATA_OFF + hbuflen;
+ buf = malloc(buflen);
+ if (!buf) {
+ log_error("nat_t_add_nat_d: malloc (%lu) failed",
+ (unsigned long)buflen);
+ free(hbuf);
+ return -1;
+ }
+
+ SET_ISAKMP_GEN_LENGTH(buf, buflen);
+ memcpy(buf + ISAKMP_NAT_D_DATA_OFF, hbuf, hbuflen);
+ free(hbuf);
+
+ if (message_add_payload(msg, ISAKMP_PAYLOAD_NAT_D, buf, buflen, 1)) {
+ free(buf);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* We add two NAT-D payloads, one each for src and dst. */
+int
+nat_t_exchange_add_nat_d(struct message *msg)
+{
+ struct sockaddr *sa;
+
+ msg->transport->vtbl->get_src(msg->transport, &sa);
+ if (nat_t_add_nat_d(msg, sa))
+ return -1;
+
+ msg->transport->vtbl->get_dst(msg->transport, &sa);
+ if (nat_t_add_nat_d(msg, sa))
+ return -1;
+
+ return 0;
+}
+
+/* Generate and match a NAT-D hash against the NAT-D payload (pl.) data. */
+static int
+nat_t_match_nat_d_payload(struct message *msg, struct sockaddr *sa)
+{
+ struct payload *p;
+ u_int8_t *hbuf;
+ size_t hbuflen;
+ int found = 0;
+
+ hbuf = nat_t_generate_nat_d_hash(msg, sa, &hbuflen);
+ if (!hbuf)
+ return 0;
+
+ for (p = TAILQ_FIRST(&msg->payload[ISAKMP_PAYLOAD_NAT_D]); p;
+ p = TAILQ_NEXT(p, link)) {
+ if (GET_ISAKMP_GEN_LENGTH (p->p) !=
+ hbuflen + ISAKMP_NAT_D_DATA_OFF)
+ continue;
+
+ if (memcmp(p->p + ISAKMP_NAT_D_DATA_OFF, hbuf, hbuflen) == 0) {
+ found++;
+ break;
+ }
+ }
+ free(hbuf);
+ return found;
+}
+
+/*
+ * Check if we need to activate NAT-T, and if we need to send keepalive
+ * messages to the other side, i.e if we are a nat:ed peer.
+ */
+int
+nat_t_exchange_check_nat_d(struct message *msg)
+{
+ struct sockaddr *sa;
+ int outgoing_path_is_clear, incoming_path_is_clear;
+
+ /* Assume trouble, i.e NAT-boxes in our path. */
+ outgoing_path_is_clear = incoming_path_is_clear = 0;
+
+ msg->transport->vtbl->get_src(msg->transport, &sa);
+ if (nat_t_match_nat_d_payload(msg, sa))
+ outgoing_path_is_clear = 1;
+
+ msg->transport->vtbl->get_dst(msg->transport, &sa);
+ if (nat_t_match_nat_d_payload(msg, sa))
+ incoming_path_is_clear = 1;
+
+ if (outgoing_path_is_clear && incoming_path_is_clear) {
+ LOG_DBG((LOG_EXCHANGE, 40, "nat_t_exchange_check_nat_d: "
+ "no NAT"));
+ return 0; /* No NAT-T required. */
+ }
+
+ /* NAT-T handling required. */
+ msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_ENABLE;
+
+ if (!outgoing_path_is_clear) {
+ msg->exchange->flags |= EXCHANGE_FLAG_NAT_T_KEEPALIVE;
+ LOG_DBG((LOG_EXCHANGE, 10, "nat_t_exchange_check_nat_d: "
+ "NAT detected, we're behind it"));
+ } else
+ LOG_DBG ((LOG_EXCHANGE, 10,
+ "nat_t_exchange_check_nat_d: NAT detected"));
+ return 1;
+}