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