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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. 26 */ 27 28 29 #include <stdlib.h> 30 #include <pwd.h> 31 #include <shadow.h> 32 #include <syslog.h> 33 #include <errno.h> 34 #include <string.h> 35 #include <crypt.h> 36 #include <unistd.h> 37 #include <user_attr.h> 38 #include <auth_attr.h> 39 #include <userdefs.h> 40 #include <deflt.h> 41 #include <sys/stat.h> 42 #include <sys/param.h> 43 #include <stdarg.h> 44 45 #include <security/pam_appl.h> 46 #include <security/pam_modules.h> 47 #include <security/pam_impl.h> 48 49 #include <libintl.h> 50 51 #include <passwdutil.h> 52 53 #define LOGINADMIN "/etc/default/login" 54 #define MAXTRYS 5 55 56 /*PRINTFLIKE2*/ 57 void 58 error(pam_handle_t *pamh, char *fmt, ...) 59 { 60 va_list ap; 61 char messages[1][PAM_MAX_MSG_SIZE]; 62 63 va_start(ap, fmt); 64 (void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap); 65 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages, NULL); 66 va_end(ap); 67 } 68 69 static int 70 get_max_failed(const char *user) 71 { 72 char *val = NULL; 73 userattr_t *uattr; 74 int do_lock = 0; 75 int retval = 0; 76 char *p; 77 void *defp; 78 79 if ((uattr = getusernam(user)) != NULL) 80 val = kva_match(uattr->attr, USERATTR_LOCK_AFTER_RETRIES_KW); 81 82 if (val != NULL) { 83 do_lock = (strcasecmp(val, "yes") == 0); 84 } else if ((defp = defopen_r(AUTH_POLICY)) != NULL) { 85 int flags; 86 flags = defcntl_r(DC_GETFLAGS, 0, defp); 87 TURNOFF(flags, DC_CASE); 88 (void) defcntl_r(DC_SETFLAGS, flags, defp); 89 if ((p = defread_r("LOCK_AFTER_RETRIES=", defp)) != NULL) 90 do_lock = (strcasecmp(p, "yes") == 0); 91 defclose_r(defp); 92 } 93 94 if (uattr != NULL) 95 free_userattr(uattr); 96 97 if (do_lock) { 98 retval = MAXTRYS; 99 if ((defp = defopen_r(LOGINADMIN)) != NULL) { 100 if ((p = defread_r("RETRIES=", defp)) != NULL) 101 retval = atoi(p); 102 defclose_r(defp); 103 } 104 } 105 106 return (retval); 107 } 108 109 static void 110 display_warning(pam_handle_t *pamh, int failures, char *homedir) 111 { 112 char hushpath[MAXPATHLEN]; 113 struct stat buf; 114 115 (void) snprintf(hushpath, sizeof (hushpath), "%s/.hushlogin", homedir); 116 if (stat(hushpath, &buf) == 0) 117 return; 118 119 if (failures == 1) 120 error(pamh, "Warning: 1 failed login attempt since last " 121 "successful login."); 122 else if (failures < FAILCOUNT_MASK) 123 error(pamh, "Warning: %d failed login attempts since last " 124 "successful login.", failures); 125 else 126 error(pamh, "Warning: at least %d failed login attempts since " 127 "last successful login.", failures); 128 } 129 130 /* 131 * int pam_sm_authenticate(pamh, flags, arc, argv) 132 * 133 * This routine verifies that the password as stored in the 134 * PAM_AUTHTOK item is indeed the password that belongs to the user 135 * as stored in PAM_USER. 136 * 137 * This routine will not establish Secure RPC Credentials, the pam_dhkeys 138 * module should be stacked before us if Secure RPC Credentials are needed 139 * to obtain passwords. 140 */ 141 int 142 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 143 { 144 int i; 145 int debug = 0; 146 int nowarn = (flags & PAM_SILENT) != 0; 147 const char *user; 148 const char *passwd; 149 char *rep_passwd; 150 char *crypt_passwd; 151 char *repository_name; 152 const struct pam_repository *auth_rep; 153 pwu_repository_t *pwu_rep; 154 attrlist attr_pw[4]; 155 int result; 156 int server_policy = 0; 157 int old_failed_count; 158 char *homedir = NULL; 159 int dolock = 1; 160 161 for (i = 0; i < argc; i++) { 162 if (strcmp(argv[i], "debug") == 0) 163 debug = 1; 164 else if (strcmp(argv[i], "nowarn") == 0) 165 nowarn = 1; 166 else if (strcmp(argv[i], "server_policy") == 0) 167 server_policy = 1; 168 else if (strcmp(argv[i], "nolock") == 0) 169 dolock = 0; 170 } 171 172 if (debug) 173 __pam_log(LOG_AUTH | LOG_DEBUG, 174 "pam_unix_auth: entering pam_sm_authenticate()"); 175 176 if (pam_get_item(pamh, PAM_USER, (const void **)&user) != PAM_SUCCESS) { 177 __pam_log(LOG_AUTH | LOG_DEBUG, "pam_unix_auth: USER not set"); 178 return (PAM_SYSTEM_ERR); 179 } 180 181 if (user == NULL || *user == '\0') { 182 __pam_log(LOG_AUTH | LOG_DEBUG, 183 "pam_unix_auth: USER NULL or empty!\n"); 184 return (PAM_USER_UNKNOWN); 185 } 186 187 if (pam_get_item(pamh, PAM_AUTHTOK, (const void **)&passwd) != 188 PAM_SUCCESS) { 189 __pam_log(LOG_AUTH | LOG_DEBUG, 190 "pam_unix_auth: AUTHTOK not set!\n"); 191 return (PAM_SYSTEM_ERR); 192 } 193 194 result = pam_get_item(pamh, PAM_REPOSITORY, (const void **)&auth_rep); 195 if (result == PAM_SUCCESS && auth_rep != NULL) { 196 if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) 197 return (PAM_BUF_ERR); 198 pwu_rep->type = auth_rep->type; 199 pwu_rep->scope = auth_rep->scope; 200 pwu_rep->scope_len = auth_rep->scope_len; 201 } else { 202 pwu_rep = PWU_DEFAULT_REP; 203 } 204 205 /* 206 * Get password and the name of the repository where the 207 * password resides. 208 */ 209 attr_pw[0].type = ATTR_PASSWD; attr_pw[0].next = &attr_pw[1]; 210 attr_pw[1].type = ATTR_REP_NAME; attr_pw[1].next = &attr_pw[2]; 211 /* 212 * Also get the current number of failed logins; we use 213 * this later to determine whether we need to reset the count 214 * on a succesful authentication. We use the home-directory 215 * to look for .hushlogin in order to optionaly surpress the 216 * "failed attempts" message. 217 */ 218 attr_pw[2].type = ATTR_FAILED_LOGINS; attr_pw[2].next = &attr_pw[3]; 219 attr_pw[3].type = ATTR_HOMEDIR; attr_pw[3].next = NULL; 220 221 result = __get_authtoken_attr(user, pwu_rep, attr_pw); 222 223 if (pwu_rep != PWU_DEFAULT_REP) 224 free(pwu_rep); 225 226 if (result == PWU_NOT_FOUND) { 227 __pam_log(LOG_AUTH | LOG_DEBUG, 228 "pam_unix_auth: user %s not found\n", user); 229 return (PAM_USER_UNKNOWN); 230 } 231 232 if (result == PWU_DENIED) { 233 __pam_log(LOG_AUTH | LOG_DEBUG, 234 "pam_unix_auth: failed to obtain attributes"); 235 return (PAM_PERM_DENIED); 236 } 237 238 if (result != PWU_SUCCESS) 239 return (PAM_SYSTEM_ERR); 240 241 rep_passwd = attr_pw[0].data.val_s; 242 repository_name = attr_pw[1].data.val_s; 243 old_failed_count = attr_pw[2].data.val_i; 244 homedir = attr_pw[3].data.val_s; 245 246 /* 247 * Chop off old SunOS-style password aging information. 248 * 249 * Note: old style password aging is only defined for UNIX-style 250 * crypt strings, hence the comma will always be at position 14. 251 * Note: This code is here because some other vendors might still 252 * support this style of password aging. If we don't remove 253 * the age field, no one will be able to login. 254 * XXX yank this code when we're certain this "compatibility" 255 * isn't needed anymore. 256 */ 257 if (rep_passwd != NULL && rep_passwd[0] != '$' && 258 strlen(rep_passwd) > 13 && rep_passwd[13] == ',') 259 rep_passwd[13] = '\0'; 260 261 /* Is a password check required? */ 262 if (rep_passwd == NULL || *rep_passwd == '\0') { 263 if (flags & PAM_DISALLOW_NULL_AUTHTOK) { 264 result = PAM_AUTH_ERR; 265 __pam_log(LOG_AUTH | LOG_NOTICE, 266 "pam_unix_auth: empty password for %s not allowed.", 267 user); 268 goto out; 269 } else { 270 result = PAM_SUCCESS; 271 goto out; 272 } 273 } 274 275 /* 276 * Password check *is* required. Make sure we have a valid 277 * pointer in PAM_AUTHTOK 278 */ 279 if (passwd == NULL) { 280 result = PAM_AUTH_ERR; 281 goto out; 282 } 283 284 if (server_policy && 285 strcmp(repository_name, "files") != 0 && 286 strcmp(repository_name, "nis") != 0) { 287 result = PAM_IGNORE; 288 goto out; 289 } 290 291 /* Now check the entered password */ 292 if ((crypt_passwd = crypt(passwd, rep_passwd)) == NULL) { 293 switch (errno) { 294 case ENOMEM: 295 result = PAM_BUF_ERR; 296 break; 297 case ELIBACC: 298 result = PAM_OPEN_ERR; 299 break; 300 default: 301 result = PAM_SYSTEM_ERR; 302 } 303 goto out; 304 } 305 306 if (strcmp(crypt_passwd, rep_passwd) == 0) 307 result = PAM_SUCCESS; 308 else 309 result = PAM_AUTH_ERR; 310 311 /* Clear or increment failed failed count */ 312 if (dolock && (result == PAM_SUCCESS && old_failed_count > 0)) { 313 old_failed_count = __rst_failed_count(user, repository_name); 314 if (nowarn == 0 && old_failed_count > 0) 315 display_warning(pamh, old_failed_count, homedir); 316 } else if (dolock && result == PAM_AUTH_ERR) { 317 int max_failed = get_max_failed(user); 318 if (max_failed != 0) { 319 if (__incr_failed_count(user, repository_name, 320 max_failed) == PWU_ACCOUNT_LOCKED) 321 result = PAM_MAXTRIES; 322 } 323 } 324 out: 325 if (rep_passwd) 326 free(rep_passwd); 327 if (repository_name) 328 free(repository_name); 329 if (homedir) 330 free(homedir); 331 return (result); 332 } 333 334 /*ARGSUSED*/ 335 int 336 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 337 { 338 return (PAM_IGNORE); 339 } 340