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