/***************************************

    This is part of frox: A simple transparent FTP proxy
    Copyright (C) 2000 James Hollingshead

    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

  linux.c -- Nasty, non-portable, linux specific stuff which changes
             from kernel release to kernel release. ie the transparent 
             proxy calls.

***************************************/

#include <sys/utsname.h>
#include <sys/wait.h>
#include "common.h"
#include "transdata.h"

#if HAVE_LINUX_NETFILTER_IPV4_H
# include <limits.h>
# include <linux/netfilter_ipv4.h>
#endif

#if TRANS_DATA
#if USE_LIBIPTC
# include <libiptc.h>
# include <linux/netfilter_ipv4/ip_nat.h>
#endif
#endif

static enum {
	LINUX_2_0,
	LINUX_2_2,
	LINUX_2_4,
	OTHER
} kernel;

/* Had to add os_init() for the purpose of IP_FILTER on *BSD, but since
 * it's there might as well use it! */
int os_init(void)
{
	struct utsname tmp;

	uname(&tmp);
	if(!strncmp(tmp.release, "2.0.", 4))
		kernel = LINUX_2_0;
	else if(!strncmp(tmp.release, "2.2.", 4))
		kernel = LINUX_2_2;
	else if(!strncmp(tmp.release, "2.4.", 4))
		kernel = LINUX_2_4;
	else
		kernel = OTHER;
	return 0;
}

/* ------------------------------------------------------------- **
**  Get the original destination address of a transparently proxied
**  socket.
**  ------------------------------------------------------------- */
int get_orig_dest(int fd, struct sockaddr_in *addr)
{
	socklen_t len;

	len = sizeof(*addr);
	switch (kernel) {
	case LINUX_2_0:
	case LINUX_2_2:
		return (getsockname(fd, (struct sockaddr *) addr, &len));
	default:
#ifdef SO_ORIGINAL_DST		/*Header support for kernel 2.4 */
		if(getsockopt(fd, SOL_IP, SO_ORIGINAL_DST,
			      (struct sockaddr *) addr, &len))
			return -1;
		if(!addr->sin_addr.s_addr)
			return -1;
		return 0;
#else
		write_log(ERROR,
			  "Running on a kernel we haven't been compiled for. Oooops.");
		return (-1);
#endif
	}
}

/* ------------------------------------------------------------- **
**  Get the address of the interface we connect to the client through
**  for putting in our 227 reply. For 2.4 do a getsockname on the
**  control socket. For 2.2 this gives us the orriginal destination of
**  the transparently proxied connection, so we do some nasty hackery
**  instead.
**  ------------------------------------------------------------- */
int get_local_address(const int fd, struct sockaddr_in *addr)
{
	int sockfd;
	socklen_t len;

	/*If ListenAddress is in the config file then use the address
	 * from that*/
	*addr = config.listen_address;
	if(addr->sin_addr.s_addr != 0) {
		addr->sin_port = 0;
		return (0);
	}

	switch (kernel) {
	case LINUX_2_2:
		/* This piece of code ought to be taken out and shot
		 **  (yes - it opens a UDP socket, does a getsockname,
		 **  and then closes the socket before anything
		 **  happens!) Suggestions for an alternative welcomed */

		if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
			return (-1);

		addr->sin_family = AF_INET;
		addr->sin_addr = info->client_control.address.sin_addr;
		addr->sin_port = htons(12345);
		len = sizeof(*addr);

		if(connect(sockfd, (struct sockaddr *) addr, len) == -1) {
			close(sockfd);
			return (-1);
		}

		if(getsockname(sockfd, (struct sockaddr *) addr, &len) == -1) {
			close(sockfd);
			return (-1);
		}

		close(sockfd);

		addr->sin_port = 0;
		return (0);
	case LINUX_2_4:
	default:
		len = sizeof(*addr);
		return (getsockname(fd, (struct sockaddr *) addr, &len));
	}
}

int bindtodevice(int fd)
{
	if(!config.device)
		return (0);
	if(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
		      (void *) config.device,
		      (socklen_t) strlen(config.device) + 1) != 0) {
		debug_perr("Binding to device");
		return (-1);
	}
	write_log(IMPORT, "Bound to device %s", config.device);
	return (0);
}

#if TRANS_DATA

/* ------------------------------------------------------------- **
**  Functions below are designed for the data link between the client
**  and the proxy. We want to fool the client that this connection
**  comes from the ftp server it connected to, so we have to be able
**  to either connect to the client with a false source address
**  (active mode), or intercept the client trying to connect to the
**  server's data port (passive mode).
**
**  On kernel 2.4 we do this using netfilter snat or dnat through
**  libiptc. On 2.2 we simply do bind-to-foreign-address.[Not tested
**  recently :) ]
**
**  Most of this stuff is a bit of a mess. Perhaps that is
**  unavoidable...
**  ------------------------------------------------------------- */
#ifndef USE_LIBIPTC
int kernel_transdata_setup()
{
	if(kernel != LINUX_2_4)
		return (0);

	fprintf(stderr,
		"You appear to be running a 2.4.x Linux kernel,"
		" but frox was not configured\n"
		"with --enable-libiptc. Data connections will NOT"
		" be transparently proxied\n");
	return (-1);
}

int kernel_td_connect(struct fd_request req)
{
	uid_t uid;
	int sockfd, i;

	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		debug_perr("socket");
		return (-1);
	}

	uid = geteuid();
	write_log(VERBOSE,
		  "TDS: Regaining priveliges for bind-to-foreign-address");
	seteuid(0);
	i = bind(sockfd, (struct sockaddr *) &req.remote, sizeof(req.remote));
	write_log(VERBOSE, "TDS: Dropping them again");
	seteuid(uid);

	if(i) {
		debug_err("bind failed");
		close(sockfd);
		return (-1);
	}

	i = connect(sockfd, (struct sockaddr *) &req.remote,
		    sizeof(req.remote));

	if(i) {
		close(sockfd);
		return (-1);
	}

	return (sockfd);
}

int kernel_td_listen(struct fd_request req)
{
	uid_t uid;
	int i, sockfd;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	uid = geteuid();
	write_log(VERBOSE,
		  "TDS: Regaining privelidges for bind-to-foreign-address");
	seteuid(0);
	i = bind(sockfd, (struct sockaddr *) &req.local, sizeof(req.local));
	write_log(VERBOSE, "TDS: Dropping them again");
	seteuid(uid);

	if(i) {
		debug_err("bind failed");
		close(sockfd);
		return (-1);
	}

	if(listen(sockfd, 5)) {
		debug_perr("listen");
		close(sockfd);
		return (-1);
	}
	return (sockfd);
}

/*Kernel 2.2.x automatically cleans up after bind-to-foreign-address*/
int kernel_td_unlisten(struct fd_request req)
{
	return (0);
}

void kernel_td_flush(void)
{
}
#else /*USE_LIBIPTC */

#define FROXSNAT "froxsnat"
#define FROXDNAT "froxdnat"

int init_chains(void);
void serve_requests(int fd);
int add_entry(const struct ipt_entry *e, const char *chain);
int delete_entry(const struct ipt_entry *e, const char *chain);
struct ipt_entry *get_entry(struct sockaddr_in src, struct sockaddr_in dst,
			    struct sockaddr_in to, int snat);

int kernel_transdata_setup()
{
	if(kernel == LINUX_2_2)
		return (0);

	if(init_chains() == -1) {
		fprintf(stderr,
			"\nChains " FROXSNAT " and/or " FROXDNAT
			" do not exist. Data connections\n"
			"will not be transparently proxied. Read"
			" README.transdata for details\n\n");
		return (-1);
	}

	return 0;
}

int kernel_td_connect(struct fd_request req)
{
	struct sockaddr_in address;
	struct ipt_entry *e;
	int sockfd, i;

	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		debug_perr("socket");
		return (-1);
	}

	memset(&address, 0, sizeof(address));
	address.sin_family = AF_INET;
	i = bind_me(sockfd, &address, req.ports);

	if(i) {
		debug_err("bind failed");
		close(sockfd);
		return (-1);
	}


	/* DO SNAT */
	address = config.listen_address;
	address.sin_port = 0;
	e = get_entry(address, req.remote, req.local, TRUE);
	if(e == NULL) {
		close(sockfd);
		return -1;
	}
	if(add_entry(e, FROXSNAT) == -1) {
		free(e);
		close(sockfd);
		return -1;
	}

	i = connect(sockfd, (struct sockaddr *) &req.remote,
		    sizeof(req.remote));

	/* UNDO SNAT */
	delete_entry(e, FROXSNAT);
	free(e);

	if(i) {
		close(sockfd);
		return (-1);
	}

	return (sockfd);
}

int kernel_td_listen(struct fd_request req)
{
	struct sockaddr_in address;
	struct ipt_entry *e;
	int i, sockfd;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	i = bind_me(sockfd, &req.local, req.ports);

	if(i) {
		debug_err("bind failed");
		close(sockfd);
		return (-1);
	}

	if(listen(sockfd, 5)) {
		debug_perr("listen");
		close(sockfd);
		return (-1);
	}

	/*DO DNAT */
	address.sin_addr.s_addr = INADDR_ANY;
	address.sin_port = 0;
	e = get_entry(address, req.remote, req.local, FALSE);
	if(e == NULL) {
		debug_err("Can't get entry");
		close(sockfd);
		return -1;
	}
	if(add_entry(e, FROXDNAT) == -1) {
		debug_err("Unable to add entry");
		free(e);
		close(sockfd);
		return -1;
	}

	return (sockfd);
}

int kernel_td_unlisten(struct fd_request req)
{
	struct ipt_entry *e;
	struct sockaddr_in address;

	address.sin_addr.s_addr = INADDR_ANY;
	address.sin_port = 0;
	e = get_entry(address, req.remote, req.local, FALSE);
	if(e == NULL) {
		debug_err("Can't get entry");
		return -1;
	}
	if(delete_entry(e, FROXDNAT) == -1) {
		debug_err("Unable to delete entry");
		free(e);
		return -1;
	}
	return (0);
}

void kernel_td_flush(void)
{
	iptc_handle_t h;
	uid_t uid;

	uid = geteuid();
	write_log(VERBOSE, "TDS: Regaining privelidges for flushing chains");
	seteuid(0);
	write_log(VERBOSE, "Flushing chains...");
	if((h = iptc_init("nat"))) {
		iptc_flush_entries(FROXSNAT, &h);
		iptc_flush_entries(FROXDNAT, &h);
		if(iptc_commit(&h))
			write_log(VERBOSE, "   Success");
		else
			write_log(VERBOSE, "   Failed");
	}
	write_log(VERBOSE, "TDS: Dropping them again");
	seteuid(uid);
}

int init_chains()
{
	iptc_handle_t h;

	if(!(h = iptc_init("nat")))
		return (-1);
	if(!iptc_is_chain(FROXSNAT, h))
		return (-1);
	if(!iptc_is_chain(FROXDNAT, h))
		return (-1);
	return (0);
}

int add_entry(const struct ipt_entry *e, const char *chain)
{
	iptc_handle_t h;
	uid_t uid;
	int ret = -1;

	uid = geteuid();
	write_log(VERBOSE,
		  "TDS: Regaining privelidges for inserting firewall rules");
	seteuid(0);

	if((h = iptc_init("nat")) &&
	   iptc_append_entry(chain, e, &h) && iptc_commit(&h))
		ret = 0;
	else
		ret = -1;

	write_log(VERBOSE, "TDS: Dropping them again");
	seteuid(uid);

	return ret;
}

/*FIXME deletion by matching entry doesn't seem to be reliable. Should
  we keep track of rule numbers and delete those?*/
int delete_entry(const struct ipt_entry *e, const char *chain)
{
	iptc_handle_t h;
	unsigned char *matchmask = NULL;
	uid_t uid;
	int ret;

	uid = geteuid();
	write_log(VERBOSE,
		  "TDS: Regaining privelidges for deleting firewall rules");
	seteuid(0);

	if((h = iptc_init("nat")) &&
	   (matchmask = malloc(e->next_offset)) &&
	   iptc_delete_entry(chain, e, matchmask, &h) && iptc_commit(&h))
		ret = 0;
	else {
		debug_err(iptc_strerror(errno));
		ret = -1;
	}

	write_log(VERBOSE, "TDS: Dropping them again");
	seteuid(uid);

	if(matchmask)
		free(matchmask);
	return ret;
}

/* ------------------------------------------------------------- **
** Set up an ipt_entry structure. Which will do the equivalent of
** "iptables -p tcp -s src -d dst -j (SNAT|DNAT) --to to". If snat is
** TRUE we do snat, otherwise dnat. This probably isn't the correct
** way to use libiptc, but there isn't much sample code/documentation
** and I really couldn't face messing around with dlopen etc.
**
** The return value should be freed by the calling function.
**
** I want my bind-to-foreign-address back :)
** ------------------------------------------------------------- */
struct ipt_entry *get_entry(struct sockaddr_in src, struct sockaddr_in dst,
			    struct sockaddr_in to, int snat)
{
	struct ipt_entry *e;

	struct ipt_entry_match *match;
	struct ipt_tcp *tcpinfo;

	struct ipt_entry_target *target;
	struct ip_nat_multi_range *mr;

	unsigned int size1, size2, size3;

	size1 = IPT_ALIGN(sizeof(struct ipt_entry));
	size2 = IPT_ALIGN(sizeof(struct ipt_entry_match) +
			  sizeof(struct ipt_tcp));
	size3 = IPT_ALIGN(sizeof(struct ipt_entry_target) +
			  sizeof(struct ip_nat_multi_range));

	e = malloc(size1 + size2 + size3);
	if(e == NULL) {
		write_log(ERROR, "Malloc failure");
		return (NULL);
	}
	memset(e, 0, size1 + size2 + size3);

	/*Offsets to the other bits */
	e->target_offset = size1 + size2;
	e->next_offset = size1 + size2 + size3;

	/*Set up packet matching rules */
	if((e->ip.src.s_addr = src.sin_addr.s_addr) == INADDR_ANY)
		e->ip.smsk.s_addr = 0;
	else
		e->ip.smsk.s_addr = inet_addr("255.255.255.255");

	if((e->ip.dst.s_addr = dst.sin_addr.s_addr) == INADDR_ANY)
		e->ip.dmsk.s_addr = 0;
	else
		e->ip.dmsk.s_addr = inet_addr("255.255.255.255");

	e->ip.proto = IPPROTO_TCP;
	e->nfcache = NFC_UNKNOWN;	/*Think this stops caching. */

	/*TCP specific matching(ie. ports) */
	match = (struct ipt_entry_match *) e->elems;
	match->u.match_size = size2;
	strcpy(match->u.user.name, "tcp");

	tcpinfo = (struct ipt_tcp *) match->data;

	if(src.sin_port == 0) {
		tcpinfo->spts[0] = ntohs(0);
		tcpinfo->spts[1] = ntohs(0xFFFF);
	} else
		tcpinfo->spts[0] = tcpinfo->spts[1] = ntohs(src.sin_port);
	if(dst.sin_port == 0) {
		tcpinfo->dpts[0] = ntohs(0);
		tcpinfo->dpts[1] = ntohs(0xFFFF);
	} else
		tcpinfo->dpts[0] = tcpinfo->dpts[1] = ntohs(dst.sin_port);

	/*And the target */
	target = (struct ipt_entry_target *) (e->elems + size2);
	target->u.target_size = size3;
	if(snat)
		strcpy(target->u.user.name, "SNAT");
	else
		strcpy(target->u.user.name, "DNAT");

	mr = (struct ip_nat_multi_range *) target->data;
	mr->rangesize = 1;

	mr->range[0].flags = IP_NAT_RANGE_PROTO_SPECIFIED |
		IP_NAT_RANGE_MAP_IPS;
	mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = to.sin_port;
	mr->range[0].min_ip = mr->range[0].max_ip = to.sin_addr.s_addr;

	return e;
}
#endif /*USE_LIBIPTC */
#endif /*TRANS_DATA */


syntax highlighted by Code2HTML, v. 0.9.1