login_access.c revision 87233
1189251Ssam /*
2189251Ssam  * This module implements a simple but effective form of login access
3281806Srpaulo  * control based on login names and on host (or domain) names, internet
4189251Ssam  * addresses (or network numbers), or on terminal line names in case of
5252726Srpaulo  * non-networked logins. Diagnostics are reported through syslog(3).
6252726Srpaulo  *
7189251Ssam  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
8189251Ssam  */
9189251Ssam
10189251Ssam#include <sys/cdefs.h>
11189251Ssam
12189251Ssam__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_login_access/login_access.c 87233 2001-12-02 20:54:57Z markm $");
13189251Ssam
14189251Ssam#ifdef LOGIN_ACCESS
15189251Ssam#ifndef lint
16189251Ssamstatic const char sccsid[] = "%Z% %M% %I% %E% %U%";
17189251Ssam#endif
18189251Ssam
19189251Ssam#include <sys/types.h>
20189251Ssam#include <ctype.h>
21281806Srpaulo#include <errno.h>
22281806Srpaulo#include <grp.h>
23281806Srpaulo#include <stdio.h>
24281806Srpaulo#include <stdlib.h>
25289549Srpaulo#include <string.h>
26289549Srpaulo#include <syslog.h>
27281806Srpaulo#include <unistd.h>
28281806Srpaulo
29189251Ssam#include "login.h"
30189251Ssam#include "pathnames.h"
31189251Ssam
32189251Ssam /* Delimiters for fields and for lists of users, ttys or hosts. */
33189251Ssam
34189251Ssamstatic char fs[] = ":";			/* field separator */
35189251Ssamstatic char sep[] = ", \t";		/* list-element separator */
36189251Ssam
37189251Ssam /* Constants to be used in assignments only, not in comparisons... */
38189251Ssam
39189251Ssam#define YES             1
40189251Ssam#define NO              0
41252726Srpaulo
42189251Ssamstatic int	from_match __P((char *, char *));
43189251Ssamstatic int	list_match __P((char *, char *, int (*)(char *, char *)));
44189251Ssamstatic int	netgroup_match __P((char *, char *, char *));
45189251Ssamstatic int	string_match __P((char *, char *));
46189251Ssamstatic int	user_match __P((char *, char *));
47189251Ssam
48189251Ssam/* login_access - match username/group and host/tty with access control file */
49189251Ssam
50189251Ssamint
51189251Ssamlogin_access(user, from)
52189251Ssamchar   *user;
53189251Ssamchar   *from;
54189251Ssam{
55252726Srpaulo    FILE   *fp;
56252726Srpaulo    char    line[BUFSIZ];
57252726Srpaulo    char   *perm;			/* becomes permission field */
58281806Srpaulo    char   *users;			/* becomes list of login names */
59281806Srpaulo    char   *froms;			/* becomes list of terminals or hosts */
60281806Srpaulo    int     match = NO;
61281806Srpaulo    int     end;
62252726Srpaulo    int     lineno = 0;			/* for diagnostics */
63281806Srpaulo
64189251Ssam    /*
65189251Ssam     * Process the table one line at a time and stop at the first match.
66189251Ssam     * Blank lines and lines that begin with a '#' character are ignored.
67189251Ssam     * Non-comment lines are broken at the ':' character. All fields are
68189251Ssam     * mandatory. The first field should be a "+" or "-" character. A
69189251Ssam     * non-existing table means no access control.
70189251Ssam     */
71189251Ssam
72252726Srpaulo    if ((fp = fopen(_PATH_LOGACCESS, "r")) != NULL) {
73252726Srpaulo	while (!match && fgets(line, sizeof(line), fp)) {
74281806Srpaulo	    lineno++;
75281806Srpaulo	    if (line[end = strlen(line) - 1] != '\n') {
76281806Srpaulo		syslog(LOG_ERR, "%s: line %d: missing newline or line too long",
77281806Srpaulo		       _PATH_LOGACCESS, lineno);
78281806Srpaulo		continue;
79189251Ssam	    }
80189251Ssam	    if (line[0] == '#')
81189251Ssam		continue;			/* comment line */
82189251Ssam	    while (end > 0 && isspace(line[end - 1]))
83189251Ssam		end--;
84189251Ssam	    line[end] = 0;			/* strip trailing whitespace */
85189251Ssam	    if (line[0] == 0)			/* skip blank lines */
86189251Ssam		continue;
87189251Ssam	    if (!(perm = strtok(line, fs))
88189251Ssam		|| !(users = strtok((char *) 0, fs))
89189251Ssam		|| !(froms = strtok((char *) 0, fs))
90189251Ssam		|| strtok((char *) 0, fs)) {
91189251Ssam		syslog(LOG_ERR, "%s: line %d: bad field count", _PATH_LOGACCESS,
92189251Ssam		       lineno);
93189251Ssam		continue;
94189251Ssam	    }
95189251Ssam	    if (perm[0] != '+' && perm[0] != '-') {
96189251Ssam		syslog(LOG_ERR, "%s: line %d: bad first field", _PATH_LOGACCESS,
97189251Ssam		       lineno);
98252726Srpaulo		continue;
99252726Srpaulo	    }
100252726Srpaulo	    match = (list_match(froms, from, from_match)
101189251Ssam		     && list_match(users, user, user_match));
102281806Srpaulo	}
103281806Srpaulo	(void) fclose(fp);
104281806Srpaulo    } else if (errno != ENOENT) {
105189251Ssam	syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS);
106189251Ssam    }
107189251Ssam    return (match == 0 || (line[0] == '+'));
108189251Ssam}
109189251Ssam
110189251Ssam/* list_match - match an item against a list of tokens with exceptions */
111189251Ssam
112189251Ssamstatic int list_match(list, item, match_fn)
113189251Ssamchar   *list;
114189251Ssamchar   *item;
115189251Ssamint   (*match_fn) __P((char *, char *));
116189251Ssam{
117189251Ssam    char   *tok;
118189251Ssam    int     match = NO;
119189251Ssam
120189251Ssam    /*
121189251Ssam     * Process tokens one at a time. We have exhausted all possible matches
122281806Srpaulo     * when we reach an "EXCEPT" token or the end of the list. If we do find
123189251Ssam     * a match, look for an "EXCEPT" list and recurse to determine whether
124189251Ssam     * the match is affected by any exceptions.
125189251Ssam     */
126189251Ssam
127189251Ssam    for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
128189251Ssam	if (strcasecmp(tok, "EXCEPT") == 0)	/* EXCEPT: give up */
129189251Ssam	    break;
130189251Ssam	if ((match = (*match_fn)(tok, item)) != NULL)	/* YES */
131189251Ssam	    break;
132189251Ssam    }
133252726Srpaulo    /* Process exceptions to matches. */
134189251Ssam
135252726Srpaulo    if (match != NO) {
136252726Srpaulo	while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
137252726Srpaulo	     /* VOID */ ;
138252726Srpaulo	if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
139252726Srpaulo	    return (match);
140189251Ssam    }
141189251Ssam    return (NO);
142189251Ssam}
143189251Ssam
144189251Ssam/* netgroup_match - match group against machine or user */
145189251Ssam
146189251Ssamstatic int netgroup_match(group, machine, user)
147189251Ssamchar   *group __unused;
148189251Ssamchar   *machine __unused;
149189251Ssamchar   *user __unused;
150189251Ssam{
151281806Srpaulo    syslog(LOG_ERR, "NIS netgroup support not configured");
152189251Ssam    return 0;
153189251Ssam}
154189251Ssam
155189251Ssam/* user_match - match a username against one token */
156189251Ssam
157189251Ssamstatic int user_match(tok, string)
158189251Ssamchar   *tok;
159189251Ssamchar   *string;
160189251Ssam{
161189251Ssam    struct group *group;
162189251Ssam    int     i;
163189251Ssam
164189251Ssam    /*
165189251Ssam     * If a token has the magic value "ALL" the match always succeeds.
166189251Ssam     * Otherwise, return YES if the token fully matches the username, or if
167189251Ssam     * the token is a group that contains the username.
168189251Ssam     */
169189251Ssam
170189251Ssam    if (tok[0] == '@') {			/* netgroup */
171189251Ssam	return (netgroup_match(tok + 1, (char *) 0, string));
172189251Ssam    } else if (string_match(tok, string)) {	/* ALL or exact match */
173189251Ssam	return (YES);
174189251Ssam    } else if ((group = getgrnam(tok)) != NULL) {/* try group membership */
175189251Ssam	for (i = 0; group->gr_mem[i]; i++)
176189251Ssam	    if (strcasecmp(string, group->gr_mem[i]) == 0)
177189251Ssam		return (YES);
178189251Ssam    }
179189251Ssam    return (NO);
180189251Ssam}
181189251Ssam
182189251Ssam/* from_match - match a host or tty against a list of tokens */
183189251Ssam
184189251Ssamstatic int from_match(tok, string)
185281806Srpaulochar   *tok;
186281806Srpaulochar   *string;
187281806Srpaulo{
188281806Srpaulo    int     tok_len;
189281806Srpaulo    int     str_len;
190281806Srpaulo
191281806Srpaulo    /*
192281806Srpaulo     * If a token has the magic value "ALL" the match always succeeds. Return
193281806Srpaulo     * YES if the token fully matches the string. If the token is a domain
194281806Srpaulo     * name, return YES if it matches the last fields of the string. If the
195281806Srpaulo     * token has the magic value "LOCAL", return YES if the string does not
196281806Srpaulo     * contain a "." character. If the token is a network number, return YES
197281806Srpaulo     * if it matches the head of the string.
198281806Srpaulo     */
199281806Srpaulo
200281806Srpaulo    if (tok[0] == '@') {			/* netgroup */
201281806Srpaulo	return (netgroup_match(tok + 1, string, (char *) 0));
202281806Srpaulo    } else if (string_match(tok, string)) {	/* ALL or exact match */
203281806Srpaulo	return (YES);
204281806Srpaulo    } else if (tok[0] == '.') {			/* domain: match last fields */
205189251Ssam	if ((str_len = strlen(string)) > (tok_len = strlen(tok))
206189251Ssam	    && strcasecmp(tok, string + str_len - tok_len) == 0)
207189251Ssam	    return (YES);
208189251Ssam    } else if (strcasecmp(tok, "LOCAL") == 0) {	/* local: no dots */
209189251Ssam	if (strchr(string, '.') == 0)
210281806Srpaulo	    return (YES);
211281806Srpaulo    } else if (tok[(tok_len = strlen(tok)) - 1] == '.'	/* network */
212281806Srpaulo	       && strncmp(tok, string, tok_len) == 0) {
213281806Srpaulo	return (YES);
214281806Srpaulo    }
215281806Srpaulo    return (NO);
216324698Sgordon}
217281806Srpaulo
218189251Ssam/* string_match - match a string against one token */
219324698Sgordon
220324698Sgordonstatic int string_match(tok, string)
221324698Sgordonchar   *tok;
222324698Sgordonchar   *string;
223189251Ssam{
224324698Sgordon
225324698Sgordon    /*
226324698Sgordon     * If the token has the magic value "ALL" the match always succeeds.
227324698Sgordon     * Otherwise, return YES if the token fully matches the string.
228324698Sgordon     */
229324698Sgordon
230324698Sgordon    if (strcasecmp(tok, "ALL") == 0) {		/* all: always matches */
231189251Ssam	return (YES);
232189251Ssam    } else if (strcasecmp(tok, string) == 0) {	/* try exact match */
233189251Ssam	return (YES);
234189251Ssam    }
235189251Ssam    return (NO);
236189251Ssam}
237189251Ssam#endif /* LOGIN_ACCES */
238189251Ssam