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