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