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