login_access.c revision 87628
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
1187628Sdwmalone#if 0
122198Sguido#ifndef lint
1387628Sdwmalonestatic char sccsid[] = "%Z% %M% %I% %E% %U%";
142198Sguido#endif
1587628Sdwmalone#endif
162198Sguido
1787628Sdwmalone#include <sys/cdefs.h>
1887628Sdwmalone__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_login_access/login_access.c 87628 2001-12-10 21:13:08Z dwmalone $");
1987628Sdwmalone
2087177Smarkm#include <sys/types.h>
212198Sguido#include <ctype.h>
2287177Smarkm#include <errno.h>
232198Sguido#include <grp.h>
2487177Smarkm#include <stdio.h>
2587177Smarkm#include <stdlib.h>
262198Sguido#include <string.h>
2787177Smarkm#include <syslog.h>
282198Sguido#include <unistd.h>
292198Sguido
3087173Smarkm#include "login.h"
312198Sguido#include "pathnames.h"
322198Sguido
332198Sguido /* Delimiters for fields and for lists of users, ttys or hosts. */
342198Sguido
352198Sguidostatic char fs[] = ":";			/* field separator */
362198Sguidostatic char sep[] = ", \t";		/* list-element separator */
372198Sguido
382198Sguido /* Constants to be used in assignments only, not in comparisons... */
392198Sguido
402198Sguido#define YES             1
412198Sguido#define NO              0
422198Sguido
4387177Smarkmstatic int	from_match __P((char *, char *));
4487177Smarkmstatic int	list_match __P((char *, char *, int (*)(char *, char *)));
4587177Smarkmstatic int	netgroup_match __P((char *, char *, char *));
4687177Smarkmstatic int	string_match __P((char *, char *));
4787177Smarkmstatic int	user_match __P((char *, char *));
482198Sguido
492198Sguido/* login_access - match username/group and host/tty with access control file */
502198Sguido
5122230Spstint
522198Sguidologin_access(user, from)
532198Sguidochar   *user;
542198Sguidochar   *from;
552198Sguido{
562198Sguido    FILE   *fp;
572198Sguido    char    line[BUFSIZ];
582198Sguido    char   *perm;			/* becomes permission field */
592198Sguido    char   *users;			/* becomes list of login names */
602198Sguido    char   *froms;			/* becomes list of terminals or hosts */
612198Sguido    int     match = NO;
622198Sguido    int     end;
632198Sguido    int     lineno = 0;			/* for diagnostics */
642198Sguido
652198Sguido    /*
662198Sguido     * Process the table one line at a time and stop at the first match.
672198Sguido     * Blank lines and lines that begin with a '#' character are ignored.
682198Sguido     * Non-comment lines are broken at the ':' character. All fields are
692198Sguido     * mandatory. The first field should be a "+" or "-" character. A
702198Sguido     * non-existing table means no access control.
712198Sguido     */
722198Sguido
7322230Spst    if ((fp = fopen(_PATH_LOGACCESS, "r")) != NULL) {
742198Sguido	while (!match && fgets(line, sizeof(line), fp)) {
752198Sguido	    lineno++;
762198Sguido	    if (line[end = strlen(line) - 1] != '\n') {
772198Sguido		syslog(LOG_ERR, "%s: line %d: missing newline or line too long",
782198Sguido		       _PATH_LOGACCESS, lineno);
792198Sguido		continue;
802198Sguido	    }
812198Sguido	    if (line[0] == '#')
822198Sguido		continue;			/* comment line */
832198Sguido	    while (end > 0 && isspace(line[end - 1]))
842198Sguido		end--;
852198Sguido	    line[end] = 0;			/* strip trailing whitespace */
862198Sguido	    if (line[0] == 0)			/* skip blank lines */
872198Sguido		continue;
882198Sguido	    if (!(perm = strtok(line, fs))
892198Sguido		|| !(users = strtok((char *) 0, fs))
902198Sguido		|| !(froms = strtok((char *) 0, fs))
912198Sguido		|| strtok((char *) 0, fs)) {
922198Sguido		syslog(LOG_ERR, "%s: line %d: bad field count", _PATH_LOGACCESS,
932198Sguido		       lineno);
942198Sguido		continue;
952198Sguido	    }
962198Sguido	    if (perm[0] != '+' && perm[0] != '-') {
972198Sguido		syslog(LOG_ERR, "%s: line %d: bad first field", _PATH_LOGACCESS,
982198Sguido		       lineno);
992198Sguido		continue;
1002198Sguido	    }
1012198Sguido	    match = (list_match(froms, from, from_match)
1022198Sguido		     && list_match(users, user, user_match));
1032198Sguido	}
1042198Sguido	(void) fclose(fp);
1052198Sguido    } else if (errno != ENOENT) {
1062198Sguido	syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS);
1072198Sguido    }
1082198Sguido    return (match == 0 || (line[0] == '+'));
1092198Sguido}
1102198Sguido
1112198Sguido/* list_match - match an item against a list of tokens with exceptions */
1122198Sguido
1132198Sguidostatic int list_match(list, item, match_fn)
1142198Sguidochar   *list;
1152198Sguidochar   *item;
11687173Smarkmint   (*match_fn) __P((char *, char *));
1172198Sguido{
1182198Sguido    char   *tok;
1192198Sguido    int     match = NO;
1202198Sguido
1212198Sguido    /*
1222198Sguido     * Process tokens one at a time. We have exhausted all possible matches
1232198Sguido     * when we reach an "EXCEPT" token or the end of the list. If we do find
1242198Sguido     * a match, look for an "EXCEPT" list and recurse to determine whether
1252198Sguido     * the match is affected by any exceptions.
1262198Sguido     */
1272198Sguido
1282198Sguido    for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
1292198Sguido	if (strcasecmp(tok, "EXCEPT") == 0)	/* EXCEPT: give up */
1302198Sguido	    break;
13122230Spst	if ((match = (*match_fn)(tok, item)) != NULL)	/* YES */
1322198Sguido	    break;
1332198Sguido    }
1342198Sguido    /* Process exceptions to matches. */
1352198Sguido
1362198Sguido    if (match != NO) {
1372198Sguido	while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
1382198Sguido	     /* VOID */ ;
1392198Sguido	if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
1402198Sguido	    return (match);
1412198Sguido    }
1422198Sguido    return (NO);
1432198Sguido}
1442198Sguido
1452198Sguido/* netgroup_match - match group against machine or user */
1462198Sguido
1472198Sguidostatic int netgroup_match(group, machine, user)
14887173Smarkmchar   *group __unused;
14987173Smarkmchar   *machine __unused;
15087173Smarkmchar   *user __unused;
1512198Sguido{
1522198Sguido    syslog(LOG_ERR, "NIS netgroup support not configured");
15322230Spst    return 0;
1542198Sguido}
1552198Sguido
1562198Sguido/* user_match - match a username against one token */
1572198Sguido
1582198Sguidostatic int user_match(tok, string)
1592198Sguidochar   *tok;
1602198Sguidochar   *string;
1612198Sguido{
1622198Sguido    struct group *group;
1632198Sguido    int     i;
1642198Sguido
1652198Sguido    /*
1662198Sguido     * If a token has the magic value "ALL" the match always succeeds.
1672198Sguido     * Otherwise, return YES if the token fully matches the username, or if
1682198Sguido     * the token is a group that contains the username.
1692198Sguido     */
1702198Sguido
1712198Sguido    if (tok[0] == '@') {			/* netgroup */
1722198Sguido	return (netgroup_match(tok + 1, (char *) 0, string));
1732198Sguido    } else if (string_match(tok, string)) {	/* ALL or exact match */
1742198Sguido	return (YES);
17522230Spst    } else if ((group = getgrnam(tok)) != NULL) {/* try group membership */
1762198Sguido	for (i = 0; group->gr_mem[i]; i++)
1772198Sguido	    if (strcasecmp(string, group->gr_mem[i]) == 0)
1782198Sguido		return (YES);
1792198Sguido    }
1802198Sguido    return (NO);
1812198Sguido}
1822198Sguido
1832198Sguido/* from_match - match a host or tty against a list of tokens */
1842198Sguido
1852198Sguidostatic int from_match(tok, string)
1862198Sguidochar   *tok;
1872198Sguidochar   *string;
1882198Sguido{
1892198Sguido    int     tok_len;
1902198Sguido    int     str_len;
1912198Sguido
1922198Sguido    /*
1932198Sguido     * If a token has the magic value "ALL" the match always succeeds. Return
1942198Sguido     * YES if the token fully matches the string. If the token is a domain
1952198Sguido     * name, return YES if it matches the last fields of the string. If the
1962198Sguido     * token has the magic value "LOCAL", return YES if the string does not
1972198Sguido     * contain a "." character. If the token is a network number, return YES
1982198Sguido     * if it matches the head of the string.
1992198Sguido     */
2002198Sguido
2012198Sguido    if (tok[0] == '@') {			/* netgroup */
2022198Sguido	return (netgroup_match(tok + 1, string, (char *) 0));
2032198Sguido    } else if (string_match(tok, string)) {	/* ALL or exact match */
2042198Sguido	return (YES);
2052198Sguido    } else if (tok[0] == '.') {			/* domain: match last fields */
2062198Sguido	if ((str_len = strlen(string)) > (tok_len = strlen(tok))
2072198Sguido	    && strcasecmp(tok, string + str_len - tok_len) == 0)
2082198Sguido	    return (YES);
2092198Sguido    } else if (strcasecmp(tok, "LOCAL") == 0) {	/* local: no dots */
2102198Sguido	if (strchr(string, '.') == 0)
2112198Sguido	    return (YES);
2122198Sguido    } else if (tok[(tok_len = strlen(tok)) - 1] == '.'	/* network */
2132198Sguido	       && strncmp(tok, string, tok_len) == 0) {
2142198Sguido	return (YES);
2152198Sguido    }
2162198Sguido    return (NO);
2172198Sguido}
2182198Sguido
2192198Sguido/* string_match - match a string against one token */
2202198Sguido
2212198Sguidostatic int string_match(tok, string)
2222198Sguidochar   *tok;
2232198Sguidochar   *string;
2242198Sguido{
2252198Sguido
2262198Sguido    /*
2272198Sguido     * If the token has the magic value "ALL" the match always succeeds.
2282198Sguido     * Otherwise, return YES if the token fully matches the string.
2292198Sguido     */
2302198Sguido
2312198Sguido    if (strcasecmp(tok, "ALL") == 0) {		/* all: always matches */
2322198Sguido	return (YES);
2332198Sguido    } else if (strcasecmp(tok, string) == 0) {	/* try exact match */
2342198Sguido	return (YES);
2352198Sguido    }
2362198Sguido    return (NO);
2372198Sguido}
2382198Sguido#endif /* LOGIN_ACCES */
239