#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"
#define DEFAULT_SERVICE "natd"
typedef u_long port_range;
#define GETLOPORT(x) ((x) >> 0x10)
#define GETNUMPORTS(x) ((x) & 0x0000ffff)
#define GETHIPORT(x) (GETLOPORT((x)) + GETNUMPORTS((x)))
#define SETLOPORT(x,y) ((x) = ((x) & 0x0000ffff) | ((y) << 0x10))
#define SETNUMPORTS(x,y) ((x) = ((x) & 0xffff0000) | (y))
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 *);
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;
PacketAliasInit();
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);
if (logIpfwDenied == -1)
logIpfwDenied = verbose;
openlog("natd", LOG_CONS | LOG_PID | (verbose ? LOG_PERROR : 0),
logFacility);
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");
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);
dropIgnoredIncoming = PacketAliasSetMode(0, 0);
dropIgnoredIncoming &= PKT_ALIAS_DENY_INCOMING;
if (inOutPort) {
divertInOut = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT);
if (divertInOut == -1)
Quit("Unable to create divert socket.");
divertIn = -1;
divertOut = -1;
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;
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.");
}
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);
}
icmpSock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (icmpSock == -1)
Quit("Unable to create ICMP socket.");
shutdown(icmpSock, SHUT_RD);
if (!verbose)
DaemonMode();
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);
if (aliasAddr.s_addr != INADDR_NONE)
PacketAliasSetAddress(aliasAddr);
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) {
DoAliasing(divertInOut, DONT_KNOW);
continue;
}
FD_ZERO(&readMask);
if (divertIn != -1)
FD_SET(divertIn, &readMask);
if (divertOut != -1)
FD_SET(divertOut, &readMask);
if (divertInOut != -1)
FD_SET(divertInOut, &readMask);
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;
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;
}
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;
}
ip = (struct ip *)buf;
if (direction == DONT_KNOW) {
if (addr.sin_addr.s_addr == INADDR_ANY)
direction = OUTPUT;
else
direction = INPUT;
}
if (verbose) {
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;
}
PrintPacket(ip);
}
if (direction == OUTPUT) {
PacketAliasOut(buf, IP_MAXPACKET);
} else {
status = PacketAliasIn(buf, IP_MAXPACKET);
if (status == PKT_ALIAS_IGNORED &&
dropIgnoredIncoming) {
if (verbose)
printf(" dropped.\n");
if (logDropped)
SyslogPacket(ip, LOG_WARNING, "denied");
return;
}
}
bytes = ntohs(ip->ip_len);
if (direction == OUTPUT &&
bytes - origBytes > aliasOverhead)
aliasOverhead = bytes - origBytes;
if (verbose) {
printf(" aliased to\n");
printf(" ");
PrintPacket(ip);
printf("\n");
}
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;
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;
mib[4] = NET_RT_IFLIST;
mib[5] = 0;
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;
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);
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;
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;
}
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
};
struct OptionInfo {
enum Option type;
int packetAliasOpt;
enum Param parm;
const char* parmDescription;
const char* description;
const char* name;
const char* shortName;
};
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;
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;
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");
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';
option = ptr;
while (*ptr && !isspace(*ptr))
++ptr;
if (*ptr != '\0') {
*ptr = '\0';
++ptr;
}
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);
protoName = strtok(buf, " \t");
if (!protoName)
errx(1, "redirect_port: missing protocol");
proto = StrToProto(protoName);
ptr = strtok(NULL, " \t");
if (!ptr)
errx(1, "redirect_port: missing local address");
separator = strchr(ptr, ',');
if (separator) {
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;
}
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);
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);
if (numLocalPorts != numPublicPorts)
errx(1, "redirect_port: port ranges must be equal in size");
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) {
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);
}
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);
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;
ptr = strtok(NULL, " \t");
if (!ptr)
errx(1, "redirect_proto: missing local address");
else
StrToAddr(ptr, &localAddr);
ptr = strtok(NULL, " \t");
if (ptr)
StrToAddr(ptr, &publicAddr);
else
publicAddr.s_addr = INADDR_ANY;
ptr = strtok(NULL, " \t");
if (ptr)
StrToAddr(ptr, &remoteAddr);
else
remoteAddr.s_addr = INADDR_ANY;
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);
ptr = strtok(buf, " \t");
if (!ptr)
errx(1, "redirect_address: missing local address");
separator = strchr(ptr, ',');
if (separator) {
localAddr.s_addr = INADDR_NONE;
serverPool = ptr;
} else {
StrToAddr(ptr, &localAddr);
serverPool = NULL;
}
ptr = strtok(NULL, " \t");
if (!ptr)
errx(1, "redirect_address: missing public address");
StrToAddr(ptr, &publicAddr);
alink = PacketAliasRedirectAddr(localAddr, publicAddr);
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;
sp = getservbyname(str,proto);
if (sp) {
SETLOPORT(*portRange, ntohs(sp->s_port));
SETNUMPORTS(*portRange, 1);
return 0;
}
sep = strchr(str, '-');
if (sep == NULL) {
SETLOPORT(*portRange, strtol(str, &end, 10));
if (end != str) {
SETNUMPORTS(*portRange, 1);
return 0;
}
errx(1, "unknown service %s/%s", str, proto);
}
sscanf(str, "%hu-%hu", &loPort, &hiPort);
SETLOPORT(*portRange, loPort);
SETNUMPORTS(*portRange, 0);
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);
}