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 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <sys/types.h> 29 #include <sys/varargs.h> 30 #include <string.h> 31 #include <syslog.h> 32 #include <stdlib.h> 33 34 #include <security/pam_appl.h> 35 #include <security/pam_modules.h> 36 #include <security/pam_impl.h> 37 38 #include <libintl.h> 39 40 #include <passwdutil.h> 41 42 #define SUNW_OLDRPCPASS "SUNW-OLD-RPC-PASSWORD" 43 44 /*PRINTFLIKE3*/ 45 static void 46 error(int nowarn, pam_handle_t *pamh, char *fmt, ...) 47 { 48 va_list ap; 49 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 50 51 va_start(ap, fmt); 52 (void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap); 53 if (nowarn == 0) 54 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages, 55 NULL); 56 va_end(ap); 57 } 58 59 /*PRINTFLIKE3*/ 60 static void 61 info(int nowarn, pam_handle_t *pamh, char *fmt, ...) 62 { 63 va_list ap; 64 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 65 66 va_start(ap, fmt); 67 (void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap); 68 if (nowarn == 0) 69 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, 70 NULL); 71 va_end(ap); 72 } 73 74 #if defined(ENABLE_AGING) 75 /* 76 * test if authtok is aged. 77 * returns 1 if it is, 0 otherwise 78 */ 79 static int 80 authtok_is_aged(pam_handle_t *pamh) 81 { 82 unix_authtok_data *status; 83 84 if (pam_get_data(pamh, UNIX_AUTHTOK_DATA, 85 (const void **)status) != PAM_SUCCESS) 86 return (0); 87 88 return (status->age_status == PAM_NEW_AUTHTOK_REQD) 89 } 90 #endif 91 92 int 93 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) 94 { 95 int i; 96 int debug = 0; 97 int nowarn = 0; 98 attrlist l; 99 pwu_repository_t *pwu_rep; 100 char *user; 101 char *oldpw; 102 char *oldrpcpw = "*NP*"; 103 char *newpw; 104 char *service; 105 struct pam_repository *auth_rep; 106 int res; 107 char msg[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 108 int updated_reps = 0; 109 int server_policy = 0; 110 111 for (i = 0; i < argc; i++) { 112 if (strcmp(argv[i], "debug") == 0) 113 debug = 1; 114 else if (strcmp(argv[i], "nowarn") == 0) 115 nowarn = 1; 116 else if (strcmp(argv[i], "server_policy") == 0) 117 server_policy = 1; 118 } 119 120 if ((flags & PAM_PRELIM_CHECK) != 0) 121 return (PAM_IGNORE); 122 123 if ((flags & PAM_UPDATE_AUTHTOK) == 0) 124 return (PAM_SYSTEM_ERR); 125 126 if ((flags & PAM_SILENT) != 0) 127 nowarn = 1; 128 129 if (debug) 130 syslog(LOG_DEBUG, "pam_authtok_store: storing authtok"); 131 132 #if defined(ENABLE_AGING) 133 if ((flags & PAM_CHANGE_EXPIRED_AUTHTOK) && !authtok_is_aged(pamh)) { 134 syslog(LOG_DEBUG, "pam_authtok_store: System password young"); 135 return (PAM_IGNORE) 136 } 137 #endif 138 139 res = pam_get_item(pamh, PAM_SERVICE, (void **)&service); 140 if (res != PAM_SUCCESS) { 141 syslog(LOG_ERR, "pam_authtok_store: error getting SERVICE"); 142 return (PAM_SYSTEM_ERR); 143 } 144 145 res = pam_get_item(pamh, PAM_USER, (void **)&user); 146 if (res != PAM_SUCCESS) { 147 syslog(LOG_ERR, "pam_authtok_store: error getting USER"); 148 return (PAM_SYSTEM_ERR); 149 } 150 151 if (user == NULL || *user == '\0') { 152 syslog(LOG_ERR, "pam_authtok_store: username is empty"); 153 return (PAM_USER_UNKNOWN); 154 } 155 156 res = pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpw); 157 if (res != PAM_SUCCESS) { 158 syslog(LOG_ERR, "pam_authtok_store: error getting OLDAUTHTOK"); 159 return (PAM_SYSTEM_ERR); 160 } 161 162 res = pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpw); 163 if (res != PAM_SUCCESS || newpw == NULL) { 164 /* 165 * A module on the stack has removed PAM_AUTHTOK. We fail 166 */ 167 return (PAM_SYSTEM_ERR); 168 } 169 170 l.data.val_s = newpw; 171 /* 172 * If the server_policy option is specified, 173 * use the special attribute, ATTR_PASSWD_SERVER_POLICY, 174 * to tell the update routine for each repository 175 * to perform the necessary special operations. 176 * For now, only the LDAP routine treats this attribute 177 * differently that ATTR_PASSWD. It will skip the 178 * crypting of the password before storing it in the LDAP 179 * server. NIS, NISPLUS, and FILES will handle 180 * ATTR_PASSWD_SERVER_POLICY the same as ATTR_PASSWD. 181 */ 182 if (server_policy) 183 l.type = ATTR_PASSWD_SERVER_POLICY; 184 else 185 l.type = ATTR_PASSWD; 186 l.next = NULL; 187 188 res = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep); 189 if (res != PAM_SUCCESS) { 190 syslog(LOG_ERR, "pam_authtok_store: error getting repository"); 191 return (PAM_SYSTEM_ERR); 192 } 193 194 if (auth_rep == NULL) { 195 pwu_rep = PWU_DEFAULT_REP; 196 } else { 197 if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) 198 return (PAM_BUF_ERR); 199 pwu_rep->type = auth_rep->type; 200 pwu_rep->scope = auth_rep->scope; 201 pwu_rep->scope_len = auth_rep->scope_len; 202 } 203 204 /* 205 * The pam_dhkeys module might have set SUNW_OLDRPCPASS if it 206 * discovered that the user's old password doesn't decrypt the 207 * user's secure RPC credentials. In that case, the 208 * item SUNW_OLDRPCPASS contains the correct password to 209 * decrypt these credentials. 210 */ 211 212 res = pam_get_data(pamh, SUNW_OLDRPCPASS, (const void **)&oldrpcpw); 213 if (res != PAM_SUCCESS && res != PAM_NO_MODULE_DATA) { 214 syslog(LOG_ERR, "pam_authtok_store: error getting OLDRPCPASS"); 215 return (PAM_SYSTEM_ERR); 216 } 217 218 res = __set_authtoken_attr(user, oldpw, oldrpcpw, pwu_rep, &l, 219 &updated_reps); 220 221 if (pwu_rep != PWU_DEFAULT_REP) 222 free(pwu_rep); 223 /* 224 * now map the various passwdutil return states to user messages 225 * and PAM return codes. 226 */ 227 switch (res) { 228 case PWU_SUCCESS: 229 for (i = 1; i <= REP_LAST; i <<= 1) { 230 if ((updated_reps & i) == 0) 231 continue; 232 info(nowarn, pamh, dgettext(TEXT_DOMAIN, 233 "%s: password successfully changed for %s"), 234 service, user); 235 } 236 237 /* 238 * If we have updated NIS+, and we got SUCCESS (not one of 239 * the partial failures), this indicates that the credential 240 * update went well too... Inform the user 241 */ 242 if (updated_reps & REP_NISPLUS) 243 info(nowarn, pamh, dgettext(TEXT_DOMAIN, 244 "%s: credential information changed for %s"), 245 service, user); 246 res = PAM_SUCCESS; 247 break; 248 case PWU_BUSY: 249 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 250 "%s: Password database busy. Try again later."), 251 service); 252 res = PAM_AUTHTOK_LOCK_BUSY; 253 break; 254 case PWU_STAT_FAILED: 255 syslog(LOG_ERR, "%s: stat of password file failed", service); 256 res = PAM_AUTHTOK_ERR; 257 break; 258 case PWU_OPEN_FAILED: 259 case PWU_WRITE_FAILED: 260 case PWU_CLOSE_FAILED: 261 case PWU_UPDATE_FAILED: 262 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 263 "%s: Unexpected failure. Password database unchanged."), 264 service); 265 res = PAM_SYSTEM_ERR; 266 break; 267 case PWU_NOT_FOUND: 268 /* Different error if repository was explicitly specified */ 269 if (auth_rep != NULL) { 270 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 271 "%s: System error: no %s password for %s."), 272 service, auth_rep->type, user); 273 } else { 274 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 275 "%s: %s does not exist."), service, user); 276 } 277 res = PAM_USER_UNKNOWN; 278 break; 279 case PWU_NOMEM: 280 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 281 "%s: Internal memory allocation failure."), service); 282 res = PAM_BUF_ERR; 283 break; 284 case PWU_SERVER_ERROR: 285 res = PAM_SYSTEM_ERR; 286 break; 287 case PWU_SYSTEM_ERROR: 288 res = PAM_SYSTEM_ERR; 289 break; 290 case PWU_DENIED: 291 res = PAM_PERM_DENIED; 292 break; 293 case PWU_NO_CHANGE: 294 /* 295 * yppasswdd detected that we're not changing anything. 296 */ 297 info(nowarn, pamh, dgettext(TEXT_DOMAIN, 298 "%s: Password information unchanged."), service); 299 res = PAM_SUCCESS; 300 break; 301 case PWU_REPOSITORY_ERROR: 302 syslog(LOG_NOTICE, "pam_authtok_store: detected " 303 "unsupported configuration in /etc/nsswitch.conf."); 304 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 305 "%s: System error: repository out of range."), service); 306 res = PAM_SYSTEM_ERR; 307 break; 308 case PWU_RECOVERY_ERR: 309 res = PAM_AUTHTOK_RECOVERY_ERR; 310 break; 311 case PWU_NO_PRIV_CRED_UPDATE: 312 /* 313 * A privileged process has updated a user's password. 314 * In this case, the password will be updated, but the 315 * credentials won't. This is not a failure, but we need 316 * to inform the user about it, and return PAM_SUCCESS 317 */ 318 319 /* First inform the user about the passsword update */ 320 info(nowarn, pamh, dgettext(TEXT_DOMAIN, 321 "%s: password successfully changed for %s"), 322 service, user); 323 324 /* and now the bad news */ 325 (void) sprintf(msg[0], " "); 326 (void) snprintf(msg[1], sizeof (msg[1]), 327 dgettext(TEXT_DOMAIN, 328 "The Secure RPC credential information for %s " 329 "will not be changed."), user); 330 (void) snprintf(msg[2], sizeof (msg[2]), 331 dgettext(TEXT_DOMAIN, "User %s must do the following to " 332 "update his/her"), user); 333 (void) snprintf(msg[3], sizeof (msg[3]), 334 dgettext(TEXT_DOMAIN, "credential information:")); 335 (void) snprintf(msg[4], sizeof (msg[4]), 336 dgettext(TEXT_DOMAIN, "Use NEW passwd for login and OLD " 337 "passwd for keylogin.")); 338 (void) snprintf(msg[5], sizeof (msg[5]), 339 dgettext(TEXT_DOMAIN, "Use \"chkey -p\" to reencrypt the " 340 "credentials with the")); 341 (void) snprintf(msg[6], sizeof (msg[6]), 342 dgettext(TEXT_DOMAIN, "new login passwd.")); 343 (void) snprintf(msg[7], sizeof (msg[7]), 344 dgettext(TEXT_DOMAIN, "The user must keylogin explicitly " 345 "after their next login.")); 346 (void) sprintf(msg[8], " "); 347 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 9, msg, NULL); 348 res = PAM_SUCCESS; 349 break; 350 case PWU_UPDATED_SOME_CREDS: 351 info(nowarn, pamh, dgettext(TEXT_DOMAIN, 352 "%s: password successfully changed for %s"), 353 service, user); 354 355 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 356 "WARNING: some but not all credentials were reencrypted " 357 "for user %s"), user); 358 res = PAM_SUCCESS; 359 break; 360 case PWU_PWD_TOO_SHORT: 361 (void) snprintf(msg[0], sizeof (msg[0]), 362 dgettext(TEXT_DOMAIN, "%s: Password too short."), service); 363 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL); 364 res = PAM_AUTHTOK_ERR; 365 break; 366 case PWU_PWD_INVALID: 367 (void) snprintf(msg[0], sizeof (msg[0]), 368 dgettext(TEXT_DOMAIN, "%s: Invalid password syntax."), 369 service); 370 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL); 371 res = PAM_AUTHTOK_ERR; 372 break; 373 case PWU_PWD_IN_HISTORY: 374 (void) snprintf(msg[0], sizeof (msg[0]), 375 dgettext(TEXT_DOMAIN, "%s: Reuse of old passwords not " 376 "allowed, the new password is in the history list."), 377 service); 378 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL); 379 res = PAM_AUTHTOK_ERR; 380 break; 381 case PWU_CHANGE_NOT_ALLOWED: 382 (void) snprintf(msg[0], sizeof (msg[0]), 383 dgettext(TEXT_DOMAIN, "%s: You may not change " 384 "this password."), service); 385 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL); 386 res = PAM_PERM_DENIED; 387 break; 388 case PWU_WITHIN_MIN_AGE: 389 (void) snprintf(msg[0], sizeof (msg[0]), 390 dgettext(TEXT_DOMAIN, 391 "%s: Password can not be changed yet, " 392 "not enough time has passed."), service); 393 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL); 394 res = PAM_PERM_DENIED; 395 break; 396 default: 397 res = PAM_SYSTEM_ERR; 398 break; 399 } 400 401 return (res); 402 } 403