1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #include <stdio.h> 28 #include <string.h> 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 #include <syslog.h> 32 #include <netdb.h> 33 #include <malloc.h> 34 #include <unistd.h> 35 #include <errno.h> 36 #include <security/pam_appl.h> 37 #include <security/pam_modules.h> 38 #include <security/pam_impl.h> 39 40 #define ILLEGAL_COMBINATION "pam_list: illegal combination of options" 41 42 typedef enum { 43 LIST_EXTERNAL_FILE, 44 LIST_PLUS_CHECK, 45 LIST_COMPAT_MODE 46 } pam_list_mode_t; 47 48 static const char * 49 string_mode_type(pam_list_mode_t op_mode, boolean_t allow) 50 { 51 return ((op_mode == LIST_COMPAT_MODE) ? "compat" : 52 (allow ? "allow" : "deny")); 53 } 54 55 static void 56 log_illegal_combination(const char *s1, const char *s2) 57 { 58 __pam_log(LOG_AUTH | LOG_ERR, ILLEGAL_COMBINATION 59 " %s and %s", s1, s2); 60 } 61 62 /*ARGSUSED*/ 63 int 64 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) 65 { 66 FILE *fd; 67 const char *allowdeny_filename = PF_PATH; 68 char buf[BUFSIZ]; 69 char hostname[MAXHOSTNAMELEN]; 70 char *username = NULL; 71 char *bufp; 72 char *rhost; 73 char *limit; 74 int userok = 0; 75 int hostok = 0; 76 int i; 77 int allow_deny_test = 0; 78 boolean_t debug = B_FALSE; 79 boolean_t allow = B_FALSE; 80 boolean_t matched = B_FALSE; 81 boolean_t check_user = B_TRUE; 82 boolean_t check_host = B_FALSE; 83 boolean_t check_exact = B_FALSE; 84 pam_list_mode_t op_mode = LIST_PLUS_CHECK; 85 86 for (i = 0; i < argc; ++i) { 87 if (strncasecmp(argv[i], "debug", sizeof ("debug")) == 0) { 88 debug = B_TRUE; 89 } else if (strncasecmp(argv[i], "user", sizeof ("user")) == 0) { 90 check_user = B_TRUE; 91 } else if (strncasecmp(argv[i], "nouser", 92 sizeof ("nouser")) == 0) { 93 check_user = B_FALSE; 94 } else if (strncasecmp(argv[i], "host", sizeof ("host")) == 0) { 95 check_host = B_TRUE; 96 } else if (strncasecmp(argv[i], "nohost", 97 sizeof ("nohost")) == 0) { 98 check_host = B_FALSE; 99 } else if (strncasecmp(argv[i], "user_host_exact", 100 sizeof ("user_host_exact")) == 0) { 101 check_exact = B_TRUE; 102 } else if (strcasecmp(argv[i], "compat") == 0) { 103 if (op_mode == LIST_PLUS_CHECK) { 104 op_mode = LIST_COMPAT_MODE; 105 } else { 106 log_illegal_combination("compat", 107 string_mode_type(op_mode, allow)); 108 return (PAM_SERVICE_ERR); 109 } 110 } else if (strncasecmp(argv[i], "allow=", 111 sizeof ("allow=") - 1) == 0) { 112 if (op_mode == LIST_PLUS_CHECK) { 113 allowdeny_filename = argv[i] + 114 sizeof ("allow=") - 1; 115 allow = B_TRUE; 116 op_mode = LIST_EXTERNAL_FILE; 117 allow_deny_test++; 118 } else { 119 log_illegal_combination("allow", 120 string_mode_type(op_mode, allow)); 121 return (PAM_SERVICE_ERR); 122 } 123 } else if (strncasecmp(argv[i], "deny=", 124 sizeof ("deny=") - 1) == 0) { 125 if (op_mode == LIST_PLUS_CHECK) { 126 allowdeny_filename = argv[i] + 127 sizeof ("deny=") - 1; 128 allow = B_FALSE; 129 op_mode = LIST_EXTERNAL_FILE; 130 allow_deny_test++; 131 } else { 132 log_illegal_combination("deny", 133 string_mode_type(op_mode, allow)); 134 return (PAM_SERVICE_ERR); 135 } 136 } else { 137 __pam_log(LOG_AUTH | LOG_ERR, 138 "pam_list: illegal option %s", argv[i]); 139 return (PAM_SERVICE_ERR); 140 } 141 } 142 143 if (((check_user || check_host || check_exact) == B_FALSE) || 144 (allow_deny_test > 1)) { 145 __pam_log(LOG_AUTH | LOG_ERR, ILLEGAL_COMBINATION); 146 return (PAM_SERVICE_ERR); 147 } 148 149 if ((op_mode == LIST_COMPAT_MODE) && (check_user == B_FALSE)) { 150 log_illegal_combination("compat", "nouser"); 151 return (PAM_SERVICE_ERR); 152 } 153 154 if (debug) { 155 __pam_log(LOG_AUTH | LOG_DEBUG, 156 "pam_list: check_user = %d, check_host = %d," 157 "check_exact = %d\n", 158 check_user, check_host, check_exact); 159 160 __pam_log(LOG_AUTH | LOG_DEBUG, 161 "pam_list: auth_file: %s, %s\n", allowdeny_filename, 162 (op_mode == LIST_COMPAT_MODE) ? "compat mode" : 163 (allow ? "allow file" : "deny file")); 164 } 165 166 (void) pam_get_item(pamh, PAM_USER, (void**)&username); 167 168 if ((check_user || check_exact) && ((username == NULL) || 169 (*username == '\0'))) { 170 __pam_log(LOG_AUTH | LOG_ERR, 171 "pam_list: username not supplied, critical error"); 172 return (PAM_USER_UNKNOWN); 173 } 174 175 (void) pam_get_item(pamh, PAM_RHOST, (void**)&rhost); 176 177 if ((check_host || check_exact) && ((rhost == NULL) || 178 (*rhost == '\0'))) { 179 if (gethostname(hostname, MAXHOSTNAMELEN) == 0) { 180 rhost = hostname; 181 } else { 182 __pam_log(LOG_AUTH | LOG_ERR, 183 "pam_list: error by gethostname - %m"); 184 return (PAM_SERVICE_ERR); 185 } 186 } 187 188 if (debug) { 189 __pam_log(LOG_AUTH | LOG_DEBUG, 190 "pam_list: pam_sm_acct_mgmt for (%s,%s,)", 191 (rhost != NULL) ? rhost : "", username); 192 } 193 194 if (strlen(allowdeny_filename) == 0) { 195 __pam_log(LOG_AUTH | LOG_ERR, 196 "pam_list: file name not specified"); 197 return (PAM_SERVICE_ERR); 198 } 199 200 if ((fd = fopen(allowdeny_filename, "rF")) == NULL) { 201 __pam_log(LOG_AUTH | LOG_ERR, "pam_list: fopen of %s: %s", 202 allowdeny_filename, strerror(errno)); 203 return (PAM_SERVICE_ERR); 204 } 205 206 while (fgets(buf, BUFSIZ, fd) != NULL) { 207 /* lines longer than BUFSIZ-1 */ 208 if ((strlen(buf) == (BUFSIZ - 1)) && 209 (buf[BUFSIZ - 2] != '\n')) { 210 while ((fgetc(fd) != '\n') && (!feof(fd))) { 211 continue; 212 } 213 __pam_log(LOG_AUTH | LOG_DEBUG, 214 "pam_list: long line in file," 215 "more than %d chars, the rest ignored", BUFSIZ - 1); 216 } 217 218 /* remove unneeded colons if necessary */ 219 if ((limit = strpbrk(buf, ":\n")) != NULL) { 220 *limit = '\0'; 221 } 222 223 /* ignore free values */ 224 if (buf[0] == '\0') { 225 continue; 226 } 227 228 bufp = buf; 229 230 /* test for interesting lines = +/- in /etc/passwd */ 231 if (op_mode == LIST_COMPAT_MODE) { 232 /* simple + matches all */ 233 if ((buf[0] == '+') && (buf[1] == '\0')) { 234 matched = B_TRUE; 235 allow = B_TRUE; 236 break; 237 } 238 239 /* simple - is not defined */ 240 if ((buf[0] == '-') && (buf[1] == '\0')) { 241 __pam_log(LOG_AUTH | LOG_ERR, 242 "pam_list: simple minus unknown, " 243 "illegal line in " PF_PATH); 244 (void) fclose(fd); 245 return (PAM_SERVICE_ERR); 246 } 247 248 /* @ is not allowed on the first position */ 249 if (buf[0] == '@') { 250 __pam_log(LOG_AUTH | LOG_ERR, 251 "pam_list: @ is not allowed on the first " 252 "position in " PF_PATH); 253 (void) fclose(fd); 254 return (PAM_SERVICE_ERR); 255 } 256 257 /* -user or -@netgroup */ 258 if (buf[0] == '-') { 259 allow = B_FALSE; 260 bufp++; 261 /* +user or +@netgroup */ 262 } else if (buf[0] == '+') { 263 allow = B_TRUE; 264 bufp++; 265 /* user */ 266 } else { 267 allow = B_TRUE; 268 } 269 } else if (op_mode == LIST_PLUS_CHECK) { 270 if (((buf[0] != '+') && (buf[0] != '-')) || 271 (buf[1] == '\0')) { 272 continue; 273 } 274 275 if (buf[0] == '+') { 276 allow = B_TRUE; 277 } else { 278 allow = B_FALSE; 279 } 280 bufp++; 281 } 282 283 /* 284 * if -> netgroup line 285 * else -> user line 286 */ 287 if ((bufp[0] == '@') && (bufp[1] != '\0')) { 288 bufp++; 289 290 if (check_exact) { 291 if (innetgr(bufp, rhost, username, 292 NULL) == 1) { 293 matched = B_TRUE; 294 break; 295 } 296 } else { 297 if (check_user) { 298 userok = innetgr(bufp, NULL, username, 299 NULL); 300 } else { 301 userok = 1; 302 } 303 if (check_host) { 304 hostok = innetgr(bufp, rhost, NULL, 305 NULL); 306 } else { 307 hostok = 1; 308 } 309 if (userok && hostok) { 310 matched = B_TRUE; 311 break; 312 } 313 } 314 } else { 315 if (check_user) { 316 if (strcmp(bufp, username) == 0) { 317 matched = B_TRUE; 318 break; 319 } 320 } 321 } 322 323 /* 324 * No match found in /etc/passwd yet. For compat mode 325 * a failure to match should result in a return of 326 * PAM_PERM_DENIED which is achieved below if 'matched' 327 * is false and 'allow' is true. 328 */ 329 if (op_mode == LIST_COMPAT_MODE) { 330 allow = B_TRUE; 331 } 332 } 333 (void) fclose(fd); 334 335 if (debug) { 336 __pam_log(LOG_AUTH | LOG_DEBUG, 337 "pam_list: %s for %s", matched ? "matched" : "no match", 338 allow ? "allow" : "deny"); 339 } 340 341 if (matched) { 342 return (allow ? PAM_SUCCESS : PAM_PERM_DENIED); 343 } 344 /* 345 * For compatibility with passwd_compat mode to prevent root access 346 * denied. 347 */ 348 if (op_mode == LIST_PLUS_CHECK) { 349 return (PAM_IGNORE); 350 } 351 return (allow ? PAM_PERM_DENIED : PAM_SUCCESS); 352 } 353