root/sbin/natd/natd.c
/*
 * 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>
 *
 * $FreeBSD: src/sbin/natd/natd.c,v 1.25.2.5 2002/02/01 09:18:32 ru Exp $
 */

#define SYSLOG_NAMES

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sysctl.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 <net/if.h>
#include <net/if_dl.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, int);
static void     DaemonMode(void);
static void     HandleRoutingInfo(int);
static void     Usage(void);
static char*    FormatPacket(struct ip *);
static void     PrintPacket(struct ip *);
static void     SyslogPacket(struct ip *, int, const char *);
static void     SetAliasAddressFromIfName(const char *);
static void     InitiateShutdown(int);
static void     Shutdown(int);
static void     RefreshAddr(int);
static void     ParseOption(const char *, const char *);
static void     ReadConfigFile(const char *);
static void     SetupPortRedirect(const char *);
static void     SetupProtoRedirect(const char *);
static void     SetupAddressRedirect(const char *);
static void     StrToAddr(const char *, struct in_addr *);
static u_short  StrToPort(const char *, const char *);
static int      StrToPortRange(const char *, const char *, port_range *);
static int      StrToProto(const char *);
static int      StrToAddrAndPortRange(const char *, struct in_addr *, char *, port_range *);
static void     ParseArgs(int, char **);
static void     SetupPunchFW(const char *);

/*
 * Globals.
 */

static  int                     verbose;
static  int                     background;
static  volatile sig_atomic_t   running;
static  volatile sig_atomic_t   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  int                     dropIgnoredIncoming;
static  int                     logDropped;
static  int                     logFacility;
static  int                     logIpfwDenied;
static  int                     exitDelay;

int
main(int argc, char **argv)
{
        int                     divertIn;
        int                     divertOut;
        int                     divertInOut;
        int                     routeSock;
        struct sockaddr_in      addr;
        fd_set                  readMask;
        int                     fdMax;
        struct sigaction        sa;
/*
 * 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;
        logIpfwDenied           = -1;
        exitDelay               = EXIT_DELAY;

        ParseArgs(argc, argv);
/*
 * Log ipfw(8) denied packets by default in verbose mode.
 */
        if (logIpfwDenied == -1)
                logIpfwDenied = verbose;
/*
 * Open syslog channel.
 */
        openlog("natd", LOG_CONS | LOG_PID | (verbose ? LOG_PERROR : 0),
                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);

/*
 * 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 and in dynamic mode.
 */
        routeSock = -1;
        if (ifName) {
                if (dynamicMode) {
                        routeSock = socket(PF_ROUTE, SOCK_RAW, 0);
                        if (routeSock == -1)
                                Quit("Unable to create routing info socket.");

                        assignAliasAddr = 1;
                } else
                        SetAliasAddressFromIfName(ifName);
        }
/*
 * 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.
 */
        sa.sa_flags = 0;
        sigemptyset(&sa.sa_mask);
        if (exitDelay)
                sa.sa_handler = InitiateShutdown;
        else
                sa.sa_handler = Shutdown;
        sigaction(SIGTERM, &sa, NULL);
        sa.sa_handler = RefreshAddr;
        sigaction(SIGHUP, &sa, NULL);
/*
 * 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) {
/*
 * 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);
/*
 * Check if new packets 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,
                           NULL,
                           NULL,
                           NULL) == -1) {
                        if (errno == EINTR)
                                continue;

                        Quit("Select failed.");
                }

                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(void)
{
        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*           opt;
        char            parmBuf[256];
        int             len; /* bounds checking */

        for (arg = 1; arg < argc; arg++) {
                opt  = argv[arg];
                if (*opt != '-') {
                        warnx("invalid option %s", opt);
                        Usage();
                }

                parmBuf[0] = '\0';
                len = 0;

                while (arg < argc - 1) {
                        if (argv[arg + 1][0] == '-')
                                break;

                        if (len) {
                                strncat(parmBuf, " ", sizeof(parmBuf) - (len + 1));
                                len += strlen(parmBuf + len);
                        }

                        ++arg;
                        strncat(parmBuf, argv[arg], sizeof(parmBuf) - (len + 1));
                        len += strlen(parmBuf + len);

                }

                ParseOption(opt + 1, (len ? parmBuf : NULL));

        }
}

static void
DoAliasing(int fd, int direction)
{
        int                     bytes;
        int                     origBytes;
        char                    buf[IP_MAXPACKET];
        struct sockaddr_in      addr;
        int                     wrote;
        int                     status;
        int                     addrSize;
        struct ip*              ip;
        char                    msgBuf[80];

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

        if (origBytes == -1) {
                if (errno != EINTR)
                        Warn("read from divert socket failed");

                return;
        }
/*
 * This is a IP packet.
 */
        ip = (struct ip *)buf;
        if (direction == DONT_KNOW) {
                if (addr.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(buf, IP_MAXPACKET);
        } else {
/*
 * Do aliasing.
 */
                status = PacketAliasIn(buf, 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");
        }

/*
 * Put packet back for processing.
 */
        wrote = sendto(fd,
                       buf,
                       bytes,
                       0,
                       (struct sockaddr *)&addr,
                       sizeof addr);

        if (wrote != bytes) {
                if (errno == EMSGSIZE) {
                        if (direction == OUTPUT &&
                            ifMTU != -1)
                                SendNeedFragIcmp(icmpSock,
                                                 (struct ip *)buf,
                                                 ifMTU - aliasOverhead);
                } else if (errno == EACCES && logIpfwDenied) {
                        sprintf(msgBuf, "failed to write packet back");
                        Warn(msgBuf);
                }
        }
}

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 || ifMsg.ifm_type == RTM_IFINFO) &&
            ifMsg.ifm_index == ifIndex) {
                if (verbose)
                        printf("Interface address/MTU has probably changed.\n");
                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(const char *ifn)
{
        size_t needed;
        int mib[6];
        char *buf, *lim, *next;
        struct if_msghdr *ifm;
        struct ifa_msghdr *ifam;
        struct sockaddr_dl *s_dl;
        struct sockaddr_in *s_in;

        mib[0] = CTL_NET;
        mib[1] = PF_ROUTE;
        mib[2] = 0;
        mib[3] = AF_INET;       /* Only IP addresses please */
        mib[4] = NET_RT_IFLIST;
        mib[5] = 0;             /* ifIndex??? */
/*
 * Get interface data.
 */
        if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
                err(1, "iflist-sysctl-estimate");
        if ((buf = malloc(needed)) == NULL)
                errx(1, "malloc failed");
        if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1)
                err(1, "iflist-sysctl-get");
        lim = buf + needed;
/*
 * Loop through interfaces until one with
 * given name is found. This is done to
 * find correct interface index for routing
 * message processing.
 */
        ifIndex = 0;
        next = buf;
        while (next < lim) {
                ifm = (struct if_msghdr *)next;
                next += ifm->ifm_msglen;
                if (ifm->ifm_version != RTM_VERSION) {
                        if (verbose)
                                warnx("routing message version %d "
                                      "not understood", ifm->ifm_version);
                        continue;
                }
                if (ifm->ifm_type == RTM_IFINFO) {
                        s_dl = (struct sockaddr_dl *)(ifm + 1);
                        if (strlen(ifn) == s_dl->sdl_nlen &&
                            strncmp(ifn, s_dl->sdl_data, s_dl->sdl_nlen) == 0) {
                                ifIndex = ifm->ifm_index;
                                ifMTU = ifm->ifm_data.ifi_mtu;
                                break;
                        }
                }
        }
        if (!ifIndex)
                errx(1, "unknown interface name %s", ifn);
/*
 * Get interface address.
 */
        s_in = NULL;
        while (next < lim) {
                ifam = (struct ifa_msghdr *)next;
                next += ifam->ifam_msglen;
                if (ifam->ifam_version != RTM_VERSION) {
                        if (verbose)
                                warnx("routing message version %d "
                                      "not understood", ifam->ifam_version);
                        continue;
                }
                if (ifam->ifam_type != RTM_NEWADDR)
                        break;
                if (ifam->ifam_addrs & RTA_IFA) {
                        int i;
                        char *cp = (char *)(ifam + 1);

                        for (i = 1; i < RTA_IFA; i <<= 1)
                                if (ifam->ifam_addrs & i)
                                        RT_ADVANCE(cp, (struct sockaddr *)cp);
                        if (((struct sockaddr *)cp)->sa_family == AF_INET) {
                                s_in = (struct sockaddr_in *)cp;
                                break;
                        }
                }
        }
        if (s_in == NULL)
                errx(1, "%s: cannot get interface address", ifn);

        PacketAliasSetAddress(s_in->sin_addr);
        syslog(LOG_INFO, "Aliasing to %s, mtu %d bytes",
               inet_ntoa(s_in->sin_addr), ifMTU);

        free(buf);
}

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

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

static void
RefreshAddr(int sig __unused)
{
        if (ifName)
                assignAliasAddr = 1;
}

static void
InitiateShutdown(int sig __unused)
{
        struct sigaction sa;
/*
 * Start timer to allow kernel gracefully
 * shutdown existing connections when system
 * is shut down.
 */
        sa.sa_handler = Shutdown;
        sa.sa_flags = 0;
        sigemptyset(&sa.sa_mask);
        sigaction(SIGALRM, &sa, NULL);
        ualarm(exitDelay*1000, 1000);
}

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

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

enum Option {
        PacketAliasOption,
        Verbose,
        InPort,
        OutPort,
        Port,
        AliasAddress,
        TargetAddress,
        InterfaceName,
        RedirectPort,
        RedirectProto,
        RedirectAddress,
        ConfigFile,
        DynamicMode,
        ProxyRule,
        LogDenied,
        LogFacility,
        PunchFW,
        LogIpfwDenied,
        ExitDelay
};

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" },

        { TargetAddress,
                0,
                Address,
                "x.x.x.x",
                "address to use for incoming sessions",
                "target_address",
                "t" },

        { 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 },

        { RedirectProto,
                0,
                String,
                "proto local_addr [public_addr] [remote_addr]",
                "redirect packets of a given proto",
                "redirect_proto",
                NULL },

        { RedirectAddress,
                0,
                String,
                "local_addr[,...] public_addr",
                "define mapping between local and public addresses",
                "redirect_address",
                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 },

        { PunchFW,
                0,
                String,
                "basenumber:count",
                "punch holes in the firewall for incoming FTP/IRC DCC connections",
                "punch_fw",
                NULL },

        { LogIpfwDenied,
                0,
                YesNo,
                "[yes|no]",
                "log packets converted by natd, but denied by ipfw",
                "log_ipfw_denied",
                NULL },
        { ExitDelay,
                0,
                Numeric,
                "ms",
                "delay in ms before daemon exit after signal",
                "exit_delay",
                NULL },
};

static void
ParseOption(const char *option, const char *parms)
{
        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 = NELEM(optionTable);
        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 TargetAddress:
                PacketAliasSetTarget(addrValue);
                break;

        case RedirectPort:
                SetupPortRedirect(strValue);
                break;

        case RedirectProto:
                SetupProtoRedirect(strValue);
                break;

        case RedirectAddress:
                SetupAddressRedirect(strValue);
                break;

        case ProxyRule:
                PacketAliasProxyRule(strValue);
                break;

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

                ifName = strdup(strValue);
                break;

        case ConfigFile:
                ReadConfigFile(strValue);
                break;

        case LogDenied:
                logDropped = yesNoValue;
                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;

        case PunchFW:
                SetupPunchFW(strValue);
                break;

        case LogIpfwDenied:
                logIpfwDenied = yesNoValue;
                break;
        case ExitDelay:
                if (numValue < 0 || numValue > MAX_EXIT_DELAY)
                        errx(1, "Incorrect exit delay: %d", numValue);
                exitDelay = numValue;
                break;
        }
}

void
ReadConfigFile(const char *fileName)
{
        FILE*   file;
        char    *buf;
        size_t  len;
        char    *ptr, *p;
        char*   option;

        file = fopen(fileName, "r");
        if (!file)
                err(1, "cannot open config file %s", fileName);

        while ((buf = fgetln(file, &len)) != NULL) {
                if (buf[len - 1] == '\n')
                        buf[len - 1] = '\0';
                else
                        errx(1, "config file format error: "
                                "last line should end with newline");

/*
 * Check for comments, strip off trailing spaces.
 */
                if ((ptr = strchr(buf, '#')))
                        *ptr = '\0';
                for (ptr = buf; isspace(*ptr); ++ptr)
                        continue;
                if (*ptr == '\0')
                        continue;
                for (p = strchr(buf, '\0'); isspace(*--p);)
                        continue;
                *++p = '\0';

/*
 * 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);
        }

        fclose(file);
}

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

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

        max = NELEM(optionTable);
        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
SetupPortRedirect(const char *parms)
{
        char            buf[128];
        char*           ptr;
        char*           serverPool;
        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;
        struct alias_link *alink = NULL;

        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");

        separator = strchr(ptr, ',');
        if (separator) {                /* LSNAT redirection syntax. */
                localAddr.s_addr = INADDR_NONE;
                localPort = ~0;
                numLocalPorts = 1;
                serverPool = ptr;
        } else {
                if (StrToAddrAndPortRange(ptr, &localAddr, protoName, &portRange) != 0 )
                        errx(1, "redirect_port: invalid local port range");

                localPort     = GETLOPORT(portRange);
                numLocalPorts = GETNUMPORTS(portRange);
                serverPool = NULL;
        }

/*
 * Extract public port and optionally 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;

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

/*
 * Setup LSNAT server pool.
 */
        if (serverPool != NULL && alink != NULL) {
                ptr = strtok(serverPool, ",");
                while (ptr != NULL) {
                        if (StrToAddrAndPortRange(ptr, &localAddr, protoName, &portRange) != 0)
                                errx(1, "redirect_port: invalid local port range");

                        localPort = GETLOPORT(portRange);
                        if (GETNUMPORTS(portRange) != 1)
                                errx(1, "redirect_port: local port must be single in this context");
                        PacketAliasAddServer(alink, localAddr, htons(localPort));
                        ptr = strtok(NULL, ",");
                }
        }
}

void
SetupProtoRedirect(const char *parms)
{
        char            buf[128];
        char*           ptr;
        struct in_addr  localAddr;
        struct in_addr  publicAddr;
        struct in_addr  remoteAddr;
        int             proto;
        char*           protoName;
        struct protoent *protoent;

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

        protoent = getprotobyname(protoName);
        if (protoent == NULL)
                errx(1, "redirect_proto: unknown protocol %s", protoName);
        else
                proto = protoent->p_proto;
/*
 * Extract local address.
 */
        ptr = strtok(NULL, " \t");
        if (!ptr)
                errx(1, "redirect_proto: missing local address");
        else
                StrToAddr(ptr, &localAddr);
/*
 * Extract optional public address.
 */
        ptr = strtok(NULL, " \t");
        if (ptr)
                StrToAddr(ptr, &publicAddr);
        else
                publicAddr.s_addr = INADDR_ANY;
/*
 * Extract optional remote address.
 */
        ptr = strtok(NULL, " \t");
        if (ptr)
                StrToAddr(ptr, &remoteAddr);
        else
                remoteAddr.s_addr = INADDR_ANY;
/*
 * Create aliasing link.
 */
        PacketAliasRedirectProto(localAddr, remoteAddr, publicAddr, proto);
}

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

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

        separator = strchr(ptr, ',');
        if (separator) {                /* LSNAT redirection syntax. */
                localAddr.s_addr = INADDR_NONE;
                serverPool = ptr;
        } else {
                StrToAddr(ptr, &localAddr);
                serverPool = NULL;
        }
/*
 * Extract public address.
 */
        ptr = strtok(NULL, " \t");
        if (!ptr)
                errx(1, "redirect_address: missing public address");

        StrToAddr(ptr, &publicAddr);
        alink = PacketAliasRedirectAddr(localAddr, publicAddr);

/*
 * Setup LSNAT server pool.
 */
        if (serverPool != NULL && alink != NULL) {
                ptr = strtok(serverPool, ",");
                while (ptr != NULL) {
                        StrToAddr(ptr, &localAddr);
                        PacketAliasAddServer(alink, localAddr, htons(~0));
                        ptr = strtok(NULL, ",");
                }
        }
}

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);
}

static void
SetupPunchFW(const char *strValue)
{
        unsigned int base, num;

        if (sscanf(strValue, "%u:%u", &base, &num) != 2)
                errx(1, "punch_fw: basenumber:count parameter required");

        PacketAliasSetFWBase(base, num);
        PacketAliasSetMode(PKT_ALIAS_PUNCH_FW, PKT_ALIAS_PUNCH_FW);
}