1 /* $NetBSD: tcpdmatch.c,v 1.2 1997/10/11 21:48:52 christos Exp $ */ 2 3 /* 4 * tcpdmatch - explain what tcpd would do in a specific case 5 * 6 * usage: tcpdmatch [-d] [-i inet_conf] daemon[@host] [user@]host 7 * 8 * -d: use the access control tables in the current directory. 9 * 10 * -i: location of inetd.conf file. 11 * 12 * All errors are reported to the standard error stream, including the errors 13 * that would normally be reported via the syslog daemon. 14 * 15 * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. 16 */ 17 18 #include <sys/cdefs.h> 19 #ifndef lint 20 #if 0 21 static char sccsid[] = "@(#) tcpdmatch.c 1.5 96/02/11 17:01:36"; 22 #else 23 __RCSID("$NetBSD: tcpdmatch.c,v 1.2 1997/10/11 21:48:52 christos Exp $"); 24 #endif 25 #endif 26 27 /* System libraries. */ 28 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 #include <sys/socket.h> 32 #include <netinet/in.h> 33 #include <arpa/inet.h> 34 #include <netdb.h> 35 #include <stdio.h> 36 #include <syslog.h> 37 #include <setjmp.h> 38 #include <string.h> 39 #include <stdlib.h> 40 41 #ifndef INADDR_NONE 42 #define INADDR_NONE (-1) /* XXX should be 0xffffffff */ 43 #endif 44 45 #ifndef S_ISDIR 46 #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) 47 #endif 48 49 /* Application-specific. */ 50 51 #include "tcpd.h" 52 #include "inetcf.h" 53 #include "scaffold.h" 54 55 static void usage __P((char *)); 56 static void expand __P((char *, char *, struct request_info *)); 57 static void tcpdmatch __P((struct request_info *)); 58 int main __P((int, char **)); 59 60 /* The main program */ 61 62 int main(argc, argv) 63 int argc; 64 char **argv; 65 { 66 struct hostent *hp; 67 char *myname = argv[0]; 68 char *client; 69 char *server; 70 char *addr; 71 char *user; 72 char *daemon; 73 struct request_info request; 74 int ch; 75 char *inetcf = 0; 76 int count; 77 struct sockaddr_in server_sin; 78 struct sockaddr_in client_sin; 79 struct stat st; 80 81 /* 82 * Show what rule actually matched. 83 */ 84 hosts_access_verbose = 2; 85 86 /* 87 * Parse the JCL. 88 */ 89 while ((ch = getopt(argc, argv, "di:")) != -1) { 90 switch (ch) { 91 case 'd': 92 hosts_allow_table = "hosts.allow"; 93 hosts_deny_table = "hosts.deny"; 94 break; 95 case 'i': 96 inetcf = optarg; 97 break; 98 default: 99 usage(myname); 100 /* NOTREACHED */ 101 } 102 } 103 if (argc != optind + 2) 104 usage(myname); 105 106 /* 107 * When confusion really strikes... 108 */ 109 if (check_path(REAL_DAEMON_DIR, &st) < 0) { 110 tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR); 111 } else if (!S_ISDIR(st.st_mode)) { 112 tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR); 113 } 114 115 /* 116 * Default is to specify a daemon process name. When daemon@host is 117 * specified, separate the two parts. 118 */ 119 if ((server = split_at(argv[optind], '@')) == 0) 120 server = unknown; 121 if (argv[optind][0] == '/') { 122 daemon = strrchr(argv[optind], '/') + 1; 123 tcpd_warn("%s: daemon name normalized to: %s", argv[optind], daemon); 124 } else { 125 daemon = argv[optind]; 126 } 127 128 /* 129 * Default is to specify a client hostname or address. When user@host is 130 * specified, separate the two parts. 131 */ 132 if ((client = split_at(argv[optind + 1], '@')) != 0) { 133 user = argv[optind + 1]; 134 } else { 135 client = argv[optind + 1]; 136 user = unknown; 137 } 138 139 /* 140 * Analyze the inetd (or tlid) configuration file, so that we can warn 141 * the user about services that may not be wrapped, services that are not 142 * configured, or services that are wrapped in an incorrect manner. Allow 143 * for services that are not run from inetd, or that have tcpd access 144 * control built into them. 145 */ 146 inetcf = inet_cfg(inetcf); 147 inet_set("portmap", WR_NOT); 148 inet_set("rpcbind", WR_NOT); 149 switch (inet_get(daemon)) { 150 case WR_UNKNOWN: 151 tcpd_warn("%s: no such process name in %s", daemon, inetcf); 152 break; 153 case WR_NOT: 154 tcpd_warn("%s: service possibly not wrapped", daemon); 155 break; 156 } 157 158 /* 159 * Check accessibility of access control files. 160 */ 161 (void) check_path(hosts_allow_table, &st); 162 (void) check_path(hosts_deny_table, &st); 163 164 /* 165 * Fill in what we have figured out sofar. Use socket and DNS routines 166 * for address and name conversions. We attach stdout to the request so 167 * that banner messages will become visible. 168 */ 169 request_init(&request, RQ_DAEMON, daemon, RQ_USER, user, RQ_FILE, 1, 0); 170 sock_methods(&request); 171 172 /* 173 * If a server hostname is specified, insist that the name maps to at 174 * most one address. eval_hostname() warns the user about name server 175 * problems, while using the request.server structure as a cache for host 176 * address and name conversion results. 177 */ 178 if (NOT_INADDR(server) == 0 || HOSTNAME_KNOWN(server)) { 179 if ((hp = find_inet_addr(server)) == 0) 180 exit(1); 181 memset((char *) &server_sin, 0, sizeof(server_sin)); 182 server_sin.sin_family = AF_INET; 183 request_set(&request, RQ_SERVER_SIN, &server_sin, 0); 184 185 for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) { 186 memcpy((char *) &server_sin.sin_addr, addr, 187 sizeof(server_sin.sin_addr)); 188 189 /* 190 * Force evaluation of server host name and address. Host name 191 * conflicts will be reported while eval_hostname() does its job. 192 */ 193 request_set(&request, RQ_SERVER_NAME, "", RQ_SERVER_ADDR, "", 0); 194 if (STR_EQ(eval_hostname(request.server), unknown)) 195 tcpd_warn("host address %s->name lookup failed", 196 eval_hostaddr(request.server)); 197 } 198 if (count > 1) { 199 fprintf(stderr, "Error: %s has more than one address\n", server); 200 fprintf(stderr, "Please specify an address instead\n"); 201 exit(1); 202 } 203 free((char *) hp); 204 } else { 205 request_set(&request, RQ_SERVER_NAME, server, 0); 206 } 207 208 /* 209 * If a client address is specified, we simulate the effect of client 210 * hostname lookup failure. 211 */ 212 if (dot_quad_addr(client) != INADDR_NONE) { 213 request_set(&request, RQ_CLIENT_ADDR, client, 0); 214 tcpdmatch(&request); 215 exit(0); 216 } 217 218 /* 219 * Perhaps they are testing special client hostname patterns that aren't 220 * really host names at all. 221 */ 222 if (NOT_INADDR(client) && HOSTNAME_KNOWN(client) == 0) { 223 request_set(&request, RQ_CLIENT_NAME, client, 0); 224 tcpdmatch(&request); 225 exit(0); 226 } 227 228 /* 229 * Otherwise, assume that a client hostname is specified, and insist that 230 * the address can be looked up. The reason for this requirement is that 231 * in real life the client address is available (at least with IP). Let 232 * eval_hostname() figure out if this host is properly registered, while 233 * using the request.client structure as a cache for host name and 234 * address conversion results. 235 */ 236 if ((hp = find_inet_addr(client)) == 0) 237 exit(1); 238 memset((char *) &client_sin, 0, sizeof(client_sin)); 239 client_sin.sin_family = AF_INET; 240 request_set(&request, RQ_CLIENT_SIN, &client_sin, 0); 241 242 for (count = 0; (addr = hp->h_addr_list[count]) != 0; count++) { 243 memcpy((char *) &client_sin.sin_addr, addr, 244 sizeof(client_sin.sin_addr)); 245 246 /* 247 * Force evaluation of client host name and address. Host name 248 * conflicts will be reported while eval_hostname() does its job. 249 */ 250 request_set(&request, RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0); 251 if (STR_EQ(eval_hostname(request.client), unknown)) 252 tcpd_warn("host address %s->name lookup failed", 253 eval_hostaddr(request.client)); 254 tcpdmatch(&request); 255 if (hp->h_addr_list[count + 1]) 256 printf("\n"); 257 } 258 free((char *) hp); 259 exit(0); 260 } 261 262 /* Explain how to use this program */ 263 264 static void usage(myname) 265 char *myname; 266 { 267 fprintf(stderr, "usage: %s [-d] [-i inet_conf] daemon[@host] [user@]host\n", 268 myname); 269 fprintf(stderr, " -d: use allow/deny files in current directory\n"); 270 fprintf(stderr, " -i: location of inetd.conf file\n"); 271 exit(1); 272 } 273 274 /* Print interesting expansions */ 275 276 static void expand(text, pattern, request) 277 char *text; 278 char *pattern; 279 struct request_info *request; 280 { 281 char buf[BUFSIZ]; 282 283 if (STR_NE(percent_x(buf, sizeof(buf), pattern, request), unknown)) 284 printf("%s %s\n", text, buf); 285 } 286 287 /* Try out a (server,client) pair */ 288 289 static void tcpdmatch(request) 290 struct request_info *request; 291 { 292 int verdict; 293 294 /* 295 * Show what we really know. Suppress uninteresting noise. 296 */ 297 expand("client: hostname", "%n", request); 298 expand("client: address ", "%a", request); 299 expand("client: username", "%u", request); 300 expand("server: hostname", "%N", request); 301 expand("server: address ", "%A", request); 302 expand("server: process ", "%d", request); 303 304 /* 305 * Reset stuff that might be changed by options handlers. In dry-run 306 * mode, extension language routines that would not return should inform 307 * us of their plan, by clearing the dry_run flag. This is a bit clumsy 308 * but we must be able to verify hosts with more than one network 309 * address. 310 */ 311 rfc931_timeout = RFC931_TIMEOUT; 312 allow_severity = SEVERITY; 313 deny_severity = LOG_WARNING; 314 dry_run = 1; 315 316 /* 317 * When paranoid mode is enabled, access is rejected no matter what the 318 * access control rules say. 319 */ 320 #ifdef PARANOID 321 if (STR_EQ(eval_hostname(request->client), paranoid)) { 322 printf("access: denied (PARANOID mode)\n\n"); 323 return; 324 } 325 #endif 326 327 /* 328 * Report the access control verdict. 329 */ 330 verdict = hosts_access(request); 331 printf("access: %s\n", 332 dry_run == 0 ? "delegated" : 333 verdict ? "granted" : "denied"); 334 } 335