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