/* * 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 * * $FreeBSD: src/sbin/natd/natd.c,v 1.25.2.5 2002/02/01 09:18:32 ru Exp $ * $DragonFly: src/sbin/natd/natd.c,v 1.11 2006/08/03 16:40:46 swildner Exp $ */ #define SYSLOG_NAMES #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; 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; 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_handler = InitiateShutdown; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); 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); #define ROUNDUP(a) \ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len)) for (i = 1; i < RTA_IFA; i <<= 1) if (ifam->ifam_addrs & i) 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); alarm(10); } 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 }; 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 }, }; 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 = 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 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; } } 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 = 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 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); }