/*-
 * SSLsplit - transparent SSL/TLS interception
 * https://www.roe.ch/SSLsplit
 *
 * Copyright (c) 2009-2019, Daniel Roethlisberger <daniel@roe.ch>.
 * 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 COPYRIGHT HOLDER AND CONTRIBUTORS ``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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "nat.h"

#include "log.h"
#include "attrib.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#ifdef HAVE_PF
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <net/if.h>
#include <netinet/in.h>
#ifdef __APPLE__
#define PRIVATE
#endif /* __APPLE__ */
#include <net/pfvar.h>
#ifdef __APPLE__
#undef PRIVATE
#endif /* __APPLE__ */
#include <unistd.h>
#endif /* HAVE_PF */

#ifdef HAVE_IPFILTER
#include <sys/ioctl.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <netinet/ipl.h>
#include <netinet/ip_compat.h>
#include <netinet/ip_fil.h>
#include <netinet/ip_nat.h>
#endif /* HAVE_IPFILTER */

#ifdef HAVE_NETFILTER
#include <limits.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
#include <linux/if.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#endif /* HAVE_NETFILTER */


/*
 * Access NAT state tables in a NAT engine independent way.
 * Adding support for additional NAT engines should require only
 * changes in this file.
 */


/*
 * pf
 */

#ifdef HAVE_PF
static int nat_pf_fd = -1;

static int
nat_pf_preinit(void)
{
	nat_pf_fd = open("/dev/pf", O_RDONLY);
	if (nat_pf_fd < 0) {
		log_err_level_printf(LOG_CRIT, "Error opening '/dev/pf': %s\n",
		               strerror(errno));
		return -1;
	}
	return 0;
}

static int
nat_pf_init(void)
{
	int rv;

	rv = fcntl(nat_pf_fd, F_SETFD, fcntl(nat_pf_fd, F_GETFD) | FD_CLOEXEC);
	if (rv == -1) {
		log_err_level_printf(LOG_CRIT, "Error setting FD_CLOEXEC on '/dev/pf': %s\n",
		               strerror(errno));
		return -1;
	}
	return 0;
}

static void
nat_pf_fini(void)
{
	close(nat_pf_fd);
}

static int
nat_pf_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen,
                 evutil_socket_t s,
                 UNUSED struct sockaddr *src_addr, UNUSED socklen_t src_addrlen)
{
	if (getsockname(s, dst_addr, dst_addrlen) == -1) {
		log_err_level_printf(LOG_CRIT, "Error from getsockname(): %s\n",
		               strerror(errno));
		return -1;
	}
	return 0;
}
#endif /* HAVE_PF */


/*
 * ipfilter
 */

#ifdef HAVE_IPFILTER
static int nat_ipfilter_fd = -1;

static int
nat_ipfilter_preinit(void)
{
	nat_ipfilter_fd = open(IPNAT_NAME, O_RDONLY);
	if (nat_ipfilter_fd < 0) {
		log_err_printf("Error opening '%s': %s\n",
		               IPNAT_NAME, strerror(errno));
		return -1;
	}
	return 0;
}

static int
nat_ipfilter_init(void)
{
	int rv;

	rv = fcntl(nat_ipfilter_fd, F_SETFD,
	           fcntl(nat_ipfilter_fd, F_GETFD) | FD_CLOEXEC);
	if (rv == -1) {
		log_err_printf("Error setting FD_CLOEXEC on '%s': %s\n",
		               IPNAT_NAME, strerror(errno));
		return -1;
	}
	return 0;
}

static void
nat_ipfilter_fini(void)
{
	close(nat_ipfilter_fd);
}

static int
nat_ipfilter_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen,
                       evutil_socket_t s,
                       struct sockaddr *src_addr, UNUSED socklen_t src_addrlen)
{
	struct sockaddr_storage our_addr;
	socklen_t our_addrlen;
	struct natlookup nl;
	struct ipfobj ipfo;

	our_addrlen = sizeof(struct sockaddr_storage);
	if (getsockname(s, (struct sockaddr *)&our_addr, &our_addrlen) == -1) {
		log_err_printf("Error from getsockname(): %s\n",
		               strerror(errno));
		return -1;
	}

	memset(&nl, 0, sizeof(struct natlookup));
	if (src_addr->sa_family == AF_INET) {
		struct sockaddr_in *src_sai = (struct sockaddr_in *)src_addr;
		struct sockaddr_in *our_sai = (struct sockaddr_in *)&our_addr;
		nl.nl_outip.s_addr = src_sai->sin_addr.s_addr;
		nl.nl_outport = src_sai->sin_port;
		nl.nl_inip.s_addr = our_sai->sin_addr.s_addr;
		nl.nl_inport = our_sai->sin_port;
	} else {
		log_err_printf("The ipfilter NAT engine does not "
		               "support IPv6 state lookups\n");
		return -1;
	}
	nl.nl_flags = IPN_TCP;

	/* assuming IPv4 from here */

	memset(&ipfo, 0, sizeof(struct ipfobj));
	ipfo.ipfo_rev = IPFILTER_VERSION;
	ipfo.ipfo_size = sizeof(struct natlookup);
	ipfo.ipfo_ptr = &nl;
	ipfo.ipfo_type = IPFOBJ_NATLOOKUP;

	if (ioctl(nat_ipfilter_fd, SIOCGNATL, &ipfo) == -1) {
		if (errno != ESRCH) {
			log_err_printf("Error from ioctl(SIOCGNATL): %s\n",
			               strerror(errno));
		}
		return -1;
	}

	if ((nl.nl_inport == nl.nl_realport) &&
	    (nl.nl_inip.s_addr == nl.nl_realip.s_addr)) {
		/* no destination address/port translation in place */
		return -1;
	}

	/* copy original destination address */
	struct sockaddr_in *dst_sai = (struct sockaddr_in *)dst_addr;
	memset(dst_sai, 0, sizeof(struct sockaddr_in));
	dst_sai->sin_addr.s_addr = nl.nl_realip.s_addr;
	dst_sai->sin_port = nl.nl_realport;
	dst_sai->sin_family = AF_INET;
	*dst_addrlen = sizeof(struct sockaddr_in);
	return 0;
}
#endif /* HAVE_IPFILTER */


/*
 * netfilter, tproxy
 */

#ifdef HAVE_NETFILTER
/*
 * Linux commit 121d1e0941e05c64ee4223064dd83eb24e871739 adding
 * IP6T_SO_ORIGINAL_DST was first released as part of Linux v3.8-rc1 in 2012.
 * Before that, this interface only supported IPv4.
 */
static int
nat_netfilter_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen,
                        evutil_socket_t s,
                        struct sockaddr *src_addr, UNUSED socklen_t src_addrlen)
{
	int rv;

	if (src_addr->sa_family == AF_INET) {
		rv = getsockopt(s, SOL_IP, SO_ORIGINAL_DST,
		                dst_addr, dst_addrlen);
		if (rv == -1) {
			log_err_printf("Error from getsockopt("
			               "SO_ORIGINAL_DST): %s\n",
			               strerror(errno));
		}
	} else {
#ifdef IP6T_SO_ORIGINAL_DST
		rv = getsockopt(s, SOL_IPV6, IP6T_SO_ORIGINAL_DST,
		                dst_addr, dst_addrlen);
		if (rv == -1) {
			log_err_printf("Error from getsockopt("
			               "IP6T_SO_ORIGINAL_DST): %s\n",
			               strerror(errno));
		}
#else /* !IP6T_SO_ORIGINAL_DST */
		log_err_printf("The netfilter NAT engine only "
		               "supports IPv4 state lookups on "
		               "this version of Linux\n");
		return -1;
#endif /* !IP6T_SO_ORIGINAL_DST */
	}
	return rv;
}

#ifdef IP_TRANSPARENT
/*
 * Set the listening socket IP_TRANSPARENT.  This makes the Linux IP routing
 * stack omit the source address checks on output, which is needed for
 * Linux TPROXY transparent proxying support.
 */
static int
nat_iptransparent_socket_cb(evutil_socket_t s)
{
	int on = 1;
	int rv;

	rv = setsockopt(s, SOL_IP, IP_TRANSPARENT, (void*)&on, sizeof(on));
	if (rv == -1) {
		log_err_printf("Error from setsockopt(IP_TRANSPARENT): %s\n",
		               strerror(errno));
	}
	return rv;
}
#endif /* IP_TRANSPARENT */
#endif /* HAVE_NETFILTER */


/*
 * generic
 */

#if defined(HAVE_IPFW) || (defined(HAVE_NETFILTER) && defined(IP_TRANSPARENT))
/*
 * Generic getsockname based implementation.  This assumes that getsockname,
 * by kernel magic, gives us the original destination.
 */
static int
nat_getsockname_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen,
                          evutil_socket_t s,
                          UNUSED struct sockaddr *src_addr,
                          UNUSED socklen_t src_addrlen)
{
	if (getsockname(s, dst_addr, dst_addrlen) == -1) {
		log_err_level_printf(LOG_CRIT, "Error from getsockname(): %s\n",
		               strerror(errno));
		return -1;
	}
	return 0;
}
#endif


/*
 * NAT engine glue code and API.
 */

typedef int (*nat_init_cb_t)(void);
typedef void (*nat_fini_cb_t)(void);

struct engine {
	const char *name;
	unsigned int ipv6 : 1;
	unsigned int used : 1;
	nat_init_cb_t preinitcb;
	nat_init_cb_t initcb;
	nat_fini_cb_t finicb;
	nat_lookup_cb_t lookupcb;
	nat_socket_cb_t socketcb;
};

struct engine engines[] = {
#ifdef HAVE_PF
	{
		"pf", 1, 0,
		nat_pf_preinit, nat_pf_init, nat_pf_fini,
		nat_pf_lookup_cb, NULL
	},
#endif /* HAVE_PF */
#ifdef HAVE_IPFW
	{
		"ipfw", 1, 0,
		NULL, NULL, NULL,
		nat_getsockname_lookup_cb, NULL
	},
#endif /* HAVE_IPFW */
#ifdef HAVE_IPFILTER
	{
		"ipfilter", 0, 0,
		nat_ipfilter_preinit, nat_ipfilter_init, nat_ipfilter_fini,
		nat_ipfilter_lookup_cb, NULL
	},
#endif /* HAVE_IPFILTER */
#ifdef HAVE_NETFILTER
	{
#ifdef IP6T_SO_ORIGINAL_DST
		"netfilter", 1, 0,
#else /* !IP6T_SO_ORIGINAL_DST */
		"netfilter", 0, 0,
#endif /* !IP6T_SO_ORIGINAL_DST */
		NULL, NULL, NULL,
		nat_netfilter_lookup_cb, NULL
	},
#ifdef IP_TRANSPARENT
	{
		"tproxy", 1, 0,
		NULL, NULL, NULL,
		nat_getsockname_lookup_cb, nat_iptransparent_socket_cb
	},
#endif /* IP_TRANSPARENT */
#endif /* HAVE_NETFILTER */
	{
		NULL, 0, 0,
		NULL, NULL, NULL,
		NULL, NULL
	}
};


/*
 * Return the name of the default NAT engine.
 */
const char *
nat_getdefaultname(void)
{
	return engines[0].name;
}

/*
 * Look for a NAT engine in the table and return the index if found.
 * If there is no NAT engine with the given name, then the index of the
 * sentinel table entry is returned.
 */
static int
nat_index(const char *name)
{
	if (name)
		for (int i = 0; engines[i].name; i++)
			if (!strcmp(name, engines[i].name))
				return i;
	return ((sizeof(engines) / sizeof(struct engine)) - 1);
}

/*
 * Returns !=0 if the named NAT engine exists, 0 if it does not exist.
 * NULL refers to the default NAT engine.
 */
int
nat_exist(const char *name)
{
	if (!name)
		name = engines[0].name;
	return !!engines[nat_index(name)].name;
}

/*
 * Returns !=0 if the named NAT engine has been marked as used, 0 if not.
 * NULL refers to the default NAT engine.
 */
int
nat_used(const char *name)
{
	if (!name)
		name = engines[0].name;
	return !!engines[nat_index(name)].used;
}

/*
 * Returns the lookup callback of the named NAT engine and marks the NAT
 * engine as used.
 * NULL refers to the default NAT engine.
 */
nat_lookup_cb_t
nat_getlookupcb(const char *name)
{
	int i;

	if (!name)
		name = engines[0].name;
	i = nat_index(name);
	engines[i].used = 1;
	return engines[i].lookupcb;
}

/*
 * Returns the socket callback of the named NAT engine.
 * NULL refers to the default NAT engine.
 */
nat_socket_cb_t
nat_getsocketcb(const char *name)
{
	if (!name)
		name = engines[0].name;
	return engines[nat_index(name)].socketcb;
}

/*
 * Returns 1 if name is a NAT engine which supports IPv6.
 * NULL refers to the default NAT engine.
 */
int
nat_ipv6ready(const char *name)
{
	if (!name)
		name = engines[0].name;
	return engines[nat_index(name)].ipv6;
}

/*
 * List all available NAT engines to standard output and flush.
 */
void
nat_list_engines(void)
{
	for (int i = 0; engines[i].name; i++) {
		fprintf(stdout, "%s%s\n", engines[i].name,
		                          i ? "" : " (default)");
	}
	fflush(stdout);
}

/*
 * Pre-initialize all NAT engines which were marked as used by previous calls
 * to nat_getlookupcb().
 *
 * Privileged initialization under root privs, before dropping privs,
 * before calling daemon().  Here should be initialization which needs
 * to provide the user feedback on errors.  This includes opening
 * special device files, for which the user may not have sufficient privs.
 *
 * Returns -1 on failure, 0 on success.
 */
int
nat_preinit(void)
{
	for (int i = 0; engines[i].preinitcb && engines[i].used; i++) {
		log_dbg_printf("NAT engine preinit '%s'\n", engines[i].name);
		if (engines[i].preinitcb() == -1)
			return -1;
	}
	return 0;
}

/*
 * Undo nat_preinit - close all file descriptors, for use in privsep parent.
 */
void
nat_preinit_undo(void)
{
	nat_fini();
}

/*
 * Initialize all NAT engines which were marked as used by previous calls to
 * nat_getlookupcb().
 *
 * Unprivileged initialization, possibly root, possibly nobody or service user.
 *
 * Returns -1 on failure, 0 on success.
 */
int
nat_init(void)
{
	for (int i = 0; engines[i].initcb && engines[i].used; i++) {
		log_dbg_printf("NAT engine init '%s'\n", engines[i].name);
		if (engines[i].initcb() == -1)
			return -1;
	}
	return 0;
}

/*
 * Cleanup all NAT engines which were marked as used by previous calls to
 * nat_getlookupcb().
 */
void
nat_fini(void)
{
	for (int i = 0; engines[i].finicb && engines[i].used; i++) {
		log_dbg_printf("NAT engine fini '%s'\n", engines[i].name);
		engines[i].finicb();
	}
}

/*
 * Print version and option availability to standard error.
 */
void
nat_version(void)
{
	fprintf(stderr, "NAT engines:");
	for (int i = 0; engines[i].name; i++) {
		fprintf(stderr, " %s%s", engines[i].name,
		                         i ? "" : "*");
	}
	if (!engines[0].name)
		fprintf(stderr, " -");
	fprintf(stderr, "\n");
#ifdef HAVE_IPFILTER
	fprintf(stderr, "ipfilter: version %d\n", IPFILTER_VERSION);
#endif /* HAVE_IPFILTER */
#ifdef HAVE_NETFILTER
	fprintf(stderr, "netfilter:");
#ifdef IP_TRANSPARENT
	fprintf(stderr, " IP_TRANSPARENT");
#else /* !IP_TRANSPARENT */
	fprintf(stderr, " !IP_TRANSPARENT");
#endif /* !IP_TRANSPARENT */
#ifdef IP6T_SO_ORIGINAL_DST
	fprintf(stderr, " IP6T_SO_ORIGINAL_DST");
#else /* !IP6T_SO_ORIGINAL_DST */
	fprintf(stderr, " !IP6T_SO_ORIGINAL_DST");
#endif /* !IP6T_SO_ORIGINAL_DST */
	fprintf(stderr, "\n");
#endif /* HAVE_NETFILTER */
}

/* vim: set noet ft=c: */
