natd.c   [plain text]


/*
 * natd - Network Address Translation Daemon for FreeBSD.
 *
 * This software is provided free of charge, with no 
 * warranty of any kind, either expressed or implied.
 * Use at your own risk.
 * 
 * You may copy, modify and distribute this software (natd.c) freely.
 *
 * Ari Suutari <suutari@iki.fi>
 *
 *	$Id: natd.c,v 1.1.1.1 2000/01/11 01:48:51 wsanchez Exp $
 */

#define SYSLOG_NAMES

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/route.h>
#include <arpa/inet.h>

#include <alias.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "natd.h"

/* 
 * Default values for input and output
 * divert socket ports.
 */

#define	DEFAULT_SERVICE	"natd"

/*
 * Definition of a port range, and macros to deal with values.
 * FORMAT:  HI 16-bits == first port in range, 0 == all ports.
 *          LO 16-bits == number of ports in range
 * NOTES:   - Port values are not stored in network byte order.
 */

typedef u_long port_range;

#define GETLOPORT(x)     ((x) >> 0x10)
#define GETNUMPORTS(x)   ((x) & 0x0000ffff)
#define GETHIPORT(x)     (GETLOPORT((x)) + GETNUMPORTS((x)))

/* Set y to be the low-port value in port_range variable x. */
#define SETLOPORT(x,y)   ((x) = ((x) & 0x0000ffff) | ((y) << 0x10))

/* Set y to be the number of ports in port_range variable x. */
#define SETNUMPORTS(x,y) ((x) = ((x) & 0xffff0000) | (y))

/*
 * Function prototypes.
 */

static void	DoAliasing (int fd, int direction);
static void	DaemonMode (void);
static void	HandleRoutingInfo (int fd);
static void	Usage (void);
static char*	FormatPacket (struct ip*);
static void	PrintPacket (struct ip*);
static void	SyslogPacket (struct ip*, int priority, const char *label);
static void	SetAliasAddressFromIfName (char* ifName);
static void	InitiateShutdown (int);
static void	Shutdown (int);
static void	RefreshAddr (int);
static void	ParseOption (const char* option, const char* parms, int cmdLine);
static void	ReadConfigFile (const char* fileName);
static void	SetupPortRedirect (const char* parms);
static void	SetupAddressRedirect (const char* parms);
static void	SetupPptpAlias (const char* parms);
static void	StrToAddr (const char* str, struct in_addr* addr);
static u_short  StrToPort (const char* str, const char* proto);
static int      StrToPortRange (const char* str, const char* proto, port_range *portRange);
static int 	StrToProto (const char* str);
static int      StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto, port_range *portRange);
static void	ParseArgs (int argc, char** argv);
static void	FlushPacketBuffer (int fd);

/*
 * Globals.
 */

static	int			verbose;
static 	int			background;
static	int			running;
static	int			assignAliasAddr;
static	char*			ifName;
static  int			ifIndex;
static	u_short			inPort;
static	u_short			outPort;
static	u_short			inOutPort;
static	struct in_addr		aliasAddr;
static 	int			dynamicMode;
static  int			ifMTU;
static	int			aliasOverhead;
static 	int			icmpSock;
static	char			packetBuf[IP_MAXPACKET];
static 	int			packetLen;
static	struct sockaddr_in	packetAddr;
static 	int			packetSock;
static 	int			packetDirection;
static  int			dropIgnoredIncoming;
static  int			logDropped;
static	int			logFacility;

int main (int argc, char** argv)
{
	int			divertIn;
	int			divertOut;
	int			divertInOut;
	int			routeSock;
	struct sockaddr_in	addr;
	fd_set			readMask;
	fd_set			writeMask;
	int			fdMax;
/* 
 * Initialize packet aliasing software.
 * Done already here to be able to alter option bits
 * during command line and configuration file processing.
 */
	PacketAliasInit ();
/*
 * Parse options.
 */
	inPort			= 0;
	outPort			= 0;
	verbose 		= 0;
	inOutPort		= 0;
	ifName			= NULL;
	ifMTU			= -1;
	background		= 0;
	running			= 1;
	assignAliasAddr		= 0;
	aliasAddr.s_addr	= INADDR_NONE;
	aliasOverhead		= 12;
	dynamicMode		= 0;
 	logDropped		= 0;
 	logFacility		= LOG_DAEMON;
/*
 * Mark packet buffer empty.
 */
	packetSock		= -1;
	packetDirection		= DONT_KNOW;

	ParseArgs (argc, argv);
/*
 * Open syslog channel.
 */
	openlog ("natd", LOG_CONS | LOG_PID, logFacility);
/*
 * Check that valid aliasing address has been given.
 */
	if (aliasAddr.s_addr == INADDR_NONE && ifName == NULL)
		errx (1, "aliasing address not given");

	if (aliasAddr.s_addr != INADDR_NONE && ifName != NULL)
		errx (1, "both alias address and interface "
			 "name are not allowed");
/*
 * Check that valid port number is known.
 */
	if (inPort != 0 || outPort != 0)
		if (inPort == 0 || outPort == 0)
			errx (1, "both input and output ports are required");

	if (inPort == 0 && outPort == 0 && inOutPort == 0)
		ParseOption ("port", DEFAULT_SERVICE, 0);

/*
 * Check if ignored packets should be dropped.
 */
	dropIgnoredIncoming = PacketAliasSetMode (0, 0);
	dropIgnoredIncoming &= PKT_ALIAS_DENY_INCOMING;
/*
 * Create divert sockets. Use only one socket if -p was specified
 * on command line. Otherwise, create separate sockets for
 * outgoing and incoming connnections.
 */
	if (inOutPort) {

		divertInOut = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
		if (divertInOut == -1)
			Quit ("Unable to create divert socket.");

		divertIn  = -1;
		divertOut = -1;
/*
 * Bind socket.
 */

		addr.sin_family		= AF_INET;
		addr.sin_addr.s_addr	= INADDR_ANY;
		addr.sin_port		= inOutPort;

		if (bind (divertInOut,
			  (struct sockaddr*) &addr,
			  sizeof addr) == -1)
			Quit ("Unable to bind divert socket.");
	}
	else {

		divertIn = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
		if (divertIn == -1)
			Quit ("Unable to create incoming divert socket.");

		divertOut = socket (PF_INET, SOCK_RAW, IPPROTO_DIVERT);
		if (divertOut == -1)
			Quit ("Unable to create outgoing divert socket.");

		divertInOut = -1;

/*
 * Bind divert sockets.
 */

		addr.sin_family		= AF_INET;
		addr.sin_addr.s_addr	= INADDR_ANY;
		addr.sin_port		= inPort;

		if (bind (divertIn,
			  (struct sockaddr*) &addr,
			  sizeof addr) == -1)
			Quit ("Unable to bind incoming divert socket.");

		addr.sin_family		= AF_INET;
		addr.sin_addr.s_addr	= INADDR_ANY;
		addr.sin_port		= outPort;

		if (bind (divertOut,
			  (struct sockaddr*) &addr,
			  sizeof addr) == -1)
			Quit ("Unable to bind outgoing divert socket.");
	}
/*
 * Create routing socket if interface name specified.
 */
	if (ifName && dynamicMode) {

		routeSock = socket (PF_ROUTE, SOCK_RAW, 0);
		if (routeSock == -1)
			Quit ("Unable to create routing info socket.");
	}
	else
		routeSock = -1;
/*
 * Create socket for sending ICMP messages.
 */
	icmpSock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (icmpSock == -1)
		Quit ("Unable to create ICMP socket.");

/*
 * And disable reads for the socket, otherwise it slowly fills
 * up with received icmps which we do not use.
 */
	shutdown(icmpSock, SHUT_RD);

/*
 * Become a daemon unless verbose mode was requested.
 */
	if (!verbose)
		DaemonMode ();
/*
 * Catch signals to manage shutdown and
 * refresh of interface address.
 */
	signal (SIGTERM, InitiateShutdown);
	signal (SIGHUP, RefreshAddr);
/*
 * Set alias address if it has been given.
 */
	if (aliasAddr.s_addr != INADDR_NONE)
		PacketAliasSetAddress (aliasAddr);
/*
 * We need largest descriptor number for select.
 */

	fdMax = -1;

	if (divertIn > fdMax)
		fdMax = divertIn;

	if (divertOut > fdMax)
		fdMax = divertOut;

	if (divertInOut > fdMax)
		fdMax = divertInOut;

	if (routeSock > fdMax)
		fdMax = routeSock;

	while (running) {

		if (divertInOut != -1 && !ifName && packetSock == -1) {
/*
 * When using only one socket, just call 
 * DoAliasing repeatedly to process packets.
 */
			DoAliasing (divertInOut, DONT_KNOW);
			continue;
		}
/* 
 * Build read mask from socket descriptors to select.
 */
		FD_ZERO (&readMask);
		FD_ZERO (&writeMask);

/*
 * If there is unsent packet in buffer, use select
 * to check when socket comes writable again.
 */
		if (packetSock != -1) {

			FD_SET (packetSock, &writeMask);
		}
		else {
/*
 * No unsent packet exists - safe to check if
 * new ones are available.
 */
			if (divertIn != -1)
				FD_SET (divertIn, &readMask);

			if (divertOut != -1)
				FD_SET (divertOut, &readMask);

			if (divertInOut != -1)
				FD_SET (divertInOut, &readMask);
		}
/*
 * Routing info is processed always.
 */
		if (routeSock != -1)
			FD_SET (routeSock, &readMask);

		if (select (fdMax + 1,
			    &readMask,
			    &writeMask,
			    NULL,
			    NULL) == -1) {

			if (errno == EINTR)
				continue;

			Quit ("Select failed.");
		}

		if (packetSock != -1)
			if (FD_ISSET (packetSock, &writeMask))
				FlushPacketBuffer (packetSock);

		if (divertIn != -1)
			if (FD_ISSET (divertIn, &readMask))
				DoAliasing (divertIn, INPUT);

		if (divertOut != -1)
			if (FD_ISSET (divertOut, &readMask))
				DoAliasing (divertOut, OUTPUT);

		if (divertInOut != -1) 
			if (FD_ISSET (divertInOut, &readMask))
				DoAliasing (divertInOut, DONT_KNOW);

		if (routeSock != -1)
			if (FD_ISSET (routeSock, &readMask))
				HandleRoutingInfo (routeSock);
	}

	if (background)
		unlink (PIDFILE);

	return 0;
}

static void DaemonMode ()
{
	FILE*	pidFile;

	daemon (0, 0);
	background = 1;

	pidFile = fopen (PIDFILE, "w");
	if (pidFile) {

		fprintf (pidFile, "%d\n", getpid ());
		fclose (pidFile);
	}
}

static void ParseArgs (int argc, char** argv)
{
	int		arg;
	char*		parm;
	char*		opt;
	char		parmBuf[256];

	for (arg = 1; arg < argc; arg++) {

		opt  = argv[arg];
		if (*opt != '-') {

			warnx ("invalid option %s", opt);
			Usage ();
		}

		parm = NULL;
		parmBuf[0] = '\0';

		while (arg < argc - 1) {

			if (argv[arg + 1][0] == '-')
				break;

			if (parm)
				strcat (parmBuf, " ");

			++arg;
			parm = parmBuf;
			strcat (parmBuf, argv[arg]);
		}

		ParseOption (opt + 1, parm, 1);
	}
}

static void DoAliasing (int fd, int direction)
{
	int			bytes;
	int			origBytes;
	int			status;
	int			addrSize;
	struct ip*		ip;

	if (assignAliasAddr) {

		SetAliasAddressFromIfName (ifName);
		assignAliasAddr = 0;
	}
/*
 * Get packet from socket.
 */
	addrSize  = sizeof packetAddr;
	origBytes = recvfrom (fd,
			      packetBuf,
			      sizeof packetBuf,
			      0,
			      (struct sockaddr*) &packetAddr,
			      &addrSize);

	if (origBytes == -1) {

		if (errno != EINTR)
			Warn ("read from divert socket failed");

		return;
	}
/*
 * This is a IP packet.
 */
	ip = (struct ip*) packetBuf;
	if (direction == DONT_KNOW)
		if (packetAddr.sin_addr.s_addr == INADDR_ANY)
			direction = OUTPUT;
		else
			direction = INPUT;

	if (verbose) {
		
/*
 * Print packet direction and protocol type.
 */
		printf (direction == OUTPUT ? "Out " : "In  ");

		switch (ip->ip_p) {
		case IPPROTO_TCP:
			printf ("[TCP]  ");
			break;

		case IPPROTO_UDP:
			printf ("[UDP]  ");
			break;

		case IPPROTO_ICMP:
			printf ("[ICMP] ");
			break;

		default:
			printf ("[%d]    ", ip->ip_p);
			break;
		}
/*
 * Print addresses.
 */
		PrintPacket (ip);
	}

	if (direction == OUTPUT) {
/*
 * Outgoing packets. Do aliasing.
 */
		PacketAliasOut (packetBuf, IP_MAXPACKET);
	}
	else {

/*
 * Do aliasing.
 */	
		status = PacketAliasIn (packetBuf, IP_MAXPACKET);
		if (status == PKT_ALIAS_IGNORED &&
		    dropIgnoredIncoming) {

			if (verbose)
				printf (" dropped.\n");

			if (logDropped)
				SyslogPacket (ip, LOG_WARNING, "denied");

			return;
		}
	}
/*
 * Length might have changed during aliasing.
 */
	bytes = ntohs (ip->ip_len);
/*
 * Update alias overhead size for outgoing packets.
 */
	if (direction == OUTPUT &&
	    bytes - origBytes > aliasOverhead)
		aliasOverhead = bytes - origBytes;

	if (verbose) {
		
/*
 * Print addresses after aliasing.
 */
		printf (" aliased to\n");
		printf ("           ");
		PrintPacket (ip);
		printf ("\n");
	}

	packetLen  	= bytes;
	packetSock 	= fd;
	packetDirection = direction;

	FlushPacketBuffer (fd);
}

static void FlushPacketBuffer (int fd)
{
	int			wrote;
	char			msgBuf[80];
/*
 * Put packet back for processing.
 */
	wrote = sendto (fd, 
		        packetBuf,
	    		packetLen,
	    		0,
	    		(struct sockaddr*) &packetAddr,
	    		sizeof packetAddr);
	
	if (wrote != packetLen) {
/*
 * If buffer space is not available,
 * just return. Main loop will take care of 
 * retrying send when space becomes available.
 */
		if (errno == ENOBUFS)
			return;

		if (errno == EMSGSIZE) {

			if (packetDirection == OUTPUT &&
			    ifMTU != -1)
				SendNeedFragIcmp (icmpSock,
						  (struct ip*) packetBuf,
						  ifMTU - aliasOverhead);
		}
		else {

			sprintf (msgBuf, "failed to write packet back");
			Warn (msgBuf);
		}
	}

	packetSock = -1;
}

static void HandleRoutingInfo (int fd)
{
	int			bytes;
	struct if_msghdr	ifMsg;
/*
 * Get packet from socket.
 */
	bytes = read (fd, &ifMsg, sizeof ifMsg);
	if (bytes == -1) {

		Warn ("read from routing socket failed");
		return;
	}

	if (ifMsg.ifm_version != RTM_VERSION) {

		Warn ("unexpected packet read from routing socket");
		return;
	}

	if (verbose)
		printf ("Routing message %X received.\n", ifMsg.ifm_type);

	if (ifMsg.ifm_type != RTM_NEWADDR)
		return;

	if (verbose && ifMsg.ifm_index == ifIndex)
		printf ("Interface address has changed.\n");

	if (ifMsg.ifm_index == ifIndex)
		assignAliasAddr = 1;
}

static void PrintPacket (struct ip* ip)
{
	printf ("%s", FormatPacket (ip));
}

static void SyslogPacket (struct ip* ip, int priority, const char *label)
{
	syslog (priority, "%s %s", label, FormatPacket (ip));
}

static char* FormatPacket (struct ip* ip)
{
	static char	buf[256];
	struct tcphdr*	tcphdr;
	struct udphdr*	udphdr;
	struct icmp*	icmphdr;
	char		src[20];
	char		dst[20];

	strcpy (src, inet_ntoa (ip->ip_src));
	strcpy (dst, inet_ntoa (ip->ip_dst));

	switch (ip->ip_p) {
	case IPPROTO_TCP:
		tcphdr = (struct tcphdr*) ((char*) ip + (ip->ip_hl << 2));
		sprintf (buf, "[TCP] %s:%d -> %s:%d",
			      src,
			      ntohs (tcphdr->th_sport),
			      dst,
			      ntohs (tcphdr->th_dport));
		break;

	case IPPROTO_UDP:
		udphdr = (struct udphdr*) ((char*) ip + (ip->ip_hl << 2));
		sprintf (buf, "[UDP] %s:%d -> %s:%d",
			      src,
			      ntohs (udphdr->uh_sport),
			      dst,
			      ntohs (udphdr->uh_dport));
		break;

	case IPPROTO_ICMP:
		icmphdr = (struct icmp*) ((char*) ip + (ip->ip_hl << 2));
		sprintf (buf, "[ICMP] %s -> %s %u(%u)",
			      src,
			      dst,
			      icmphdr->icmp_type,
			      icmphdr->icmp_code);
		break;

	default:
		sprintf (buf, "[%d] %s -> %s ", ip->ip_p, src, dst);
		break;
	}

	return buf;
}

static void SetAliasAddressFromIfName (char* ifn)
{
	struct ifconf		cf;
	struct ifreq		buf[32];
	char			msg[80];
	struct ifreq*		ifPtr;
	int			extra;
	int			helperSock;
	int			bytes;
	struct sockaddr_in*	addr;
	int			found;
	struct ifreq		req;
	char			last[10];
/*
 * Create a dummy socket to access interface information.
 */
	helperSock = socket (AF_INET, SOCK_DGRAM, 0);
	if (helperSock == -1) {

		Quit ("Failed to create helper socket.");
		exit (1);
	}

	cf.ifc_len = sizeof (buf);
	cf.ifc_req = buf;
/*
 * Get interface data.
 */
	if (ioctl (helperSock, SIOCGIFCONF, &cf) == -1) {

		Quit ("Ioctl SIOCGIFCONF failed.");
		exit (1);
	}

	ifIndex	= 0;
	ifPtr	= buf;
	bytes	= cf.ifc_len;
	found   = 0;
	last[0] = '\0';
/*
 * Loop through interfaces until one with
 * given name is found. This is done to
 * find correct interface index for routing
 * message processing.
 */
	while (bytes) {

		if (ifPtr->ifr_addr.sa_family == AF_INET &&
                    !strcmp (ifPtr->ifr_name, ifn)) {

			found = 1;
			break;
		}

		if (strcmp (last, ifPtr->ifr_name)) {

			strcpy (last, ifPtr->ifr_name);
			++ifIndex;
		}

		extra = ifPtr->ifr_addr.sa_len - sizeof (struct sockaddr);

		ifPtr++;
		ifPtr = (struct ifreq*) ((char*) ifPtr + extra);
		bytes -= sizeof (struct ifreq) + extra;
	}

	if (!found) {

		close (helperSock);
		sprintf (msg, "Unknown interface name %s.\n", ifn);
		Quit (msg);
	}
/*
 * Get MTU size.
 */
	strcpy (req.ifr_name, ifn);

	if (ioctl (helperSock, SIOCGIFMTU, &req) == -1)
		Quit ("Cannot get interface mtu size.");

	ifMTU = req.ifr_mtu;
/*
 * Get interface address.
 */
	if (ioctl (helperSock, SIOCGIFADDR, &req) == -1)
		Quit ("Cannot get interface address.");

	addr = (struct sockaddr_in*) &req.ifr_addr;
	PacketAliasSetAddress (addr->sin_addr);
	syslog (LOG_INFO, "Aliasing to %s, mtu %d bytes",
			  inet_ntoa (addr->sin_addr),
			  ifMTU);

	close (helperSock);
}

void Quit (const char* msg)
{
	Warn (msg);
	exit (1);
}

void Warn (const char* msg)
{
	if (background)
		syslog (LOG_ALERT, "%s (%m)", msg);
	else
		warn (msg);
}

static void RefreshAddr (int sig)
{
	signal (SIGHUP, RefreshAddr);
	if (ifName)
		assignAliasAddr = 1;
}

static void InitiateShutdown (int sig)
{
/*
 * Start timer to allow kernel gracefully
 * shutdown existing connections when system
 * is shut down.
 */
	signal (SIGALRM, Shutdown);
	alarm (10);
}

static void Shutdown (int sig)
{
	running = 0;
}

/* 
 * Different options recognized by this program.
 */

enum Option {

	PacketAliasOption,
	Verbose,
	InPort,
	OutPort,
	Port,
	AliasAddress,
	InterfaceName,
	RedirectPort,
	RedirectAddress,
	ConfigFile,
	DynamicMode,
	PptpAlias,
	ProxyRule,
 	LogDenied,
 	LogFacility
};

enum Param {
	
	YesNo,
	Numeric,
	String,
	None,
	Address,
	Service
};

/*
 * Option information structure (used by ParseOption).
 */

struct OptionInfo {
	
	enum Option		type;
	int			packetAliasOpt;
	enum Param		parm;
	const char*		parmDescription;
	const char*		description;
	const char*		name; 
	const char*		shortName;
};

/*
 * Table of known options.
 */

static struct OptionInfo optionTable[] = {

	{ PacketAliasOption,
		PKT_ALIAS_UNREGISTERED_ONLY,
		YesNo,
		"[yes|no]",
		"alias only unregistered addresses",
		"unregistered_only",
		"u" },

	{ PacketAliasOption,
		PKT_ALIAS_LOG,
		YesNo,
		"[yes|no]",
		"enable logging",
		"log",
		"l" },

	{ PacketAliasOption,
		PKT_ALIAS_PROXY_ONLY,
		YesNo,
		"[yes|no]",
		"proxy only",
		"proxy_only",
		NULL },

	{ PacketAliasOption,
		PKT_ALIAS_REVERSE,
		YesNo,
		"[yes|no]",
		"operate in reverse mode",
		"reverse",
		NULL },

	{ PacketAliasOption,
		PKT_ALIAS_DENY_INCOMING,
		YesNo,
		"[yes|no]",
		"allow incoming connections",
		"deny_incoming",
		"d" },

	{ PacketAliasOption,
		PKT_ALIAS_USE_SOCKETS,
		YesNo,
		"[yes|no]",
		"use sockets to inhibit port conflict",
		"use_sockets",
		"s" },

	{ PacketAliasOption,
		PKT_ALIAS_SAME_PORTS,
		YesNo,
		"[yes|no]",
		"try to keep original port numbers for connections",
		"same_ports",
		"m" },

	{ Verbose,
		0,
		YesNo,
		"[yes|no]",
		"verbose mode, dump packet information",
		"verbose",
		"v" },
	
	{ DynamicMode,
		0,
		YesNo,
		"[yes|no]",
		"dynamic mode, automatically detect interface address changes",
		"dynamic",
		NULL },
	
	{ InPort,
		0,
		Service,
		"number|service_name",
		"set port for incoming packets",
		"in_port",
		"i" },
	
	{ OutPort,
		0,
		Service,
		"number|service_name",
		"set port for outgoing packets",
		"out_port",
		"o" },
	
	{ Port,
		0,
		Service,
		"number|service_name",
		"set port (defaults to natd/divert)",
		"port",
		"p" },
	
	{ AliasAddress,
		0,
		Address,
		"x.x.x.x",
		"address to use for aliasing",
		"alias_address",
		"a" },
	
	{ InterfaceName,
		0,
		String,
	        "network_if_name",
		"take aliasing address from interface",
		"interface",
		"n" },

	{ ProxyRule,
		0,
		String,
	        "[type encode_ip_hdr|encode_tcp_stream] port xxxx server "
		"a.b.c.d:yyyy",
		"add transparent proxying / destination NAT",
		"proxy_rule",
		NULL },

	{ RedirectPort,
		0,
		String,
	        "tcp|udp local_addr:local_port_range [public_addr:]public_port_range"
	 	" [remote_addr[:remote_port_range]]",
		"redirect a port (or ports) for incoming traffic",
		"redirect_port",
		NULL },

	{ RedirectAddress,
		0,
		String,
	        "local_addr public_addr",
		"define mapping between local and public addresses",
		"redirect_address",
		NULL },

       { PptpAlias,
		0,
		String,
		"src",
		"define inside machine for PPTP traffic",
		"pptpalias",
		NULL },

	{ ConfigFile,
		0,
		String,
		"file_name",
		"read options from configuration file",
		"config",
		"f" },

	{ LogDenied,
		0,
		YesNo,
	        "[yes|no]",
		"enable logging of denied incoming packets",
		"log_denied",
		NULL },

	{ LogFacility,
		0,
		String,
	        "facility",
		"name of syslog facility to use for logging",
		"log_facility",
		NULL }

};
	
static void ParseOption (const char* option, const char* parms, int cmdLine)
{
	int			i;
	struct OptionInfo*	info;
	int			yesNoValue;
	int			aliasValue;
	int			numValue;
	u_short			uNumValue;
	const char*		strValue;
	struct in_addr		addrValue;
	int			max;
	char*			end;
	CODE* 			fac_record = NULL;
/*
 * Find option from table.
 */
	max = sizeof (optionTable) / sizeof (struct OptionInfo);
	for (i = 0, info = optionTable; i < max; i++, info++) {

		if (!strcmp (info->name, option))
			break;

		if (info->shortName)
			if (!strcmp (info->shortName, option))
				break;
	}

	if (i >= max) {

		warnx ("unknown option %s", option);
		Usage ();
	}

	uNumValue	= 0;
	yesNoValue	= 0;
	numValue	= 0;
	strValue	= NULL;
/*
 * Check parameters.
 */
	switch (info->parm) {
	case YesNo:
		if (!parms)
			parms = "yes";

		if (!strcmp (parms, "yes"))
			yesNoValue = 1;
		else
			if (!strcmp (parms, "no"))
				yesNoValue = 0;
			else
				errx (1, "%s needs yes/no parameter", option);
		break;

	case Service:
		if (!parms)
			errx (1, "%s needs service name or "
				 "port number parameter",
				 option);

		uNumValue = StrToPort (parms, "divert");
		break;

	case Numeric:
		if (parms)
			numValue = strtol (parms, &end, 10);
		else
			end = NULL;

		if (end == parms)
			errx (1, "%s needs numeric parameter", option);
		break;

	case String:
		strValue = parms;
		if (!strValue)
			errx (1, "%s needs parameter", option);
		break;

	case None:
		if (parms)
			errx (1, "%s does not take parameters", option);
		break;

	case Address:
		if (!parms)
			errx (1, "%s needs address/host parameter", option);

		StrToAddr (parms, &addrValue);
		break;
	}

	switch (info->type) {
	case PacketAliasOption:
	
		aliasValue = yesNoValue ? info->packetAliasOpt : 0;
		PacketAliasSetMode (aliasValue, info->packetAliasOpt);
		break;

	case Verbose:
		verbose = yesNoValue;
		break;

	case DynamicMode:
		dynamicMode = yesNoValue;
		break;

	case InPort:
		inPort = uNumValue;
		break;

	case OutPort:
		outPort = uNumValue;
		break;

	case Port:
		inOutPort = uNumValue;
		break;

	case AliasAddress:
		memcpy (&aliasAddr, &addrValue, sizeof (struct in_addr));
		break;

	case RedirectPort:
		SetupPortRedirect (strValue);
		break;

	case RedirectAddress:
		SetupAddressRedirect (strValue);
		break;

	case PptpAlias:
		SetupPptpAlias (strValue);
		break;

	case ProxyRule:
		PacketAliasProxyRule (strValue);
		break;

	case InterfaceName:
		if (ifName)
			free (ifName);

		ifName = strdup (strValue);
		assignAliasAddr = 1;
		break;

	case ConfigFile:
		ReadConfigFile (strValue);
		break;

	case LogDenied:
		logDropped = 1;
		break;

	case LogFacility:

		fac_record = facilitynames;
		while (fac_record->c_name != NULL) {

			if (!strcmp (fac_record->c_name, strValue)) {

				logFacility = fac_record->c_val;
				break;

			}
			else
				fac_record++;
		}

		if(fac_record->c_name == NULL)
			errx(1, "Unknown log facility name: %s", strValue);	

		break;
	}
}

void ReadConfigFile (const char* fileName)
{
	FILE*	file;
	char	buf[128];
	char*	ptr;
	char*	option;

	file = fopen (fileName, "r");
	if (!file) {

		sprintf (buf, "Cannot open config file %s.\n", fileName);
		Quit (buf);
	}

	while (fgets (buf, sizeof (buf), file)) {

		ptr = strchr (buf, '\n');
		if (!ptr)
			errx (1, "config line too long: %s", buf);

		*ptr = '\0';
		if (buf[0] == '#')
			continue;

		ptr = buf;
/*
 * Skip white space at beginning of line.
 */
		while (*ptr && isspace (*ptr))
			++ptr;

		if (*ptr == '\0')
			continue;
/*
 * Extract option name.
 */
		option = ptr;
		while (*ptr && !isspace (*ptr))
			++ptr;

		if (*ptr != '\0') {

			*ptr = '\0';
			++ptr;
		}
/*
 * Skip white space between name and parms.
 */
		while (*ptr && isspace (*ptr))
			++ptr;

		ParseOption (option, *ptr ? ptr : NULL, 0);
	}

	fclose (file);
}

static void Usage ()
{
	int			i;
	int			max;
	struct OptionInfo*	info;

	fprintf (stderr, "Recognized options:\n\n");

	max = sizeof (optionTable) / sizeof (struct OptionInfo);
	for (i = 0, info = optionTable; i < max; i++, info++) {

		fprintf (stderr, "-%-20s %s\n", info->name,
						info->parmDescription);

		if (info->shortName)
			fprintf (stderr, "-%-20s %s\n", info->shortName,
							info->parmDescription);

		fprintf (stderr, "      %s\n\n", info->description);
	}

	exit (1);
}

void SetupPptpAlias (const char* parms)
{
	char		buf[128];
	char*		ptr;
	struct in_addr	srcAddr;

	strcpy (buf, parms);

/*
 * Extract source address.
 */
	ptr = strtok (buf, " \t");
	if (!ptr)
		errx(1, "pptpalias: missing src address");

	StrToAddr (ptr, &srcAddr);
	PacketAliasPptp (srcAddr);
}

void SetupPortRedirect (const char* parms)
{
	char		buf[128];
	char*		ptr;
	struct in_addr	localAddr;
	struct in_addr	publicAddr;
	struct in_addr	remoteAddr;
	port_range      portRange;
	u_short         localPort      = 0;
	u_short         publicPort     = 0;
	u_short         remotePort     = 0;
	u_short         numLocalPorts  = 0;
	u_short         numPublicPorts = 0;
	u_short         numRemotePorts = 0;
	int		proto;
	char*		protoName;
	char*		separator;
	int             i;

	strcpy (buf, parms);
/*
 * Extract protocol.
 */
	protoName = strtok (buf, " \t");
	if (!protoName)
		errx (1, "redirect_port: missing protocol");

	proto = StrToProto (protoName);
/*
 * Extract local address.
 */
	ptr = strtok (NULL, " \t");
	if (!ptr)
		errx (1, "redirect_port: missing local address");

	if ( StrToAddrAndPortRange (ptr, &localAddr, protoName, &portRange) != 0 )
	        errx (1, "redirect_port: invalid local port range");

	localPort     = GETLOPORT(portRange);
	numLocalPorts = GETNUMPORTS(portRange);

/*
 * Extract public port and optinally address.
 */
	ptr = strtok (NULL, " \t");
	if (!ptr)
		errx (1, "redirect_port: missing public port");

	separator = strchr (ptr, ':');
	if (separator) {
	        if (StrToAddrAndPortRange (ptr, &publicAddr, protoName, &portRange) != 0 )
		        errx (1, "redirect_port: invalid public port range");
	}
	else {
		publicAddr.s_addr = INADDR_ANY;
		if (StrToPortRange (ptr, protoName, &portRange) != 0)
		        errx (1, "redirect_port: invalid public port range");
	}

	publicPort     = GETLOPORT(portRange);
	numPublicPorts = GETNUMPORTS(portRange);

/*
 * Extract remote address and optionally port.
 */
	ptr = strtok (NULL, " \t");
	if (ptr) {
		separator = strchr (ptr, ':');
		if (separator)
		        if (StrToAddrAndPortRange (ptr, &remoteAddr, protoName, &portRange) != 0)
			        errx (1, "redirect_port: invalid remote port range");
		else {
		        SETLOPORT(portRange, 0);
			SETNUMPORTS(portRange, 1);
			StrToAddr (ptr, &remoteAddr);
		}
	}
	else {
	        SETLOPORT(portRange, 0);
		SETNUMPORTS(portRange, 1);
		remoteAddr.s_addr = INADDR_ANY;
	}

	remotePort     = GETLOPORT(portRange);
	numRemotePorts = GETNUMPORTS(portRange);

/*
 * Make sure port ranges match up, then add the redirect ports.
 */
	if (numLocalPorts != numPublicPorts)
	        errx (1, "redirect_port: port ranges must be equal in size");

	/* Remote port range is allowed to be '0' which means all ports. */
	if (numRemotePorts != numLocalPorts && numRemotePorts != 1 && remotePort != 0)
	        errx (1, "redirect_port: remote port must be 0 or equal to local port range in size");

	for (i = 0 ; i < numPublicPorts ; ++i) {
	        /* If remotePort is all ports, set it to 0. */
	        u_short remotePortCopy = remotePort + i;
	        if (numRemotePorts == 1 && remotePort == 0)
		        remotePortCopy = 0;

	        PacketAliasRedirectPort (localAddr,
					 htons(localPort + i),
					 remoteAddr,
					 htons(remotePortCopy),
					 publicAddr,
					 htons(publicPort + i),
					 proto);
	}
}

void SetupAddressRedirect (const char* parms)
{
	char		buf[128];
	char*		ptr;
	struct in_addr	localAddr;
	struct in_addr	publicAddr;

	strcpy (buf, parms);
/*
 * Extract local address.
 */
	ptr = strtok (buf, " \t");
	if (!ptr)
		errx (1, "redirect_address: missing local address");

	StrToAddr (ptr, &localAddr);
/*
 * Extract public address.
 */
	ptr = strtok (NULL, " \t");
	if (!ptr)
		errx (1, "redirect_address: missing public address");

	StrToAddr (ptr, &publicAddr);
	PacketAliasRedirectAddr (localAddr, publicAddr);
}

void StrToAddr (const char* str, struct in_addr* addr)
{
	struct hostent* hp;

	if (inet_aton (str, addr))
		return;

	hp = gethostbyname (str);
	if (!hp)
		errx (1, "unknown host %s", str);

	memcpy (addr, hp->h_addr, sizeof (struct in_addr));
}

u_short StrToPort (const char* str, const char* proto)
{
	u_short		port;
	struct servent*	sp;
	char*		end;

	port = strtol (str, &end, 10);
	if (end != str)
		return htons (port);

	sp = getservbyname (str, proto);
	if (!sp)
		errx (1, "unknown service %s/%s", str, proto);

	return sp->s_port;
}

int StrToPortRange (const char* str, const char* proto, port_range *portRange)
{
	char*           sep;
	struct servent*	sp;
	char*		end;
	u_short         loPort;
	u_short         hiPort;
	
	/* First see if this is a service, return corresponding port if so. */
	sp = getservbyname (str,proto);
	if (sp) {
	        SETLOPORT(*portRange, ntohs(sp->s_port));
		SETNUMPORTS(*portRange, 1);
		return 0;
	}
	        
	/* Not a service, see if it's a single port or port range. */
	sep = strchr (str, '-');
	if (sep == NULL) {
	        SETLOPORT(*portRange, strtol(str, &end, 10));
		if (end != str) {
		        /* Single port. */
		        SETNUMPORTS(*portRange, 1);
			return 0;
		}

		/* Error in port range field. */
		errx (1, "unknown service %s/%s", str, proto);
	}

	/* Port range, get the values and sanity check. */
	sscanf (str, "%hu-%hu", &loPort, &hiPort);
	SETLOPORT(*portRange, loPort);
	SETNUMPORTS(*portRange, 0);	/* Error by default */
	if (loPort <= hiPort)
	        SETNUMPORTS(*portRange, hiPort - loPort + 1);

	if (GETNUMPORTS(*portRange) == 0)
	        errx (1, "invalid port range %s", str);

	return 0;
}


int StrToProto (const char* str)
{
	if (!strcmp (str, "tcp"))
		return IPPROTO_TCP;

	if (!strcmp (str, "udp"))
		return IPPROTO_UDP;

	errx (1, "unknown protocol %s. Expected tcp or udp", str);
}

int StrToAddrAndPortRange (const char* str, struct in_addr* addr, char* proto, port_range *portRange)
{
	char*	ptr;

	ptr = strchr (str, ':');
	if (!ptr)
		errx (1, "%s is missing port number", str);

	*ptr = '\0';
	++ptr;

	StrToAddr (str, addr);
	return StrToPortRange (ptr, proto, portRange);
}