1139749Simp /* 277542Swpaul * This module implements a simple access control language that is based on 377542Swpaul * host (or domain) names, NIS (host) netgroup names, IP addresses (or 477542Swpaul * network numbers) and daemon process names. When a match is found the 577542Swpaul * search is terminated, and depending on whether PROCESS_OPTIONS is defined, 677542Swpaul * a list of options is executed or an optional shell command is executed. 777542Swpaul * 877542Swpaul * Host and user names are looked up on demand, provided that suitable endpoint 977542Swpaul * information is available as sockaddr_in structures or TLI netbufs. As a 1077542Swpaul * side effect, the pattern matching process may change the contents of 1177542Swpaul * request structure fields. 1277542Swpaul * 1377542Swpaul * Diagnostics are reported through syslog(3). 1477542Swpaul * 1577542Swpaul * Compile with -DNETGROUP if your library provides support for netgroups. 1677542Swpaul * 1777542Swpaul * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. 1877542Swpaul * 1977542Swpaul * $FreeBSD: releng/10.3/contrib/tcp_wrappers/hosts_access.c 201782 2010-01-08 10:54:15Z sobomax $ 2077542Swpaul */ 2177542Swpaul 2277542Swpaul#ifndef lint 2377542Swpaulstatic char sccsid[] = "@(#) hosts_access.c 1.21 97/02/12 02:13:22"; 2477542Swpaul#endif 2577542Swpaul 2677542Swpaul/* System libraries. */ 2777542Swpaul 2877542Swpaul#include <sys/types.h> 2977542Swpaul#ifdef INT32_T 3077542Swpaul typedef uint32_t u_int32_t; 3177542Swpaul#endif 3277542Swpaul#include <sys/param.h> 3377542Swpaul#ifdef INET6 34119418Sobrien#include <sys/socket.h> 35119418Sobrien#endif 36119418Sobrien#include <netinet/in.h> 3777542Swpaul#include <arpa/inet.h> 3877542Swpaul#include <stdio.h> 3977542Swpaul#include <syslog.h> 4077542Swpaul#include <ctype.h> 4177542Swpaul#include <errno.h> 4277542Swpaul#include <setjmp.h> 4377542Swpaul#include <string.h> 4477542Swpaul#ifdef INET6 4577542Swpaul#include <netdb.h> 4677542Swpaul#endif 4777542Swpaul 4877542Swpaulextern char *fgets(); 4977542Swpaulextern int errno; 5077542Swpaul 5177542Swpaul#ifndef INADDR_NONE 5277542Swpaul#define INADDR_NONE (-1) /* XXX should be 0xffffffff */ 5377542Swpaul#endif 5477542Swpaul 5577542Swpaul/* Local stuff. */ 5677542Swpaul 5777542Swpaul#include "tcpd.h" 5877542Swpaul 5977542Swpaul/* Error handling. */ 6077542Swpaul 6177542Swpaulextern jmp_buf tcpd_buf; 6277542Swpaul 6377542Swpaul/* Delimiters for lists of daemons or clients. */ 6477542Swpaul 6577542Swpaulstatic char sep[] = ", \t\r\n"; 6677542Swpaul 6777542Swpaul/* Constants to be used in assignments only, not in comparisons... */ 6877542Swpaul 6977542Swpaul#define YES 1 7077542Swpaul#define NO 0 7177542Swpaul 7277542Swpaul /* 7377542Swpaul * These variables are globally visible so that they can be redirected in 7477542Swpaul * verification mode. 7577542Swpaul */ 7677542Swpaul 7777542Swpaulchar *hosts_allow_table = HOSTS_ALLOW; 78129879Sphkchar *hosts_deny_table = HOSTS_DENY; 7977542Swpaulint hosts_access_verbose = 0; 8077542Swpaul 8177542Swpaul /* 8277542Swpaul * In a long-running process, we are not at liberty to just go away. 8377542Swpaul */ 8477542Swpaul 8577542Swpaulint resident = (-1); /* -1, 0: unknown; +1: yes */ 86147256Sbrooks 8777542Swpaul/* Forward declarations. */ 8877542Swpaul 8977542Swpaulstatic int table_match(); 9077542Swpaulstatic int list_match(); 9177542Swpaulstatic int server_match(); 9277542Swpaulstatic int client_match(); 9377542Swpaulstatic int host_match(); 9477542Swpaulstatic int string_match(); 9577542Swpaulstatic int masked_match(); 9677542Swpaul#ifdef INET6 9777542Swpaulstatic int masked_match4(); 9877542Swpaulstatic int masked_match6(); 9977542Swpaul#endif 100119291Simp 101119291Simp/* Size of logical line buffer. */ 10277542Swpaul 10377542Swpaul#define BUFLEN 2048 10477542Swpaul 10577542Swpaul/* hosts_access - host access control facility */ 10677542Swpaul 107151545Simpint hosts_access(request) 10877542Swpaulstruct request_info *request; 10977542Swpaul{ 11077542Swpaul int verdict; 11177542Swpaul 11277542Swpaul /* 113242908Sdim * If the (daemon, client) pair is matched by an entry in the file 11477542Swpaul * /etc/hosts.allow, access is granted. Otherwise, if the (daemon, 11577542Swpaul * client) pair is matched by an entry in the file /etc/hosts.deny, 11677542Swpaul * access is denied. Otherwise, access is granted. A non-existent 11777542Swpaul * access-control file is treated as an empty file. 11899498Salfred * 11999498Salfred * After a rule has been matched, the optional language extensions may 12099498Salfred * decide to grant or refuse service anyway. Or, while a rule is being 12177542Swpaul * processed, a serious error is found, and it seems better to play safe 12299498Salfred * and deny service. All this is done by jumping back into the 12399498Salfred * hosts_access() routine, bypassing the regular return from the 12499498Salfred * table_match() function calls below. 12599498Salfred */ 12677542Swpaul 12799498Salfred if (resident <= 0) 12899498Salfred resident++; 12999498Salfred verdict = setjmp(tcpd_buf); 13099498Salfred if (verdict != 0) 13199498Salfred return (verdict == AC_PERMIT); 13299498Salfred if (table_match(hosts_allow_table, request)) 13399498Salfred return (YES); 13499498Salfred if (table_match(hosts_deny_table, request)) 135152727Sjhb return (NO); 13699498Salfred return (YES); 13799498Salfred} 138152727Sjhb 13999498Salfred/* table_match - match table entries with (daemon, client) pair */ 140199560Sjhb 141173839Syongaristatic int table_match(table, request) 14299498Salfredchar *table; 143152727Sjhbstruct request_info *request; 14499498Salfred{ 14577542Swpaul FILE *fp; 14699498Salfred char sv_list[BUFLEN]; /* becomes list of daemons */ 14799498Salfred char *cl_list; /* becomes list of clients */ 14877542Swpaul char *sh_cmd; /* becomes optional shell command */ 14999498Salfred int match = NO; 15099498Salfred struct tcpd_context saved_context; 15199498Salfred char *cp; 15277542Swpaul 15399498Salfred saved_context = tcpd_context; /* stupid compilers */ 15499498Salfred 15599498Salfred /* 15699498Salfred * Between the fopen() and fclose() calls, avoid jumps that may cause 15777542Swpaul * file descriptor leaks. 15877542Swpaul */ 15977542Swpaul 16077542Swpaul if ((fp = fopen(table, "r")) != 0) { 16177542Swpaul tcpd_context.file = table; 16277542Swpaul tcpd_context.line = 0; 16377542Swpaul while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) { 16477542Swpaul if (sv_list[strlen(sv_list) - 1] != '\n') { 16577542Swpaul tcpd_warn("missing newline or line too long"); 16677542Swpaul continue; 16777542Swpaul } 16877542Swpaul /* Ignore anything after unescaped # character */ 16977542Swpaul for (cp = strchr(sv_list, '#'); cp != NULL;) { 17077542Swpaul if (cp > sv_list && cp[-1] == '\\') { 17177542Swpaul cp = strchr(cp + 1, '#'); 17277542Swpaul continue; 17377542Swpaul } 17477542Swpaul *cp = '\0'; 17577542Swpaul break; 17677542Swpaul } 17777542Swpaul if (sv_list[strspn(sv_list, " \t\r\n")] == 0) 178229093Shselasky continue; 17977542Swpaul if ((cl_list = split_at(sv_list, ':')) == 0) { 18077542Swpaul tcpd_warn("missing \":\" separator"); 18177542Swpaul continue; 18277542Swpaul } 18377542Swpaul sh_cmd = split_at(cl_list, ':'); 18477542Swpaul match = list_match(sv_list, request, server_match) 18577542Swpaul && list_match(cl_list, request, client_match); 18677542Swpaul } 18777542Swpaul (void) fclose(fp); 18877542Swpaul } else if (errno != ENOENT) { 189113506Smdodd tcpd_warn("cannot open %s: %m", table); 19077542Swpaul } 191113506Smdodd if (match) { 192113506Smdodd if (hosts_access_verbose > 1) 193113506Smdodd syslog(LOG_DEBUG, "matched: %s line %d", 19477542Swpaul tcpd_context.file, tcpd_context.line); 19577542Swpaul if (sh_cmd) { 19677542Swpaul#ifdef PROCESS_OPTIONS 19777542Swpaul process_options(sh_cmd, request); 19877542Swpaul#else 19977542Swpaul char cmd[BUFSIZ]; 20077542Swpaul shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request)); 20177542Swpaul#endif 20277542Swpaul } 20377542Swpaul } 20477542Swpaul tcpd_context = saved_context; 20577542Swpaul return (match); 20677542Swpaul} 20777542Swpaul 20877542Swpaul/* list_match - match a request against a list of patterns with exceptions */ 20977542Swpaul 21077542Swpaulstatic int list_match(list, request, match_fn) 21177542Swpaulchar *list; 21299498Salfredstruct request_info *request; 21399498Salfredint (*match_fn) (); 21477542Swpaul{ 21577542Swpaul char *tok; 21677542Swpaul 21777542Swpaul /* 21877542Swpaul * Process tokens one at a time. We have exhausted all possible matches 21977542Swpaul * when we reach an "EXCEPT" token or the end of the list. If we do find 22077542Swpaul * a match, look for an "EXCEPT" list and recurse to determine whether 22177542Swpaul * the match is affected by any exceptions. 22277542Swpaul */ 22377542Swpaul 22477542Swpaul for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { 22577542Swpaul if (STR_EQ(tok, "EXCEPT")) /* EXCEPT: give up */ 22677542Swpaul return (NO); 22777542Swpaul if (match_fn(tok, request)) { /* YES: look for exceptions */ 22877542Swpaul while ((tok = strtok((char *) 0, sep)) && STR_NE(tok, "EXCEPT")) 229162321Sglebius /* VOID */ ; 23077542Swpaul return (tok == 0 || list_match((char *) 0, request, match_fn) == 0); 23177542Swpaul } 23277542Swpaul } 23377542Swpaul return (NO); 23477542Swpaul} 23577542Swpaul 23677542Swpaul/* server_match - match server information */ 23777542Swpaul 23877542Swpaulstatic int server_match(tok, request) 23977542Swpaulchar *tok; 24077542Swpaulstruct request_info *request; 24177542Swpaul{ 24277542Swpaul char *host; 24377542Swpaul 24477542Swpaul if ((host = split_at(tok + 1, '@')) == 0) { /* plain daemon */ 24577542Swpaul return (string_match(tok, eval_daemon(request))); 24699498Salfred } else { /* daemon@host */ 24799498Salfred return (string_match(tok, eval_daemon(request)) 24877542Swpaul && host_match(host, request->server)); 24977542Swpaul } 25077542Swpaul} 25177542Swpaul 25277542Swpaul/* client_match - match client information */ 25377542Swpaul 25477542Swpaulstatic int client_match(tok, request) 25577542Swpaulchar *tok; 25677542Swpaulstruct request_info *request; 25777542Swpaul{ 25877542Swpaul char *host; 25977542Swpaul 26077542Swpaul if ((host = split_at(tok + 1, '@')) == 0) { /* plain host */ 26177542Swpaul return (host_match(tok, request->client)); 26277542Swpaul } else { /* user@host */ 26377542Swpaul return (host_match(host, request->client) 26477542Swpaul && string_match(tok, eval_user(request))); 26577542Swpaul } 26677542Swpaul} 26777542Swpaul 26877542Swpaul/* hostfile_match - look up host patterns from file */ 26999498Salfred 27099498Salfredstatic int hostfile_match(path, host) 27177542Swpaulchar *path; 27277542Swpaulstruct hosts_info *host; 27377542Swpaul{ 27477542Swpaul char tok[BUFSIZ]; 27577542Swpaul int match = NO; 27677542Swpaul FILE *fp; 27777542Swpaul 27877542Swpaul if ((fp = fopen(path, "r")) != 0) { 27977542Swpaul while (fscanf(fp, "%s", tok) == 1 && !(match = host_match(tok, host))) 28077542Swpaul /* void */ ; 28177542Swpaul fclose(fp); 28277542Swpaul } else if (errno != ENOENT) { 28377542Swpaul tcpd_warn("open %s: %m", path); 28477542Swpaul } 28577542Swpaul return (match); 28677542Swpaul} 28777542Swpaul 28877542Swpaul/* host_match - match host name and/or address against pattern */ 28977542Swpaul 29077542Swpaulstatic int host_match(tok, host) 29177542Swpaulchar *tok; 29277542Swpaulstruct host_info *host; 29377542Swpaul{ 294162321Sglebius char *mask; 29577542Swpaul 29677542Swpaul /* 29777542Swpaul * This code looks a little hairy because we want to avoid unnecessary 29877542Swpaul * hostname lookups. 29977542Swpaul * 30077542Swpaul * The KNOWN pattern requires that both address AND name be known; some 30199498Salfred * patterns are specific to host names or to host addresses; all other 30299498Salfred * patterns are satisfied when either the address OR the name match. 30377542Swpaul */ 30477542Swpaul 30577542Swpaul if (tok[0] == '@') { /* netgroup: look it up */ 30677542Swpaul#ifdef NETGROUP 30777542Swpaul static char *mydomain = 0; 30877542Swpaul if (mydomain == 0) 30977542Swpaul yp_get_default_domain(&mydomain); 31077542Swpaul return (innetgr(tok + 1, eval_hostname(host), (char *) 0, mydomain)); 31177542Swpaul#else 31277542Swpaul tcpd_warn("netgroup support is disabled"); /* not tcpd_jump() */ 31377542Swpaul return (NO); 31477542Swpaul#endif 31577542Swpaul } else if (tok[0] == '/') { /* /file hack */ 31677542Swpaul return (hostfile_match(tok, host)); 31777542Swpaul } else if (STR_EQ(tok, "KNOWN")) { /* check address and name */ 31877542Swpaul char *name = eval_hostname(host); 319162321Sglebius return (STR_NE(eval_hostaddr(host), unknown) && HOSTNAME_KNOWN(name)); 32077542Swpaul } else if (STR_EQ(tok, "LOCAL")) { /* local: no dots in name */ 32177542Swpaul char *name = eval_hostname(host); 32277542Swpaul return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name)); 32377542Swpaul } else if ((mask = split_at(tok, '/')) != 0) { /* net/mask */ 32477542Swpaul return (masked_match(tok, mask, eval_hostaddr(host))); 32577542Swpaul } else { /* anything else */ 32699498Salfred return (string_match(tok, eval_hostaddr(host)) 32799498Salfred || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host)))); 32877542Swpaul } 32977542Swpaul} 33077542Swpaul 33177542Swpaul/* string_match - match string against pattern */ 33277542Swpaul 33377542Swpaulstatic int string_match(tok, string) 33477542Swpaulchar *tok; 33577542Swpaulchar *string; 33677542Swpaul{ 33777542Swpaul int n; 33895673Sphk 33977542Swpaul#ifdef INET6 34077542Swpaul /* convert IPv4 mapped IPv6 address to IPv4 address */ 34177542Swpaul if (STRN_EQ(string, "::ffff:", 7) 34277542Swpaul && dot_quad_addr(string + 7) != INADDR_NONE) { 34377542Swpaul string += 7; 34477542Swpaul } 34577542Swpaul#endif 34677542Swpaul if (tok[0] == '.') { /* suffix */ 34777542Swpaul n = strlen(string) - strlen(tok); 34877542Swpaul return (n > 0 && STR_EQ(tok, string + n)); 34977542Swpaul } else if (STR_EQ(tok, "ALL")) { /* all: match any */ 35077542Swpaul return (YES); 35177542Swpaul } else if (STR_EQ(tok, "KNOWN")) { /* not unknown */ 35277542Swpaul return (STR_NE(string, unknown)); 35377542Swpaul } else if (tok[(n = strlen(tok)) - 1] == '.') { /* prefix */ 35477542Swpaul return (STRN_EQ(tok, string, n)); 35577542Swpaul } else { /* exact match */ 35677542Swpaul#ifdef INET6 35777542Swpaul struct addrinfo hints, *res; 35877542Swpaul struct sockaddr_in6 pat, addr; 35977542Swpaul int len, ret; 36077542Swpaul char ch; 36177542Swpaul 36277542Swpaul len = strlen(tok); 36377542Swpaul if (*tok == '[' && tok[len - 1] == ']') { 36477542Swpaul ch = tok[len - 1]; 36577542Swpaul tok[len - 1] = '\0'; 36677542Swpaul memset(&hints, 0, sizeof(hints)); 36799498Salfred hints.ai_family = AF_INET6; 36899498Salfred hints.ai_socktype = SOCK_STREAM; 36977542Swpaul hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; 37077542Swpaul if ((ret = getaddrinfo(tok + 1, NULL, &hints, &res)) == 0) { 37177542Swpaul memcpy(&pat, res->ai_addr, sizeof(pat)); 37277542Swpaul freeaddrinfo(res); 37377542Swpaul } 37477542Swpaul tok[len - 1] = ch; 375147256Sbrooks if (ret != 0 || getaddrinfo(string, NULL, &hints, &res) != 0) 376152727Sjhb return NO; 37777542Swpaul memcpy(&addr, res->ai_addr, sizeof(addr)); 37877542Swpaul freeaddrinfo(res); 37977542Swpaul if (pat.sin6_scope_id != 0 && 38077542Swpaul addr.sin6_scope_id != pat.sin6_scope_id) 38177542Swpaul return NO; 38277542Swpaul return (!memcmp(&pat.sin6_addr, &addr.sin6_addr, 38377542Swpaul sizeof(struct in6_addr))); 38477542Swpaul return (ret); 38577542Swpaul } 38677542Swpaul#endif 38777542Swpaul return (STR_EQ(tok, string)); 38877542Swpaul } 38977542Swpaul} 39077542Swpaul 39177542Swpaul/* masked_match - match address against netnumber/netmask */ 392195049Srwatson 39377542Swpaul#ifdef INET6 39477542Swpaulstatic int masked_match(net_tok, mask_tok, string) 39577542Swpaulchar *net_tok; 396130270Snaddychar *mask_tok; 397130270Snaddychar *string; 39877542Swpaul{ 39977542Swpaul return (masked_match4(net_tok, mask_tok, string) || 40077542Swpaul masked_match6(net_tok, mask_tok, string)); 40177542Swpaul} 40277542Swpaul 403195049Srwatsonstatic int masked_match4(net_tok, mask_tok, string) 40477542Swpaul#else 40577542Swpaulstatic int masked_match(net_tok, mask_tok, string) 40677542Swpaul#endif 40777542Swpaulchar *net_tok; 40877542Swpaulchar *mask_tok; 40977542Swpaulchar *string; 41077542Swpaul{ 41199498Salfred#ifdef INET6 41299498Salfred u_int32_t net; 41377542Swpaul u_int32_t mask; 41477542Swpaul u_int32_t addr; 41577542Swpaul#else 41677542Swpaul unsigned long net; 41777542Swpaul unsigned long mask; 41877542Swpaul unsigned long addr; 41977542Swpaul#endif 42077542Swpaul 42177542Swpaul /* 42277542Swpaul * Disallow forms other than dotted quad: the treatment that inet_addr() 42377542Swpaul * gives to forms with less than four components is inconsistent with the 42477542Swpaul * access control language. John P. Rouillard <rouilj@cs.umb.edu>. 425162321Sglebius */ 42677542Swpaul 42777542Swpaul if ((addr = dot_quad_addr(string)) == INADDR_NONE) 42877542Swpaul return (NO); 42977542Swpaul if ((net = dot_quad_addr(net_tok)) == INADDR_NONE 43077542Swpaul || (mask = dot_quad_addr(mask_tok)) == INADDR_NONE) { 43177542Swpaul#ifndef INET6 43277542Swpaul tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok); 43377542Swpaul#endif 43477542Swpaul return (NO); /* not tcpd_jump() */ 43577542Swpaul } 43677542Swpaul return ((addr & mask) == net); 43799498Salfred} 43899498Salfred 43977542Swpaul#ifdef INET6 44077542Swpaulstatic int masked_match6(net_tok, mask_tok, string) 441230697Smariuschar *net_tok; 44277542Swpaulchar *mask_tok; 44377542Swpaulchar *string; 44477542Swpaul{ 44577542Swpaul struct addrinfo hints, *res; 44677542Swpaul struct sockaddr_in6 net, addr; 44777542Swpaul u_int32_t mask; 44877542Swpaul int len, mask_len, i = 0; 449143160Simp char ch; 45077542Swpaul 45177542Swpaul memset(&hints, 0, sizeof(hints)); 45277542Swpaul hints.ai_family = AF_INET6; 45377542Swpaul hints.ai_socktype = SOCK_STREAM; 45477542Swpaul hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; 45577542Swpaul if (getaddrinfo(string, NULL, &hints, &res) != 0) 45677542Swpaul return NO; 45777542Swpaul memcpy(&addr, res->ai_addr, sizeof(addr)); 45877542Swpaul freeaddrinfo(res); 45977542Swpaul 46077542Swpaul if (IN6_IS_ADDR_V4MAPPED(&addr.sin6_addr)) { 46199498Salfred if ((*(u_int32_t *)&net.sin6_addr.s6_addr[12] = dot_quad_addr(net_tok)) == INADDR_NONE 46299498Salfred || (mask = dot_quad_addr(mask_tok)) == INADDR_NONE) 46377542Swpaul return (NO); 46477542Swpaul return ((*(u_int32_t *)&addr.sin6_addr.s6_addr[12] & mask) == *(u_int32_t *)&net.sin6_addr.s6_addr[12]); 46577542Swpaul } 46677542Swpaul 467150879Sjhb /* match IPv6 address against netnumber/prefixlen */ 468150879Sjhb len = strlen(net_tok); 46977542Swpaul if (*net_tok != '[' || net_tok[len - 1] != ']') 47077542Swpaul return NO; 471162321Sglebius ch = net_tok[len - 1]; 472162321Sglebius net_tok[len - 1] = '\0'; 473152727Sjhb if (getaddrinfo(net_tok + 1, NULL, &hints, &res) != 0) { 474152727Sjhb net_tok[len - 1] = ch; 475152727Sjhb return NO; 476150879Sjhb } 47777542Swpaul memcpy(&net, res->ai_addr, sizeof(net)); 47877542Swpaul freeaddrinfo(res); 47977542Swpaul net_tok[len - 1] = ch; 48077542Swpaul if ((mask_len = atoi(mask_tok)) < 0 || mask_len > 128) 48177542Swpaul return NO; 48277542Swpaul 483127135Snjl if (net.sin6_scope_id != 0 && addr.sin6_scope_id != net.sin6_scope_id) 48477542Swpaul return NO; 48577542Swpaul while (mask_len > 0) { 486150879Sjhb if (mask_len < 32) { 48777542Swpaul mask = htonl(~(0xffffffff >> mask_len)); 48877542Swpaul if ((*(u_int32_t *)&addr.sin6_addr.s6_addr[i] & mask) != (*(u_int32_t *)&net.sin6_addr.s6_addr[i] & mask)) 48977542Swpaul return NO; 49077542Swpaul break; 49177542Swpaul } 49277542Swpaul if (*(u_int32_t *)&addr.sin6_addr.s6_addr[i] != *(u_int32_t *)&net.sin6_addr.s6_addr[i]) 49377542Swpaul return NO; 49477542Swpaul i += 4; 49577542Swpaul mask_len -= 32; 496127135Snjl } 49777542Swpaul return YES; 49877542Swpaul} 49977542Swpaul#endif /* INET6 */ 500150879Sjhb