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