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