diff --git a/contrib/tcp_wrappers/hosts_access.c b/contrib/tcp_wrappers/hosts_access.c index 05c62d194091..e55f3f34dd20 100644 --- a/contrib/tcp_wrappers/hosts_access.c +++ b/contrib/tcp_wrappers/hosts_access.c @@ -1,479 +1,480 @@ /* * This module implements a simple access control language that is based on * host (or domain) names, NIS (host) netgroup names, IP addresses (or * network numbers) and daemon process names. When a match is found the * search is terminated, and depending on whether PROCESS_OPTIONS is defined, * a list of options is executed or an optional shell command is executed. * * Host and user names are looked up on demand, provided that suitable endpoint * information is available as sockaddr_in structures or TLI netbufs. As a * side effect, the pattern matching process may change the contents of * request structure fields. * * Diagnostics are reported through syslog(3). * * Compile with -DNETGROUP if your library provides support for netgroups. * * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. * * $FreeBSD$ */ #ifndef lint static char sccsid[] = "@(#) hosts_access.c 1.21 97/02/12 02:13:22"; #endif /* System libraries. */ #include #ifdef INT32_T typedef uint32_t u_int32_t; #endif #include #ifdef INET6 #include #endif #include #include #include #include #include #include #include #include #ifdef INET6 #include #endif #include #ifndef INADDR_NONE #define INADDR_NONE (-1) /* XXX should be 0xffffffff */ #endif /* Local stuff. */ #include "tcpd.h" /* Error handling. */ extern jmp_buf tcpd_buf; /* Delimiters for lists of daemons or clients. */ static char sep[] = ", \t\r\n"; /* Constants to be used in assignments only, not in comparisons... */ #define YES 1 #define NO 0 /* * These variables are globally visible so that they can be redirected in * verification mode. */ char *hosts_allow_table = HOSTS_ALLOW; char *hosts_deny_table = HOSTS_DENY; int hosts_access_verbose = 0; /* * In a long-running process, we are not at liberty to just go away. */ int resident = (-1); /* -1, 0: unknown; +1: yes */ /* Forward declarations. */ static int table_match(char *table, struct request_info *request); static int list_match(char *list, struct request_info *request, int (*match_fn)(char *, struct request_info *)); static int server_match(char *tok, struct request_info *request); static int client_match(char *tok, struct request_info *request); static int host_match(char *tok, struct host_info *host); static int string_match(char *tok, char *string); static int masked_match(char *net_tok, char *mask_tok, char *string); #ifdef INET6 static int masked_match4(char *net_tok, char *mask_tok, char *string); static int masked_match6(char *net_tok, char *mask_tok, char *string); #endif /* Size of logical line buffer. */ #define BUFLEN 2048 /* definition to be used from workarounds.c */ #ifdef NETGROUP int yp_get_default_domain(char **); #endif /* hosts_access - host access control facility */ int hosts_access(struct request_info *request) { int verdict; /* * If the (daemon, client) pair is matched by an entry in the file * /etc/hosts.allow, access is granted. Otherwise, if the (daemon, * client) pair is matched by an entry in the file /etc/hosts.deny, * access is denied. Otherwise, access is granted. A non-existent * access-control file is treated as an empty file. * * After a rule has been matched, the optional language extensions may * decide to grant or refuse service anyway. Or, while a rule is being * processed, a serious error is found, and it seems better to play safe * and deny service. All this is done by jumping back into the * hosts_access() routine, bypassing the regular return from the * table_match() function calls below. */ if (resident <= 0) resident++; verdict = setjmp(tcpd_buf); if (verdict != 0) return (verdict == AC_PERMIT); if (table_match(hosts_allow_table, request)) return (YES); if (table_match(hosts_deny_table, request)) return (NO); return (YES); } /* table_match - match table entries with (daemon, client) pair */ static int table_match(char *table, struct request_info *request) { FILE *fp; char sv_list[BUFLEN]; /* becomes list of daemons */ char *cl_list; /* becomes list of clients */ char *sh_cmd; /* becomes optional shell command */ int match = NO; struct tcpd_context saved_context; char *cp; saved_context = tcpd_context; /* stupid compilers */ /* * Between the fopen() and fclose() calls, avoid jumps that may cause * file descriptor leaks. */ if ((fp = fopen(table, "r")) != 0) { tcpd_context.file = table; tcpd_context.line = 0; while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) { if (sv_list[strlen(sv_list) - 1] != '\n') { tcpd_warn("missing newline or line too long"); continue; } /* Ignore anything after unescaped # character */ for (cp = strchr(sv_list, '#'); cp != NULL;) { if (cp > sv_list && cp[-1] == '\\') { cp = strchr(cp + 1, '#'); continue; } *cp = '\0'; break; } if (sv_list[strspn(sv_list, " \t\r\n")] == 0) continue; if ((cl_list = split_at(sv_list, ':')) == 0) { tcpd_warn("missing \":\" separator"); continue; } sh_cmd = split_at(cl_list, ':'); match = list_match(sv_list, request, server_match) && list_match(cl_list, request, client_match); } (void) fclose(fp); } else if (errno != ENOENT) { tcpd_warn("cannot open %s: %m", table); } if (match) { if (hosts_access_verbose > 1) syslog(LOG_DEBUG, "matched: %s line %d", tcpd_context.file, tcpd_context.line); if (sh_cmd) { #ifdef PROCESS_OPTIONS process_options(sh_cmd, request); #else char cmd[BUFSIZ]; shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request)); #endif } } tcpd_context = saved_context; return (match); } /* list_match - match a request against a list of patterns with exceptions */ static int list_match(char *list, struct request_info *request, int (*match_fn)(char *, struct request_info *)) { char *tok; /* * Process tokens one at a time. We have exhausted all possible matches * when we reach an "EXCEPT" token or the end of the list. If we do find * a match, look for an "EXCEPT" list and recurse to determine whether * the match is affected by any exceptions. */ for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { if (STR_EQ(tok, "EXCEPT")) /* EXCEPT: give up */ return (NO); if (match_fn(tok, request)) { /* YES: look for exceptions */ while ((tok = strtok((char *) 0, sep)) && STR_NE(tok, "EXCEPT")) /* VOID */ ; return (tok == 0 || list_match((char *) 0, request, match_fn) == 0); } } return (NO); } /* server_match - match server information */ static int server_match(char *tok, struct request_info *request) { char *host; if ((host = split_at(tok + 1, '@')) == 0) { /* plain daemon */ return (string_match(tok, eval_daemon(request))); } else { /* daemon@host */ return (string_match(tok, eval_daemon(request)) && host_match(host, request->server)); } } /* client_match - match client information */ static int client_match(char *tok, struct request_info *request) { char *host; if ((host = split_at(tok + 1, '@')) == 0) { /* plain host */ return (host_match(tok, request->client)); } else { /* user@host */ return (host_match(host, request->client) && string_match(tok, eval_user(request))); } } /* hostfile_match - look up host patterns from file */ static int hostfile_match(char *path, struct host_info *host) { char tok[BUFSIZ]; int match = NO; FILE *fp; if ((fp = fopen(path, "r")) != 0) { while (fscanf(fp, "%s", tok) == 1 && !(match = host_match(tok, host))) /* void */ ; fclose(fp); } else if (errno != ENOENT) { tcpd_warn("open %s: %m", path); } return (match); } /* host_match - match host name and/or address against pattern */ static int host_match(char *tok, struct host_info *host) { char *mask; /* * This code looks a little hairy because we want to avoid unnecessary * hostname lookups. * * The KNOWN pattern requires that both address AND name be known; some * patterns are specific to host names or to host addresses; all other * patterns are satisfied when either the address OR the name match. */ if (tok[0] == '@') { /* netgroup: look it up */ #ifdef NETGROUP static char *mydomain = 0; if (mydomain == 0) yp_get_default_domain(&mydomain); return (innetgr(tok + 1, eval_hostname(host), (char *) 0, mydomain)); #else tcpd_warn("netgroup support is disabled"); /* not tcpd_jump() */ return (NO); #endif } else if (tok[0] == '/') { /* /file hack */ return (hostfile_match(tok, host)); } else if (STR_EQ(tok, "KNOWN")) { /* check address and name */ char *name = eval_hostname(host); return (STR_NE(eval_hostaddr(host), unknown) && HOSTNAME_KNOWN(name)); } else if (STR_EQ(tok, "LOCAL")) { /* local: no dots in name */ char *name = eval_hostname(host); return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name)); } else if ((mask = split_at(tok, '/')) != 0) { /* net/mask */ return (masked_match(tok, mask, eval_hostaddr(host))); } else { /* anything else */ return (string_match(tok, eval_hostaddr(host)) - || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host)))); + || (NOT_INADDR(tok) && NOT_INADDR6(tok) + && string_match(tok, eval_hostname(host)))); } } /* string_match - match string against pattern */ static int string_match(char *tok, char *string) { int n; #ifdef INET6 /* convert IPv4 mapped IPv6 address to IPv4 address */ if (STRN_EQ(string, "::ffff:", 7) && dot_quad_addr(string + 7) != INADDR_NONE) { string += 7; } #endif if (tok[0] == '.') { /* suffix */ n = strlen(string) - strlen(tok); return (n > 0 && STR_EQ(tok, string + n)); } else if (STR_EQ(tok, "ALL")) { /* all: match any */ return (YES); } else if (STR_EQ(tok, "KNOWN")) { /* not unknown */ return (STR_NE(string, unknown)); } else if (tok[(n = strlen(tok)) - 1] == '.') { /* prefix */ return (STRN_EQ(tok, string, n)); } else { /* exact match */ #ifdef INET6 struct addrinfo hints, *res; struct sockaddr_in6 pat, addr; int len, ret; char ch; len = strlen(tok); if (*tok == '[' && tok[len - 1] == ']') { ch = tok[len - 1]; tok[len - 1] = '\0'; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; if ((ret = getaddrinfo(tok + 1, NULL, &hints, &res)) == 0) { memcpy(&pat, res->ai_addr, sizeof(pat)); freeaddrinfo(res); } tok[len - 1] = ch; if (ret != 0 || getaddrinfo(string, NULL, &hints, &res) != 0) return NO; memcpy(&addr, res->ai_addr, sizeof(addr)); freeaddrinfo(res); if (pat.sin6_scope_id != 0 && addr.sin6_scope_id != pat.sin6_scope_id) return NO; return (!memcmp(&pat.sin6_addr, &addr.sin6_addr, sizeof(struct in6_addr))); return (ret); } #endif return (STR_EQ(tok, string)); } } /* masked_match - match address against netnumber/netmask */ #ifdef INET6 static int masked_match(char *net_tok, char *mask_tok, char *string) { return (masked_match4(net_tok, mask_tok, string) || masked_match6(net_tok, mask_tok, string)); } static int masked_match4(char *net_tok, char *mask_tok, char *string) #else static int masked_match(char *net_tok, char *mask_tok, char *string) #endif { #ifdef INET6 u_int32_t net; u_int32_t mask; u_int32_t addr; #else unsigned long net; unsigned long mask; unsigned long addr; #endif /* * Disallow forms other than dotted quad: the treatment that inet_addr() * gives to forms with less than four components is inconsistent with the * access control language. John P. Rouillard . */ if ((addr = dot_quad_addr(string)) == INADDR_NONE) return (NO); if ((net = dot_quad_addr(net_tok)) == INADDR_NONE || (mask = dot_quad_addr(mask_tok)) == INADDR_NONE) { #ifndef INET6 tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok); #endif return (NO); /* not tcpd_jump() */ } return ((addr & mask) == net); } #ifdef INET6 static int masked_match6(char *net_tok, char *mask_tok, char *string) { struct addrinfo hints, *res; struct sockaddr_in6 net, addr; u_int32_t mask; int len, mask_len, i = 0; char ch; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; if (getaddrinfo(string, NULL, &hints, &res) != 0) return NO; memcpy(&addr, res->ai_addr, sizeof(addr)); freeaddrinfo(res); if (IN6_IS_ADDR_V4MAPPED(&addr.sin6_addr)) { if ((*(u_int32_t *)&net.sin6_addr.s6_addr[12] = dot_quad_addr(net_tok)) == INADDR_NONE || (mask = dot_quad_addr(mask_tok)) == INADDR_NONE) return (NO); return ((*(u_int32_t *)&addr.sin6_addr.s6_addr[12] & mask) == *(u_int32_t *)&net.sin6_addr.s6_addr[12]); } /* match IPv6 address against netnumber/prefixlen */ len = strlen(net_tok); if (*net_tok != '[' || net_tok[len - 1] != ']') return NO; ch = net_tok[len - 1]; net_tok[len - 1] = '\0'; if (getaddrinfo(net_tok + 1, NULL, &hints, &res) != 0) { net_tok[len - 1] = ch; return NO; } memcpy(&net, res->ai_addr, sizeof(net)); freeaddrinfo(res); net_tok[len - 1] = ch; if ((mask_len = atoi(mask_tok)) < 0 || mask_len > 128) return NO; if (net.sin6_scope_id != 0 && addr.sin6_scope_id != net.sin6_scope_id) return NO; while (mask_len > 0) { if (mask_len < 32) { mask = htonl(~(0xffffffff >> mask_len)); if ((*(u_int32_t *)&addr.sin6_addr.s6_addr[i] & mask) != (*(u_int32_t *)&net.sin6_addr.s6_addr[i] & mask)) return NO; break; } if (*(u_int32_t *)&addr.sin6_addr.s6_addr[i] != *(u_int32_t *)&net.sin6_addr.s6_addr[i]) return NO; i += 4; mask_len -= 32; } return YES; } #endif /* INET6 */ diff --git a/contrib/tcp_wrappers/tcpd.h b/contrib/tcp_wrappers/tcpd.h index 1078073c8e3a..194cde378c1c 100644 --- a/contrib/tcp_wrappers/tcpd.h +++ b/contrib/tcp_wrappers/tcpd.h @@ -1,224 +1,225 @@ /* * @(#) tcpd.h 1.5 96/03/19 16:22:24 * * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. * * $FreeBSD$ */ #ifdef INET6 #define TCPD_SOCKADDR struct sockaddr #else #define TCPD_SOCKADDR struct sockaddr_in #endif #ifndef _STDFILE_DECLARED #define _STDFILE_DECLARED typedef struct __sFILE FILE; #endif /* Structure to describe one communications endpoint. */ #define STRING_LENGTH 128 /* hosts, users, processes */ struct host_info { char name[STRING_LENGTH]; /* access via eval_hostname(host) */ char addr[STRING_LENGTH]; /* access via eval_hostaddr(host) */ TCPD_SOCKADDR *sin; /* socket address or 0 */ struct t_unitdata *unit; /* TLI transport address or 0 */ struct request_info *request; /* for shared information */ }; /* Structure to describe what we know about a service request. */ struct request_info { int fd; /* socket handle */ char user[STRING_LENGTH]; /* access via eval_user(request) */ char daemon[STRING_LENGTH]; /* access via eval_daemon(request) */ char pid[10]; /* access via eval_pid(request) */ struct host_info client[1]; /* client endpoint info */ struct host_info server[1]; /* server endpoint info */ void (*sink) (int); /* datagram sink function or 0 */ void (*hostname) (struct host_info *); /* address to printable hostname */ void (*hostaddr) (struct host_info *); /* address to printable address */ void (*cleanup) (struct request_info *); /* cleanup function or 0 */ struct netconfig *config; /* netdir handle */ }; /* Common string operations. Less clutter should be more readable. */ #define STRN_CPY(d,s,l) { strncpy((d),(s),(l)); (d)[(l)-1] = 0; } #define STRN_EQ(x,y,l) (strncasecmp((x),(y),(l)) == 0) #define STRN_NE(x,y,l) (strncasecmp((x),(y),(l)) != 0) #define STR_EQ(x,y) (strcasecmp((x),(y)) == 0) #define STR_NE(x,y) (strcasecmp((x),(y)) != 0) /* * Initially, all above strings have the empty value. Information that * cannot be determined at runtime is set to "unknown", so that we can * distinguish between `unavailable' and `not yet looked up'. A hostname * that we do not believe in is set to "paranoid". */ #define STRING_UNKNOWN "unknown" /* lookup failed */ #define STRING_PARANOID "paranoid" /* hostname conflict */ extern char unknown[]; extern char paranoid[]; #define HOSTNAME_KNOWN(s) (STR_NE((s),unknown) && STR_NE((s),paranoid)) #define NOT_INADDR(s) (s[strspn(s,"01234567890./")] != 0) +#define NOT_INADDR6(s) (strchr(s, ':') == NULL) /* Global functions. */ #if defined(TLI) || defined(PTX) || defined(TLI_SEQUENT) void fromhost(struct request_info *); /* get/validate client host info */ #else #define fromhost sock_host /* no TLI support needed */ #endif int hosts_access(struct request_info *); /* access control */ int hosts_ctl(char *, char *, char *, char *); /* wrapper around request_init() */ void shell_cmd(char *); /* execute shell command */ char *percent_x(char *, int, char *, struct request_info *); /* do % expansion */ void rfc931(TCPD_SOCKADDR *, TCPD_SOCKADDR *, char *); /* client name from RFC 931 daemon */ void clean_exit(struct request_info *); /* clean up and exit */ void refuse(struct request_info *); /* clean up and exit */ char *xgets(char *, int, FILE *); /* fgets() on steroids */ char *split_at(char *, int); /* strchr() and split */ unsigned long dot_quad_addr(char *); /* restricted inet_addr() */ /* Global variables. */ extern int allow_severity; /* for connection logging */ extern int deny_severity; /* for connection logging */ extern char *hosts_allow_table; /* for verification mode redirection */ extern char *hosts_deny_table; /* for verification mode redirection */ extern int hosts_access_verbose; /* for verbose matching mode */ extern int rfc931_timeout; /* user lookup timeout */ extern int resident; /* > 0 if resident process */ /* * Routines for controlled initialization and update of request structure * attributes. Each attribute has its own key. */ struct request_info *request_init(struct request_info *,...); /* initialize request */ struct request_info *request_set(struct request_info *,...); /* update request structure */ #define RQ_FILE 1 /* file descriptor */ #define RQ_DAEMON 2 /* server process (argv[0]) */ #define RQ_USER 3 /* client user name */ #define RQ_CLIENT_NAME 4 /* client host name */ #define RQ_CLIENT_ADDR 5 /* client host address */ #define RQ_CLIENT_SIN 6 /* client endpoint (internal) */ #define RQ_SERVER_NAME 7 /* server host name */ #define RQ_SERVER_ADDR 8 /* server host address */ #define RQ_SERVER_SIN 9 /* server endpoint (internal) */ /* * Routines for delayed evaluation of request attributes. Each attribute * type has its own access method. The trivial ones are implemented by * macros. The other ones are wrappers around the transport-specific host * name, address, and client user lookup methods. The request_info and * host_info structures serve as caches for the lookup results. */ char *eval_user(struct request_info *); /* client user */ char *eval_hostname(struct host_info *); /* printable hostname */ char *eval_hostaddr(struct host_info *); /* printable host address */ char *eval_hostinfo(struct host_info *); /* host name or address */ char *eval_client(struct request_info *); /* whatever is available */ char *eval_server(struct request_info *); /* whatever is available */ #define eval_daemon(r) ((r)->daemon) /* daemon process name */ #define eval_pid(r) ((r)->pid) /* process id */ /* Socket-specific methods, including DNS hostname lookups. */ void sock_host(struct request_info *); /* look up endpoint addresses */ void sock_hostname(struct host_info *); /* translate address to hostname */ void sock_hostaddr(struct host_info *); /* address to printable address */ #define sock_methods(r) \ { (r)->hostname = sock_hostname; (r)->hostaddr = sock_hostaddr; } /* The System V Transport-Level Interface (TLI) interface. */ #if defined(TLI) || defined(PTX) || defined(TLI_SEQUENT) void tli_host(struct request_info *); /* look up endpoint addresses etc. */ #endif /* * Problem reporting interface. Additional file/line context is reported * when available. The jump buffer (tcpd_buf) is not declared here, or * everyone would have to include . */ void tcpd_warn(char *, ...); /* report problem and proceed */ void tcpd_jump(char *, ...); /* report problem and jump */ struct tcpd_context { char *file; /* current file */ int line; /* current line */ }; extern struct tcpd_context tcpd_context; /* * While processing access control rules, error conditions are handled by * jumping back into the hosts_access() routine. This is cleaner than * checking the return value of each and every silly little function. The * (-1) returns are here because zero is already taken by longjmp(). */ #define AC_PERMIT 1 /* permit access */ #define AC_DENY (-1) /* deny_access */ #define AC_ERROR AC_DENY /* XXX */ /* * In verification mode an option function should just say what it would do, * instead of really doing it. An option function that would not return * should clear the dry_run flag to inform the caller of this unusual * behavior. */ void process_options(char *, struct request_info *); /* execute options */ extern int dry_run; /* verification flag */ /* Bug workarounds. */ #ifdef INET_ADDR_BUG /* inet_addr() returns struct */ #define inet_addr fix_inet_addr long fix_inet_addr(char *); #endif #ifdef BROKEN_FGETS /* partial reads from sockets */ #define fgets fix_fgets char *fix_fgets(char *, int, FILE *); #endif #ifdef RECVFROM_BUG /* no address family info */ #define recvfrom fix_recvfrom int fix_recvfrom(int, char *, int, int, struct sockaddr *, int *); #endif #ifdef GETPEERNAME_BUG /* claims success with UDP */ #define getpeername fix_getpeername int fix_getpeername(int, struct sockaddr *, int *); #endif #ifdef SOLARIS_24_GETHOSTBYNAME_BUG /* lists addresses as aliases */ #define gethostbyname fix_gethostbyname struct hostent *fix_gethostbyname(char *); #endif #ifdef USE_STRSEP /* libc calls strtok() */ #define strtok fix_strtok char *fix_strtok(char *, char *); #endif #ifdef LIBC_CALLS_STRTOK /* libc calls strtok() */ #define strtok my_strtok char *my_strtok(char *, char *); #endif