login_access.c revision 87628
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#ifdef LOGIN_ACCESS 11#if 0 12#ifndef lint 13static char sccsid[] = "%Z% %M% %I% %E% %U%"; 14#endif 15#endif 16 17#include <sys/cdefs.h> 18__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_login_access/login_access.c 87628 2001-12-10 21:13:08Z dwmalone $"); 19 20#include <sys/types.h> 21#include <ctype.h> 22#include <errno.h> 23#include <grp.h> 24#include <stdio.h> 25#include <stdlib.h> 26#include <string.h> 27#include <syslog.h> 28#include <unistd.h> 29 30#include "login.h" 31#include "pathnames.h" 32 33 /* Delimiters for fields and for lists of users, ttys or hosts. */ 34 35static char fs[] = ":"; /* field separator */ 36static char sep[] = ", \t"; /* list-element separator */ 37 38 /* Constants to be used in assignments only, not in comparisons... */ 39 40#define YES 1 41#define NO 0 42 43static int from_match __P((char *, char *)); 44static int list_match __P((char *, char *, int (*)(char *, char *))); 45static int netgroup_match __P((char *, char *, char *)); 46static int string_match __P((char *, char *)); 47static int user_match __P((char *, char *)); 48 49/* login_access - match username/group and host/tty with access control file */ 50 51int 52login_access(user, from) 53char *user; 54char *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 list_match(list, item, match_fn) 114char *list; 115char *item; 116int (*match_fn) __P((char *, char *)); 117{ 118 char *tok; 119 int match = NO; 120 121 /* 122 * Process tokens one at a time. We have exhausted all possible matches 123 * when we reach an "EXCEPT" token or the end of the list. If we do find 124 * a match, look for an "EXCEPT" list and recurse to determine whether 125 * the match is affected by any exceptions. 126 */ 127 128 for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { 129 if (strcasecmp(tok, "EXCEPT") == 0) /* EXCEPT: give up */ 130 break; 131 if ((match = (*match_fn)(tok, item)) != NULL) /* YES */ 132 break; 133 } 134 /* Process exceptions to matches. */ 135 136 if (match != NO) { 137 while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT")) 138 /* VOID */ ; 139 if (tok == 0 || list_match((char *) 0, item, match_fn) == NO) 140 return (match); 141 } 142 return (NO); 143} 144 145/* netgroup_match - match group against machine or user */ 146 147static int netgroup_match(group, machine, user) 148char *group __unused; 149char *machine __unused; 150char *user __unused; 151{ 152 syslog(LOG_ERR, "NIS netgroup support not configured"); 153 return 0; 154} 155 156/* user_match - match a username against one token */ 157 158static int user_match(tok, string) 159char *tok; 160char *string; 161{ 162 struct group *group; 163 int i; 164 165 /* 166 * If a token has the magic value "ALL" the match always succeeds. 167 * Otherwise, return YES if the token fully matches the username, or if 168 * the token is a group that contains the username. 169 */ 170 171 if (tok[0] == '@') { /* netgroup */ 172 return (netgroup_match(tok + 1, (char *) 0, string)); 173 } else if (string_match(tok, string)) { /* ALL or exact match */ 174 return (YES); 175 } else if ((group = getgrnam(tok)) != NULL) {/* try group membership */ 176 for (i = 0; group->gr_mem[i]; i++) 177 if (strcasecmp(string, group->gr_mem[i]) == 0) 178 return (YES); 179 } 180 return (NO); 181} 182 183/* from_match - match a host or tty against a list of tokens */ 184 185static int from_match(tok, string) 186char *tok; 187char *string; 188{ 189 int tok_len; 190 int str_len; 191 192 /* 193 * If a token has the magic value "ALL" the match always succeeds. Return 194 * YES if the token fully matches the string. If the token is a domain 195 * name, return YES if it matches the last fields of the string. If the 196 * token has the magic value "LOCAL", return YES if the string does not 197 * contain a "." character. If the token is a network number, return YES 198 * if it matches the head of the string. 199 */ 200 201 if (tok[0] == '@') { /* netgroup */ 202 return (netgroup_match(tok + 1, string, (char *) 0)); 203 } else if (string_match(tok, string)) { /* ALL or exact match */ 204 return (YES); 205 } else if (tok[0] == '.') { /* domain: match last fields */ 206 if ((str_len = strlen(string)) > (tok_len = strlen(tok)) 207 && strcasecmp(tok, string + str_len - tok_len) == 0) 208 return (YES); 209 } else if (strcasecmp(tok, "LOCAL") == 0) { /* local: no dots */ 210 if (strchr(string, '.') == 0) 211 return (YES); 212 } else if (tok[(tok_len = strlen(tok)) - 1] == '.' /* network */ 213 && strncmp(tok, string, tok_len) == 0) { 214 return (YES); 215 } 216 return (NO); 217} 218 219/* string_match - match a string against one token */ 220 221static int string_match(tok, string) 222char *tok; 223char *string; 224{ 225 226 /* 227 * If the token has the magic value "ALL" the match always succeeds. 228 * Otherwise, return YES if the token fully matches the string. 229 */ 230 231 if (strcasecmp(tok, "ALL") == 0) { /* all: always matches */ 232 return (YES); 233 } else if (strcasecmp(tok, string) == 0) { /* try exact match */ 234 return (YES); 235 } 236 return (NO); 237} 238#endif /* LOGIN_ACCES */ 239