mirror of
https://github.com/sonertari/SSLproxy
synced 2024-11-16 06:12:44 +00:00
599 lines
14 KiB
C
599 lines
14 KiB
C
/*-
|
|
* 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: */
|