2 * tcpdchk - examine all tcpd access control rules and inetd.conf entries
4 * Usage: tcpdchk [-a] [-d] [-i inet_conf] [-v]
6 * -a: complain about implicit "allow" at end of rule.
8 * -d: rules in current directory.
10 * -i: location of inetd.conf file.
14 * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
18 static char sccsid
[] = "@(#) tcpdchk.c 1.8 97/02/12 02:13:25";
21 /* System libraries. */
23 #include <sys/types.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
40 #define INADDR_NONE (-1) /* XXX should be 0xffffffff */
44 #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
47 /* Application-specific. */
54 * Stolen from hosts_access.c...
56 static char sep
[] = ", \t\n";
61 int hosts_access_verbose
= 0;
62 char *hosts_allow_table
= HOSTS_ALLOW
;
63 char *hosts_deny_table
= HOSTS_DENY
;
64 extern jmp_buf tcpd_buf
;
70 static void parse_table();
71 static void print_list();
72 static void check_daemon_list();
73 static void check_client_list();
74 static void check_daemon();
75 static void check_user();
76 static int check_host();
77 static int reserved_name();
85 static int defl_verdict
;
87 static int allow_check
;
94 struct request_info request
;
103 while ((c
= getopt(argc
, argv
, "adi:v")) != EOF
) {
109 hosts_allow_table
= "hosts.allow";
110 hosts_deny_table
= "hosts.deny";
116 hosts_access_verbose
++;
127 * When confusion really strikes...
129 if (check_path(REAL_DAEMON_DIR
, &st
) < 0) {
130 tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR
);
131 } else if (!S_ISDIR(st
.st_mode
)) {
132 tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR
);
136 * Process the inet configuration file (or its moral equivalent). This
137 * information is used later to find references in hosts.allow/deny to
138 * unwrapped services, and other possible problems.
140 inetcf
= inet_cfg(inetcf
);
141 if (hosts_access_verbose
)
142 printf("Using network configuration file: %s\n", inetcf
);
145 * These are not run from inetd but may have built-in access control.
147 inet_set("portmap", WR_NOT
);
148 inet_set("rpcbind", WR_NOT
);
151 * Check accessibility of access control files.
153 (void) check_path(hosts_allow_table
, &st
);
154 (void) check_path(hosts_deny_table
, &st
);
157 * Fake up an arbitrary service request.
159 request_init(&request
,
160 RQ_DAEMON
, "daemon_name",
161 RQ_SERVER_NAME
, "server_hostname",
162 RQ_SERVER_ADDR
, "server_addr",
163 RQ_USER
, "user_name",
164 RQ_CLIENT_NAME
, "client_hostname",
165 RQ_CLIENT_ADDR
, "client_addr",
170 * Examine all access-control rules.
172 defl_verdict
= PERMIT
;
173 parse_table(hosts_allow_table
, &request
);
175 parse_table(hosts_deny_table
, &request
);
179 /* usage - explain */
183 fprintf(stderr
, "usage: %s [-a] [-d] [-i inet_conf] [-v]\n", myname
);
184 fprintf(stderr
, " -a: report rules with implicit \"ALLOW\" at end\n");
185 fprintf(stderr
, " -d: use allow/deny files in current directory\n");
186 fprintf(stderr
, " -i: location of inetd.conf file\n");
187 fprintf(stderr
, " -v: list all rules\n");
191 /* parse_table - like table_match(), but examines _all_ entries */
193 static void parse_table(table
, request
)
195 struct request_info
*request
;
199 char sv_list
[BUFLEN
]; /* becomes list of daemons */
200 char *cl_list
; /* becomes list of requests */
201 char *sh_cmd
; /* becomes optional shell command */
204 struct tcpd_context saved_context
;
206 saved_context
= tcpd_context
; /* stupid compilers */
208 if (fp
= fopen(table
, "r")) {
209 tcpd_context
.file
= table
;
210 tcpd_context
.line
= 0;
211 while (xgets(sv_list
, sizeof(sv_list
), fp
)) {
212 if (sv_list
[strlen(sv_list
) - 1] != '\n') {
213 tcpd_warn("missing newline or line too long");
216 if (sv_list
[0] == '#' || sv_list
[strspn(sv_list
, " \t\r\n")] == 0)
218 if ((cl_list
= split_at(skip_ipv6_addrs(sv_list
), ':')) == 0) {
219 tcpd_warn("missing \":\" separator");
222 sh_cmd
= split_at(skip_ipv6_addrs(cl_list
), ':');
224 if (hosts_access_verbose
)
225 printf("\n>>> Rule %s line %d:\n",
226 tcpd_context
.file
, tcpd_context
.line
);
228 if (hosts_access_verbose
)
229 print_list("daemons: ", sv_list
);
230 check_daemon_list(sv_list
);
232 if (hosts_access_verbose
)
233 print_list("clients: ", cl_list
);
234 check_client_list(cl_list
);
236 #ifdef PROCESS_OPTIONS
237 real_verdict
= defl_verdict
;
239 verdict
= setjmp(tcpd_buf
);
241 real_verdict
= (verdict
== AC_PERMIT
);
244 process_options(sh_cmd
, request
);
245 if (dry_run
== 1 && real_verdict
&& allow_check
)
246 tcpd_warn("implicit \"allow\" at end of rule");
248 } else if (defl_verdict
&& allow_check
) {
249 tcpd_warn("implicit \"allow\" at end of rule");
251 if (hosts_access_verbose
)
252 printf("access: %s\n", real_verdict
? "granted" : "denied");
255 shell_cmd(percent_x(buf
, sizeof(buf
), sh_cmd
, request
));
256 if (hosts_access_verbose
)
257 printf("access: %s\n", defl_verdict
? "granted" : "denied");
261 } else if (errno
!= ENOENT
) {
262 tcpd_warn("cannot open %s: %m", table
);
264 tcpd_context
= saved_context
;
267 /* print_list - pretty-print a list */
269 static void print_list(title
, list
)
277 fputs(title
, stdout
);
280 for (cp
= strtok(buf
, sep
); cp
!= 0; cp
= next
) {
282 next
= strtok((char *) 0, sep
);
289 /* check_daemon_list - criticize daemon list */
291 static void check_daemon_list(list
)
301 for (cp
= strtok(buf
, sep
); cp
!= 0; cp
= strtok((char *) 0, sep
)) {
302 if (STR_EQ(cp
, "EXCEPT")) {
306 if ((host
= split_at(cp
+ 1, '@')) != 0 && check_host(host
) > 1) {
307 tcpd_warn("host %s has more than one address", host
);
308 tcpd_warn("(consider using an address instead)");
314 tcpd_warn("daemon list is empty or ends in EXCEPT");
317 /* check_client_list - criticize client list */
319 static void check_client_list(list
)
329 for (cp
= strtok(buf
, sep
); cp
!= 0; cp
= strtok((char *) 0, sep
)) {
330 if (STR_EQ(cp
, "EXCEPT")) {
334 if (host
= split_at(cp
+ 1, '@')) { /* user@host */
343 tcpd_warn("client list is empty or ends in EXCEPT");
346 /* check_daemon - criticize daemon pattern */
348 static void check_daemon(pat
)
352 tcpd_warn("%s: daemon name begins with \"@\"", pat
);
353 } else if (pat
[0] == '.') {
354 tcpd_warn("%s: daemon name begins with dot", pat
);
355 } else if (pat
[strlen(pat
) - 1] == '.') {
356 tcpd_warn("%s: daemon name ends in dot", pat
);
357 } else if (STR_EQ(pat
, "ALL") || STR_EQ(pat
, unknown
)) {
359 } else if (STR_EQ(pat
, "FAIL")) { /* obsolete */
360 tcpd_warn("FAIL is no longer recognized");
361 tcpd_warn("(use EXCEPT or DENY instead)");
362 } else if (reserved_name(pat
)) {
363 tcpd_warn("%s: daemon name may be reserved word", pat
);
365 switch (inet_get(pat
)) {
367 tcpd_warn("%s: no such process name in %s", pat
, inetcf
);
368 inet_set(pat
, WR_YES
); /* shut up next time */
371 tcpd_warn("%s: service possibly not wrapped", pat
);
372 inet_set(pat
, WR_YES
);
378 /* check_user - criticize user pattern */
380 static void check_user(pat
)
383 if (pat
[0] == '@') { /* @netgroup */
384 tcpd_warn("%s: user name begins with \"@\"", pat
);
385 } else if (pat
[0] == '.') {
386 tcpd_warn("%s: user name begins with dot", pat
);
387 } else if (pat
[strlen(pat
) - 1] == '.') {
388 tcpd_warn("%s: user name ends in dot", pat
);
389 } else if (STR_EQ(pat
, "ALL") || STR_EQ(pat
, unknown
)
390 || STR_EQ(pat
, "KNOWN")) {
392 } else if (STR_EQ(pat
, "FAIL")) { /* obsolete */
393 tcpd_warn("FAIL is no longer recognized");
394 tcpd_warn("(use EXCEPT or DENY instead)");
395 } else if (reserved_name(pat
)) {
396 tcpd_warn("%s: user name may be reserved word", pat
);
400 /* check_host - criticize host pattern */
402 static int check_host(pat
)
408 if (pat
[0] == '@') { /* @netgroup */
410 /* SCO has no *netgrent() support */
417 setnetgrent(pat
+ 1);
418 if (getnetgrent(&machinep
, &userp
, &domainp
) == 0)
419 tcpd_warn("%s: unknown or empty netgroup", pat
+ 1);
422 tcpd_warn("netgroup support disabled");
426 } else if (pat
[0] == '[') {
428 char *cbr
= strchr(pat
, ']');
429 char *slash
= strchr(pat
, '/');
431 int mask
= IPV6_ABITS
;
435 mask
= atoi(slash
+ 1);
436 err
= mask
< 0 || mask
> IPV6_ABITS
;
442 err
+= inet_pton(AF_INET6
, pat
+1, &in6
) != 1;
444 if (slash
) *slash
= '/';
447 tcpd_warn("bad IP6 address specification: %s", pat
);
449 } else if (mask
= split_at(pat
, '/')) { /* network/netmask */
450 if (dot_quad_addr(pat
) == INADDR_NONE
451 || dot_quad_addr(mask
) == INADDR_NONE
)
452 tcpd_warn("%s/%s: bad net/mask pattern", pat
, mask
);
453 } else if (STR_EQ(pat
, "FAIL")) { /* obsolete */
454 tcpd_warn("FAIL is no longer recognized");
455 tcpd_warn("(use EXCEPT or DENY instead)");
456 } else if (reserved_name(pat
)) { /* other reserved */
458 } else if (NOT_INADDR(pat
)) { /* internet name */
459 if (pat
[strlen(pat
) - 1] == '.') {
460 tcpd_warn("%s: domain or host name ends in dot", pat
);
461 } else if (pat
[0] != '.') {
462 addr_count
= check_dns(pat
);
464 } else { /* numeric form */
465 if (STR_EQ(pat
, "0.0.0.0") || STR_EQ(pat
, "255.255.255.255")) {
467 } else if (pat
[0] == '.') {
468 tcpd_warn("%s: network number begins with dot", pat
);
469 } else if (pat
[strlen(pat
) - 1] != '.') {
476 /* reserved_name - determine if name is reserved */
478 static int reserved_name(pat
)
481 return (STR_EQ(pat
, unknown
)
482 || STR_EQ(pat
, "KNOWN")
483 || STR_EQ(pat
, paranoid
)
484 || STR_EQ(pat
, "ALL")
485 || STR_EQ(pat
, "LOCAL"));