144743Smarkm /* 244743Smarkm * tcpdmatch - explain what tcpd would do in a specific case 344743Smarkm * 444743Smarkm * usage: tcpdmatch [-d] [-i inet_conf] daemon[@host] [user@]host 544743Smarkm * 644743Smarkm * -d: use the access control tables in the current directory. 744743Smarkm * 844743Smarkm * -i: location of inetd.conf file. 944743Smarkm * 1044743Smarkm * All errors are reported to the standard error stream, including the errors 1144743Smarkm * that would normally be reported via the syslog daemon. 1244743Smarkm * 1344743Smarkm * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. 1456977Sshin * 1556977Sshin * $FreeBSD: releng/10.3/contrib/tcp_wrappers/tcpdmatch.c 63158 2000-07-14 17:15:34Z ume $ 1644743Smarkm */ 1744743Smarkm 1844743Smarkm#ifndef lint 1944743Smarkmstatic char sccsid[] = "@(#) tcpdmatch.c 1.5 96/02/11 17:01:36"; 2044743Smarkm#endif 2144743Smarkm 2244743Smarkm/* System libraries. */ 2344743Smarkm 2444743Smarkm#include <sys/types.h> 2544743Smarkm#include <sys/stat.h> 2644743Smarkm#include <sys/socket.h> 2744743Smarkm#include <netinet/in.h> 2844743Smarkm#include <arpa/inet.h> 2944743Smarkm#include <netdb.h> 3044743Smarkm#include <stdio.h> 3144743Smarkm#include <syslog.h> 3244743Smarkm#include <setjmp.h> 3344743Smarkm#include <string.h> 3444743Smarkm 3544743Smarkmextern void exit(); 3644743Smarkmextern int optind; 3744743Smarkmextern char *optarg; 3844743Smarkm 3944743Smarkm#ifndef INADDR_NONE 4044743Smarkm#define INADDR_NONE (-1) /* XXX should be 0xffffffff */ 4144743Smarkm#endif 4244743Smarkm 4344743Smarkm#ifndef S_ISDIR 4444743Smarkm#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) 4544743Smarkm#endif 4644743Smarkm 4744743Smarkm/* Application-specific. */ 4844743Smarkm 4944743Smarkm#include "tcpd.h" 5044743Smarkm#include "inetcf.h" 5144743Smarkm#include "scaffold.h" 5244743Smarkm 5344743Smarkmstatic void usage(); 5444743Smarkmstatic void tcpdmatch(); 5544743Smarkm 5644743Smarkm/* The main program */ 5744743Smarkm 5844743Smarkmint main(argc, argv) 5944743Smarkmint argc; 6044743Smarkmchar **argv; 6144743Smarkm{ 6263158Sume#ifdef INET6 6363158Sume struct addrinfo hints, *hp, *res; 6463158Sume#else 6544743Smarkm struct hostent *hp; 6663158Sume#endif 6744743Smarkm char *myname = argv[0]; 6844743Smarkm char *client; 6944743Smarkm char *server; 7044743Smarkm char *addr; 7144743Smarkm char *user; 7244743Smarkm char *daemon; 7344743Smarkm struct request_info request; 7444743Smarkm int ch; 7544743Smarkm char *inetcf = 0; 7644743Smarkm int count; 7756977Sshin#ifdef INET6 7856977Sshin struct sockaddr_storage server_sin; 7956977Sshin struct sockaddr_storage client_sin; 8056977Sshin#else 8144743Smarkm struct sockaddr_in server_sin; 8244743Smarkm struct sockaddr_in client_sin; 8356977Sshin#endif 8444743Smarkm struct stat st; 8544743Smarkm 8644743Smarkm /* 8744743Smarkm * Show what rule actually matched. 8844743Smarkm */ 8944743Smarkm hosts_access_verbose = 2; 9044743Smarkm 9144743Smarkm /* 9244743Smarkm * Parse the JCL. 9344743Smarkm */ 9444743Smarkm while ((ch = getopt(argc, argv, "di:")) != EOF) { 9544743Smarkm switch (ch) { 9644743Smarkm case 'd': 9744743Smarkm hosts_allow_table = "hosts.allow"; 9844743Smarkm hosts_deny_table = "hosts.deny"; 9944743Smarkm break; 10044743Smarkm case 'i': 10144743Smarkm inetcf = optarg; 10244743Smarkm break; 10344743Smarkm default: 10444743Smarkm usage(myname); 10544743Smarkm /* NOTREACHED */ 10644743Smarkm } 10744743Smarkm } 10844743Smarkm if (argc != optind + 2) 10944743Smarkm usage(myname); 11044743Smarkm 11144743Smarkm /* 11244743Smarkm * When confusion really strikes... 11344743Smarkm */ 11444743Smarkm if (check_path(REAL_DAEMON_DIR, &st) < 0) { 11544743Smarkm tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR); 11644743Smarkm } else if (!S_ISDIR(st.st_mode)) { 11744743Smarkm tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR); 11844743Smarkm } 11944743Smarkm 12044743Smarkm /* 12144743Smarkm * Default is to specify a daemon process name. When daemon@host is 12244743Smarkm * specified, separate the two parts. 12344743Smarkm */ 12444743Smarkm if ((server = split_at(argv[optind], '@')) == 0) 12544743Smarkm server = unknown; 12644743Smarkm if (argv[optind][0] == '/') { 12744743Smarkm daemon = strrchr(argv[optind], '/') + 1; 12844743Smarkm tcpd_warn("%s: daemon name normalized to: %s", argv[optind], daemon); 12944743Smarkm } else { 13044743Smarkm daemon = argv[optind]; 13144743Smarkm } 13244743Smarkm 13344743Smarkm /* 13444743Smarkm * Default is to specify a client hostname or address. When user@host is 13544743Smarkm * specified, separate the two parts. 13644743Smarkm */ 13744743Smarkm if ((client = split_at(argv[optind + 1], '@')) != 0) { 13844743Smarkm user = argv[optind + 1]; 13944743Smarkm } else { 14044743Smarkm client = argv[optind + 1]; 14144743Smarkm user = unknown; 14244743Smarkm } 14344743Smarkm 14444743Smarkm /* 14544743Smarkm * Analyze the inetd (or tlid) configuration file, so that we can warn 14644743Smarkm * the user about services that may not be wrapped, services that are not 14744743Smarkm * configured, or services that are wrapped in an incorrect manner. Allow 14844743Smarkm * for services that are not run from inetd, or that have tcpd access 14944743Smarkm * control built into them. 15044743Smarkm */ 15144743Smarkm inetcf = inet_cfg(inetcf); 15244743Smarkm inet_set("portmap", WR_NOT); 15344743Smarkm inet_set("rpcbind", WR_NOT); 15444743Smarkm switch (inet_get(daemon)) { 15544743Smarkm case WR_UNKNOWN: 15644743Smarkm tcpd_warn("%s: no such process name in %s", daemon, inetcf); 15744743Smarkm break; 15844743Smarkm case WR_NOT: 15944743Smarkm tcpd_warn("%s: service possibly not wrapped", daemon); 16044743Smarkm break; 16144743Smarkm } 16244743Smarkm 16344743Smarkm /* 16444743Smarkm * Check accessibility of access control files. 16544743Smarkm */ 16644743Smarkm (void) check_path(hosts_allow_table, &st); 16744743Smarkm (void) check_path(hosts_deny_table, &st); 16844743Smarkm 16944743Smarkm /* 17044743Smarkm * Fill in what we have figured out sofar. Use socket and DNS routines 17144743Smarkm * for address and name conversions. We attach stdout to the request so 17244743Smarkm * that banner messages will become visible. 17344743Smarkm */ 17444743Smarkm request_init(&request, RQ_DAEMON, daemon, RQ_USER, user, RQ_FILE, 1, 0); 17544743Smarkm sock_methods(&request); 17644743Smarkm 17744743Smarkm /* 17844743Smarkm * If a server hostname is specified, insist that the name maps to at 17944743Smarkm * most one address. eval_hostname() warns the user about name server 18044743Smarkm * problems, while using the request.server structure as a cache for host 18144743Smarkm * address and name conversion results. 18244743Smarkm */ 18344743Smarkm if (NOT_INADDR(server) == 0 || HOSTNAME_KNOWN(server)) { 18444743Smarkm if ((hp = find_inet_addr(server)) == 0) 18544743Smarkm exit(1); 18663158Sume#ifndef INET6 18744743Smarkm memset((char *) &server_sin, 0, sizeof(server_sin)); 18844743Smarkm server_sin.sin_family = AF_INET; 18956977Sshin#endif 19044743Smarkm request_set(&request, RQ_SERVER_SIN, &server_sin, 0); 19144743Smarkm 19256977Sshin#ifdef INET6 19363158Sume for (res = hp, count = 0; res; res = res->ai_next, count++) { 19463158Sume memcpy(&server_sin, res->ai_addr, res->ai_addrlen); 19556977Sshin#else 19663158Sume for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) { 19744743Smarkm memcpy((char *) &server_sin.sin_addr, addr, 19844743Smarkm sizeof(server_sin.sin_addr)); 19956977Sshin#endif 20044743Smarkm 20144743Smarkm /* 20244743Smarkm * Force evaluation of server host name and address. Host name 20344743Smarkm * conflicts will be reported while eval_hostname() does its job. 20444743Smarkm */ 20544743Smarkm request_set(&request, RQ_SERVER_NAME, "", RQ_SERVER_ADDR, "", 0); 20644743Smarkm if (STR_EQ(eval_hostname(request.server), unknown)) 20744743Smarkm tcpd_warn("host address %s->name lookup failed", 20844743Smarkm eval_hostaddr(request.server)); 20944743Smarkm } 21044743Smarkm if (count > 1) { 21144743Smarkm fprintf(stderr, "Error: %s has more than one address\n", server); 21244743Smarkm fprintf(stderr, "Please specify an address instead\n"); 21344743Smarkm exit(1); 21444743Smarkm } 21563158Sume#ifdef INET6 21663158Sume freeaddrinfo(hp); 21763158Sume#else 21844743Smarkm free((char *) hp); 21963158Sume#endif 22044743Smarkm } else { 22144743Smarkm request_set(&request, RQ_SERVER_NAME, server, 0); 22244743Smarkm } 22344743Smarkm 22444743Smarkm /* 22544743Smarkm * If a client address is specified, we simulate the effect of client 22644743Smarkm * hostname lookup failure. 22744743Smarkm */ 22844743Smarkm if (dot_quad_addr(client) != INADDR_NONE) { 22944743Smarkm request_set(&request, RQ_CLIENT_ADDR, client, 0); 23044743Smarkm tcpdmatch(&request); 23144743Smarkm exit(0); 23244743Smarkm } 23363158Sume#ifdef INET6 23463158Sume memset(&hints, 0, sizeof(hints)); 23563158Sume hints.ai_family = AF_INET6; 23663158Sume hints.ai_socktype = SOCK_STREAM; 23763158Sume hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; 23863158Sume if (getaddrinfo(client, NULL, &hints, &res) == 0) { 23963158Sume freeaddrinfo(res); 24063158Sume request_set(&request, RQ_CLIENT_ADDR, client, 0); 24163158Sume tcpdmatch(&request); 24263158Sume exit(0); 24363158Sume } 24463158Sume#endif 24544743Smarkm 24644743Smarkm /* 24744743Smarkm * Perhaps they are testing special client hostname patterns that aren't 24844743Smarkm * really host names at all. 24944743Smarkm */ 25044743Smarkm if (NOT_INADDR(client) && HOSTNAME_KNOWN(client) == 0) { 25144743Smarkm request_set(&request, RQ_CLIENT_NAME, client, 0); 25244743Smarkm tcpdmatch(&request); 25344743Smarkm exit(0); 25444743Smarkm } 25544743Smarkm 25644743Smarkm /* 25744743Smarkm * Otherwise, assume that a client hostname is specified, and insist that 25844743Smarkm * the address can be looked up. The reason for this requirement is that 25944743Smarkm * in real life the client address is available (at least with IP). Let 26044743Smarkm * eval_hostname() figure out if this host is properly registered, while 26144743Smarkm * using the request.client structure as a cache for host name and 26244743Smarkm * address conversion results. 26344743Smarkm */ 26444743Smarkm if ((hp = find_inet_addr(client)) == 0) 26544743Smarkm exit(1); 26656977Sshin#ifdef INET6 26763158Sume request_set(&request, RQ_CLIENT_SIN, &client_sin, 0); 26863158Sume 26963158Sume for (res = hp, count = 0; res; res = res->ai_next, count++) { 27063158Sume memcpy(&client_sin, res->ai_addr, res->ai_addrlen); 27163158Sume 27263158Sume /* 27363158Sume * getnameinfo() doesn't do reverse lookup against link-local 27463158Sume * address. So, we pass through host name evaluation against 27563158Sume * such addresses. 27663158Sume */ 27763158Sume if (res->ai_family != AF_INET6 || 27863158Sume !IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr)) { 27963158Sume /* 28063158Sume * Force evaluation of client host name and address. Host name 28163158Sume * conflicts will be reported while eval_hostname() does its job. 28263158Sume */ 28363158Sume request_set(&request, RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0); 28463158Sume if (STR_EQ(eval_hostname(request.client), unknown)) 28563158Sume tcpd_warn("host address %s->name lookup failed", 28663158Sume eval_hostaddr(request.client)); 28763158Sume } 28863158Sume tcpdmatch(&request); 28963158Sume if (res->ai_next) 29063158Sume printf("\n"); 29156977Sshin } 29263158Sume freeaddrinfo(hp); 29356977Sshin#else 29463158Sume memset((char *) &client_sin, 0, sizeof(client_sin)); 29544743Smarkm client_sin.sin_family = AF_INET; 29644743Smarkm request_set(&request, RQ_CLIENT_SIN, &client_sin, 0); 29744743Smarkm 29844743Smarkm for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) { 29944743Smarkm memcpy((char *) &client_sin.sin_addr, addr, 30044743Smarkm sizeof(client_sin.sin_addr)); 30144743Smarkm 30244743Smarkm /* 30344743Smarkm * Force evaluation of client host name and address. Host name 30444743Smarkm * conflicts will be reported while eval_hostname() does its job. 30544743Smarkm */ 30644743Smarkm request_set(&request, RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0); 30744743Smarkm if (STR_EQ(eval_hostname(request.client), unknown)) 30844743Smarkm tcpd_warn("host address %s->name lookup failed", 30944743Smarkm eval_hostaddr(request.client)); 31044743Smarkm tcpdmatch(&request); 31144743Smarkm if (hp->h_addr_list[count + 1]) 31244743Smarkm printf("\n"); 31344743Smarkm } 31444743Smarkm free((char *) hp); 31563158Sume#endif 31644743Smarkm exit(0); 31744743Smarkm} 31844743Smarkm 31944743Smarkm/* Explain how to use this program */ 32044743Smarkm 32144743Smarkmstatic void usage(myname) 32244743Smarkmchar *myname; 32344743Smarkm{ 32444743Smarkm fprintf(stderr, "usage: %s [-d] [-i inet_conf] daemon[@host] [user@]host\n", 32544743Smarkm myname); 32644743Smarkm fprintf(stderr, " -d: use allow/deny files in current directory\n"); 32744743Smarkm fprintf(stderr, " -i: location of inetd.conf file\n"); 32844743Smarkm exit(1); 32944743Smarkm} 33044743Smarkm 33144743Smarkm/* Print interesting expansions */ 33244743Smarkm 33344743Smarkmstatic void expand(text, pattern, request) 33444743Smarkmchar *text; 33544743Smarkmchar *pattern; 33644743Smarkmstruct request_info *request; 33744743Smarkm{ 33844743Smarkm char buf[BUFSIZ]; 33944743Smarkm 34044743Smarkm if (STR_NE(percent_x(buf, sizeof(buf), pattern, request), unknown)) 34144743Smarkm printf("%s %s\n", text, buf); 34244743Smarkm} 34344743Smarkm 34444743Smarkm/* Try out a (server,client) pair */ 34544743Smarkm 34644743Smarkmstatic void tcpdmatch(request) 34744743Smarkmstruct request_info *request; 34844743Smarkm{ 34944743Smarkm int verdict; 35044743Smarkm 35144743Smarkm /* 35244743Smarkm * Show what we really know. Suppress uninteresting noise. 35344743Smarkm */ 35444743Smarkm expand("client: hostname", "%n", request); 35544743Smarkm expand("client: address ", "%a", request); 35644743Smarkm expand("client: username", "%u", request); 35744743Smarkm expand("server: hostname", "%N", request); 35844743Smarkm expand("server: address ", "%A", request); 35944743Smarkm expand("server: process ", "%d", request); 36044743Smarkm 36144743Smarkm /* 36244743Smarkm * Reset stuff that might be changed by options handlers. In dry-run 36344743Smarkm * mode, extension language routines that would not return should inform 36444743Smarkm * us of their plan, by clearing the dry_run flag. This is a bit clumsy 36544743Smarkm * but we must be able to verify hosts with more than one network 36644743Smarkm * address. 36744743Smarkm */ 36844743Smarkm rfc931_timeout = RFC931_TIMEOUT; 36944743Smarkm allow_severity = SEVERITY; 37044743Smarkm deny_severity = LOG_WARNING; 37144743Smarkm dry_run = 1; 37244743Smarkm 37344743Smarkm /* 37444743Smarkm * When paranoid mode is enabled, access is rejected no matter what the 37544743Smarkm * access control rules say. 37644743Smarkm */ 37744743Smarkm#ifdef PARANOID 37844743Smarkm if (STR_EQ(eval_hostname(request->client), paranoid)) { 37944743Smarkm printf("access: denied (PARANOID mode)\n\n"); 38044743Smarkm return; 38144743Smarkm } 38244743Smarkm#endif 38344743Smarkm 38444743Smarkm /* 38544743Smarkm * Report the access control verdict. 38644743Smarkm */ 38744743Smarkm verdict = hosts_access(request); 38844743Smarkm printf("access: %s\n", 38944743Smarkm dry_run == 0 ? "delegated" : 39044743Smarkm verdict ? "granted" : "denied"); 39144743Smarkm} 392