Home | History | Annotate | Download | only in authtok_store
      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 2008 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 
     27 #include <sys/types.h>
     28 #include <sys/varargs.h>
     29 #include <string.h>
     30 #include <syslog.h>
     31 #include <stdlib.h>
     32 
     33 #include <security/pam_appl.h>
     34 #include <security/pam_modules.h>
     35 #include <security/pam_impl.h>
     36 
     37 #include <libintl.h>
     38 
     39 #include <passwdutil.h>
     40 #include <shadow.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 = NOPWDRTR;
    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