login_access.c revision 90093
1 /*
2  * This module implements a simple but effective form of login access
3  * control based on login names and on host (or domain) names, internet
4  * addresses (or network numbers), or on terminal line names in case of
5  * non-networked logins. Diagnostics are reported through syslog(3).
6  *
7  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
8  */
9
10#if 0
11#ifndef lint
12static char sccsid[] = "%Z% %M% %I% %E% %U%";
13#endif
14#endif
15
16#include <sys/cdefs.h>
17__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_login_access/login_access.c 90093 2002-02-01 22:25:07Z des $");
18
19#include <sys/types.h>
20#include <ctype.h>
21#include <errno.h>
22#include <grp.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <syslog.h>
27#include <unistd.h>
28
29#define _PATH_LOGACCESS		"/etc/login.access"
30
31 /* Delimiters for fields and for lists of users, ttys or hosts. */
32
33static char fs[] = ":";			/* field separator */
34static char sep[] = ", \t";		/* list-element separator */
35
36 /* Constants to be used in assignments only, not in comparisons... */
37
38#define YES             1
39#define NO              0
40
41static int	from_match __P((const char *, const char *));
42static int	list_match __P((char *, const char *,
43				int (*)(const char *, const char *)));
44static int	netgroup_match __P((const char *, const char *, const char *));
45static int	string_match __P((const char *, const char *));
46static int	user_match __P((const char *, const char *));
47
48/* login_access - match username/group and host/tty with access control file */
49
50int
51login_access(user, from)
52const char   *user;
53const char   *from;
54{
55    FILE   *fp;
56    char    line[BUFSIZ];
57    char   *perm;			/* becomes permission field */
58    char   *users;			/* becomes list of login names */
59    char   *froms;			/* becomes list of terminals or hosts */
60    int     match = NO;
61    int     end;
62    int     lineno = 0;			/* for diagnostics */
63
64    /*
65     * Process the table one line at a time and stop at the first match.
66     * Blank lines and lines that begin with a '#' character are ignored.
67     * Non-comment lines are broken at the ':' character. All fields are
68     * mandatory. The first field should be a "+" or "-" character. A
69     * non-existing table means no access control.
70     */
71
72    if ((fp = fopen(_PATH_LOGACCESS, "r")) != NULL) {
73	while (!match && fgets(line, sizeof(line), fp)) {
74	    lineno++;
75	    if (line[end = strlen(line) - 1] != '\n') {
76		syslog(LOG_ERR, "%s: line %d: missing newline or line too long",
77		       _PATH_LOGACCESS, lineno);
78		continue;
79	    }
80	    if (line[0] == '#')
81		continue;			/* comment line */
82	    while (end > 0 && isspace(line[end - 1]))
83		end--;
84	    line[end] = 0;			/* strip trailing whitespace */
85	    if (line[0] == 0)			/* skip blank lines */
86		continue;
87	    if (!(perm = strtok(line, fs))
88		|| !(users = strtok((char *) 0, fs))
89		|| !(froms = strtok((char *) 0, fs))
90		|| strtok((char *) 0, fs)) {
91		syslog(LOG_ERR, "%s: line %d: bad field count", _PATH_LOGACCESS,
92		       lineno);
93		continue;
94	    }
95	    if (perm[0] != '+' && perm[0] != '-') {
96		syslog(LOG_ERR, "%s: line %d: bad first field", _PATH_LOGACCESS,
97		       lineno);
98		continue;
99	    }
100	    match = (list_match(froms, from, from_match)
101		     && list_match(users, user, user_match));
102	}
103	(void) fclose(fp);
104    } else if (errno != ENOENT) {
105	syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS);
106    }
107    return (match == 0 || (line[0] == '+'));
108}
109
110/* list_match - match an item against a list of tokens with exceptions */
111
112static int list_match(list, item, match_fn)
113char         *list;
114const char   *item;
115int         (*match_fn) __P((const char *, const char *));
116{
117    char   *tok;
118    int     match = NO;
119
120    /*
121     * Process tokens one at a time. We have exhausted all possible matches
122     * when we reach an "EXCEPT" token or the end of the list. If we do find
123     * a match, look for an "EXCEPT" list and recurse to determine whether
124     * the match is affected by any exceptions.
125     */
126
127    for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
128	if (strcasecmp(tok, "EXCEPT") == 0)	/* EXCEPT: give up */
129	    break;
130	if ((match = (*match_fn)(tok, item)) != NULL)	/* YES */
131	    break;
132    }
133    /* Process exceptions to matches. */
134
135    if (match != NO) {
136	while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
137	     /* VOID */ ;
138	if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
139	    return (match);
140    }
141    return (NO);
142}
143
144/* netgroup_match - match group against machine or user */
145
146static int netgroup_match(group, machine, user)
147const char   *group __unused;
148const char   *machine __unused;
149const char   *user __unused;
150{
151    syslog(LOG_ERR, "NIS netgroup support not configured");
152    return 0;
153}
154
155/* user_match - match a username against one token */
156
157static int user_match(tok, string)
158const char   *tok;
159const char   *string;
160{
161    struct group *group;
162    int     i;
163
164    /*
165     * If a token has the magic value "ALL" the match always succeeds.
166     * Otherwise, return YES if the token fully matches the username, or if
167     * the token is a group that contains the username.
168     */
169
170    if (tok[0] == '@') {			/* netgroup */
171	return (netgroup_match(tok + 1, (char *) 0, string));
172    } else if (string_match(tok, string)) {	/* ALL or exact match */
173	return (YES);
174    } else if ((group = getgrnam(tok)) != NULL) {/* try group membership */
175	for (i = 0; group->gr_mem[i]; i++)
176	    if (strcasecmp(string, group->gr_mem[i]) == 0)
177		return (YES);
178    }
179    return (NO);
180}
181
182/* from_match - match a host or tty against a list of tokens */
183
184static int from_match(tok, string)
185const char   *tok;
186const char   *string;
187{
188    int     tok_len;
189    int     str_len;
190
191    /*
192     * If a token has the magic value "ALL" the match always succeeds. Return
193     * YES if the token fully matches the string. If the token is a domain
194     * name, return YES if it matches the last fields of the string. If the
195     * token has the magic value "LOCAL", return YES if the string does not
196     * contain a "." character. If the token is a network number, return YES
197     * if it matches the head of the string.
198     */
199
200    if (tok[0] == '@') {			/* netgroup */
201	return (netgroup_match(tok + 1, string, (char *) 0));
202    } else if (string_match(tok, string)) {	/* ALL or exact match */
203	return (YES);
204    } else if (tok[0] == '.') {			/* domain: match last fields */
205	if ((str_len = strlen(string)) > (tok_len = strlen(tok))
206	    && strcasecmp(tok, string + str_len - tok_len) == 0)
207	    return (YES);
208    } else if (strcasecmp(tok, "LOCAL") == 0) {	/* local: no dots */
209	if (strchr(string, '.') == 0)
210	    return (YES);
211    } else if (tok[(tok_len = strlen(tok)) - 1] == '.'	/* network */
212	       && strncmp(tok, string, tok_len) == 0) {
213	return (YES);
214    }
215    return (NO);
216}
217
218/* string_match - match a string against one token */
219
220static int string_match(tok, string)
221const char   *tok;
222const char   *string;
223{
224
225    /*
226     * If the token has the magic value "ALL" the match always succeeds.
227     * Otherwise, return YES if the token fully matches the string.
228     */
229
230    if (strcasecmp(tok, "ALL") == 0) {		/* all: always matches */
231	return (YES);
232    } else if (strcasecmp(tok, string) == 0) {	/* try exact match */
233	return (YES);
234    }
235    return (NO);
236}
237