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
error(int nowarn,pam_handle_t * pamh,char * fmt,...)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
info(int nowarn,pam_handle_t * pamh,char * fmt,...)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
authtok_is_aged(pam_handle_t * pamh)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