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 <grp.h> 37 #include <security/pam_appl.h> 38 #include <security/pam_modules.h> 39 #include <security/pam_impl.h> 40 41 #define ILLEGAL_COMBINATION "pam_list: illegal combination of options" 42 43 typedef enum { 44 LIST_EXTERNAL_FILE, 45 LIST_PLUS_CHECK, 46 LIST_COMPAT_MODE 47 } pam_list_mode_t; 48 49 static const char * 50 string_mode_type(pam_list_mode_t op_mode, boolean_t allow) 51 { 52 return ((op_mode == LIST_COMPAT_MODE) ? "compat" : 53 (allow ? "allow" : "deny")); 54 } 55 56 static void 57 log_illegal_combination(const char *s1, const char *s2) 58 { 59 __pam_log(LOG_AUTH | LOG_ERR, ILLEGAL_COMBINATION 60 " %s and %s", s1, s2); 61 } 62 63 /*ARGSUSED*/ 64 int 65 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) 66 { 67 FILE *fd; 68 const char *allowdeny_filename = PF_PATH; 69 char buf[BUFSIZ]; 70 char hostname[MAXHOSTNAMELEN]; 71 char *username = NULL; 72 char *grbuf = NULL; 73 char *bufp; 74 char *rhost; 75 char *limit; 76 int userok = 0; 77 int hostok = 0; 78 int i; 79 int allow_deny_test = 0; 80 long grbuflen = 0; 81 boolean_t debug = B_FALSE; 82 boolean_t allow = B_FALSE; 83 boolean_t matched = B_FALSE; 84 boolean_t check_user = B_TRUE; 85 boolean_t check_group = B_FALSE; 86 boolean_t check_host = B_FALSE; 87 boolean_t check_exact = B_FALSE; 88 pam_list_mode_t op_mode = LIST_PLUS_CHECK; 89 90 // group reentrant interfaces limits 91 if ((grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX)) <= 0) 92 return (PAM_BUF_ERR); 93 94 for (i = 0; i < argc; ++i) { 95 if (strncasecmp(argv[i], "debug", sizeof ("debug")) == 0) { 96 debug = B_TRUE; 97 } else if (strncasecmp(argv[i], "group", 98 sizeof ("group")) == 0) { 99 check_group = B_TRUE; 100 } else if (strncasecmp(argv[i], "user", sizeof ("user")) == 0) { 101 check_user = B_TRUE; 102 } else if (strncasecmp(argv[i], "nouser", 103 sizeof ("nouser")) == 0) { 104 check_user = B_FALSE; 105 } else if (strncasecmp(argv[i], "host", sizeof ("host")) == 0) { 106 check_host = B_TRUE; 107 } else if (strncasecmp(argv[i], "nohost", 108 sizeof ("nohost")) == 0) { 109 check_host = B_FALSE; 110 } else if (strncasecmp(argv[i], "user_host_exact", 111 sizeof ("user_host_exact")) == 0) { 112 check_exact = B_TRUE; 113 } else if (strcasecmp(argv[i], "compat") == 0) { 114 if (op_mode == LIST_PLUS_CHECK) { 115 op_mode = LIST_COMPAT_MODE; 116 } else { 117 log_illegal_combination("compat", 118 string_mode_type(op_mode, allow)); 119 return (PAM_SERVICE_ERR); 120 } 121 } else if (strncasecmp(argv[i], "allow=", 122 sizeof ("allow=") - 1) == 0) { 123 if (op_mode == LIST_PLUS_CHECK) { 124 allowdeny_filename = argv[i] + 125 sizeof ("allow=") - 1; 126 allow = B_TRUE; 127 op_mode = LIST_EXTERNAL_FILE; 128 allow_deny_test++; 129 } else { 130 log_illegal_combination("allow", 131 string_mode_type(op_mode, allow)); 132 return (PAM_SERVICE_ERR); 133 } 134 } else if (strncasecmp(argv[i], "deny=", 135 sizeof ("deny=") - 1) == 0) { 136 if (op_mode == LIST_PLUS_CHECK) { 137 allowdeny_filename = argv[i] + 138 sizeof ("deny=") - 1; 139 allow = B_FALSE; 140 op_mode = LIST_EXTERNAL_FILE; 141 allow_deny_test++; 142 } else { 143 log_illegal_combination("deny", 144 string_mode_type(op_mode, allow)); 145 return (PAM_SERVICE_ERR); 146 } 147 } else { 148 __pam_log(LOG_AUTH | LOG_ERR, 149 "pam_list: illegal option %s", argv[i]); 150 return (PAM_SERVICE_ERR); 151 } 152 } 153 154 if (((check_user || check_group || check_host || 155 check_exact) == B_FALSE) || (allow_deny_test > 1)) { 156 __pam_log(LOG_AUTH | LOG_ERR, ILLEGAL_COMBINATION); 157 return (PAM_SERVICE_ERR); 158 } 159 160 if ((op_mode == LIST_COMPAT_MODE) && (check_user == B_FALSE)) { 161 log_illegal_combination("compat", "nouser"); 162 return (PAM_SERVICE_ERR); 163 } 164 165 if ((op_mode == LIST_COMPAT_MODE) && (check_group == B_TRUE)) { 166 log_illegal_combination("compat", "group"); 167 return (PAM_SERVICE_ERR); 168 } 169 170 if (debug) { 171 __pam_log(LOG_AUTH | LOG_DEBUG, 172 "pam_list: check_user = %d, check_host = %d," 173 "check_exact = %d\n", 174 check_user, check_host, check_exact); 175 176 __pam_log(LOG_AUTH | LOG_DEBUG, 177 "pam_list: auth_file: %s, %s\n", allowdeny_filename, 178 (op_mode == LIST_COMPAT_MODE) ? "compat mode" : 179 (allow ? "allow file" : "deny file")); 180 } 181 182 (void) pam_get_item(pamh, PAM_USER, (void**)&username); 183 184 if ((check_user || check_group || check_exact) && ((username == NULL) || 185 (*username == '\0'))) { 186 __pam_log(LOG_AUTH | LOG_ERR, 187 "pam_list: username not supplied, critical error"); 188 return (PAM_USER_UNKNOWN); 189 } 190 191 (void) pam_get_item(pamh, PAM_RHOST, (void**)&rhost); 192 193 if ((check_host || check_exact) && ((rhost == NULL) || 194 (*rhost == '\0'))) { 195 if (gethostname(hostname, MAXHOSTNAMELEN) == 0) { 196 rhost = hostname; 197 } else { 198 __pam_log(LOG_AUTH | LOG_ERR, 199 "pam_list: error by gethostname - %m"); 200 return (PAM_SERVICE_ERR); 201 } 202 } 203 204 if (debug) { 205 __pam_log(LOG_AUTH | LOG_DEBUG, 206 "pam_list: pam_sm_acct_mgmt for (%s,%s,)", 207 (rhost != NULL) ? rhost : "", username); 208 } 209 210 if (strlen(allowdeny_filename) == 0) { 211 __pam_log(LOG_AUTH | LOG_ERR, 212 "pam_list: file name not specified"); 213 return (PAM_SERVICE_ERR); 214 } 215 216 if ((fd = fopen(allowdeny_filename, "rF")) == NULL) { 217 __pam_log(LOG_AUTH | LOG_ERR, "pam_list: fopen of %s: %s", 218 allowdeny_filename, strerror(errno)); 219 return (PAM_SERVICE_ERR); 220 } 221 222 if (check_group && ((grbuf = calloc(1, grbuflen)) == NULL)) { 223 __pam_log(LOG_AUTH | LOG_ERR, 224 "pam_list: could not allocate memory for group"); 225 return (PAM_BUF_ERR); 226 } 227 228 while (fgets(buf, BUFSIZ, fd) != NULL) { 229 /* lines longer than BUFSIZ-1 */ 230 if ((strlen(buf) == (BUFSIZ - 1)) && 231 (buf[BUFSIZ - 2] != '\n')) { 232 while ((fgetc(fd) != '\n') && (!feof(fd))) { 233 continue; 234 } 235 __pam_log(LOG_AUTH | LOG_DEBUG, 236 "pam_list: long line in file," 237 "more than %d chars, the rest ignored", BUFSIZ - 1); 238 } 239 240 /* remove unneeded colons if necessary */ 241 if ((limit = strpbrk(buf, ":\n")) != NULL) { 242 *limit = '\0'; 243 } 244 245 /* ignore free values */ 246 if (buf[0] == '\0') { 247 continue; 248 } 249 250 bufp = buf; 251 252 /* test for interesting lines = +/- in /etc/passwd */ 253 if (op_mode == LIST_COMPAT_MODE) { 254 /* simple + matches all */ 255 if ((buf[0] == '+') && (buf[1] == '\0')) { 256 matched = B_TRUE; 257 allow = B_TRUE; 258 break; 259 } 260 261 /* simple - is not defined */ 262 if ((buf[0] == '-') && (buf[1] == '\0')) { 263 __pam_log(LOG_AUTH | LOG_ERR, 264 "pam_list: simple minus unknown, " 265 "illegal line in " PF_PATH); 266 (void) fclose(fd); 267 free(grbuf); 268 return (PAM_SERVICE_ERR); 269 } 270 271 /* @ is not allowed on the first position */ 272 if (buf[0] == '@') { 273 __pam_log(LOG_AUTH | LOG_ERR, 274 "pam_list: @ is not allowed on the first " 275 "position in " PF_PATH); 276 (void) fclose(fd); 277 free(grbuf); 278 return (PAM_SERVICE_ERR); 279 } 280 281 /* -user or -@netgroup */ 282 if (buf[0] == '-') { 283 allow = B_FALSE; 284 bufp++; 285 /* +user or +@netgroup */ 286 } else if (buf[0] == '+') { 287 allow = B_TRUE; 288 bufp++; 289 /* user */ 290 } else { 291 allow = B_TRUE; 292 } 293 } else if (op_mode == LIST_PLUS_CHECK) { 294 if (((buf[0] != '+') && (buf[0] != '-')) || 295 (buf[1] == '\0')) { 296 continue; 297 } 298 299 if (buf[0] == '+') { 300 allow = B_TRUE; 301 } else { 302 allow = B_FALSE; 303 } 304 bufp++; 305 } 306 307 /* 308 * if -> netgroup line 309 * else if -> group line 310 * else -> user line 311 */ 312 if ((bufp[0] == '@') && (bufp[1] != '\0')) { 313 bufp++; 314 315 if (check_exact) { 316 if (innetgr(bufp, rhost, username, 317 NULL) == 1) { 318 matched = B_TRUE; 319 break; 320 } 321 } else { 322 if (check_user) { 323 userok = innetgr(bufp, NULL, username, 324 NULL); 325 } else { 326 userok = 1; 327 } 328 if (check_host) { 329 hostok = innetgr(bufp, rhost, NULL, 330 NULL); 331 } else { 332 hostok = 1; 333 } 334 if (userok && hostok) { 335 matched = B_TRUE; 336 break; 337 } 338 } 339 } else if ((bufp[0] == '%') && (bufp[1] != '\0')) { 340 char **member; 341 struct group grp; 342 343 if (check_group == B_FALSE) 344 continue; 345 346 bufp++; 347 348 if (getgrnam_r(bufp, &grp, grbuf, grbuflen) != NULL) { 349 for (member = grp.gr_mem; *member != NULL; 350 member++) { 351 if (strcmp(*member, username) == 0) { 352 matched = B_TRUE; 353 break; 354 } 355 } 356 } else { 357 __pam_log(LOG_AUTH | LOG_ERR, 358 "pam_list: %s is not a known group", 359 bufp); 360 } 361 } else { 362 if (check_user) { 363 if (strcmp(bufp, username) == 0) { 364 matched = B_TRUE; 365 break; 366 } 367 } 368 } 369 370 /* 371 * No match found in /etc/passwd yet. For compat mode 372 * a failure to match should result in a return of 373 * PAM_PERM_DENIED which is achieved below if 'matched' 374 * is false and 'allow' is true. 375 */ 376 if (op_mode == LIST_COMPAT_MODE) { 377 allow = B_TRUE; 378 } 379 } 380 (void) fclose(fd); 381 free(grbuf); 382 383 if (debug) { 384 __pam_log(LOG_AUTH | LOG_DEBUG, 385 "pam_list: %s for %s", matched ? "matched" : "no match", 386 allow ? "allow" : "deny"); 387 } 388 389 if (matched) { 390 return (allow ? PAM_SUCCESS : PAM_PERM_DENIED); 391 } 392 /* 393 * For compatibility with passwd_compat mode to prevent root access 394 * denied. 395 */ 396 if (op_mode == LIST_PLUS_CHECK) { 397 return (PAM_IGNORE); 398 } 399 return (allow ? PAM_PERM_DENIED : PAM_SUCCESS); 400 } 401