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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 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(char *user) 71 { 72 char *val = NULL; 73 userattr_t *uattr; 74 int do_lock = 0; 75 int retval = 0; 76 char *p; 77 78 if ((uattr = getusernam(user)) != NULL) 79 val = kva_match(uattr->attr, USERATTR_LOCK_AFTER_RETRIES_KW); 80 81 if (val != NULL) 82 do_lock = (strcasecmp(val, "yes") == 0); 83 else if (defopen(AUTH_POLICY) == 0) { 84 int flags; 85 flags = defcntl(DC_GETFLAGS, 0); 86 TURNOFF(flags, DC_CASE); 87 (void) defcntl(DC_SETFLAGS, flags); 88 if ((p = defread("LOCK_AFTER_RETRIES=")) != NULL) 89 do_lock = (strcasecmp(p, "yes") == 0); 90 (void) defopen(NULL); 91 } 92 93 if (uattr != NULL) 94 free_userattr(uattr); 95 96 if (do_lock) { 97 retval = MAXTRYS; 98 if (defopen(LOGINADMIN) == 0) { 99 if ((p = defread("RETRIES=")) != NULL) 100 retval = atoi(p); 101 (void) defopen(NULL); 102 } 103 } 104 105 return (retval); 106 } 107 108 static void 109 display_warning(pam_handle_t *pamh, int failures, char *homedir) 110 { 111 char hushpath[MAXPATHLEN]; 112 struct stat buf; 113 114 (void) snprintf(hushpath, sizeof (hushpath), "%s/.hushlogin", homedir); 115 if (stat(hushpath, &buf) == 0) 116 return; 117 118 if (failures == 1) 119 error(pamh, "Warning: 1 failed login attempt since last " 120 "successful login."); 121 else if (failures < FAILCOUNT_MASK) 122 error(pamh, "Warning: %d failed login attempts since last " 123 "successful login.", failures); 124 else 125 error(pamh, "Warning: at least %d failed login attempts since " 126 "last successful login.", failures); 127 } 128 129 /* 130 * int pam_sm_authenticate(pamh, flags, arc, argv) 131 * 132 * This routine verifies that the password as stored in the 133 * PAM_AUTHTOK item is indeed the password that belongs to the user 134 * as stored in PAM_USER. 135 * 136 * This routine will not establish Secure RPC Credentials. If these 137 * credentials are needed to obtain the password from the NIS+ service, 138 * the pam_dhkeys module should be stacked before us! 139 */ 140 int 141 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 142 { 143 int i; 144 int debug = 0; 145 int nowarn = (flags & PAM_SILENT) != 0; 146 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 147 char *user; 148 char *passwd; 149 char *rep_passwd; 150 char *repository_name; 151 struct pam_repository *auth_rep; 152 pwu_repository_t *pwu_rep; 153 attrlist attr_pw[4]; 154 int result; 155 int server_policy = 0; 156 int old_failed_count; 157 char *homedir = NULL; 158 int dolock = 1; 159 160 for (i = 0; i < argc; i++) { 161 if (strcmp(argv[i], "debug") == 0) 162 debug = 1; 163 else if (strcmp(argv[i], "nowarn") == 0) 164 nowarn = 1; 165 else if (strcmp(argv[i], "server_policy") == 0) 166 server_policy = 1; 167 else if (strcmp(argv[i], "nolock") == 0) 168 dolock = 0; 169 } 170 171 if (debug) 172 syslog(LOG_DEBUG, 173 "pam_unix_auth: entering pam_sm_authenticate()"); 174 175 if (pam_get_item(pamh, PAM_USER, (void **)&user) != PAM_SUCCESS) { 176 syslog(LOG_DEBUG, "pam_unix_auth: USER not set"); 177 return (PAM_SYSTEM_ERR); 178 } 179 180 if (user == NULL || *user == '\0') { 181 syslog(LOG_DEBUG, "pam_unix_auth: USER NULL or empty!\n"); 182 return (PAM_USER_UNKNOWN); 183 } 184 185 if (pam_get_item(pamh, PAM_AUTHTOK, (void **)&passwd) != PAM_SUCCESS) { 186 syslog(LOG_DEBUG, "pam_unix_auth: AUTHTOK not set!\n"); 187 return (PAM_SYSTEM_ERR); 188 } 189 190 result = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep); 191 if (result == PAM_SUCCESS && auth_rep != NULL) { 192 if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) 193 return (PAM_BUF_ERR); 194 pwu_rep->type = auth_rep->type; 195 pwu_rep->scope = auth_rep->scope; 196 pwu_rep->scope_len = auth_rep->scope_len; 197 } else { 198 pwu_rep = PWU_DEFAULT_REP; 199 } 200 201 /* 202 * Get password and the name of the repository where the 203 * password resides. 204 */ 205 attr_pw[0].type = ATTR_PASSWD; attr_pw[0].next = &attr_pw[1]; 206 attr_pw[1].type = ATTR_REP_NAME; attr_pw[1].next = &attr_pw[2]; 207 /* 208 * Also get the current number of failed logins; we use 209 * this later to determine whether we need to reset the count 210 * on a succesful authentication. We use the home-directory 211 * to look for .hushlogin in order to optionaly surpress the 212 * "failed attempts" message. 213 */ 214 attr_pw[2].type = ATTR_FAILED_LOGINS; attr_pw[2].next = &attr_pw[3]; 215 attr_pw[3].type = ATTR_HOMEDIR; attr_pw[3].next = NULL; 216 217 result = __get_authtoken_attr(user, pwu_rep, attr_pw); 218 219 if (pwu_rep != PWU_DEFAULT_REP) 220 free(pwu_rep); 221 222 if (result == PWU_NOT_FOUND) { 223 syslog(LOG_DEBUG, "pam_unix_auth: user %s not found\n", 224 user); 225 return (PAM_USER_UNKNOWN); 226 } 227 228 if (result == PWU_DENIED) { 229 syslog(LOG_DEBUG, "pam_unix_auth: failed to obtain attributes"); 230 return (PAM_PERM_DENIED); 231 } 232 233 if (result != PWU_SUCCESS) 234 return (PAM_SYSTEM_ERR); 235 236 rep_passwd = attr_pw[0].data.val_s; 237 repository_name = attr_pw[1].data.val_s; 238 old_failed_count = attr_pw[2].data.val_i; 239 homedir = attr_pw[3].data.val_s; 240 241 /* 242 * Chop off old SunOS-style password aging information. 243 * 244 * Note: old style password aging is only defined for UNIX-style 245 * crypt strings, hence the comma will always be at position 14. 246 * Note: This code is here because some other vendors might still 247 * support this style of password aging. If we don't remove 248 * the age field, no one will be able to login. 249 * XXX yank this code when we're certain this "compatibility" 250 * isn't needed anymore. 251 */ 252 if (rep_passwd != NULL && rep_passwd[0] != '$' && 253 strlen(rep_passwd) > 13 && rep_passwd[13] == ',') 254 rep_passwd[13] = '\0'; 255 256 /* Is a password check required? */ 257 if (rep_passwd == NULL || *rep_passwd == '\0') { 258 if (flags & PAM_DISALLOW_NULL_AUTHTOK) { 259 result = PAM_AUTH_ERR; 260 goto out; 261 } else { 262 result = PAM_SUCCESS; 263 goto out; 264 } 265 } 266 267 /* 268 * Password check *is* required. Make sure we have a valid 269 * pointer in PAM_AUTHTOK 270 */ 271 if (passwd == NULL) { 272 result = PAM_AUTH_ERR; 273 goto out; 274 } 275 276 /* 277 * "rep_passwd" holds the encrypted password. 278 * If, however, we detect that the password equals "*NP*", 279 * while we've obtained it from NIS+, it 280 * means that the permissions on the NIS+ table are too tight 281 * for us to get the password without having Secure RPC 282 * Credentials. In that case, we syslog an error stating that 283 * the Secure RPC credential Module should be on the PAM stack 284 * before the unix_auth module. We also tell the user to go 285 * and inform the administrator of this error. 286 */ 287 if (strcmp(repository_name, "nisplus") == 0 && 288 strcmp(rep_passwd, "*NP*") == 0) { 289 syslog(LOG_ERR, "pam_unix_auth: NIS+ permissions require that" 290 "the pam_dhkeys module is on the PAM stack before " 291 "pam_unix_auth"); 292 if (nowarn == 0) { 293 (void) snprintf(messages[0], sizeof (messages[0]), 294 dgettext(TEXT_DOMAIN, 295 "NIS+ permissions are too tight. " 296 "Please inform your administrator.")); 297 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, 298 messages, NULL); 299 } 300 result = PAM_USER_UNKNOWN; 301 goto out; 302 } 303 304 if (server_policy && 305 strcmp(repository_name, "files") != 0 && 306 strcmp(repository_name, "nis") != 0 && 307 strcmp(repository_name, "nisplus") != 0) { 308 result = PAM_IGNORE; 309 goto out; 310 } 311 312 /* Now check the entered password */ 313 if (strcmp(crypt(passwd, rep_passwd), rep_passwd) == 0) 314 result = PAM_SUCCESS; 315 else 316 result = PAM_AUTH_ERR; 317 318 /* Clear or increment failed failed count */ 319 if (dolock && (result == PAM_SUCCESS && old_failed_count > 0)) { 320 old_failed_count = __rst_failed_count(user, repository_name); 321 if (nowarn == 0 && old_failed_count > 0) 322 display_warning(pamh, old_failed_count, homedir); 323 } else if (dolock && result == PAM_AUTH_ERR) { 324 int max_failed = get_max_failed(user); 325 if (max_failed != 0) 326 (void) __incr_failed_count(user, repository_name, 327 max_failed); 328 } 329 out: 330 if (rep_passwd) 331 free(rep_passwd); 332 if (repository_name) 333 free(repository_name); 334 if (homedir) 335 free(homedir); 336 return (result); 337 } 338 339 /*ARGSUSED*/ 340 int 341 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 342 { 343 return (PAM_IGNORE); 344 } 345