hosts_access.c revision 44743
1214077Sgibbs /* 2214077Sgibbs * This module implements a simple access control language that is based on 3214077Sgibbs * host (or domain) names, NIS (host) netgroup names, IP addresses (or 4214077Sgibbs * network numbers) and daemon process names. When a match is found the 5214077Sgibbs * search is terminated, and depending on whether PROCESS_OPTIONS is defined, 6214077Sgibbs * a list of options is executed or an optional shell command is executed. 7214077Sgibbs * 8214077Sgibbs * Host and user names are looked up on demand, provided that suitable endpoint 9214077Sgibbs * information is available as sockaddr_in structures or TLI netbufs. As a 10214077Sgibbs * side effect, the pattern matching process may change the contents of 11214077Sgibbs * request structure fields. 12214077Sgibbs * 13214077Sgibbs * Diagnostics are reported through syslog(3). 14214077Sgibbs * 15214077Sgibbs * Compile with -DNETGROUP if your library provides support for netgroups. 16214077Sgibbs * 17214077Sgibbs * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. 18214077Sgibbs */ 19214077Sgibbs 20214077Sgibbs#ifndef lint 21214077Sgibbsstatic char sccsid[] = "@(#) hosts_access.c 1.21 97/02/12 02:13:22"; 22214077Sgibbs#endif 23214077Sgibbs 24214077Sgibbs/* System libraries. */ 25214077Sgibbs 26214077Sgibbs#include <sys/types.h> 27214077Sgibbs#include <sys/param.h> 28214077Sgibbs#include <netinet/in.h> 29214077Sgibbs#include <arpa/inet.h> 30214077Sgibbs#include <stdio.h> 31214077Sgibbs#include <syslog.h> 32214077Sgibbs#include <ctype.h> 33214077Sgibbs#include <errno.h> 34214077Sgibbs#include <setjmp.h> 35214077Sgibbs#include <string.h> 36214077Sgibbs 37214077Sgibbsextern char *fgets(); 38214077Sgibbsextern int errno; 39214077Sgibbs 40214077Sgibbs#ifndef INADDR_NONE 41214077Sgibbs#define INADDR_NONE (-1) /* XXX should be 0xffffffff */ 42214077Sgibbs#endif 43214077Sgibbs 44214077Sgibbs/* Local stuff. */ 45214077Sgibbs 46214077Sgibbs#include "tcpd.h" 47214077Sgibbs 48214077Sgibbs/* Error handling. */ 49214077Sgibbs 50214077Sgibbsextern jmp_buf tcpd_buf; 51214077Sgibbs 52214077Sgibbs/* Delimiters for lists of daemons or clients. */ 53214077Sgibbs 54214077Sgibbsstatic char sep[] = ", \t\r\n"; 55214077Sgibbs 56214077Sgibbs/* Constants to be used in assignments only, not in comparisons... */ 57214077Sgibbs 58214077Sgibbs#define YES 1 59214077Sgibbs#define NO 0 60214077Sgibbs 61214077Sgibbs /* 62214077Sgibbs * These variables are globally visible so that they can be redirected in 63214077Sgibbs * verification mode. 64214077Sgibbs */ 65214077Sgibbs 66214077Sgibbschar *hosts_allow_table = HOSTS_ALLOW; 67214077Sgibbschar *hosts_deny_table = HOSTS_DENY; 68214077Sgibbsint hosts_access_verbose = 0; 69214077Sgibbs 70214077Sgibbs /* 71214077Sgibbs * In a long-running process, we are not at liberty to just go away. 72214077Sgibbs */ 73214077Sgibbs 74214077Sgibbsint resident = (-1); /* -1, 0: unknown; +1: yes */ 75214077Sgibbs 76214077Sgibbs/* Forward declarations. */ 77214077Sgibbs 78214077Sgibbsstatic int table_match(); 79214077Sgibbsstatic int list_match(); 80214077Sgibbsstatic int server_match(); 81214077Sgibbsstatic int client_match(); 82214077Sgibbsstatic int host_match(); 83214077Sgibbsstatic int string_match(); 84214077Sgibbsstatic int masked_match(); 85214077Sgibbs 86214077Sgibbs/* Size of logical line buffer. */ 87214077Sgibbs 88214077Sgibbs#define BUFLEN 2048 89214077Sgibbs 90214077Sgibbs/* hosts_access - host access control facility */ 91214077Sgibbs 92214077Sgibbsint hosts_access(request) 93214077Sgibbsstruct request_info *request; 94214077Sgibbs{ 95214077Sgibbs int verdict; 96214077Sgibbs 97214077Sgibbs /* 98214077Sgibbs * If the (daemon, client) pair is matched by an entry in the file 99214077Sgibbs * /etc/hosts.allow, access is granted. Otherwise, if the (daemon, 100214077Sgibbs * client) pair is matched by an entry in the file /etc/hosts.deny, 101214077Sgibbs * access is denied. Otherwise, access is granted. A non-existent 102214077Sgibbs * access-control file is treated as an empty file. 103214077Sgibbs * 104214077Sgibbs * After a rule has been matched, the optional language extensions may 105214077Sgibbs * decide to grant or refuse service anyway. Or, while a rule is being 106214077Sgibbs * processed, a serious error is found, and it seems better to play safe 107214077Sgibbs * and deny service. All this is done by jumping back into the 108214077Sgibbs * hosts_access() routine, bypassing the regular return from the 109214077Sgibbs * table_match() function calls below. 110214077Sgibbs */ 111214077Sgibbs 112214077Sgibbs if (resident <= 0) 113214077Sgibbs resident++; 114214077Sgibbs verdict = setjmp(tcpd_buf); 115214077Sgibbs if (verdict != 0) 116214077Sgibbs return (verdict == AC_PERMIT); 117214077Sgibbs if (table_match(hosts_allow_table, request)) 118214077Sgibbs return (YES); 119214077Sgibbs if (table_match(hosts_deny_table, request)) 120214077Sgibbs return (NO); 121214077Sgibbs return (YES); 122214077Sgibbs} 123214077Sgibbs 124214077Sgibbs/* table_match - match table entries with (daemon, client) pair */ 125214077Sgibbs 126214077Sgibbsstatic int table_match(table, request) 127214077Sgibbschar *table; 128214077Sgibbsstruct request_info *request; 129214077Sgibbs{ 130214077Sgibbs FILE *fp; 131214077Sgibbs char sv_list[BUFLEN]; /* becomes list of daemons */ 132214077Sgibbs char *cl_list; /* becomes list of clients */ 133214077Sgibbs char *sh_cmd; /* becomes optional shell command */ 134214077Sgibbs int match = NO; 135214077Sgibbs struct tcpd_context saved_context; 136214077Sgibbs 137214077Sgibbs saved_context = tcpd_context; /* stupid compilers */ 138214077Sgibbs 139214077Sgibbs /* 140214077Sgibbs * Between the fopen() and fclose() calls, avoid jumps that may cause 141214077Sgibbs * file descriptor leaks. 142214077Sgibbs */ 143214077Sgibbs 144214077Sgibbs if ((fp = fopen(table, "r")) != 0) { 145214077Sgibbs tcpd_context.file = table; 146214077Sgibbs tcpd_context.line = 0; 147214077Sgibbs while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) { 148214077Sgibbs if (sv_list[strlen(sv_list) - 1] != '\n') { 149214077Sgibbs tcpd_warn("missing newline or line too long"); 150214077Sgibbs continue; 151214077Sgibbs } 152214077Sgibbs if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0) 153214077Sgibbs continue; 154214077Sgibbs if ((cl_list = split_at(sv_list, ':')) == 0) { 155214077Sgibbs tcpd_warn("missing \":\" separator"); 156214077Sgibbs continue; 157214077Sgibbs } 158214077Sgibbs sh_cmd = split_at(cl_list, ':'); 159214077Sgibbs match = list_match(sv_list, request, server_match) 160214077Sgibbs && list_match(cl_list, request, client_match); 161214077Sgibbs } 162214077Sgibbs (void) fclose(fp); 163214077Sgibbs } else if (errno != ENOENT) { 164214077Sgibbs tcpd_warn("cannot open %s: %m", table); 165214077Sgibbs } 166214077Sgibbs if (match) { 167214077Sgibbs if (hosts_access_verbose > 1) 168214077Sgibbs syslog(LOG_DEBUG, "matched: %s line %d", 169214077Sgibbs tcpd_context.file, tcpd_context.line); 170214077Sgibbs if (sh_cmd) { 171214077Sgibbs#ifdef PROCESS_OPTIONS 172214077Sgibbs process_options(sh_cmd, request); 173214077Sgibbs#else 174214077Sgibbs char cmd[BUFSIZ]; 175214077Sgibbs shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request)); 176214077Sgibbs#endif 177214077Sgibbs } 178214077Sgibbs } 179214077Sgibbs tcpd_context = saved_context; 180214077Sgibbs return (match); 181214077Sgibbs} 182214077Sgibbs 183214077Sgibbs/* list_match - match a request against a list of patterns with exceptions */ 184214077Sgibbs 185214077Sgibbsstatic int list_match(list, request, match_fn) 186214077Sgibbschar *list; 187214077Sgibbsstruct request_info *request; 188214077Sgibbsint (*match_fn) (); 189214077Sgibbs{ 190214077Sgibbs char *tok; 191214077Sgibbs 192214077Sgibbs /* 193214077Sgibbs * Process tokens one at a time. We have exhausted all possible matches 194214077Sgibbs * when we reach an "EXCEPT" token or the end of the list. If we do find 195214077Sgibbs * a match, look for an "EXCEPT" list and recurse to determine whether 196214077Sgibbs * the match is affected by any exceptions. 197214077Sgibbs */ 198214077Sgibbs 199214077Sgibbs for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { 200214077Sgibbs if (STR_EQ(tok, "EXCEPT")) /* EXCEPT: give up */ 201214077Sgibbs return (NO); 202214077Sgibbs if (match_fn(tok, request)) { /* YES: look for exceptions */ 203214077Sgibbs while ((tok = strtok((char *) 0, sep)) && STR_NE(tok, "EXCEPT")) 204214077Sgibbs /* VOID */ ; 205214077Sgibbs return (tok == 0 || list_match((char *) 0, request, match_fn) == 0); 206214077Sgibbs } 207214077Sgibbs } 208214077Sgibbs return (NO); 209214077Sgibbs} 210214077Sgibbs 211222975Sgibbs/* server_match - match server information */ 212214077Sgibbs 213214077Sgibbsstatic int server_match(tok, request) 214214077Sgibbschar *tok; 215214077Sgibbsstruct request_info *request; 216214077Sgibbs{ 217214077Sgibbs char *host; 218222975Sgibbs 219222975Sgibbs if ((host = split_at(tok + 1, '@')) == 0) { /* plain daemon */ 220222975Sgibbs return (string_match(tok, eval_daemon(request))); 221222975Sgibbs } else { /* daemon@host */ 222222975Sgibbs return (string_match(tok, eval_daemon(request)) 223214077Sgibbs && host_match(host, request->server)); 224222975Sgibbs } 225222975Sgibbs} 226222975Sgibbs 227214077Sgibbs/* client_match - match client information */ 228222975Sgibbs 229222975Sgibbsstatic int client_match(tok, request) 230214077Sgibbschar *tok; 231222975Sgibbsstruct request_info *request; 232222975Sgibbs{ 233222975Sgibbs char *host; 234222975Sgibbs 235222975Sgibbs if ((host = split_at(tok + 1, '@')) == 0) { /* plain host */ 236222975Sgibbs return (host_match(tok, request->client)); 237222975Sgibbs } else { /* user@host */ 238222975Sgibbs return (host_match(host, request->client) 239222975Sgibbs && string_match(tok, eval_user(request))); 240222975Sgibbs } 241222975Sgibbs} 242222975Sgibbs 243222975Sgibbs/* host_match - match host name and/or address against pattern */ 244214077Sgibbs 245222975Sgibbsstatic int host_match(tok, host) 246222975Sgibbschar *tok; 247222975Sgibbsstruct host_info *host; 248222975Sgibbs{ 249222975Sgibbs char *mask; 250222975Sgibbs 251222975Sgibbs /* 252222975Sgibbs * This code looks a little hairy because we want to avoid unnecessary 253222975Sgibbs * hostname lookups. 254222975Sgibbs * 255222975Sgibbs * The KNOWN pattern requires that both address AND name be known; some 256222975Sgibbs * patterns are specific to host names or to host addresses; all other 257214077Sgibbs * patterns are satisfied when either the address OR the name match. 258222975Sgibbs */ 259214077Sgibbs 260222975Sgibbs if (tok[0] == '@') { /* netgroup: look it up */ 261222975Sgibbs#ifdef NETGROUP 262222975Sgibbs static char *mydomain = 0; 263222975Sgibbs if (mydomain == 0) 264222975Sgibbs yp_get_default_domain(&mydomain); 265222975Sgibbs return (innetgr(tok + 1, eval_hostname(host), (char *) 0, mydomain)); 266222975Sgibbs#else 267222975Sgibbs tcpd_warn("netgroup support is disabled"); /* not tcpd_jump() */ 268222975Sgibbs return (NO); 269222975Sgibbs#endif 270222975Sgibbs } else if (STR_EQ(tok, "KNOWN")) { /* check address and name */ 271222975Sgibbs char *name = eval_hostname(host); 272222975Sgibbs return (STR_NE(eval_hostaddr(host), unknown) && HOSTNAME_KNOWN(name)); 273222975Sgibbs } else if (STR_EQ(tok, "LOCAL")) { /* local: no dots in name */ 274222975Sgibbs char *name = eval_hostname(host); 275222975Sgibbs return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name)); 276222975Sgibbs } else if ((mask = split_at(tok, '/')) != 0) { /* net/mask */ 277222975Sgibbs return (masked_match(tok, mask, eval_hostaddr(host))); 278222975Sgibbs } else { /* anything else */ 279222975Sgibbs return (string_match(tok, eval_hostaddr(host)) 280222975Sgibbs || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host)))); 281222975Sgibbs } 282222975Sgibbs} 283222975Sgibbs 284214077Sgibbs/* string_match - match string against pattern */ 285214077Sgibbs 286214077Sgibbsstatic int string_match(tok, string) 287214077Sgibbschar *tok; 288214077Sgibbschar *string; 289214077Sgibbs{ 290214077Sgibbs int n; 291214077Sgibbs 292214077Sgibbs if (tok[0] == '.') { /* suffix */ 293214077Sgibbs n = strlen(string) - strlen(tok); 294214077Sgibbs return (n > 0 && STR_EQ(tok, string + n)); 295225704Sgibbs } else if (STR_EQ(tok, "ALL")) { /* all: match any */ 296214077Sgibbs return (YES); 297214077Sgibbs } else if (STR_EQ(tok, "KNOWN")) { /* not unknown */ 298214077Sgibbs return (STR_NE(string, unknown)); 299214077Sgibbs } else if (tok[(n = strlen(tok)) - 1] == '.') { /* prefix */ 300222975Sgibbs return (STRN_EQ(tok, string, n)); 301214077Sgibbs } else { /* exact match */ 302214077Sgibbs return (STR_EQ(tok, string)); 303214077Sgibbs } 304214077Sgibbs} 305214077Sgibbs 306214077Sgibbs/* masked_match - match address against netnumber/netmask */ 307214077Sgibbs 308214077Sgibbsstatic int masked_match(net_tok, mask_tok, string) 309222975Sgibbschar *net_tok; 310222975Sgibbschar *mask_tok; 311214077Sgibbschar *string; 312214077Sgibbs{ 313214077Sgibbs unsigned long net; 314214077Sgibbs unsigned long mask; 315214077Sgibbs unsigned long addr; 316214077Sgibbs 317214077Sgibbs /* 318214077Sgibbs * Disallow forms other than dotted quad: the treatment that inet_addr() 319214077Sgibbs * gives to forms with less than four components is inconsistent with the 320 * access control language. John P. Rouillard <rouilj@cs.umb.edu>. 321 */ 322 323 if ((addr = dot_quad_addr(string)) == INADDR_NONE) 324 return (NO); 325 if ((net = dot_quad_addr(net_tok)) == INADDR_NONE 326 || (mask = dot_quad_addr(mask_tok)) == INADDR_NONE) { 327 tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok); 328 return (NO); /* not tcpd_jump() */ 329 } 330 return ((addr & mask) == net); 331} 332