login_access.c revision 358197
1 /* 2 * This module implements a simple but effective form of login access 3 * control based on login names and on host (or domain) names, internet 4 * addresses (or network numbers), or on terminal line names in case of 5 * non-networked logins. Diagnostics are reported through syslog(3). 6 * 7 * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. 8 */ 9 10#if 0 11#ifndef lint 12static char sccsid[] = "%Z% %M% %I% %E% %U%"; 13#endif 14#endif 15 16#include <sys/cdefs.h> 17__FBSDID("$FreeBSD: stable/11/lib/libpam/modules/pam_login_access/login_access.c 358197 2020-02-21 04:23:54Z cy $"); 18 19#include <sys/types.h> 20#include <ctype.h> 21#include <errno.h> 22#include <grp.h> 23#include <netdb.h> 24#include <stdio.h> 25#include <stdlib.h> 26#include <string.h> 27#include <syslog.h> 28#include <unistd.h> 29 30#include "pam_login_access.h" 31 32#define _PATH_LOGACCESS "/etc/login.access" 33 34 /* Delimiters for fields and for lists of users, ttys or hosts. */ 35 36static char fs[] = ":"; /* field separator */ 37static char sep[] = ", \t"; /* list-element separator */ 38 39 /* Constants to be used in assignments only, not in comparisons... */ 40 41#define YES 1 42#define NO 0 43 44static int from_match(const char *, const char *); 45static int list_match(char *, const char *, 46 int (*)(const char *, const char *)); 47static int netgroup_match(const char *, const char *, const char *); 48static int string_match(const char *, const char *); 49static int user_match(const char *, const char *); 50 51/* login_access - match username/group and host/tty with access control file */ 52 53int 54login_access(const char *user, const char *from) 55{ 56 FILE *fp; 57 char line[BUFSIZ]; 58 char *perm; /* becomes permission field */ 59 char *users; /* becomes list of login names */ 60 char *froms; /* becomes list of terminals or hosts */ 61 int match = NO; 62 int end; 63 int lineno = 0; /* for diagnostics */ 64 65 /* 66 * Process the table one line at a time and stop at the first match. 67 * Blank lines and lines that begin with a '#' character are ignored. 68 * Non-comment lines are broken at the ':' character. All fields are 69 * mandatory. The first field should be a "+" or "-" character. A 70 * non-existing table means no access control. 71 */ 72 73 if ((fp = fopen(_PATH_LOGACCESS, "r")) != NULL) { 74 while (!match && fgets(line, sizeof(line), fp)) { 75 lineno++; 76 if (line[end = strlen(line) - 1] != '\n') { 77 syslog(LOG_ERR, "%s: line %d: missing newline or line too long", 78 _PATH_LOGACCESS, lineno); 79 continue; 80 } 81 if (line[0] == '#') 82 continue; /* comment line */ 83 while (end > 0 && isspace(line[end - 1])) 84 end--; 85 line[end] = 0; /* strip trailing whitespace */ 86 if (line[0] == 0) /* skip blank lines */ 87 continue; 88 if (!(perm = strtok(line, fs)) 89 || !(users = strtok((char *) 0, fs)) 90 || !(froms = strtok((char *) 0, fs)) 91 || strtok((char *) 0, fs)) { 92 syslog(LOG_ERR, "%s: line %d: bad field count", _PATH_LOGACCESS, 93 lineno); 94 continue; 95 } 96 if (perm[0] != '+' && perm[0] != '-') { 97 syslog(LOG_ERR, "%s: line %d: bad first field", _PATH_LOGACCESS, 98 lineno); 99 continue; 100 } 101 match = (list_match(froms, from, from_match) 102 && list_match(users, user, user_match)); 103 } 104 (void) fclose(fp); 105 } else if (errno != ENOENT) { 106 syslog(LOG_ERR, "cannot open %s: %m", _PATH_LOGACCESS); 107 } 108 return (match == 0 || (line[0] == '+')); 109} 110 111/* list_match - match an item against a list of tokens with exceptions */ 112 113static int 114list_match(char *list, const char *item, 115 int (*match_fn)(const char *, const char *)) 116{ 117 char *tok; 118 int match = NO; 119 120 /* 121 * Process tokens one at a time. We have exhausted all possible matches 122 * when we reach an "EXCEPT" token or the end of the list. If we do find 123 * a match, look for an "EXCEPT" list and recurse to determine whether 124 * the match is affected by any exceptions. 125 */ 126 127 for (tok = strtok(list, sep); tok != NULL; tok = strtok((char *) 0, sep)) { 128 if (strcasecmp(tok, "EXCEPT") == 0) /* EXCEPT: give up */ 129 break; 130 if ((match = (*match_fn)(tok, item)) != 0) /* YES */ 131 break; 132 } 133 /* Process exceptions to matches. */ 134 135 if (match != NO) { 136 while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT")) 137 /* VOID */ ; 138 if (tok == NULL || list_match((char *) 0, item, match_fn) == NO) 139 return (match); 140 } 141 return (NO); 142} 143 144/* netgroup_match - match group against machine or user */ 145 146static int 147netgroup_match(const char *group, const char *machine, const char *user) 148{ 149 char domain[1024]; 150 unsigned int i; 151 152 if (getdomainname(domain, sizeof(domain)) != 0 || *domain == '\0') { 153 syslog(LOG_ERR, "NIS netgroup support disabled: no NIS domain"); 154 return (NO); 155 } 156 157 /* getdomainname() does not reliably terminate the string */ 158 for (i = 0; i < sizeof(domain); ++i) 159 if (domain[i] == '\0') 160 break; 161 if (i == sizeof(domain)) { 162 syslog(LOG_ERR, "NIS netgroup support disabled: invalid NIS domain"); 163 return (NO); 164 } 165 166 if (innetgr(group, machine, user, domain) == 1) 167 return (YES); 168 return (NO); 169} 170 171/* user_match - match a username against one token */ 172 173static int 174user_match(const char *tok, const char *string) 175{ 176 struct group *group; 177 int i; 178 179 /* 180 * If a token has the magic value "ALL" the match always succeeds. 181 * Otherwise, return YES if the token fully matches the username, or if 182 * the token is a group that contains the username. 183 */ 184 185 if (tok[0] == '@') { /* netgroup */ 186 return (netgroup_match(tok + 1, (char *) 0, string)); 187 } else if (string_match(tok, string)) { /* ALL or exact match */ 188 return (YES); 189 } else if ((group = getgrnam(tok)) != NULL) {/* try group membership */ 190 for (i = 0; group->gr_mem[i]; i++) 191 if (strcasecmp(string, group->gr_mem[i]) == 0) 192 return (YES); 193 } 194 return (NO); 195} 196 197/* from_match - match a host or tty against a list of tokens */ 198 199static int 200from_match(const char *tok, const char *string) 201{ 202 int tok_len; 203 int str_len; 204 205 /* 206 * If a token has the magic value "ALL" the match always succeeds. Return 207 * YES if the token fully matches the string. If the token is a domain 208 * name, return YES if it matches the last fields of the string. If the 209 * token has the magic value "LOCAL", return YES if the string does not 210 * contain a "." character. If the token is a network number, return YES 211 * if it matches the head of the string. 212 */ 213 214 if (tok[0] == '@') { /* netgroup */ 215 return (netgroup_match(tok + 1, string, (char *) 0)); 216 } else if (string_match(tok, string)) { /* ALL or exact match */ 217 return (YES); 218 } else if (tok[0] == '.') { /* domain: match last fields */ 219 if ((str_len = strlen(string)) > (tok_len = strlen(tok)) 220 && strcasecmp(tok, string + str_len - tok_len) == 0) 221 return (YES); 222 } else if (strcasecmp(tok, "LOCAL") == 0) { /* local: no dots */ 223 if (strchr(string, '.') == NULL) 224 return (YES); 225 } else if (tok[(tok_len = strlen(tok)) - 1] == '.' /* network */ 226 && strncmp(tok, string, tok_len) == 0) { 227 return (YES); 228 } 229 return (NO); 230} 231 232/* string_match - match a string against one token */ 233 234static int 235string_match(const char *tok, const char *string) 236{ 237 238 /* 239 * If the token has the magic value "ALL" the match always succeeds. 240 * Otherwise, return YES if the token fully matches the string. 241 */ 242 243 if (strcasecmp(tok, "ALL") == 0) { /* all: always matches */ 244 return (YES); 245 } else if (strcasecmp(tok, string) == 0) { /* try exact match */ 246 return (YES); 247 } 248 return (NO); 249} 250