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