login_access.c revision 169976
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
1087628Sdwmalone#if 0
112198Sguido#ifndef lint
1287628Sdwmalonestatic char sccsid[] = "%Z% %M% %I% %E% %U%";
132198Sguido#endif
1487628Sdwmalone#endif
152198Sguido
1687628Sdwmalone#include <sys/cdefs.h>
1787628Sdwmalone__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_login_access/login_access.c 169976 2007-05-25 07:50:18Z des $");
1887628Sdwmalone
1987177Smarkm#include <sys/types.h>
202198Sguido#include <ctype.h>
2187177Smarkm#include <errno.h>
222198Sguido#include <grp.h>
23169976Sdes#include <netdb.h>
2487177Smarkm#include <stdio.h>
2587177Smarkm#include <stdlib.h>
262198Sguido#include <string.h>
2787177Smarkm#include <syslog.h>
282198Sguido#include <unistd.h>
292198Sguido
3090145Smarkm#include "pam_login_access.h"
3190145Smarkm
3290093Sdes#define _PATH_LOGACCESS		"/etc/login.access"
332198Sguido
342198Sguido /* Delimiters for fields and for lists of users, ttys or hosts. */
352198Sguido
362198Sguidostatic char fs[] = ":";			/* field separator */
372198Sguidostatic char sep[] = ", \t";		/* list-element separator */
382198Sguido
392198Sguido /* Constants to be used in assignments only, not in comparisons... */
402198Sguido
412198Sguido#define YES             1
422198Sguido#define NO              0
432198Sguido
4490145Smarkmstatic int	from_match(const char *, const char *);
4590145Smarkmstatic int	list_match(char *, const char *,
4690145Smarkm				int (*)(const char *, const char *));
4790145Smarkmstatic int	netgroup_match(const char *, const char *, const char *);
4890145Smarkmstatic int	string_match(const char *, const char *);
4990145Smarkmstatic int	user_match(const char *, const char *);
502198Sguido
512198Sguido/* login_access - match username/group and host/tty with access control file */
522198Sguido
5322230Spstint
5490145Smarkmlogin_access(const char *user, const char *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
11390145Smarkmstatic int
11490145Smarkmlist_match(char *list, const char *item,
11590145Smarkm    int (*match_fn)(const char *, const char *))
1162198Sguido{
1172198Sguido    char   *tok;
1182198Sguido    int     match = NO;
1192198Sguido
1202198Sguido    /*
1212198Sguido     * Process tokens one at a time. We have exhausted all possible matches
1222198Sguido     * when we reach an "EXCEPT" token or the end of the list. If we do find
1232198Sguido     * a match, look for an "EXCEPT" list and recurse to determine whether
1242198Sguido     * the match is affected by any exceptions.
1252198Sguido     */
1262198Sguido
1272198Sguido    for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
1282198Sguido	if (strcasecmp(tok, "EXCEPT") == 0)	/* EXCEPT: give up */
1292198Sguido	    break;
130126643Smarkm	if ((match = (*match_fn)(tok, item)) != 0)	/* YES */
1312198Sguido	    break;
1322198Sguido    }
1332198Sguido    /* Process exceptions to matches. */
1342198Sguido
1352198Sguido    if (match != NO) {
1362198Sguido	while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
1372198Sguido	     /* VOID */ ;
1382198Sguido	if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
1392198Sguido	    return (match);
1402198Sguido    }
1412198Sguido    return (NO);
1422198Sguido}
1432198Sguido
1442198Sguido/* netgroup_match - match group against machine or user */
1452198Sguido
14690145Smarkmstatic int
147169976Sdesnetgroup_match(const char *group, const char *machine, const char *user)
1482198Sguido{
149169976Sdes    char domain[1024];
150169976Sdes    unsigned int i;
151169976Sdes
152169976Sdes    if (getdomainname(domain, sizeof(domain)) != 0 || *domain == '\0') {
153169976Sdes	syslog(LOG_ERR, "NIS netgroup support disabled: no NIS domain");
154169976Sdes	return (NO);
155169976Sdes    }
156169976Sdes
157169976Sdes    /* getdomainname() does not reliably terminate the string */
158169976Sdes    for (i = 0; i < sizeof(domain); ++i)
159169976Sdes	if (domain[i] == '\0')
160169976Sdes	    break;
161169976Sdes    if (i == sizeof(domain)) {
162169976Sdes	syslog(LOG_ERR, "NIS netgroup support disabled: invalid NIS domain");
163169976Sdes	return (NO);
164169976Sdes    }
165169976Sdes
166169976Sdes    if (innetgr(group, machine, user, domain) == 1)
167169976Sdes	return (YES);
168169976Sdes    return (NO);
1692198Sguido}
1702198Sguido
1712198Sguido/* user_match - match a username against one token */
1722198Sguido
17390145Smarkmstatic int
17490145Smarkmuser_match(const char *tok, const char *string)
1752198Sguido{
1762198Sguido    struct group *group;
1772198Sguido    int     i;
1782198Sguido
1792198Sguido    /*
1802198Sguido     * If a token has the magic value "ALL" the match always succeeds.
1812198Sguido     * Otherwise, return YES if the token fully matches the username, or if
1822198Sguido     * the token is a group that contains the username.
1832198Sguido     */
1842198Sguido
1852198Sguido    if (tok[0] == '@') {			/* netgroup */
1862198Sguido	return (netgroup_match(tok + 1, (char *) 0, string));
1872198Sguido    } else if (string_match(tok, string)) {	/* ALL or exact match */
1882198Sguido	return (YES);
18922230Spst    } else if ((group = getgrnam(tok)) != NULL) {/* try group membership */
1902198Sguido	for (i = 0; group->gr_mem[i]; i++)
1912198Sguido	    if (strcasecmp(string, group->gr_mem[i]) == 0)
1922198Sguido		return (YES);
1932198Sguido    }
1942198Sguido    return (NO);
1952198Sguido}
1962198Sguido
1972198Sguido/* from_match - match a host or tty against a list of tokens */
1982198Sguido
19990145Smarkmstatic int
20090145Smarkmfrom_match(const char *tok, const char *string)
2012198Sguido{
2022198Sguido    int     tok_len;
2032198Sguido    int     str_len;
2042198Sguido
2052198Sguido    /*
2062198Sguido     * If a token has the magic value "ALL" the match always succeeds. Return
2072198Sguido     * YES if the token fully matches the string. If the token is a domain
2082198Sguido     * name, return YES if it matches the last fields of the string. If the
2092198Sguido     * token has the magic value "LOCAL", return YES if the string does not
2102198Sguido     * contain a "." character. If the token is a network number, return YES
2112198Sguido     * if it matches the head of the string.
2122198Sguido     */
2132198Sguido
2142198Sguido    if (tok[0] == '@') {			/* netgroup */
2152198Sguido	return (netgroup_match(tok + 1, string, (char *) 0));
2162198Sguido    } else if (string_match(tok, string)) {	/* ALL or exact match */
2172198Sguido	return (YES);
2182198Sguido    } else if (tok[0] == '.') {			/* domain: match last fields */
2192198Sguido	if ((str_len = strlen(string)) > (tok_len = strlen(tok))
2202198Sguido	    && strcasecmp(tok, string + str_len - tok_len) == 0)
2212198Sguido	    return (YES);
2222198Sguido    } else if (strcasecmp(tok, "LOCAL") == 0) {	/* local: no dots */
2232198Sguido	if (strchr(string, '.') == 0)
2242198Sguido	    return (YES);
2252198Sguido    } else if (tok[(tok_len = strlen(tok)) - 1] == '.'	/* network */
2262198Sguido	       && strncmp(tok, string, tok_len) == 0) {
2272198Sguido	return (YES);
2282198Sguido    }
2292198Sguido    return (NO);
2302198Sguido}
2312198Sguido
2322198Sguido/* string_match - match a string against one token */
2332198Sguido
23490145Smarkmstatic int
23590145Smarkmstring_match(const char *tok, const char *string)
2362198Sguido{
2372198Sguido
2382198Sguido    /*
2392198Sguido     * If the token has the magic value "ALL" the match always succeeds.
2402198Sguido     * Otherwise, return YES if the token fully matches the string.
2412198Sguido     */
2422198Sguido
2432198Sguido    if (strcasecmp(tok, "ALL") == 0) {		/* all: always matches */
2442198Sguido	return (YES);
2452198Sguido    } else if (strcasecmp(tok, string) == 0) {	/* try exact match */
2462198Sguido	return (YES);
2472198Sguido    }
2482198Sguido    return (NO);
2492198Sguido}
250