hosts_access.c revision 51495
144743Smarkm /* 244743Smarkm * This module implements a simple access control language that is based on 344743Smarkm * host (or domain) names, NIS (host) netgroup names, IP addresses (or 444743Smarkm * network numbers) and daemon process names. When a match is found the 544743Smarkm * search is terminated, and depending on whether PROCESS_OPTIONS is defined, 644743Smarkm * a list of options is executed or an optional shell command is executed. 744743Smarkm * 844743Smarkm * Host and user names are looked up on demand, provided that suitable endpoint 944743Smarkm * information is available as sockaddr_in structures or TLI netbufs. As a 1044743Smarkm * side effect, the pattern matching process may change the contents of 1144743Smarkm * request structure fields. 1244743Smarkm * 1344743Smarkm * Diagnostics are reported through syslog(3). 1444743Smarkm * 1544743Smarkm * Compile with -DNETGROUP if your library provides support for netgroups. 1644743Smarkm * 1744743Smarkm * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. 1851495Ssheldonh * 1951495Ssheldonh * $FreeBSD: head/contrib/tcp_wrappers/hosts_access.c 51495 1999-09-21 09:09:57Z sheldonh $ 2044743Smarkm */ 2144743Smarkm 2244743Smarkm#ifndef lint 2344743Smarkmstatic char sccsid[] = "@(#) hosts_access.c 1.21 97/02/12 02:13:22"; 2444743Smarkm#endif 2544743Smarkm 2644743Smarkm/* System libraries. */ 2744743Smarkm 2844743Smarkm#include <sys/types.h> 2944743Smarkm#include <sys/param.h> 3044743Smarkm#include <netinet/in.h> 3144743Smarkm#include <arpa/inet.h> 3244743Smarkm#include <stdio.h> 3344743Smarkm#include <syslog.h> 3444743Smarkm#include <ctype.h> 3544743Smarkm#include <errno.h> 3644743Smarkm#include <setjmp.h> 3744743Smarkm#include <string.h> 3844743Smarkm 3944743Smarkmextern char *fgets(); 4044743Smarkmextern int errno; 4144743Smarkm 4244743Smarkm#ifndef INADDR_NONE 4344743Smarkm#define INADDR_NONE (-1) /* XXX should be 0xffffffff */ 4444743Smarkm#endif 4544743Smarkm 4644743Smarkm/* Local stuff. */ 4744743Smarkm 4844743Smarkm#include "tcpd.h" 4944743Smarkm 5044743Smarkm/* Error handling. */ 5144743Smarkm 5244743Smarkmextern jmp_buf tcpd_buf; 5344743Smarkm 5444743Smarkm/* Delimiters for lists of daemons or clients. */ 5544743Smarkm 5644743Smarkmstatic char sep[] = ", \t\r\n"; 5744743Smarkm 5844743Smarkm/* Constants to be used in assignments only, not in comparisons... */ 5944743Smarkm 6044743Smarkm#define YES 1 6144743Smarkm#define NO 0 6244743Smarkm 6344743Smarkm /* 6444743Smarkm * These variables are globally visible so that they can be redirected in 6544743Smarkm * verification mode. 6644743Smarkm */ 6744743Smarkm 6844743Smarkmchar *hosts_allow_table = HOSTS_ALLOW; 6944743Smarkmchar *hosts_deny_table = HOSTS_DENY; 7044743Smarkmint hosts_access_verbose = 0; 7144743Smarkm 7244743Smarkm /* 7344743Smarkm * In a long-running process, we are not at liberty to just go away. 7444743Smarkm */ 7544743Smarkm 7644743Smarkmint resident = (-1); /* -1, 0: unknown; +1: yes */ 7744743Smarkm 7844743Smarkm/* Forward declarations. */ 7944743Smarkm 8044743Smarkmstatic int table_match(); 8144743Smarkmstatic int list_match(); 8244743Smarkmstatic int server_match(); 8344743Smarkmstatic int client_match(); 8444743Smarkmstatic int host_match(); 8544743Smarkmstatic int string_match(); 8644743Smarkmstatic int masked_match(); 8744743Smarkm 8844743Smarkm/* Size of logical line buffer. */ 8944743Smarkm 9044743Smarkm#define BUFLEN 2048 9144743Smarkm 9244743Smarkm/* hosts_access - host access control facility */ 9344743Smarkm 9444743Smarkmint hosts_access(request) 9544743Smarkmstruct request_info *request; 9644743Smarkm{ 9744743Smarkm int verdict; 9844743Smarkm 9944743Smarkm /* 10044743Smarkm * If the (daemon, client) pair is matched by an entry in the file 10144743Smarkm * /etc/hosts.allow, access is granted. Otherwise, if the (daemon, 10244743Smarkm * client) pair is matched by an entry in the file /etc/hosts.deny, 10344743Smarkm * access is denied. Otherwise, access is granted. A non-existent 10444743Smarkm * access-control file is treated as an empty file. 10544743Smarkm * 10644743Smarkm * After a rule has been matched, the optional language extensions may 10744743Smarkm * decide to grant or refuse service anyway. Or, while a rule is being 10844743Smarkm * processed, a serious error is found, and it seems better to play safe 10944743Smarkm * and deny service. All this is done by jumping back into the 11044743Smarkm * hosts_access() routine, bypassing the regular return from the 11144743Smarkm * table_match() function calls below. 11244743Smarkm */ 11344743Smarkm 11444743Smarkm if (resident <= 0) 11544743Smarkm resident++; 11644743Smarkm verdict = setjmp(tcpd_buf); 11744743Smarkm if (verdict != 0) 11844743Smarkm return (verdict == AC_PERMIT); 11944743Smarkm if (table_match(hosts_allow_table, request)) 12044743Smarkm return (YES); 12144743Smarkm if (table_match(hosts_deny_table, request)) 12244743Smarkm return (NO); 12344743Smarkm return (YES); 12444743Smarkm} 12544743Smarkm 12644743Smarkm/* table_match - match table entries with (daemon, client) pair */ 12744743Smarkm 12844743Smarkmstatic int table_match(table, request) 12944743Smarkmchar *table; 13044743Smarkmstruct request_info *request; 13144743Smarkm{ 13244743Smarkm FILE *fp; 13344743Smarkm char sv_list[BUFLEN]; /* becomes list of daemons */ 13444743Smarkm char *cl_list; /* becomes list of clients */ 13544743Smarkm char *sh_cmd; /* becomes optional shell command */ 13644743Smarkm int match = NO; 13744743Smarkm struct tcpd_context saved_context; 13844743Smarkm 13944743Smarkm saved_context = tcpd_context; /* stupid compilers */ 14044743Smarkm 14144743Smarkm /* 14244743Smarkm * Between the fopen() and fclose() calls, avoid jumps that may cause 14344743Smarkm * file descriptor leaks. 14444743Smarkm */ 14544743Smarkm 14644743Smarkm if ((fp = fopen(table, "r")) != 0) { 14744743Smarkm tcpd_context.file = table; 14844743Smarkm tcpd_context.line = 0; 14944743Smarkm while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) { 15044743Smarkm if (sv_list[strlen(sv_list) - 1] != '\n') { 15144743Smarkm tcpd_warn("missing newline or line too long"); 15244743Smarkm continue; 15344743Smarkm } 15444743Smarkm if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0) 15544743Smarkm continue; 15644743Smarkm if ((cl_list = split_at(sv_list, ':')) == 0) { 15744743Smarkm tcpd_warn("missing \":\" separator"); 15844743Smarkm continue; 15944743Smarkm } 16044743Smarkm sh_cmd = split_at(cl_list, ':'); 16144743Smarkm match = list_match(sv_list, request, server_match) 16244743Smarkm && list_match(cl_list, request, client_match); 16344743Smarkm } 16444743Smarkm (void) fclose(fp); 16544743Smarkm } else if (errno != ENOENT) { 16644743Smarkm tcpd_warn("cannot open %s: %m", table); 16744743Smarkm } 16844743Smarkm if (match) { 16944743Smarkm if (hosts_access_verbose > 1) 17044743Smarkm syslog(LOG_DEBUG, "matched: %s line %d", 17144743Smarkm tcpd_context.file, tcpd_context.line); 17244743Smarkm if (sh_cmd) { 17344743Smarkm#ifdef PROCESS_OPTIONS 17444743Smarkm process_options(sh_cmd, request); 17544743Smarkm#else 17644743Smarkm char cmd[BUFSIZ]; 17744743Smarkm shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request)); 17844743Smarkm#endif 17944743Smarkm } 18044743Smarkm } 18144743Smarkm tcpd_context = saved_context; 18244743Smarkm return (match); 18344743Smarkm} 18444743Smarkm 18544743Smarkm/* list_match - match a request against a list of patterns with exceptions */ 18644743Smarkm 18744743Smarkmstatic int list_match(list, request, match_fn) 18844743Smarkmchar *list; 18944743Smarkmstruct request_info *request; 19044743Smarkmint (*match_fn) (); 19144743Smarkm{ 19244743Smarkm char *tok; 19344743Smarkm 19444743Smarkm /* 19544743Smarkm * Process tokens one at a time. We have exhausted all possible matches 19644743Smarkm * when we reach an "EXCEPT" token or the end of the list. If we do find 19744743Smarkm * a match, look for an "EXCEPT" list and recurse to determine whether 19844743Smarkm * the match is affected by any exceptions. 19944743Smarkm */ 20044743Smarkm 20144743Smarkm for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { 20244743Smarkm if (STR_EQ(tok, "EXCEPT")) /* EXCEPT: give up */ 20344743Smarkm return (NO); 20444743Smarkm if (match_fn(tok, request)) { /* YES: look for exceptions */ 20544743Smarkm while ((tok = strtok((char *) 0, sep)) && STR_NE(tok, "EXCEPT")) 20644743Smarkm /* VOID */ ; 20744743Smarkm return (tok == 0 || list_match((char *) 0, request, match_fn) == 0); 20844743Smarkm } 20944743Smarkm } 21044743Smarkm return (NO); 21144743Smarkm} 21244743Smarkm 21344743Smarkm/* server_match - match server information */ 21444743Smarkm 21544743Smarkmstatic int server_match(tok, request) 21644743Smarkmchar *tok; 21744743Smarkmstruct request_info *request; 21844743Smarkm{ 21944743Smarkm char *host; 22044743Smarkm 22144743Smarkm if ((host = split_at(tok + 1, '@')) == 0) { /* plain daemon */ 22244743Smarkm return (string_match(tok, eval_daemon(request))); 22344743Smarkm } else { /* daemon@host */ 22444743Smarkm return (string_match(tok, eval_daemon(request)) 22544743Smarkm && host_match(host, request->server)); 22644743Smarkm } 22744743Smarkm} 22844743Smarkm 22944743Smarkm/* client_match - match client information */ 23044743Smarkm 23144743Smarkmstatic int client_match(tok, request) 23244743Smarkmchar *tok; 23344743Smarkmstruct request_info *request; 23444743Smarkm{ 23544743Smarkm char *host; 23644743Smarkm 23744743Smarkm if ((host = split_at(tok + 1, '@')) == 0) { /* plain host */ 23844743Smarkm return (host_match(tok, request->client)); 23944743Smarkm } else { /* user@host */ 24044743Smarkm return (host_match(host, request->client) 24144743Smarkm && string_match(tok, eval_user(request))); 24244743Smarkm } 24344743Smarkm} 24444743Smarkm 24551495Ssheldonh/* hostfile_match - look up host patterns from file */ 24651495Ssheldonh 24751495Ssheldonhstatic int hostfile_match(path, host) 24851495Ssheldonhchar *path; 24951495Ssheldonhstruct hosts_info *host; 25051495Ssheldonh{ 25151495Ssheldonh char tok[BUFSIZ]; 25251495Ssheldonh int match = NO; 25351495Ssheldonh FILE *fp; 25451495Ssheldonh 25551495Ssheldonh if ((fp = fopen(path, "r")) != 0) { 25651495Ssheldonh while (fscanf(fp, "%s", tok) == 1 && !(match = host_match(tok, host))) 25751495Ssheldonh /* void */ ; 25851495Ssheldonh fclose(fp); 25951495Ssheldonh } else if (errno != ENOENT) { 26051495Ssheldonh tcpd_warn("open %s: %m", path); 26151495Ssheldonh } 26251495Ssheldonh return (match); 26351495Ssheldonh} 26451495Ssheldonh 26544743Smarkm/* host_match - match host name and/or address against pattern */ 26644743Smarkm 26744743Smarkmstatic int host_match(tok, host) 26844743Smarkmchar *tok; 26944743Smarkmstruct host_info *host; 27044743Smarkm{ 27144743Smarkm char *mask; 27244743Smarkm 27344743Smarkm /* 27444743Smarkm * This code looks a little hairy because we want to avoid unnecessary 27544743Smarkm * hostname lookups. 27644743Smarkm * 27744743Smarkm * The KNOWN pattern requires that both address AND name be known; some 27844743Smarkm * patterns are specific to host names or to host addresses; all other 27944743Smarkm * patterns are satisfied when either the address OR the name match. 28044743Smarkm */ 28144743Smarkm 28244743Smarkm if (tok[0] == '@') { /* netgroup: look it up */ 28344743Smarkm#ifdef NETGROUP 28444743Smarkm static char *mydomain = 0; 28544743Smarkm if (mydomain == 0) 28644743Smarkm yp_get_default_domain(&mydomain); 28744743Smarkm return (innetgr(tok + 1, eval_hostname(host), (char *) 0, mydomain)); 28844743Smarkm#else 28944743Smarkm tcpd_warn("netgroup support is disabled"); /* not tcpd_jump() */ 29044743Smarkm return (NO); 29144743Smarkm#endif 29251495Ssheldonh } else if (tok[0] == '/') { /* /file hack */ 29351495Ssheldonh return (hostfile_match(tok, host)); 29444743Smarkm } else if (STR_EQ(tok, "KNOWN")) { /* check address and name */ 29544743Smarkm char *name = eval_hostname(host); 29644743Smarkm return (STR_NE(eval_hostaddr(host), unknown) && HOSTNAME_KNOWN(name)); 29744743Smarkm } else if (STR_EQ(tok, "LOCAL")) { /* local: no dots in name */ 29844743Smarkm char *name = eval_hostname(host); 29944743Smarkm return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name)); 30044743Smarkm } else if ((mask = split_at(tok, '/')) != 0) { /* net/mask */ 30144743Smarkm return (masked_match(tok, mask, eval_hostaddr(host))); 30244743Smarkm } else { /* anything else */ 30344743Smarkm return (string_match(tok, eval_hostaddr(host)) 30444743Smarkm || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host)))); 30544743Smarkm } 30644743Smarkm} 30744743Smarkm 30844743Smarkm/* string_match - match string against pattern */ 30944743Smarkm 31044743Smarkmstatic int string_match(tok, string) 31144743Smarkmchar *tok; 31244743Smarkmchar *string; 31344743Smarkm{ 31444743Smarkm int n; 31544743Smarkm 31644743Smarkm if (tok[0] == '.') { /* suffix */ 31744743Smarkm n = strlen(string) - strlen(tok); 31844743Smarkm return (n > 0 && STR_EQ(tok, string + n)); 31944743Smarkm } else if (STR_EQ(tok, "ALL")) { /* all: match any */ 32044743Smarkm return (YES); 32144743Smarkm } else if (STR_EQ(tok, "KNOWN")) { /* not unknown */ 32244743Smarkm return (STR_NE(string, unknown)); 32344743Smarkm } else if (tok[(n = strlen(tok)) - 1] == '.') { /* prefix */ 32444743Smarkm return (STRN_EQ(tok, string, n)); 32544743Smarkm } else { /* exact match */ 32644743Smarkm return (STR_EQ(tok, string)); 32744743Smarkm } 32844743Smarkm} 32944743Smarkm 33044743Smarkm/* masked_match - match address against netnumber/netmask */ 33144743Smarkm 33244743Smarkmstatic int masked_match(net_tok, mask_tok, string) 33344743Smarkmchar *net_tok; 33444743Smarkmchar *mask_tok; 33544743Smarkmchar *string; 33644743Smarkm{ 33744743Smarkm unsigned long net; 33844743Smarkm unsigned long mask; 33944743Smarkm unsigned long addr; 34044743Smarkm 34144743Smarkm /* 34244743Smarkm * Disallow forms other than dotted quad: the treatment that inet_addr() 34344743Smarkm * gives to forms with less than four components is inconsistent with the 34444743Smarkm * access control language. John P. Rouillard <rouilj@cs.umb.edu>. 34544743Smarkm */ 34644743Smarkm 34744743Smarkm if ((addr = dot_quad_addr(string)) == INADDR_NONE) 34844743Smarkm return (NO); 34944743Smarkm if ((net = dot_quad_addr(net_tok)) == INADDR_NONE 35044743Smarkm || (mask = dot_quad_addr(mask_tok)) == INADDR_NONE) { 35144743Smarkm tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok); 35244743Smarkm return (NO); /* not tcpd_jump() */ 35344743Smarkm } 35444743Smarkm return ((addr & mask) == net); 35544743Smarkm} 356