Merge branch 'vendor/MDOCML'
[dragonfly.git] / contrib / tcp_wrappers / tcpdchk.c
1  /*
2   * tcpdchk - examine all tcpd access control rules and inetd.conf entries
3   * 
4   * Usage: tcpdchk [-a] [-d] [-i inet_conf] [-v]
5   * 
6   * -a: complain about implicit "allow" at end of rule.
7   * 
8   * -d: rules in current directory.
9   * 
10   * -i: location of inetd.conf file.
11   * 
12   * -v: show all rules.
13   * 
14   * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
15   *
16   * $FreeBSD: src/contrib/tcp_wrappers/tcpdchk.c,v 1.3.2.1 2000/07/18 08:34:55 ume Exp $
17   * $DragonFly: src/contrib/tcp_wrappers/tcpdchk.c,v 1.3 2005/04/29 01:00:27 joerg Exp $
18   */
19
20 #ifndef lint
21 static char sccsid[] = "@(#) tcpdchk.c 1.8 97/02/12 02:13:25";
22 #endif
23
24 /* System libraries. */
25
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #ifdef INET6
29 #include <sys/socket.h>
30 #endif
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <stdio.h>
34 #include <syslog.h>
35 #include <setjmp.h>
36 #include <errno.h>
37 #include <netdb.h>
38 #include <string.h>
39
40 extern void exit();
41 extern int optind;
42 extern char *optarg;
43
44 #ifndef INADDR_NONE
45 #define INADDR_NONE     (-1)            /* XXX should be 0xffffffff */
46 #endif
47
48 #ifndef S_ISDIR
49 #define S_ISDIR(m)      (((m) & S_IFMT) == S_IFDIR)
50 #endif
51
52 /* Application-specific. */
53
54 #include "tcpd.h"
55 #include "inetcf.h"
56 #include "scaffold.h"
57
58  /*
59   * Stolen from hosts_access.c...
60   */
61 static char sep[] = ", \t\n";
62
63 #define BUFLEN 2048
64
65 int     resident = 0;
66 int     hosts_access_verbose = 0;
67 char   *hosts_allow_table = HOSTS_ALLOW;
68 char   *hosts_deny_table = HOSTS_DENY;
69 extern jmp_buf tcpd_buf;
70
71  /*
72   * Local stuff.
73   */
74 static void usage();
75 static void parse_table();
76 static void print_list();
77 static void check_daemon_list();
78 static void check_client_list();
79 static void check_daemon();
80 static void check_user();
81 static int check_host();
82 static int reserved_name();
83
84 #define PERMIT  1
85 #define DENY    0
86
87 #define YES     1
88 #define NO      0
89
90 static int defl_verdict;
91 static char *myname;
92 static int allow_check;
93 static char *inetcf;
94
95 int     main(argc, argv)
96 int     argc;
97 char  **argv;
98 {
99     struct request_info request;
100     struct stat st;
101     int     c;
102
103     myname = argv[0];
104
105     /*
106      * Parse the JCL.
107      */
108     while ((c = getopt(argc, argv, "adi:v")) != EOF) {
109         switch (c) {
110         case 'a':
111             allow_check = 1;
112             break;
113         case 'd':
114             hosts_allow_table = "hosts.allow";
115             hosts_deny_table = "hosts.deny";
116             break;
117         case 'i':
118             inetcf = optarg;
119             break;
120         case 'v':
121             hosts_access_verbose++;
122             break;
123         default:
124             usage();
125             /* NOTREACHED */
126         }
127     }
128     if (argc != optind)
129         usage();
130
131     /*
132      * When confusion really strikes...
133      */
134     if (check_path(REAL_DAEMON_DIR, &st) < 0) {
135         tcpd_warn("REAL_DAEMON_DIR %s: %m", REAL_DAEMON_DIR);
136     } else if (!S_ISDIR(st.st_mode)) {
137         tcpd_warn("REAL_DAEMON_DIR %s is not a directory", REAL_DAEMON_DIR);
138     }
139
140     /*
141      * Process the inet configuration file (or its moral equivalent). This
142      * information is used later to find references in hosts.allow/deny to
143      * unwrapped services, and other possible problems.
144      */
145     inetcf = inet_cfg(inetcf);
146     if (hosts_access_verbose)
147         printf("Using network configuration file: %s\n", inetcf);
148
149     /*
150      * These are not run from inetd but may have built-in access control.
151      */
152     inet_set("portmap", WR_NOT);
153     inet_set("rpcbind", WR_NOT);
154
155     /*
156      * Check accessibility of access control files.
157      */
158     (void) check_path(hosts_allow_table, &st);
159     (void) check_path(hosts_deny_table, &st);
160
161     /*
162      * Fake up an arbitrary service request.
163      */
164     request_init(&request,
165                  RQ_DAEMON, "daemon_name",
166                  RQ_SERVER_NAME, "server_hostname",
167                  RQ_SERVER_ADDR, "server_addr",
168                  RQ_USER, "user_name",
169                  RQ_CLIENT_NAME, "client_hostname",
170                  RQ_CLIENT_ADDR, "client_addr",
171                  RQ_FILE, 1,
172                  0);
173
174     /*
175      * Examine all access-control rules.
176      */
177     defl_verdict = PERMIT;
178     parse_table(hosts_allow_table, &request);
179     defl_verdict = DENY;
180     parse_table(hosts_deny_table, &request);
181     return (0);
182 }
183
184 /* usage - explain */
185
186 static void usage()
187 {
188     fprintf(stderr, "usage: %s [-a] [-d] [-i inet_conf] [-v]\n", myname);
189     fprintf(stderr, "   -a: report rules with implicit \"ALLOW\" at end\n");
190     fprintf(stderr, "   -d: use allow/deny files in current directory\n");
191     fprintf(stderr, "   -i: location of inetd.conf file\n");
192     fprintf(stderr, "   -v: list all rules\n");
193     exit(1);
194 }
195
196 /* parse_table - like table_match(), but examines _all_ entries */
197
198 static void parse_table(table, request)
199 char   *table;
200 struct request_info *request;
201 {
202     FILE   *fp;
203     int     real_verdict;
204     char    sv_list[BUFLEN];            /* becomes list of daemons */
205     char   *cl_list;                    /* becomes list of requests */
206     char   *sh_cmd;                     /* becomes optional shell command */
207     char    buf[BUFSIZ];
208     int     verdict;
209     struct tcpd_context saved_context;
210
211     saved_context = tcpd_context;               /* stupid compilers */
212
213     if (fp = fopen(table, "r")) {
214         tcpd_context.file = table;
215         tcpd_context.line = 0;
216         while (xgets(sv_list, sizeof(sv_list), fp)) {
217             if (sv_list[strlen(sv_list) - 1] != '\n') {
218                 tcpd_warn("missing newline or line too long");
219                 continue;
220             }
221             if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0)
222                 continue;
223             if ((cl_list = split_at(sv_list, ':')) == 0) {
224                 tcpd_warn("missing \":\" separator");
225                 continue;
226             }
227             sh_cmd = split_at(cl_list, ':');
228
229             if (hosts_access_verbose)
230                 printf("\n>>> Rule %s line %d:\n",
231                        tcpd_context.file, tcpd_context.line);
232
233             if (hosts_access_verbose)
234                 print_list("daemons:  ", sv_list);
235             check_daemon_list(sv_list);
236
237             if (hosts_access_verbose)
238                 print_list("clients:  ", cl_list);
239             check_client_list(cl_list);
240
241 #ifdef PROCESS_OPTIONS
242             real_verdict = defl_verdict;
243             if (sh_cmd) {
244                 verdict = setjmp(tcpd_buf);
245                 if (verdict != 0) {
246                     real_verdict = (verdict == AC_PERMIT);
247                 } else {
248                     dry_run = 1;
249                     process_options(sh_cmd, request);
250                     if (dry_run == 1 && real_verdict && allow_check)
251                         tcpd_warn("implicit \"allow\" at end of rule");
252                 }
253             } else if (defl_verdict && allow_check) {
254                 tcpd_warn("implicit \"allow\" at end of rule");
255             }
256             if (hosts_access_verbose)
257                 printf("access:   %s\n", real_verdict ? "granted" : "denied");
258 #else
259             if (sh_cmd)
260                 shell_cmd(percent_x(buf, sizeof(buf), sh_cmd, request));
261             if (hosts_access_verbose)
262                 printf("access:   %s\n", defl_verdict ? "granted" : "denied");
263 #endif
264         }
265         (void) fclose(fp);
266     } else if (errno != ENOENT) {
267         tcpd_warn("cannot open %s: %m", table);
268     }
269     tcpd_context = saved_context;
270 }
271
272 /* print_list - pretty-print a list */
273
274 static void print_list(title, list)
275 char   *title;
276 char   *list;
277 {
278     char    buf[BUFLEN];
279     char   *cp;
280     char   *next;
281
282     fputs(title, stdout);
283     strcpy(buf, list);
284
285     for (cp = strtok(buf, sep); cp != 0; cp = next) {
286         fputs(cp, stdout);
287         next = strtok((char *) 0, sep);
288         if (next != 0)
289             fputs(" ", stdout);
290     }
291     fputs("\n", stdout);
292 }
293
294 /* check_daemon_list - criticize daemon list */
295
296 static void check_daemon_list(list)
297 char   *list;
298 {
299     char    buf[BUFLEN];
300     char   *cp;
301     char   *host;
302     int     daemons = 0;
303
304     strcpy(buf, list);
305
306     for (cp = strtok(buf, sep); cp != 0; cp = strtok((char *) 0, sep)) {
307         if (STR_EQ(cp, "EXCEPT")) {
308             daemons = 0;
309         } else {
310             daemons++;
311             if ((host = split_at(cp + 1, '@')) != 0 && check_host(host) > 1) {
312                 tcpd_warn("host %s has more than one address", host);
313                 tcpd_warn("(consider using an address instead)");
314             }
315             check_daemon(cp);
316         }
317     }
318     if (daemons == 0)
319         tcpd_warn("daemon list is empty or ends in EXCEPT");
320 }
321
322 /* check_client_list - criticize client list */
323
324 static void check_client_list(list)
325 char   *list;
326 {
327     char    buf[BUFLEN];
328     char   *cp;
329     char   *host;
330     int     clients = 0;
331
332     strcpy(buf, list);
333
334     for (cp = strtok(buf, sep); cp != 0; cp = strtok((char *) 0, sep)) {
335         if (STR_EQ(cp, "EXCEPT")) {
336             clients = 0;
337         } else {
338             clients++;
339             if (host = split_at(cp + 1, '@')) { /* user@host */
340                 check_user(cp);
341                 check_host(host);
342             } else {
343                 check_host(cp);
344             }
345         }
346     }
347     if (clients == 0)
348         tcpd_warn("client list is empty or ends in EXCEPT");
349 }
350
351 /* check_daemon - criticize daemon pattern */
352
353 static void check_daemon(pat)
354 char   *pat;
355 {
356     if (pat[0] == '@') {
357         tcpd_warn("%s: daemon name begins with \"@\"", pat);
358     } else if (pat[0] == '/') {
359         tcpd_warn("%s: daemon name begins with \"/\"", pat);
360     } else if (pat[0] == '.') {
361         tcpd_warn("%s: daemon name begins with dot", pat);
362     } else if (pat[strlen(pat) - 1] == '.') {
363         tcpd_warn("%s: daemon name ends in dot", pat);
364     } else if (STR_EQ(pat, "ALL") || STR_EQ(pat, unknown)) {
365          /* void */ ;
366     } else if (STR_EQ(pat, "FAIL")) {           /* obsolete */
367         tcpd_warn("FAIL is no longer recognized");
368         tcpd_warn("(use EXCEPT or DENY instead)");
369     } else if (reserved_name(pat)) {
370         tcpd_warn("%s: daemon name may be reserved word", pat);
371     } else {
372         switch (inet_get(pat)) {
373         case WR_UNKNOWN:
374             tcpd_warn("%s: no such process name in %s", pat, inetcf);
375             inet_set(pat, WR_YES);              /* shut up next time */
376             break;
377         case WR_NOT:
378             tcpd_warn("%s: service possibly not wrapped", pat);
379             inet_set(pat, WR_YES);
380             break;
381         }
382     }
383 }
384
385 /* check_user - criticize user pattern */
386
387 static void check_user(pat)
388 char   *pat;
389 {
390     if (pat[0] == '@') {                        /* @netgroup */
391         tcpd_warn("%s: user name begins with \"@\"", pat);
392     } else if (pat[0] == '/') {
393         tcpd_warn("%s: user name begins with \"/\"", pat);
394     } else if (pat[0] == '.') {
395         tcpd_warn("%s: user name begins with dot", pat);
396     } else if (pat[strlen(pat) - 1] == '.') {
397         tcpd_warn("%s: user name ends in dot", pat);
398     } else if (STR_EQ(pat, "ALL") || STR_EQ(pat, unknown)
399                || STR_EQ(pat, "KNOWN")) {
400          /* void */ ;
401     } else if (STR_EQ(pat, "FAIL")) {           /* obsolete */
402         tcpd_warn("FAIL is no longer recognized");
403         tcpd_warn("(use EXCEPT or DENY instead)");
404     } else if (reserved_name(pat)) {
405         tcpd_warn("%s: user name may be reserved word", pat);
406     }
407 }
408
409 #ifdef INET6
410 static int is_inet6_addr(pat)
411     char *pat;
412 {
413     struct addrinfo hints, *res;
414     int len, ret;
415     char ch;
416
417     if (*pat != '[')
418         return (0);
419     len = strlen(pat);
420     if ((ch = pat[len - 1]) != ']')
421         return (0);
422     pat[len - 1] = '\0';
423     memset(&hints, 0, sizeof(hints));
424     hints.ai_family = AF_INET6;
425     hints.ai_socktype = SOCK_STREAM;
426     hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
427     if ((ret = getaddrinfo(pat + 1, NULL, &hints, &res)) == 0)
428         freeaddrinfo(res);
429     pat[len - 1] = ch;
430     return (ret == 0);
431 }
432 #endif
433
434 /* check_host - criticize host pattern */
435
436 static int check_host(pat)
437 char   *pat;
438 {
439     char    buf[BUFSIZ];
440     char   *mask;
441     int     addr_count = 1;
442     FILE   *fp;
443     struct tcpd_context saved_context;
444     char   *cp;
445     char   *wsp = " \t\r\n";
446
447     if (pat[0] == '@') {                        /* @netgroup */
448 #ifdef NO_NETGRENT
449         /* SCO has no *netgrent() support */
450 #else
451 #ifdef NETGROUP
452         char   *machinep;
453         char   *userp;
454         char   *domainp;
455
456         setnetgrent(pat + 1);
457         if (getnetgrent(&machinep, &userp, &domainp) == 0)
458             tcpd_warn("%s: unknown or empty netgroup", pat + 1);
459         endnetgrent();
460 #else
461         tcpd_warn("netgroup support disabled");
462 #endif
463 #endif
464     } else if (pat[0] == '/') {                 /* /path/name */
465         if ((fp = fopen(pat, "r")) != 0) {
466             saved_context = tcpd_context;
467             tcpd_context.file = pat;
468             tcpd_context.line = 0;
469             while (fgets(buf, sizeof(buf), fp)) {
470                 tcpd_context.line++;
471                 for (cp = strtok(buf, wsp); cp; cp = strtok((char *) 0, wsp))
472                     check_host(cp);
473             }
474             tcpd_context = saved_context;
475             fclose(fp);
476         } else if (errno != ENOENT) {
477             tcpd_warn("open %s: %m", pat);
478         }
479     } else if (mask = split_at(pat, '/')) {     /* network/netmask */
480 #ifdef INET6
481         int mask_len;
482
483         if ((dot_quad_addr(pat) == INADDR_NONE
484             || dot_quad_addr(mask) == INADDR_NONE)
485             && (!is_inet6_addr(pat)
486                 || ((mask_len = atoi(mask)) < 0 || mask_len > 128)))
487 #else
488         if (dot_quad_addr(pat) == INADDR_NONE
489             || dot_quad_addr(mask) == INADDR_NONE)
490 #endif
491             tcpd_warn("%s/%s: bad net/mask pattern", pat, mask);
492     } else if (STR_EQ(pat, "FAIL")) {           /* obsolete */
493         tcpd_warn("FAIL is no longer recognized");
494         tcpd_warn("(use EXCEPT or DENY instead)");
495     } else if (reserved_name(pat)) {            /* other reserved */
496          /* void */ ;
497 #ifdef INET6
498     } else if (is_inet6_addr(pat)) { /* IPv6 address */
499         addr_count = 1;
500 #endif
501     } else if (NOT_INADDR(pat)) {               /* internet name */
502         if (pat[strlen(pat) - 1] == '.') {
503             tcpd_warn("%s: domain or host name ends in dot", pat);
504         } else if (pat[0] != '.') {
505             addr_count = check_dns(pat);
506         }
507     } else {                                    /* numeric form */
508         if (STR_EQ(pat, "0.0.0.0") || STR_EQ(pat, "255.255.255.255")) {
509             /* void */ ;
510         } else if (pat[0] == '.') {
511             tcpd_warn("%s: network number begins with dot", pat);
512         } else if (pat[strlen(pat) - 1] != '.') {
513             check_dns(pat);
514         }
515     }
516     return (addr_count);
517 }
518
519 /* reserved_name - determine if name is reserved */
520
521 static int reserved_name(pat)
522 char   *pat;
523 {
524     return (STR_EQ(pat, unknown)
525             || STR_EQ(pat, "KNOWN")
526             || STR_EQ(pat, paranoid)
527             || STR_EQ(pat, "ALL")
528             || STR_EQ(pat, "LOCAL"));
529 }