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

    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

  transdata.c -- System independent code for transparently proxying
                 data connections
  
  ***************************************/

#include <signal.h>
#include <sys/un.h>

#include "common.h"
#include "transdata.h"

static void serve_requests(int listen);
static int td_listenfd;
static int td_reqfd;


void transdata_newsocketpair()
{
	int fds[2];

	if(!config.transdata)
		return;
	if(config.inetd)
		write_log(IMPORT, "Transdata not recommended when running "
			  "out of inetd");
	if(socketpair(PF_UNIX, SOCK_STREAM, 0, fds) == -1) {
		config.transdata = FALSE;
		return;
	}
	td_reqfd = fds[0];
	send_fd(td_listenfd, fds[1], 'N');
	close(fds[1]);
}

/* ------------------------------------------------------------- **
**  Make a connection to dest which appears to have come from src. If
**  we can't then we just make the connection anyway.
**  ------------------------------------------------------------- */
int transp_connect(struct sockaddr_in dest, struct sockaddr_in src)
{
	struct fd_request req;
	int ret;

	write_log(VERBOSE, " TD: transp_connect(). Setting up req structure");
	req.type = CONNECT;
	req.local = src;
	req.remote = dest;
	req.ports[0] = config.actvports[0];
	req.ports[1] = config.actvports[1];

	write_log(VERBOSE, " TD: transp_connect(). Sending fd request.");
	send(td_reqfd, &req, sizeof(req), 0);
	recv_fd(td_reqfd, &ret);
	write_log(VERBOSE, " TD: transp_connect(). Received fd.");
	return ret;
}

/* ------------------------------------------------------------- **
**  Listen in order to intercept any connections that are headed for
**  realdest, on a port from the range specified by use. You must call
**  il_free at some point before exiting to remove the iptables rule
**  this function adds under 2.4. Current implementation of il_free
**  means you can only have one intercept_listening socket per
**  process.
**  ------------------------------------------------------------- */
int intercept_listen(struct sockaddr_in intercept,
		     struct sockaddr_in listen_on, int portrange[2])
{
	struct fd_request req;
	int ret;

	write_log(VERBOSE,
		  " TD: intercept_listen(). Setting up req structure");
	req.type = LISTEN;
	req.local = listen_on;
	req.remote = intercept;
	req.ports[0] = portrange[0];
	req.ports[1] = portrange[1];

	write_log(VERBOSE, " TD: intercept_listen(). Sending fd request.");
	send(td_reqfd, &req, sizeof(req), 0);
	recv_fd(td_reqfd, &ret);
	write_log(VERBOSE, " TD: intercept_listen(). Received fd.");

	return ret;
}

/* ------------------------------------------------------------- **
**  Call this to remove the iptables rule introduced by a previous
**  intercept_listen.
**  ------------------------------------------------------------- */
int il_free(void)
{
	struct fd_request req;

	if(!config.transdata)
		return 0;
	req.type = UNLISTEN;
	send(td_reqfd, &req, sizeof(req), 0);
	return 0;
}

void transdata_flush(void)
{
	if(!config.transdata)
		return;
	send(td_listenfd, "F", 1, 0);
}

/*******************************************************************
 *  Code below here runs as root in a separate process. It accepts
 *  connections from the socket, and then returns fds back across the
 *  connections as and when requested.
 ******************************************************************/

void transdata_setup()
{
	int fds[2];

	if(!config.transdata)
		return;
	if(kernel_transdata_setup() == -1) {
		write_log(ERROR, "Failed to setup transparent data. "
			  "Will not do it");
		config.transdata = FALSE;
		return;
	}
	if(socketpair(PF_UNIX, SOCK_STREAM, 0, fds) == -1) {
		config.transdata = FALSE;
		return;
	}

	switch ((tdatapid = fork())) {
	case -1:
		tdatapid = 0;
		config.transdata = FALSE;
		return;
	case 0:
		signal(SIGHUP, SIG_IGN);
		write_log(VERBOSE, "TDS: Running transdata server");
		break;
	default:
		close(fds[1]);
		td_listenfd = fds[0];
		return;
	}
	close(fds[0]);

	/*Drop privileges while we can */
	setgid(config.gid);
	setgid(config.gid);
	seteuid(config.uid);
	serve_requests(fds[1]);

	exit(0);
}

struct td_client {
	int fd;
	struct fd_request req;
	struct td_client *next;
};

void serve_client(struct td_client *p);
void purge_clients(void);

struct td_client *head = NULL;

static void serve_requests(int listen)
{
	int fd;
	struct td_client *p;

	do {
		fd_set reads;
		FD_ZERO(&reads);
		for(p = head; p != NULL; p = p->next)
			FD_SET(p->fd, &reads);
		FD_SET(listen, &reads);

		select(FD_SETSIZE, &reads, NULL, NULL, NULL);
		if(FD_ISSET(listen, &reads)) {
			switch (recv_fd(listen, &fd)) {
			case 0:
				write_log(IMPORT, "Transdata exiting");
				exit(0);
			case 'F':
				kernel_td_flush();
				break;
			case 'N':
				write_log(VERBOSE,
					  "TDS: Accepted new client with fd=%d",
					  fd);
				p = malloc(sizeof(struct td_client));
				p->next = head;
				p->fd = fd;
				p->req.type = NONE;
				head = p;
				break;
			}
		}
		for(p = head; p != NULL; p = p->next) {
			if(FD_ISSET(p->fd, &reads)) {
				if(p->req.type == LISTEN) {
					kernel_td_unlisten(p->req);
					p->req.type = NONE;
				}
				if(recv(fd, &p->req, sizeof(p->req),
					MSG_WAITALL) <= 1) {
					write_log(VERBOSE,
						  "TDS: Closing fd %d",
						  p->fd);
					close(p->fd);
					p->fd = -1;
					continue;
				}
				serve_client(p);
			}
		}
		purge_clients();
	} while(TRUE);
}

void serve_client(struct td_client *p)
{
	int ret;
	switch (p->req.type) {
	case CONNECT:
		ret = kernel_td_connect(p->req);
		break;
	case LISTEN:
		ret = kernel_td_listen(p->req);
		break;
	case UNLISTEN:
		/*Don't need to do anything - kernel_td_unlisten()
		   already called from serve_requests before dealing
		   with this one. */
		return;
	default:
		return;
	}
	write_log(VERBOSE, "TDS: Sending fd %d", ret);
	if(ret == -1)
		write(p->fd, "X", 1);
	else
		send_fd(p->fd, ret, 'x');
	close(ret);
}

void purge_clients(void)
{
	struct td_client *p, *pp;

	while(head && head->fd == -1) {
		pp = head;
		head = pp->next;
		free(pp);
	}
	for(p = head; p && p->next; p = p->next) {
		if(p->next->fd == -1) {
			pp = p->next;
			p->next = p->next->next;
			free(pp);
		}
	}
}


syntax highlighted by Code2HTML, v. 0.9.1