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