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 2009 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 /*PRINTFLIKE3*/
     43 static void
     44 error(int nowarn, pam_handle_t *pamh, char *fmt, ...)
     45 {
     46 	va_list ap;
     47 	char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
     48 
     49 	va_start(ap, fmt);
     50 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
     51 	if (nowarn == 0)
     52 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages,
     53 		    NULL);
     54 	va_end(ap);
     55 }
     56 
     57 /*PRINTFLIKE3*/
     58 static void
     59 info(int nowarn, pam_handle_t *pamh, char *fmt, ...)
     60 {
     61 	va_list ap;
     62 	char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
     63 
     64 	va_start(ap, fmt);
     65 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
     66 	if (nowarn == 0)
     67 		(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages,
     68 		    NULL);
     69 	va_end(ap);
     70 }
     71 
     72 #if defined(ENABLE_AGING)
     73 /*
     74  * test if authtok is aged.
     75  * returns 1 if it is, 0 otherwise
     76  */
     77 static int
     78 authtok_is_aged(pam_handle_t *pamh)
     79 {
     80 	unix_authtok_data *status;
     81 
     82 	if (pam_get_data(pamh, UNIX_AUTHTOK_DATA,
     83 	    (const void **)status) != PAM_SUCCESS)
     84 		return (0);
     85 
     86 	return (status->age_status == PAM_NEW_AUTHTOK_REQD)
     87 }
     88 #endif
     89 
     90 int
     91 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
     92 {
     93 	int i;
     94 	int debug = 0;
     95 	int nowarn = 0;
     96 	attrlist l;
     97 	pwu_repository_t *pwu_rep;
     98 	char *user;
     99 	char *oldpw;
    100 	char *newpw;
    101 	char *service;
    102 	struct pam_repository *auth_rep;
    103 	int res;
    104 	char msg[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
    105 	int updated_reps = 0;
    106 	int server_policy = 0;
    107 
    108 	for (i = 0; i < argc; i++) {
    109 		if (strcmp(argv[i], "debug") == 0)
    110 			debug = 1;
    111 		else if (strcmp(argv[i], "nowarn") == 0)
    112 			nowarn = 1;
    113 		else if (strcmp(argv[i], "server_policy") == 0)
    114 			server_policy = 1;
    115 	}
    116 
    117 	if ((flags & PAM_PRELIM_CHECK) != 0)
    118 		return (PAM_IGNORE);
    119 
    120 	if ((flags & PAM_UPDATE_AUTHTOK) == 0)
    121 		return (PAM_SYSTEM_ERR);
    122 
    123 	if ((flags & PAM_SILENT) != 0)
    124 		nowarn = 1;
    125 
    126 	if (debug)
    127 		syslog(LOG_DEBUG, "pam_authtok_store: storing authtok");
    128 
    129 #if defined(ENABLE_AGING)
    130 	if ((flags & PAM_CHANGE_EXPIRED_AUTHTOK) && !authtok_is_aged(pamh)) {
    131 		syslog(LOG_DEBUG, "pam_authtok_store: System password young");
    132 		return (PAM_IGNORE);
    133 	}
    134 #endif
    135 
    136 	res = pam_get_item(pamh, PAM_SERVICE, (void **)&service);
    137 	if (res != PAM_SUCCESS) {
    138 		syslog(LOG_ERR, "pam_authtok_store: error getting SERVICE");
    139 		return (PAM_SYSTEM_ERR);
    140 	}
    141 
    142 	res = pam_get_item(pamh, PAM_USER, (void **)&user);
    143 	if (res != PAM_SUCCESS) {
    144 		syslog(LOG_ERR, "pam_authtok_store: error getting USER");
    145 		return (PAM_SYSTEM_ERR);
    146 	}
    147 
    148 	if (user == NULL || *user == '\0') {
    149 		syslog(LOG_ERR, "pam_authtok_store: username is empty");
    150 		return (PAM_USER_UNKNOWN);
    151 	}
    152 
    153 	res = pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpw);
    154 	if (res != PAM_SUCCESS) {
    155 		syslog(LOG_ERR, "pam_authtok_store: error getting OLDAUTHTOK");
    156 		return (PAM_SYSTEM_ERR);
    157 	}
    158 
    159 	res = pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpw);
    160 	if (res != PAM_SUCCESS || newpw == NULL) {
    161 		/*
    162 		 * A module on the stack has removed PAM_AUTHTOK. We fail
    163 		 */
    164 		return (PAM_SYSTEM_ERR);
    165 	}
    166 
    167 	l.data.val_s = newpw;
    168 	/*
    169 	 * If the server_policy option is specified,
    170 	 * use the special attribute, ATTR_PASSWD_SERVER_POLICY,
    171 	 * to tell the update routine for each repository
    172 	 * to perform the necessary special operations.
    173 	 * For now, only the LDAP routine treats this attribute
    174 	 * differently that ATTR_PASSWD. It will skip the
    175 	 * crypting of the password before storing it in the LDAP
    176 	 * server. NIS, and FILES will handle ATTR_PASSWD_SERVER_POLICY
    177 	 * the same as ATTR_PASSWD.
    178 	 */
    179 	if (server_policy)
    180 		l.type = ATTR_PASSWD_SERVER_POLICY;
    181 	else
    182 		l.type = ATTR_PASSWD;
    183 	l.next = NULL;
    184 
    185 	res = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
    186 	if (res != PAM_SUCCESS) {
    187 		syslog(LOG_ERR, "pam_authtok_store: error getting repository");
    188 		return (PAM_SYSTEM_ERR);
    189 	}
    190 
    191 	if (auth_rep == NULL) {
    192 		pwu_rep = PWU_DEFAULT_REP;
    193 	} else {
    194 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
    195 			return (PAM_BUF_ERR);
    196 		pwu_rep->type = auth_rep->type;
    197 		pwu_rep->scope = auth_rep->scope;
    198 		pwu_rep->scope_len = auth_rep->scope_len;
    199 	}
    200 
    201 	res = __set_authtoken_attr(user, oldpw, pwu_rep, &l, &updated_reps);
    202 
    203 	if (pwu_rep != PWU_DEFAULT_REP)
    204 		free(pwu_rep);
    205 	/*
    206 	 * now map the various passwdutil return states to user messages
    207 	 * and PAM return codes.
    208 	 */
    209 	switch (res) {
    210 	case PWU_SUCCESS:
    211 		for (i = 1; i <= REP_LAST; i <<= 1) {
    212 			if ((updated_reps & i) == 0)
    213 				continue;
    214 			info(nowarn, pamh, dgettext(TEXT_DOMAIN,
    215 			    "%s: password successfully changed for %s"),
    216 			    service, user);
    217 		}
    218 		res = PAM_SUCCESS;
    219 		break;
    220 	case PWU_BUSY:
    221 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
    222 		    "%s: Password database busy. Try again later."),
    223 		    service);
    224 		res = PAM_AUTHTOK_LOCK_BUSY;
    225 		break;
    226 	case PWU_STAT_FAILED:
    227 		syslog(LOG_ERR, "%s: stat of password file failed", service);
    228 		res = PAM_AUTHTOK_ERR;
    229 		break;
    230 	case PWU_OPEN_FAILED:
    231 	case PWU_WRITE_FAILED:
    232 	case PWU_CLOSE_FAILED:
    233 	case PWU_UPDATE_FAILED:
    234 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
    235 		    "%s: Unexpected failure. Password database unchanged."),
    236 		    service);
    237 		res = PAM_SYSTEM_ERR;
    238 		break;
    239 	case PWU_NOT_FOUND:
    240 		/* Different error if repository was explicitly specified */
    241 		if (auth_rep != NULL) {
    242 			error(nowarn, pamh, dgettext(TEXT_DOMAIN,
    243 			    "%s: System error: no %s password for %s."),
    244 			    service, auth_rep->type, user);
    245 		} else {
    246 			error(nowarn, pamh, dgettext(TEXT_DOMAIN,
    247 			    "%s: %s does not exist."), service, user);
    248 		}
    249 		res = PAM_USER_UNKNOWN;
    250 		break;
    251 	case PWU_NOMEM:
    252 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
    253 		    "%s: Internal memory allocation failure."), service);
    254 		res = PAM_BUF_ERR;
    255 		break;
    256 	case PWU_SERVER_ERROR:
    257 		res = PAM_SYSTEM_ERR;
    258 		break;
    259 	case PWU_SYSTEM_ERROR:
    260 		res = PAM_SYSTEM_ERR;
    261 		break;
    262 	case PWU_DENIED:
    263 		res = PAM_PERM_DENIED;
    264 		break;
    265 	case PWU_NO_CHANGE:
    266 		/*
    267 		 * yppasswdd detected that we're not changing anything.
    268 		 */
    269 		info(nowarn, pamh, dgettext(TEXT_DOMAIN,
    270 		    "%s: Password information unchanged."), service);
    271 		res = PAM_SUCCESS;
    272 		break;
    273 	case PWU_REPOSITORY_ERROR:
    274 		syslog(LOG_NOTICE, "pam_authtok_store: detected "
    275 		    "unsupported configuration in /etc/nsswitch.conf.");
    276 		error(nowarn, pamh, dgettext(TEXT_DOMAIN,
    277 		    "%s: System error: repository out of range."), service);
    278 		res = PAM_SYSTEM_ERR;
    279 		break;
    280 	case PWU_PWD_TOO_SHORT:
    281 		(void) snprintf(msg[0], sizeof (msg[0]),
    282 		    dgettext(TEXT_DOMAIN, "%s: Password too short."), service);
    283 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
    284 		res = PAM_AUTHTOK_ERR;
    285 		break;
    286 	case PWU_PWD_INVALID:
    287 		(void) snprintf(msg[0], sizeof (msg[0]),
    288 		    dgettext(TEXT_DOMAIN, "%s: Invalid password syntax."),
    289 		    service);
    290 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
    291 		res = PAM_AUTHTOK_ERR;
    292 		break;
    293 	case PWU_PWD_IN_HISTORY:
    294 		(void) snprintf(msg[0], sizeof (msg[0]),
    295 		    dgettext(TEXT_DOMAIN, "%s: Reuse of old passwords not "
    296 		    "allowed, the new password is in the history list."),
    297 		    service);
    298 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
    299 		res = PAM_AUTHTOK_ERR;
    300 		break;
    301 	case PWU_CHANGE_NOT_ALLOWED:
    302 		(void) snprintf(msg[0], sizeof (msg[0]),
    303 		    dgettext(TEXT_DOMAIN, "%s: You may not change "
    304 		    "this password."), service);
    305 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
    306 		res = PAM_PERM_DENIED;
    307 		break;
    308 	case PWU_WITHIN_MIN_AGE:
    309 		(void) snprintf(msg[0], sizeof (msg[0]),
    310 		    dgettext(TEXT_DOMAIN,
    311 		    "%s: Password can not be changed yet, "
    312 		    "not enough time has passed."), service);
    313 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
    314 		res = PAM_PERM_DENIED;
    315 		break;
    316 	default:
    317 		res = PAM_SYSTEM_ERR;
    318 		break;
    319 	}
    320 
    321 	return (res);
    322 }
    323