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