login_access.c revision 87177
1290001Sglebius /*
2290001Sglebius  * This module implements a simple but effective form of login access
3290001Sglebius  * control based on login names and on host (or domain) names, internet
4290001Sglebius  * addresses (or network numbers), or on terminal line names in case of
5290001Sglebius  * non-networked logins. Diagnostics are reported through syslog(3).
6290001Sglebius  *
7290001Sglebius  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
8290001Sglebius  * $FreeBSD: head/lib/libpam/modules/pam_login_access/login_access.c 87177 2001-12-01 21:12:04Z markm $
9132451Sroberto  */
10132451Sroberto
11290001Sglebius#ifdef LOGIN_ACCESS
12290001Sglebius#ifndef lint
13132451Srobertostatic const char sccsid[] = "%Z% %M% %I% %E% %U%";
14132451Sroberto#endif
15132451Sroberto
16290001Sglebius#include <sys/types.h>
17132451Sroberto#include <ctype.h>
18290001Sglebius#include <errno.h>
19132451Sroberto#include <grp.h>
20132451Sroberto#include <stdio.h>
21132451Sroberto#include <stdlib.h>
22132451Sroberto#include <string.h>
23132451Sroberto#include <syslog.h>
24132451Sroberto#include <unistd.h>
25132451Sroberto
26132451Sroberto#include "login.h"
27132451Sroberto#include "pathnames.h"
28132451Sroberto
29290001Sglebius /* Delimiters for fields and for lists of users, ttys or hosts. */
30132451Sroberto
31290001Sglebiusstatic char fs[] = ":";			/* field separator */
32132451Srobertostatic char sep[] = ", \t";		/* list-element separator */
33290001Sglebius
34290001Sglebius /* Constants to be used in assignments only, not in comparisons... */
35290001Sglebius
36290001Sglebius#define YES             1
37290001Sglebius#define NO              0
38290001Sglebius
39290001Sglebiusstatic int	from_match __P((char *, char *));
40290001Sglebiusstatic int	list_match __P((char *, char *, int (*)(char *, char *)));
41290001Sglebiusstatic int	netgroup_match __P((char *, char *, char *));
42290001Sglebiusstatic int	string_match __P((char *, char *));
43290001Sglebiusstatic int	user_match __P((char *, char *));
44290001Sglebius
45290001Sglebius/* login_access - match username/group and host/tty with access control file */
46290001Sglebius
47290001Sglebiusint
48290001Sglebiuslogin_access(user, from)
49290001Sglebiuschar   *user;
50290001Sglebiuschar   *from;
51290001Sglebius{
52132451Sroberto    FILE   *fp;
53290001Sglebius    char    line[BUFSIZ];
54290001Sglebius    char   *perm;			/* becomes permission field */
55290001Sglebius    char   *users;			/* becomes list of login names */
56132451Sroberto    char   *froms;			/* becomes list of terminals or hosts */
57290001Sglebius    int     match = NO;
58290001Sglebius    int     end;
59290001Sglebius    int     lineno = 0;			/* for diagnostics */
60290001Sglebius
61132451Sroberto    /*
62132451Sroberto     * Process the table one line at a time and stop at the first match.
63290001Sglebius     * Blank lines and lines that begin with a '#' character are ignored.
64290001Sglebius     * Non-comment lines are broken at the ':' character. All fields are
65290001Sglebius     * mandatory. The first field should be a "+" or "-" character. A
66132451Sroberto     * non-existing table means no access control.
67290001Sglebius     */
68290001Sglebius
69290001Sglebius    if ((fp = fopen(_PATH_LOGACCESS, "r")) != NULL) {
70290001Sglebius	while (!match && fgets(line, sizeof(line), fp)) {
71290001Sglebius	    lineno++;
72290001Sglebius	    if (line[end = strlen(line) - 1] != '\n') {
73132451Sroberto		syslog(LOG_ERR, "%s: line %d: missing newline or line too long",
74132451Sroberto		       _PATH_LOGACCESS, lineno);
75132451Sroberto		continue;
76132451Sroberto	    }
77132451Sroberto	    if (line[0] == '#')
78290001Sglebius		continue;			/* comment line */
79290001Sglebius	    while (end > 0 && isspace(line[end - 1]))
80290001Sglebius		end--;
81290001Sglebius	    line[end] = 0;			/* strip trailing whitespace */
82290001Sglebius	    if (line[0] == 0)			/* skip blank lines */
83290001Sglebius		continue;
84290001Sglebius	    if (!(perm = strtok(line, fs))
85290001Sglebius		|| !(users = strtok((char *) 0, fs))
86290001Sglebius		|| !(froms = strtok((char *) 0, fs))
87290001Sglebius		|| strtok((char *) 0, fs)) {
88290001Sglebius		syslog(LOG_ERR, "%s: line %d: bad field count", _PATH_LOGACCESS,
89132451Sroberto		       lineno);
90290001Sglebius		continue;
91132451Sroberto	    }
92132451Sroberto	    if (perm[0] != '+' && perm[0] != '-') {
93290001Sglebius		syslog(LOG_ERR, "%s: line %d: bad first field", _PATH_LOGACCESS,
94132451Sroberto		       lineno);
95290001Sglebius		continue;
96290001Sglebius	    }
97290001Sglebius	    match = (list_match(froms, from, from_match)
98290001Sglebius		     && list_match(users, user, user_match));
99290001Sglebius	}
100290001Sglebius	(void) fclose(fp);
101290001Sglebius    } else if (errno != ENOENT) {
102290001Sglebius	syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS);
103132451Sroberto    }
104290001Sglebius    return (match == 0 || (line[0] == '+'));
105132451Sroberto}
106290001Sglebius
107290001Sglebius/* list_match - match an item against a list of tokens with exceptions */
108290001Sglebius
109290001Sglebiusstatic int list_match(list, item, match_fn)
110290001Sglebiuschar   *list;
111290001Sglebiuschar   *item;
112290001Sglebiusint   (*match_fn) __P((char *, char *));
113290001Sglebius{
114290001Sglebius    char   *tok;
115290001Sglebius    int     match = NO;
116290001Sglebius
117290001Sglebius    /*
118290001Sglebius     * Process tokens one at a time. We have exhausted all possible matches
119290001Sglebius     * when we reach an "EXCEPT" token or the end of the list. If we do find
120290001Sglebius     * a match, look for an "EXCEPT" list and recurse to determine whether
121290001Sglebius     * the match is affected by any exceptions.
122290001Sglebius     */
123290001Sglebius
124290001Sglebius    for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
125290001Sglebius	if (strcasecmp(tok, "EXCEPT") == 0)	/* EXCEPT: give up */
126290001Sglebius	    break;
127290001Sglebius	if ((match = (*match_fn)(tok, item)) != NULL)	/* YES */
128290001Sglebius	    break;
129290001Sglebius    }
130290001Sglebius    /* Process exceptions to matches. */
131290001Sglebius
132290001Sglebius    if (match != NO) {
133290001Sglebius	while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
134290001Sglebius	     /* VOID */ ;
135290001Sglebius	if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
136290001Sglebius	    return (match);
137290001Sglebius    }
138290001Sglebius    return (NO);
139290001Sglebius}
140290001Sglebius
141290001Sglebius/* netgroup_match - match group against machine or user */
142290001Sglebius
143290001Sglebiusstatic int netgroup_match(group, machine, user)
144290001Sglebiuschar   *group __unused;
145290001Sglebiuschar   *machine __unused;
146290001Sglebiuschar   *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