home *** CD-ROM | disk | FTP | other *** search
/ Il CD di internet / CD.iso / SOURCE / N / TCPIP / NETKIT-A.06 / NETKIT-A / NetKit-A-0.06 / tcp_wrapper-6.3 / hosts_access.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-03-27  |  12.9 KB  |  456 lines

  1.  /*
  2.   * This module implements a simple access control language that is based on
  3.   * host (or domain) names, NIS netgroup names, IP addresses (or network
  4.   * numbers) and daemon process names. When a match is found an optional
  5.   * shell command is executed and the search is terminated.
  6.   *
  7.   * The language supports rule-driven remote username lookup via the RFC931
  8.   * protocol. This feature is supported only for the connection-oriented TCP
  9.   * protocol, and requires that the caller provides sockaddr_in structures
  10.   * that describe both ends of the connection.
  11.   * 
  12.   * Diagnostics are reported through syslog(3).
  13.   * 
  14.   * Compile with -DNETGROUP if your library provides support for netgroups.
  15.   * 
  16.   * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
  17.   */
  18.  
  19. #ifndef lint
  20. static char sccsid[] = "@(#) hosts_access.c 1.16 94/02/01 22:12:05";
  21. #endif
  22.  
  23. /* System libraries. */
  24.  
  25. #include <sys/types.h>
  26. #include <sys/param.h>
  27. #include <netinet/in.h>
  28. #include <arpa/inet.h>
  29. #include <stdio.h>
  30. #include <syslog.h>
  31. #include <ctype.h>
  32. #include <errno.h>
  33.  
  34. extern char *fgets();
  35. extern char *strchr();
  36. extern char *strtok();
  37. extern int errno;
  38.  
  39. #ifndef    INADDR_NONE
  40. #define    INADDR_NONE    (-1)        /* XXX should be 0xffffffff */
  41. #endif
  42.  
  43. /* Local stuff. */
  44.  
  45. #include "log_tcp.h"
  46.  
  47. #ifdef PROCESS_OPTIONS
  48. #include <setjmp.h>
  49. #include "options.h"
  50. #endif
  51.  
  52. /* Delimiters for lists of daemons or clients. */
  53.  
  54. static char sep[] = ", \t";
  55.  
  56. /* Constants to be used in assignments only, not in comparisons... */
  57.  
  58. #define    YES        1
  59. #define    NO        0
  60. #define    FAIL        (-1)
  61.  
  62.  /*
  63.   * These variables are globally visible so that they can be redirected in
  64.   * verification mode.
  65.   */
  66.  
  67. char   *hosts_allow_table = HOSTS_ALLOW;
  68. char   *hosts_deny_table = HOSTS_DENY;
  69.  
  70. /* These are global so they can be consulted for error reports. */
  71.  
  72. char   *hosts_access_file = 0;        /* current access control table */
  73. int     hosts_access_line;        /* current line (approximately) */
  74.  
  75. /* Forward declarations. */
  76.  
  77. static int table_match();
  78. static int list_match();
  79. static int client_match();
  80. static int host_match();
  81. static int string_match();
  82. static int masked_match();
  83. static FILE *xfopen();
  84. static char *xgets();
  85.  
  86. /* Size of logical line buffer. */
  87.  
  88. #define    BUFLEN 2048
  89.  
  90. /* hosts_access - host access control facility */
  91.  
  92. int     hosts_access(daemon, client)
  93. char   *daemon;
  94. struct client_info *client;        /* host or user name may be empty */
  95. {
  96.  
  97. #ifdef PROCESS_OPTIONS
  98.  
  99.     /*
  100.      * After a rule has been matched, the optional language extensions may
  101.      * decide to grant or refuse service anyway. This is done by jumping back
  102.      * into the hosts_access() routine, bypassing the regular return from the
  103.      * table_match() function calls below.
  104.      */
  105.  
  106.     switch (setjmp(options_buf)) {
  107.     case OPT_ALLOW:
  108.     return (YES);
  109.     case OPT_DENY:
  110.     return (NO);
  111.     }
  112. #endif /* PROCESS_OPTIONS */
  113.  
  114.     /*
  115.      * If the (daemon, client) pair is matched by an entry in the file
  116.      * /etc/hosts.allow, access is granted. Otherwise, if the (daemon,
  117.      * client) pair is matched by an entry in the file /etc/hosts.deny,
  118.      * access is denied. Otherwise, access is granted. A non-existent
  119.      * access-control file is treated as an empty file.
  120.      */
  121.  
  122.     if (table_match(hosts_allow_table, daemon, client))
  123.     return (YES);
  124.     if (table_match(hosts_deny_table, daemon, client))
  125.     return (NO);
  126.     return (YES);
  127. }
  128.  
  129. /* table_match - match table entries with (daemon, client) pair */
  130.  
  131. static int table_match(table, daemon, client)
  132. char   *table;
  133. char   *daemon;
  134. struct client_info *client;        /* host or user name may be empty */
  135. {
  136.     FILE   *fp;
  137.     char    sv_list[BUFLEN];        /* becomes list of daemons */
  138.     char   *cl_list;            /* becomes list of clients */
  139.     char   *sh_cmd;            /* becomes optional shell command */
  140.     int     match;
  141.     int     end;
  142.  
  143.     /* The following variables should always be tested together. */
  144.  
  145.     int     sv_match = NO;        /* daemon matched */
  146.     int     cl_match = NO;        /* client matched */
  147.  
  148.     /*
  149.      * Process the table one logical line at a time. Lines that begin with a
  150.      * '#' character are ignored. Non-comment lines are broken at the ':'
  151.      * character (we complain if there is none). The first field is matched
  152.      * against the daemon process name (argv[0]), the second field against
  153.      * the host name or address. A non-existing table is treated as if it
  154.      * were an empty table. The search terminates at the first matching rule.
  155.      * When a match is found an optional shell command is executed.
  156.      */
  157.  
  158.     if (fp = xfopen(table, "r")) {
  159.     while (!(sv_match && cl_match) && xgets(sv_list, sizeof(sv_list), fp)) {
  160.         if (sv_list[end = strlen(sv_list) - 1] != '\n') {
  161.         syslog(LOG_ERR, "error: %s, line %d: missing newline or line too long",
  162.                hosts_access_file, hosts_access_line);
  163.         continue;
  164.         }
  165.         if (sv_list[0] == '#')        /* skip comments */
  166.         continue;
  167.         while (end > 0 && isspace(sv_list[end - 1]))
  168.         end--;
  169.         sv_list[end] = '\0';        /* strip trailing whitespace */
  170.         if (sv_list[0] == 0)        /* skip blank lines */
  171.         continue;
  172.         if ((cl_list = strchr(sv_list, ':')) == 0) {
  173.         syslog(LOG_ERR, "error: %s, line %d: malformed entry: \"%s\"",
  174.                hosts_access_file, hosts_access_line, sv_list);
  175.         continue;
  176.         }
  177.         *cl_list++ = '\0';            /* split 1st and 2nd fields */
  178.         if ((sh_cmd = strchr(cl_list, ':')) != 0)
  179.         *sh_cmd++ = '\0';        /* split 2nd and 3rd fields */
  180.         if ((sv_match = list_match(sv_list, daemon, string_match)))
  181.         cl_match = list_match(cl_list, (char *) client, client_match);
  182.     }
  183.     (void) fclose(fp);
  184.     } else if (errno != ENOENT) {
  185.     syslog(LOG_ERR, "error: cannot open %s: %m", table);
  186.     }
  187.     match = (sv_match == YES && cl_match == YES);
  188.     if (match && sh_cmd)
  189. #ifdef PROCESS_OPTIONS
  190.     process_options(sh_cmd, daemon, client);
  191. #else
  192.     shell_cmd(sh_cmd, daemon, client);
  193. #endif
  194.     return (match);
  195. }
  196.  
  197. /* list_match - match an item against a list of tokens with exceptions */
  198.  
  199. static int list_match(list, item, match_fn)
  200. char   *list;
  201. char   *item;
  202. int   (*match_fn) ();
  203. {
  204.     char   *tok;
  205.     int     match = NO;
  206.  
  207.     /*
  208.      * Process tokens one at a time. We have exhausted all possible matches
  209.      * when we reach an "EXCEPT" token or the end of the list. If we do find
  210.      * a match, look for an "EXCEPT" list and recurse to determine whether
  211.      * the match is affected by any exceptions.
  212.      */
  213.  
  214.     for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
  215.     if (strcasecmp(tok, "EXCEPT") == 0)    /* EXCEPT: give up */
  216.         break;
  217.     if (match = (*match_fn) (tok, item))    /* YES or FAIL */
  218.         break;
  219.     }
  220.     /* Process exceptions to YES or FAIL matches. */
  221.  
  222.     if (match != NO) {
  223.     while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
  224.          /* VOID */ ;
  225.     if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
  226.         return (match);
  227.     }
  228.     return (NO);
  229. }
  230.  
  231. /* host_match - match host name and/or address against token */
  232.  
  233. static int host_match(tok, client)
  234. char   *tok;
  235. struct client_info *client;
  236. {
  237.     int     match;
  238.  
  239.     /*
  240.      * The KNOWN pattern requires that both name AND address match; all other
  241.      * patterns are satisfied when either the name OR the address match.
  242.      */
  243.  
  244.     if (strcasecmp(tok, "KNOWN") == 0) {
  245.     (match = string_match(tok, client->name))
  246.         && (match = string_match(tok, client->addr));
  247.     } else {
  248.     (match = string_match(tok, client->name))
  249.         || (match = string_match(tok, client->addr));
  250.     }
  251.     return (match);
  252. }
  253.  
  254.  
  255. /* client_match - match client information */
  256.  
  257. static int client_match(tok, item)
  258. char   *tok;
  259. char   *item;
  260. {
  261.     struct client_info *client = (struct client_info *) item;
  262.     int     match = NO;
  263.     char   *at;
  264.     int     host_does_match;
  265.     int     user_does_match;
  266.  
  267.     /*
  268.      * Perform username lookups when we see user_pat@host_pat, but only when
  269.      * host_pat matches the remote host, and when no other attempt was done
  270.      * to look up the username. Username lookup is possible only with TCP
  271.      * clients.
  272.      */
  273.  
  274.     if ((at = strchr(tok + 1, '@')) == 0) {    /* host pattern */
  275.     match = host_match(tok, client);
  276.     } else {                    /* user@host */
  277.     *at = 0;
  278.     if (host_does_match = host_match(at + 1, client)) {
  279.         if (client->user[0] == 0 && RFC931_POSSIBLE(client))
  280.         client->user = rfc931(client->rmt_sin, client->our_sin);
  281.         user_does_match = string_match(tok, client->user);
  282.         if (user_does_match == NO || user_does_match == FAIL) {
  283.         match = user_does_match;
  284.         } else {
  285.         match = host_does_match;
  286.         }
  287.     }
  288.     *at = '@';
  289.     }
  290.     return (match);
  291. }
  292.  
  293. /* string_match - match string against token */
  294.  
  295. static int string_match(tok, string)
  296. char   *tok;
  297. char   *string;
  298. {
  299.     int     tok_len;
  300.     int     str_len;
  301.     char   *cut;
  302. #ifdef    NETGROUP
  303.     static char *mydomain = 0;
  304. #endif
  305.  
  306.     /*
  307.      * Return YES if a token has the magic value "ALL". Return FAIL if the
  308.      * token is "FAIL". If the token starts with a "." (domain name), return
  309.      * YES if it matches the last fields of the string. If the token has the
  310.      * magic value "LOCAL", return YES if the string does not contain a "."
  311.      * character. If the token ends on a "." (network number), return YES if
  312.      * it matches the first fields of the string. If the token begins with a
  313.      * "@" (netgroup name), return YES if the string is a (host) member of
  314.      * the netgroup. Return YES if the token is "KNOWN" and if the string is
  315.      * not empty or equal to FROM_UNKNOWN. Return YES if the token fully
  316.      * matches the string. If the token is a netnumber/netmask pair, return
  317.      * YES if the address is a member of the specified subnet.
  318.      */
  319.  
  320.     if (string[0] == 0)                /* no info implies unknown */
  321.     string = FROM_UNKNOWN;
  322.  
  323.     if (tok[0] == '.') {            /* domain: match last fields */
  324.     if ((str_len = strlen(string)) > (tok_len = strlen(tok))
  325.         && strcasecmp(tok, string + str_len - tok_len) == 0)
  326.         return (YES);
  327.     } else if (tok[0] == '@') {            /* netgroup: look it up */
  328. #ifdef    NETGROUP
  329.     if (mydomain == 0)
  330.         yp_get_default_domain(&mydomain);
  331.     if (!isdigit(string[0])
  332.         && innetgr(tok + 1, string, (char *) 0, mydomain))
  333.         return (YES);
  334. #else
  335.     syslog(LOG_ERR, "error: %s, line %d: netgroup support is not configured",
  336.            hosts_access_file, hosts_access_line);
  337.     return (NO);
  338. #endif
  339.     } else if (strcasecmp(tok, "ALL") == 0) {    /* all: match any */
  340.     return (YES);
  341.     } else if (strcasecmp(tok, "FAIL") == 0) {    /* fail: match any */
  342.     return (FAIL);
  343.     } else if (strcasecmp(tok, "LOCAL") == 0) {    /* local: no dots */
  344.     if (strchr(string, '.') == 0 && strcasecmp(string, FROM_UNKNOWN) != 0)
  345.         return (YES);
  346.     } else if (strcasecmp(tok, "KNOWN") == 0) {    /* not empty or unknown */
  347.     if (strcasecmp(string, FROM_UNKNOWN) != 0)
  348.         return (YES);
  349.     } else if (!strcasecmp(tok, string)) {    /* match host name or address */
  350.     return (YES);
  351.     } else if (tok[(tok_len = strlen(tok)) - 1] == '.') {    /* network */
  352.     if (strncmp(tok, string, tok_len) == 0)
  353.         return (YES);
  354.     } else if ((cut = strchr(tok, '/')) != 0) {    /* netnumber/netmask */
  355.     if (isdigit(string[0]) && masked_match(tok, cut, string))
  356.         return (YES);
  357.     }
  358.     return (NO);
  359. }
  360.  
  361. /* is_dotted_quad - determine if string looks like dotted quad */
  362.  
  363. static int is_dotted_quad(str)
  364. char   *str;
  365. {
  366.     int     in_run = 0;
  367.     int     runs = 0;
  368.  
  369.     /* Count the number of runs of characters between the dots. */
  370.  
  371.     while (*str) {
  372.     if (*str == '.') {
  373.         in_run = 0;
  374.     } else if (in_run == 0) {
  375.         in_run = 1;
  376.         runs++;
  377.     }
  378.     str++;
  379.     }
  380.     return (runs == 4);
  381. }
  382.  
  383. /* masked_match - match address against netnumber/netmask */
  384.  
  385. static int masked_match(tok, slash, string)
  386. char   *tok;
  387. char   *slash;
  388. char   *string;
  389. {
  390.     unsigned long net;
  391.     unsigned long mask;
  392.     unsigned long addr;
  393.  
  394.     /*
  395.      * Disallow forms other than dotted quad: the treatment that inet_addr()
  396.      * gives to (<4)-quad forms is not consistent with the access control
  397.      * language. John P. Rouillard <rouilj@cs.umb.edu>.
  398.      */
  399.  
  400. #define DOT_QUAD_ADDR(s) (is_dotted_quad(s) ? inet_addr(s) : INADDR_NONE)
  401.  
  402.     if ((addr = DOT_QUAD_ADDR(string)) == INADDR_NONE)
  403.     return (NO);
  404.     *slash = 0;
  405.     net = DOT_QUAD_ADDR(tok);
  406.     *slash = '/';
  407.     if (net == INADDR_NONE || (mask = DOT_QUAD_ADDR(slash + 1)) == INADDR_NONE) {
  408.     syslog(LOG_ERR, "error: %s, line %d: bad net/mask access control: %s",
  409.            hosts_access_file, hosts_access_line, tok);
  410.     return (NO);
  411.     }
  412.     return (((addr & mask) == net) ? YES : NO);
  413. }
  414.  
  415. /* xfopen - open file and set context for diagnostics */
  416.  
  417. static FILE *xfopen(path, mode)
  418. char   *path;
  419. char   *mode;
  420. {
  421.     FILE   *fp;
  422.  
  423.     if ((fp = fopen(path, mode)) != 0) {
  424.     hosts_access_file = path;
  425.     hosts_access_line = 0;
  426.     }
  427.     return (fp);
  428. }
  429.  
  430. /* xgets - fgets() with backslash-newline stripping */
  431.  
  432. static char *xgets(buf, len, fp)
  433. char   *buf;
  434. int     len;
  435. FILE   *fp;
  436. {
  437.     int     got;
  438.     char   *start = buf;
  439.  
  440.     for (;;) {
  441.     if (fgets(buf, len, fp) == 0)
  442.         return (buf > start ? start : 0);
  443.     got = strlen(buf);
  444.     if (got >= 1 && buf[got - 1] == '\n')
  445.         hosts_access_line++;        /* XXX */
  446.     if (got >= 2 && buf[got - 2] == '\\' && buf[got - 1] == '\n') {
  447.         got -= 2;
  448.         buf += got;
  449.         len -= got;
  450.         buf[0] = 0;
  451.     } else {
  452.         return (start);
  453.     }
  454.     }
  455. }
  456.