login_access.c revision 87177
1141104Sharti /*
294589Sobrien  * This module implements a simple but effective form of login access
394589Sobrien  * control based on login names and on host (or domain) names, internet
45814Sjkh  * addresses (or network numbers), or on terminal line names in case of
51590Srgrimes  * non-networked logins. Diagnostics are reported through syslog(3).
61590Srgrimes  *
71590Srgrimes  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
81590Srgrimes  * $FreeBSD: head/lib/libpam/modules/pam_login_access/login_access.c 87177 2001-12-01 21:12:04Z markm $
91590Srgrimes  */
101590Srgrimes
111590Srgrimes#ifdef LOGIN_ACCESS
121590Srgrimes#ifndef lint
131590Srgrimesstatic const char sccsid[] = "%Z% %M% %I% %E% %U%";
141590Srgrimes#endif
151590Srgrimes
161590Srgrimes#include <sys/types.h>
171590Srgrimes#include <ctype.h>
181590Srgrimes#include <errno.h>
191590Srgrimes#include <grp.h>
201590Srgrimes#include <stdio.h>
211590Srgrimes#include <stdlib.h>
221590Srgrimes#include <string.h>
231590Srgrimes#include <syslog.h>
241590Srgrimes#include <unistd.h>
251590Srgrimes
261590Srgrimes#include "login.h"
271590Srgrimes#include "pathnames.h"
281590Srgrimes
291590Srgrimes /* Delimiters for fields and for lists of users, ttys or hosts. */
301590Srgrimes
311590Srgrimesstatic char fs[] = ":";			/* field separator */
321590Srgrimesstatic char sep[] = ", \t";		/* list-element separator */
331590Srgrimes
341590Srgrimes /* Constants to be used in assignments only, not in comparisons... */
351590Srgrimes
361590Srgrimes#define YES             1
371590Srgrimes#define NO              0
3862833Swsanchez
3962833Swsanchezstatic int	from_match __P((char *, char *));
401590Srgrimesstatic int	list_match __P((char *, char *, int (*)(char *, char *)));
411590Srgrimesstatic int	netgroup_match __P((char *, char *, char *));
4262833Swsanchezstatic int	string_match __P((char *, char *));
4394587Sobrienstatic int	user_match __P((char *, char *));
441590Srgrimes
4535483Simp/* login_access - match username/group and host/tty with access control file */
46103503Sjmallett
4735483Simpint
4835483Simplogin_access(user, from)
491590Srgrimeschar   *user;
501590Srgrimeschar   *from;
511590Srgrimes{
521590Srgrimes    FILE   *fp;
531590Srgrimes    char    line[BUFSIZ];
54144467Sharti    char   *perm;			/* becomes permission field */
551590Srgrimes    char   *users;			/* becomes list of login names */
56144467Sharti    char   *froms;			/* becomes list of terminals or hosts */
57144467Sharti    int     match = NO;
58144467Sharti    int     end;
59144467Sharti    int     lineno = 0;			/* for diagnostics */
60144467Sharti
61144467Sharti    /*
62144467Sharti     * Process the table one line at a time and stop at the first match.
631590Srgrimes     * Blank lines and lines that begin with a '#' character are ignored.
64144467Sharti     * Non-comment lines are broken at the ':' character. All fields are
65144467Sharti     * mandatory. The first field should be a "+" or "-" character. A
66144467Sharti     * non-existing table means no access control.
67144467Sharti     */
68144467Sharti
691590Srgrimes    if ((fp = fopen(_PATH_LOGACCESS, "r")) != NULL) {
70144467Sharti	while (!match && fgets(line, sizeof(line), fp)) {
71144467Sharti	    lineno++;
72144467Sharti	    if (line[end = strlen(line) - 1] != '\n') {
73144467Sharti		syslog(LOG_ERR, "%s: line %d: missing newline or line too long",
741590Srgrimes		       _PATH_LOGACCESS, lineno);
75144467Sharti		continue;
761590Srgrimes	    }
77144467Sharti	    if (line[0] == '#')
781590Srgrimes		continue;			/* comment line */
79144467Sharti	    while (end > 0 && isspace(line[end - 1]))
80144467Sharti		end--;
81144467Sharti	    line[end] = 0;			/* strip trailing whitespace */
821590Srgrimes	    if (line[0] == 0)			/* skip blank lines */
83144467Sharti		continue;
84144467Sharti	    if (!(perm = strtok(line, fs))
85144467Sharti		|| !(users = strtok((char *) 0, fs))
86144467Sharti		|| !(froms = strtok((char *) 0, fs))
871590Srgrimes		|| strtok((char *) 0, fs)) {
88144467Sharti		syslog(LOG_ERR, "%s: line %d: bad field count", _PATH_LOGACCESS,
89144467Sharti		       lineno);
90144467Sharti		continue;
911590Srgrimes	    }
92144467Sharti	    if (perm[0] != '+' && perm[0] != '-') {
93144467Sharti		syslog(LOG_ERR, "%s: line %d: bad first field", _PATH_LOGACCESS,
94144467Sharti		       lineno);
951590Srgrimes		continue;
96144467Sharti	    }
971590Srgrimes	    match = (list_match(froms, from, from_match)
98144467Sharti		     && list_match(users, user, user_match));
99146132Sharti	}
100146132Sharti	(void) fclose(fp);
101146132Sharti    } else if (errno != ENOENT) {
102146132Sharti	syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS);
103146132Sharti    }
104146132Sharti    return (match == 0 || (line[0] == '+'));
105146132Sharti}
106146132Sharti
107146132Sharti/* list_match - match an item against a list of tokens with exceptions */
108146132Sharti
109146132Shartistatic int list_match(list, item, match_fn)
1101590Srgrimeschar   *list;
1111590Srgrimeschar   *item;
112144494Shartiint   (*match_fn) __P((char *, char *));
1131590Srgrimes{
114141104Sharti    char   *tok;
1151590Srgrimes    int     match = NO;
116107447Sru
117104475Sphk    /*
118107447Sru     * Process tokens one at a time. We have exhausted all possible matches
1191590Srgrimes     * when we reach an "EXCEPT" token or the end of the list. If we do find
120141104Sharti     * a match, look for an "EXCEPT" list and recurse to determine whether
121146160Sjmallett     * the match is affected by any exceptions.
12294506Scharnier     */
1235814Sjkh
124144665Sharti    for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
1251590Srgrimes	if (strcasecmp(tok, "EXCEPT") == 0)	/* EXCEPT: give up */
1265814Sjkh	    break;
127141104Sharti	if ((match = (*match_fn)(tok, item)) != NULL)	/* YES */
12880381Ssheldonh	    break;
12994506Scharnier    }
130141104Sharti    /* Process exceptions to matches. */
131141104Sharti
132142457Sharti    if (match != NO) {
133146056Sharti	while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
1341590Srgrimes	     /* VOID */ ;
135141104Sharti	if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
136141104Sharti	    return (match);
1371590Srgrimes    }
138141104Sharti    return (NO);
139141104Sharti}
1401590Srgrimes
141141104Sharti/* netgroup_match - match group against machine or user */
142146056Sharti
143141104Shartistatic int netgroup_match(group, machine, user)
144141104Shartichar   *group __unused;
145141104Shartichar   *machine __unused;
1461590Srgrimeschar   *user __unused;
147146057Sharti{
148146057Sharti    syslog(LOG_ERR, "NIS netgroup support not configured");
149146057Sharti    return 0;
1501590Srgrimes}
151146057Sharti
152146057Sharti/* user_match - match a username against one token */
153146057Sharti
154146057Shartistatic int user_match(tok, string)
155146057Shartichar   *tok;
156146057Shartichar   *string;
157146057Sharti{
158146057Sharti    struct group *group;
159146057Sharti    int     i;
160144483Sharti
161144483Sharti    /*
162144483Sharti     * If a token has the magic value "ALL" the match always succeeds.
163144483Sharti     * Otherwise, return YES if the token fully matches the username, or if
164144483Sharti     * the token is a group that contains the username.
165144483Sharti     */
166144483Sharti
167144483Sharti    if (tok[0] == '@') {			/* netgroup */
168144483Sharti	return (netgroup_match(tok + 1, (char *) 0, string));
169144483Sharti    } else if (string_match(tok, string)) {	/* ALL or exact match */
170144483Sharti	return (YES);
171144483Sharti    } else if ((group = getgrnam(tok)) != NULL) {/* try group membership */
172144665Sharti	for (i = 0; group->gr_mem[i]; i++)
173144483Sharti	    if (strcasecmp(string, group->gr_mem[i]) == 0)
174144483Sharti		return (YES);
175144483Sharti    }
176144483Sharti    return (NO);
177144483Sharti}
178144483Sharti
179144483Sharti/* from_match - match a host or tty against a list of tokens */
180144483Sharti
181144483Shartistatic int from_match(tok, string)
182144483Shartichar   *tok;
183144483Shartichar   *string;
184144483Sharti{
185144483Sharti    int     tok_len;
186144483Sharti    int     str_len;
187144483Sharti
188144483Sharti    /*
189144483Sharti     * If a token has the magic value "ALL" the match always succeeds. Return
190144483Sharti     * YES if the token fully matches the string. If the token is a domain
191144483Sharti     * name, return YES if it matches the last fields of the string. If the
192144483Sharti     * token has the magic value "LOCAL", return YES if the string does not
193144483Sharti     * contain a "." character. If the token is a network number, return YES
194144483Sharti     * if it matches the head of the string.
195144483Sharti     */
196144483Sharti
197146061Sharti    if (tok[0] == '@') {			/* netgroup */
198144483Sharti	return (netgroup_match(tok + 1, string, (char *) 0));
199144483Sharti    } else if (string_match(tok, string)) {	/* ALL or exact match */
200144483Sharti	return (YES);
201144483Sharti    } else if (tok[0] == '.') {			/* domain: match last fields */
202144483Sharti	if ((str_len = strlen(string)) > (tok_len = strlen(tok))
203144483Sharti	    && strcasecmp(tok, string + str_len - tok_len) == 0)
204144483Sharti	    return (YES);
205144483Sharti    } else if (strcasecmp(tok, "LOCAL") == 0) {	/* local: no dots */
206144483Sharti	if (strchr(string, '.') == 0)
207144483Sharti	    return (YES);
208144483Sharti    } else if (tok[(tok_len = strlen(tok)) - 1] == '.'	/* network */
209144483Sharti	       && strncmp(tok, string, tok_len) == 0) {
210144483Sharti	return (YES);
211146061Sharti    }
212144483Sharti    return (NO);
213144483Sharti}
214144483Sharti
215144483Sharti/* string_match - match a string against one token */
216144483Sharti
217144483Shartistatic int string_match(tok, string)
218144483Shartichar   *tok;
219144483Shartichar   *string;
220144483Sharti{
221144483Sharti
222144483Sharti    /*
223144483Sharti     * If the token has the magic value "ALL" the match always succeeds.
224144483Sharti     * Otherwise, return YES if the token fully matches the string.
225144483Sharti     */
226144483Sharti
227144483Sharti    if (strcasecmp(tok, "ALL") == 0) {		/* all: always matches */
228144483Sharti	return (YES);
229144483Sharti    } else if (strcasecmp(tok, string) == 0) {	/* try exact match */
230144483Sharti	return (YES);
231144483Sharti    }
232144483Sharti    return (NO);
233144483Sharti}
234144483Sharti#endif /* LOGIN_ACCES */
235144483Sharti