190926Snectar/************************************************************************
290926Snectar* Copyright 1995 by Wietse Venema.  All rights reserved.  Some individual
390926Snectar* files may be covered by other copyrights.
490926Snectar*
590926Snectar* This material was originally written and compiled by Wietse Venema at
690926Snectar* Eindhoven University of Technology, The Netherlands, in 1990, 1991,
790926Snectar* 1992, 1993, 1994 and 1995.
890926Snectar*
990926Snectar* Redistribution and use in source and binary forms, with or without
1090926Snectar* modification, are permitted provided that this entire copyright notice
1190926Snectar* is duplicated in all such copies.
1290926Snectar*
1390926Snectar* This software is provided "as is" and without any expressed or implied
1490926Snectar* warranties, including, without limitation, the implied warranties of
1590926Snectar* merchantibility and fitness for any particular purpose.
1690926Snectar************************************************************************/
1755682Smarkm /*
1855682Smarkm  * This module implements a simple but effective form of login access
1955682Smarkm  * control based on login names and on host (or domain) names, internet
2055682Smarkm  * addresses (or network numbers), or on terminal line names in case of
2155682Smarkm  * non-networked logins. Diagnostics are reported through syslog(3).
2255682Smarkm  *
2355682Smarkm  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
2455682Smarkm  */
2555682Smarkm
2655682Smarkm#include "login_locl.h"
2755682Smarkm
28233294SstasRCSID("$Id$");
2955682Smarkm
3055682Smarkm /* Delimiters for fields and for lists of users, ttys or hosts. */
3155682Smarkm
3255682Smarkmstatic char fs[] = ":";			/* field separator */
3355682Smarkmstatic char sep[] = ", \t";		/* list-element separator */
3455682Smarkm
3555682Smarkm /* Constants to be used in assignments only, not in comparisons... */
3655682Smarkm
3755682Smarkm#define YES             1
3855682Smarkm#define NO              0
3955682Smarkm
4055682Smarkm /*
4155682Smarkm  * A structure to bundle up all login-related information to keep the
4255682Smarkm  * functional interfaces as generic as possible.
4355682Smarkm  */
4455682Smarkmstruct login_info {
4555682Smarkm    struct passwd *user;
4655682Smarkm    char   *from;
4755682Smarkm};
4855682Smarkm
4955682Smarkmstatic int list_match(char *list, struct login_info *item,
5055682Smarkm		      int (*match_fn)(char *, struct login_info *));
5155682Smarkmstatic int user_match(char *tok, struct login_info *item);
5255682Smarkmstatic int from_match(char *tok, struct login_info *item);
5355682Smarkmstatic int string_match(char *tok, char *string);
5455682Smarkm
5555682Smarkm/* login_access - match username/group and host/tty with access control file */
5655682Smarkm
5755682Smarkmint login_access(struct passwd *user, char *from)
5855682Smarkm{
5955682Smarkm    struct login_info item;
6055682Smarkm    FILE   *fp;
6155682Smarkm    char    line[BUFSIZ];
6255682Smarkm    char   *perm;			/* becomes permission field */
6355682Smarkm    char   *users;			/* becomes list of login names */
6455682Smarkm    char   *froms;			/* becomes list of terminals or hosts */
6555682Smarkm    int     match = NO;
6655682Smarkm    int     end;
6755682Smarkm    int     lineno = 0;			/* for diagnostics */
6855682Smarkm    char   *foo;
6955682Smarkm
7055682Smarkm    /*
7155682Smarkm     * Bundle up the arguments to avoid unnecessary clumsiness lateron.
7255682Smarkm     */
7355682Smarkm    item.user = user;
7455682Smarkm    item.from = from;
7555682Smarkm
7655682Smarkm    /*
7755682Smarkm     * Process the table one line at a time and stop at the first match.
7855682Smarkm     * Blank lines and lines that begin with a '#' character are ignored.
7955682Smarkm     * Non-comment lines are broken at the ':' character. All fields are
8055682Smarkm     * mandatory. The first field should be a "+" or "-" character. A
8155682Smarkm     * non-existing table means no access control.
8255682Smarkm     */
8355682Smarkm
8455682Smarkm    if ((fp = fopen(_PATH_LOGACCESS, "r")) != 0) {
8555682Smarkm	while (!match && fgets(line, sizeof(line), fp)) {
8655682Smarkm	    lineno++;
8755682Smarkm	    if (line[end = strlen(line) - 1] != '\n') {
8855682Smarkm		syslog(LOG_ERR, "%s: line %d: missing newline or line too long",
8955682Smarkm		       _PATH_LOGACCESS, lineno);
9055682Smarkm		continue;
9155682Smarkm	    }
9255682Smarkm	    if (line[0] == '#')
9355682Smarkm		continue;			/* comment line */
9455682Smarkm	    while (end > 0 && isspace((unsigned char)line[end - 1]))
9555682Smarkm		end--;
9655682Smarkm	    line[end] = 0;			/* strip trailing whitespace */
9755682Smarkm	    if (line[0] == 0)			/* skip blank lines */
9855682Smarkm		continue;
9955682Smarkm	    foo = NULL;
10055682Smarkm	    if (!(perm = strtok_r(line, fs, &foo))
10155682Smarkm		|| !(users = strtok_r(NULL, fs, &foo))
10255682Smarkm		|| !(froms = strtok_r(NULL, fs, &foo))
10355682Smarkm		|| strtok_r(NULL, fs, &foo)) {
104233294Sstas		syslog(LOG_ERR, "%s: line %d: bad field count",
10555682Smarkm		       _PATH_LOGACCESS,
10655682Smarkm		       lineno);
10755682Smarkm		continue;
10855682Smarkm	    }
10955682Smarkm	    if (perm[0] != '+' && perm[0] != '-') {
110233294Sstas		syslog(LOG_ERR, "%s: line %d: bad first field",
11155682Smarkm		       _PATH_LOGACCESS,
11255682Smarkm		       lineno);
11355682Smarkm		continue;
11455682Smarkm	    }
11555682Smarkm	    match = (list_match(froms, &item, from_match)
11655682Smarkm		     && list_match(users, &item, user_match));
11755682Smarkm	}
11855682Smarkm	fclose(fp);
11955682Smarkm    } else if (errno != ENOENT) {
12055682Smarkm	syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS);
12155682Smarkm    }
12255682Smarkm    return (match == 0 || (line[0] == '+'));
12355682Smarkm}
12455682Smarkm
12555682Smarkm/* list_match - match an item against a list of tokens with exceptions */
12655682Smarkm
12755682Smarkmstatic int
12855682Smarkmlist_match(char *list,
12955682Smarkm	   struct login_info *item,
13055682Smarkm	   int (*match_fn)(char *, struct login_info *))
13155682Smarkm{
13255682Smarkm    char   *tok;
13355682Smarkm    int     match = NO;
13455682Smarkm    char   *foo = NULL;
13555682Smarkm
13655682Smarkm    /*
13755682Smarkm     * Process tokens one at a time. We have exhausted all possible matches
13855682Smarkm     * when we reach an "EXCEPT" token or the end of the list. If we do find
13955682Smarkm     * a match, look for an "EXCEPT" list and recurse to determine whether
14055682Smarkm     * the match is affected by any exceptions.
14155682Smarkm     */
14255682Smarkm
14355682Smarkm    for (tok = strtok_r(list, sep, &foo);
14455682Smarkm	 tok != NULL;
14555682Smarkm	 tok = strtok_r(NULL, sep, &foo)) {
14655682Smarkm	if (strcasecmp(tok, "EXCEPT") == 0)	/* EXCEPT: give up */
14755682Smarkm	    break;
14855682Smarkm	if ((match = (*match_fn) (tok, item)) != 0)	/* YES */
14955682Smarkm	    break;
15055682Smarkm    }
15155682Smarkm    /* Process exceptions to matches. */
15255682Smarkm
15355682Smarkm    if (match != NO) {
15455682Smarkm	while ((tok = strtok_r(NULL, sep, &foo)) && strcasecmp(tok, "EXCEPT"))
15555682Smarkm	     /* VOID */ ;
15655682Smarkm	if (tok == 0 || list_match(NULL, item, match_fn) == NO)
15755682Smarkm	    return (match);
15855682Smarkm    }
15955682Smarkm    return (NO);
16055682Smarkm}
16155682Smarkm
16255682Smarkm/* myhostname - figure out local machine name */
16355682Smarkm
16455682Smarkmstatic char *myhostname(void)
16555682Smarkm{
16655682Smarkm    static char name[MAXHOSTNAMELEN + 1] = "";
16755682Smarkm
16855682Smarkm    if (name[0] == 0) {
16955682Smarkm	gethostname(name, sizeof(name));
17055682Smarkm	name[MAXHOSTNAMELEN] = 0;
17155682Smarkm    }
17255682Smarkm    return (name);
17355682Smarkm}
17455682Smarkm
17555682Smarkm/* netgroup_match - match group against machine or user */
17655682Smarkm
17755682Smarkmstatic int netgroup_match(char *group, char *machine, char *user)
17855682Smarkm{
17955682Smarkm#ifdef HAVE_YP_GET_DEFAULT_DOMAIN
18055682Smarkm    static char *mydomain = 0;
18155682Smarkm
18255682Smarkm    if (mydomain == 0)
18355682Smarkm	yp_get_default_domain(&mydomain);
18455682Smarkm    return (innetgr(group, machine, user, mydomain));
18555682Smarkm#else
18655682Smarkm    syslog(LOG_ERR, "NIS netgroup support not configured");
18755682Smarkm    return 0;
18855682Smarkm#endif
18955682Smarkm}
19055682Smarkm
19155682Smarkm/* user_match - match a username against one token */
19255682Smarkm
19355682Smarkmstatic int user_match(char *tok, struct login_info *item)
19455682Smarkm{
19555682Smarkm    char   *string = item->user->pw_name;
19655682Smarkm    struct login_info fake_item;
19755682Smarkm    struct group *group;
19855682Smarkm    int     i;
19955682Smarkm    char   *at;
20055682Smarkm
20155682Smarkm    /*
20255682Smarkm     * If a token has the magic value "ALL" the match always succeeds.
20355682Smarkm     * Otherwise, return YES if the token fully matches the username, if the
20455682Smarkm     * token is a group that contains the username, or if the token is the
20555682Smarkm     * name of the user's primary group.
20655682Smarkm     */
20755682Smarkm
20855682Smarkm    if ((at = strchr(tok + 1, '@')) != 0) {	/* split user@host pattern */
20955682Smarkm	*at = 0;
21055682Smarkm	fake_item.from = myhostname();
21155682Smarkm	return (user_match(tok, item) && from_match(at + 1, &fake_item));
21255682Smarkm    } else if (tok[0] == '@') {			/* netgroup */
21355682Smarkm	return (netgroup_match(tok + 1, (char *) 0, string));
21455682Smarkm    } else if (string_match(tok, string)) {	/* ALL or exact match */
21555682Smarkm	return (YES);
21655682Smarkm    } else if ((group = getgrnam(tok)) != 0) { /* try group membership */
21755682Smarkm	if (item->user->pw_gid == group->gr_gid)
21855682Smarkm	    return (YES);
21955682Smarkm	for (i = 0; group->gr_mem[i]; i++)
22055682Smarkm	    if (strcasecmp(string, group->gr_mem[i]) == 0)
22155682Smarkm		return (YES);
22255682Smarkm    }
22355682Smarkm    return (NO);
22455682Smarkm}
22555682Smarkm
22655682Smarkm/* from_match - match a host or tty against a list of tokens */
22755682Smarkm
22855682Smarkmstatic int from_match(char *tok, struct login_info *item)
22955682Smarkm{
23055682Smarkm    char   *string = item->from;
23155682Smarkm    int     tok_len;
23255682Smarkm    int     str_len;
23355682Smarkm
23455682Smarkm    /*
23555682Smarkm     * If a token has the magic value "ALL" the match always succeeds. Return
23655682Smarkm     * YES if the token fully matches the string. If the token is a domain
23755682Smarkm     * name, return YES if it matches the last fields of the string. If the
23855682Smarkm     * token has the magic value "LOCAL", return YES if the string does not
23955682Smarkm     * contain a "." character. If the token is a network number, return YES
24055682Smarkm     * if it matches the head of the string.
24155682Smarkm     */
24255682Smarkm
24355682Smarkm    if (tok[0] == '@') {			/* netgroup */
24455682Smarkm	return (netgroup_match(tok + 1, string, (char *) 0));
24555682Smarkm    } else if (string_match(tok, string)) {	/* ALL or exact match */
24655682Smarkm	return (YES);
24755682Smarkm    } else if (tok[0] == '.') {			/* domain: match last fields */
24855682Smarkm	if ((str_len = strlen(string)) > (tok_len = strlen(tok))
24955682Smarkm	    && strcasecmp(tok, string + str_len - tok_len) == 0)
25055682Smarkm	    return (YES);
25155682Smarkm    } else if (strcasecmp(tok, "LOCAL") == 0) {	/* local: no dots */
25255682Smarkm	if (strchr(string, '.') == 0)
25355682Smarkm	    return (YES);
25455682Smarkm    } else if (tok[(tok_len = strlen(tok)) - 1] == '.'	/* network */
25555682Smarkm	       && strncmp(tok, string, tok_len) == 0) {
25655682Smarkm	return (YES);
25755682Smarkm    }
25855682Smarkm    return (NO);
25955682Smarkm}
26055682Smarkm
26155682Smarkm/* string_match - match a string against one token */
26255682Smarkm
26355682Smarkmstatic int string_match(char *tok, char *string)
26455682Smarkm{
26555682Smarkm
26655682Smarkm    /*
26755682Smarkm     * If the token has the magic value "ALL" the match always succeeds.
26855682Smarkm     * Otherwise, return YES if the token fully matches the string.
26955682Smarkm     */
27055682Smarkm
27155682Smarkm    if (strcasecmp(tok, "ALL") == 0) {		/* all: always matches */
27255682Smarkm	return (YES);
27355682Smarkm    } else if (strcasecmp(tok, string) == 0) {	/* try exact match */
27455682Smarkm	return (YES);
27555682Smarkm    }
27655682Smarkm    return (NO);
27755682Smarkm}
278