login_access.c revision 22230
12198Sguido /*
22198Sguido  * This module implements a simple but effective form of login access
32198Sguido  * control based on login names and on host (or domain) names, internet
42198Sguido  * addresses (or network numbers), or on terminal line names in case of
52198Sguido  * non-networked logins. Diagnostics are reported through syslog(3).
68874Srgrimes  *
72198Sguido  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
82198Sguido  */
92198Sguido
102198Sguido#ifdef LOGIN_ACCESS
112198Sguido#ifndef lint
122198Sguidostatic char sccsid[] = "%Z% %M% %I% %E% %U%";
132198Sguido#endif
142198Sguido
152198Sguido#include <stdio.h>
162198Sguido#include <syslog.h>
172198Sguido#include <ctype.h>
182198Sguido#include <sys/types.h>
192198Sguido#include <grp.h>
202198Sguido#include <errno.h>
212198Sguido#include <string.h>
222198Sguido#include <unistd.h>
232198Sguido#include <stdlib.h>
242198Sguido
252198Sguido#include "pathnames.h"
262198Sguido
272198Sguido /* Delimiters for fields and for lists of users, ttys or hosts. */
282198Sguido
292198Sguidostatic char fs[] = ":";			/* field separator */
302198Sguidostatic char sep[] = ", \t";		/* list-element separator */
312198Sguido
322198Sguido /* Constants to be used in assignments only, not in comparisons... */
332198Sguido
342198Sguido#define YES             1
352198Sguido#define NO              0
362198Sguido
372198Sguidostatic int list_match();
382198Sguidostatic int user_match();
392198Sguidostatic int from_match();
402198Sguidostatic int string_match();
412198Sguido
422198Sguido/* login_access - match username/group and host/tty with access control file */
432198Sguido
4422230Spstint
452198Sguidologin_access(user, from)
462198Sguidochar   *user;
472198Sguidochar   *from;
482198Sguido{
492198Sguido    FILE   *fp;
502198Sguido    char    line[BUFSIZ];
512198Sguido    char   *perm;			/* becomes permission field */
522198Sguido    char   *users;			/* becomes list of login names */
532198Sguido    char   *froms;			/* becomes list of terminals or hosts */
542198Sguido    int     match = NO;
552198Sguido    int     end;
562198Sguido    int     lineno = 0;			/* for diagnostics */
572198Sguido
582198Sguido    /*
592198Sguido     * Process the table one line at a time and stop at the first match.
602198Sguido     * Blank lines and lines that begin with a '#' character are ignored.
612198Sguido     * Non-comment lines are broken at the ':' character. All fields are
622198Sguido     * mandatory. The first field should be a "+" or "-" character. A
632198Sguido     * non-existing table means no access control.
642198Sguido     */
652198Sguido
6622230Spst    if ((fp = fopen(_PATH_LOGACCESS, "r")) != NULL) {
672198Sguido	while (!match && fgets(line, sizeof(line), fp)) {
682198Sguido	    lineno++;
692198Sguido	    if (line[end = strlen(line) - 1] != '\n') {
702198Sguido		syslog(LOG_ERR, "%s: line %d: missing newline or line too long",
712198Sguido		       _PATH_LOGACCESS, lineno);
722198Sguido		continue;
732198Sguido	    }
742198Sguido	    if (line[0] == '#')
752198Sguido		continue;			/* comment line */
762198Sguido	    while (end > 0 && isspace(line[end - 1]))
772198Sguido		end--;
782198Sguido	    line[end] = 0;			/* strip trailing whitespace */
792198Sguido	    if (line[0] == 0)			/* skip blank lines */
802198Sguido		continue;
812198Sguido	    if (!(perm = strtok(line, fs))
822198Sguido		|| !(users = strtok((char *) 0, fs))
832198Sguido		|| !(froms = strtok((char *) 0, fs))
842198Sguido		|| strtok((char *) 0, fs)) {
852198Sguido		syslog(LOG_ERR, "%s: line %d: bad field count", _PATH_LOGACCESS,
862198Sguido		       lineno);
872198Sguido		continue;
882198Sguido	    }
892198Sguido	    if (perm[0] != '+' && perm[0] != '-') {
902198Sguido		syslog(LOG_ERR, "%s: line %d: bad first field", _PATH_LOGACCESS,
912198Sguido		       lineno);
922198Sguido		continue;
932198Sguido	    }
942198Sguido	    match = (list_match(froms, from, from_match)
952198Sguido		     && list_match(users, user, user_match));
962198Sguido	}
972198Sguido	(void) fclose(fp);
982198Sguido    } else if (errno != ENOENT) {
992198Sguido	syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS);
1002198Sguido    }
1012198Sguido    return (match == 0 || (line[0] == '+'));
1022198Sguido}
1032198Sguido
1042198Sguido/* list_match - match an item against a list of tokens with exceptions */
1052198Sguido
1062198Sguidostatic int list_match(list, item, match_fn)
1072198Sguidochar   *list;
1082198Sguidochar   *item;
1092198Sguidoint   (*match_fn) ();
1102198Sguido{
1112198Sguido    char   *tok;
1122198Sguido    int     match = NO;
1132198Sguido
1142198Sguido    /*
1152198Sguido     * Process tokens one at a time. We have exhausted all possible matches
1162198Sguido     * when we reach an "EXCEPT" token or the end of the list. If we do find
1172198Sguido     * a match, look for an "EXCEPT" list and recurse to determine whether
1182198Sguido     * the match is affected by any exceptions.
1192198Sguido     */
1202198Sguido
1212198Sguido    for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
1222198Sguido	if (strcasecmp(tok, "EXCEPT") == 0)	/* EXCEPT: give up */
1232198Sguido	    break;
12422230Spst	if ((match = (*match_fn)(tok, item)) != NULL)	/* YES */
1252198Sguido	    break;
1262198Sguido    }
1272198Sguido    /* Process exceptions to matches. */
1282198Sguido
1292198Sguido    if (match != NO) {
1302198Sguido	while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
1312198Sguido	     /* VOID */ ;
1322198Sguido	if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
1332198Sguido	    return (match);
1342198Sguido    }
1352198Sguido    return (NO);
1362198Sguido}
1372198Sguido
1382198Sguido/* netgroup_match - match group against machine or user */
1392198Sguido
1402198Sguidostatic int netgroup_match(group, machine, user)
14122230Spstgid_t   group;
1422198Sguidochar   *machine;
1432198Sguidochar   *user;
1442198Sguido{
1452198Sguido#ifdef NIS
1462198Sguido    static char *mydomain = 0;
1472198Sguido
1482198Sguido    if (mydomain == 0)
1492198Sguido	yp_get_default_domain(&mydomain);
1502198Sguido    return (innetgr(group, machine, user, mydomain));
1512198Sguido#else
1522198Sguido    syslog(LOG_ERR, "NIS netgroup support not configured");
15322230Spst    return 0;
1542198Sguido#endif
1552198Sguido}
1562198Sguido
1572198Sguido/* user_match - match a username against one token */
1582198Sguido
1592198Sguidostatic int user_match(tok, string)
1602198Sguidochar   *tok;
1612198Sguidochar   *string;
1622198Sguido{
1632198Sguido    struct group *group;
1642198Sguido    int     i;
1652198Sguido
1662198Sguido    /*
1672198Sguido     * If a token has the magic value "ALL" the match always succeeds.
1682198Sguido     * Otherwise, return YES if the token fully matches the username, or if
1692198Sguido     * the token is a group that contains the username.
1702198Sguido     */
1712198Sguido
1722198Sguido    if (tok[0] == '@') {			/* netgroup */
1732198Sguido	return (netgroup_match(tok + 1, (char *) 0, string));
1742198Sguido    } else if (string_match(tok, string)) {	/* ALL or exact match */
1752198Sguido	return (YES);
17622230Spst    } else if ((group = getgrnam(tok)) != NULL) {/* try group membership */
1772198Sguido	for (i = 0; group->gr_mem[i]; i++)
1782198Sguido	    if (strcasecmp(string, group->gr_mem[i]) == 0)
1792198Sguido		return (YES);
1802198Sguido    }
1812198Sguido    return (NO);
1822198Sguido}
1832198Sguido
1842198Sguido/* from_match - match a host or tty against a list of tokens */
1852198Sguido
1862198Sguidostatic int from_match(tok, string)
1872198Sguidochar   *tok;
1882198Sguidochar   *string;
1892198Sguido{
1902198Sguido    int     tok_len;
1912198Sguido    int     str_len;
1922198Sguido
1932198Sguido    /*
1942198Sguido     * If a token has the magic value "ALL" the match always succeeds. Return
1952198Sguido     * YES if the token fully matches the string. If the token is a domain
1962198Sguido     * name, return YES if it matches the last fields of the string. If the
1972198Sguido     * token has the magic value "LOCAL", return YES if the string does not
1982198Sguido     * contain a "." character. If the token is a network number, return YES
1992198Sguido     * if it matches the head of the string.
2002198Sguido     */
2012198Sguido
2022198Sguido    if (tok[0] == '@') {			/* netgroup */
2032198Sguido	return (netgroup_match(tok + 1, string, (char *) 0));
2042198Sguido    } else if (string_match(tok, string)) {	/* ALL or exact match */
2052198Sguido	return (YES);
2062198Sguido    } else if (tok[0] == '.') {			/* domain: match last fields */
2072198Sguido	if ((str_len = strlen(string)) > (tok_len = strlen(tok))
2082198Sguido	    && strcasecmp(tok, string + str_len - tok_len) == 0)
2092198Sguido	    return (YES);
2102198Sguido    } else if (strcasecmp(tok, "LOCAL") == 0) {	/* local: no dots */
2112198Sguido	if (strchr(string, '.') == 0)
2122198Sguido	    return (YES);
2132198Sguido    } else if (tok[(tok_len = strlen(tok)) - 1] == '.'	/* network */
2142198Sguido	       && strncmp(tok, string, tok_len) == 0) {
2152198Sguido	return (YES);
2162198Sguido    }
2172198Sguido    return (NO);
2182198Sguido}
2192198Sguido
2202198Sguido/* string_match - match a string against one token */
2212198Sguido
2222198Sguidostatic int string_match(tok, string)
2232198Sguidochar   *tok;
2242198Sguidochar   *string;
2252198Sguido{
2262198Sguido
2272198Sguido    /*
2282198Sguido     * If the token has the magic value "ALL" the match always succeeds.
2292198Sguido     * Otherwise, return YES if the token fully matches the string.
2302198Sguido     */
2312198Sguido
2322198Sguido    if (strcasecmp(tok, "ALL") == 0) {		/* all: always matches */
2332198Sguido	return (YES);
2342198Sguido    } else if (strcasecmp(tok, string) == 0) {	/* try exact match */
2352198Sguido	return (YES);
2362198Sguido    }
2372198Sguido    return (NO);
2382198Sguido}
2392198Sguido#endif /* LOGIN_ACCES */
240