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 #include <sys/types.h>
29 #include <sys/varargs.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <syslog.h>
33 #include <unistd.h>
34 #include <crypt.h>
35 #include <pwd.h>
36 #include <libintl.h>
37
38 #include <security/pam_appl.h>
39 #include <security/pam_modules.h>
40 #include <security/pam_impl.h>
41
42 #include <passwdutil.h>
43
44 #include <sys/note.h>
45
46 /*PRINTFLIKE3*/
47 void
error(int nowarn,pam_handle_t * pamh,char * fmt,...)48 error(int nowarn, pam_handle_t *pamh, char *fmt, ...)
49 {
50 va_list ap;
51 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
52
53 va_start(ap, fmt);
54 (void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
55 if (nowarn == 0)
56 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages,
57 NULL);
58 va_end(ap);
59 }
60
61 /*
62 * int pam_sm_authenticate(pamh, flags, argc, argv)
63 *
64 * Read authentication token from user.
65 */
66 int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)67 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
68 {
69
70 const char *user;
71 char *password;
72 const char *service;
73 int i;
74 int debug = 0;
75 int nowarn = 0;
76 int res;
77 char prompt[PAM_MAX_MSG_SIZE];
78 char *auth_user = NULL;
79 int retval;
80 int privileged;
81 char *rep_passwd = NULL;
82 char *repository_name = NULL;
83 attrlist al[8];
84 int min;
85 int max;
86 int lstchg;
87 int server_policy = 0;
88
89 const pam_repository_t *auth_rep = NULL;
90 pwu_repository_t *pwu_rep = NULL;
91
92 for (i = 0; i < argc; i++) {
93 if (strcmp(argv[i], "debug") == 0)
94 debug = 1;
95 if (strcmp(argv[i], "nowarn") == 0)
96 nowarn = 1;
97 if (strcmp(argv[i], "server_policy") == 0)
98 server_policy = 1;
99 }
100
101 if (flags & PAM_SILENT)
102 nowarn = 1;
103
104 if ((res = pam_get_user(pamh, (const char **)&user, NULL)) !=
105 PAM_SUCCESS) {
106 if (debug)
107 syslog(LOG_DEBUG, "pam_passwd_auth: "
108 "get user failed: %s", pam_strerror(pamh, res));
109 return (res);
110 }
111
112 if (user == NULL || *user == '\0') {
113 syslog(LOG_ERR, "pam_passwd_auth: pam_sm_authenticate: "
114 "PAM_USER NULL or empty");
115 return (PAM_SYSTEM_ERR);
116 }
117
118 res = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&password);
119 if (res != PAM_SUCCESS)
120 return (res);
121
122 if (password != NULL)
123 return (PAM_IGNORE);
124
125 res = pam_get_item(pamh, PAM_SERVICE, (const void **)&service);
126 if (res != PAM_SUCCESS)
127 return (res);
128
129 res = pam_get_item(pamh, PAM_REPOSITORY, (const void **)&auth_rep);
130 if (res != PAM_SUCCESS) {
131 syslog(LOG_ERR, "pam_passwd_auth: pam_sm_authenticate: "
132 "error getting repository");
133 return (PAM_SYSTEM_ERR);
134 }
135
136 if (auth_rep == NULL) {
137 pwu_rep = PWU_DEFAULT_REP;
138 } else {
139 if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
140 return (PAM_BUF_ERR);
141 pwu_rep->type = auth_rep->type;
142 pwu_rep->scope = auth_rep->scope;
143 pwu_rep->scope_len = auth_rep->scope_len;
144 }
145
146 res = __user_to_authenticate(user, pwu_rep, &auth_user, &privileged);
147 if (res != PWU_SUCCESS) {
148 if (res == PWU_NOT_FOUND)
149 retval = PAM_USER_UNKNOWN;
150 else if (res == PWU_DENIED)
151 retval = PAM_PERM_DENIED;
152 else if (res == PWU_REPOSITORY_ERROR) {
153 syslog(LOG_NOTICE,
154 "pam_passwd_auth: detected unsupported "
155 "configuration in /etc/nsswitch.conf.");
156 error(nowarn, pamh, dgettext(TEXT_DOMAIN, "%s: "
157 "Unsupported nsswitch entry for \"passwd:\"."
158 " Use \"-r repository \"."), service);
159 retval = PAM_SYSTEM_ERR;
160 } else
161 retval = PAM_SYSTEM_ERR;
162 if (debug)
163 syslog(LOG_DEBUG, "passwd_auth: __user_to_authenticate "
164 "returned %d", retval);
165 goto out;
166 }
167
168 if (auth_user == NULL) { /* No authentication needed */
169 if (debug)
170 syslog(LOG_DEBUG,
171 "passwd_auth: no authentication needed.");
172 retval = PAM_SUCCESS;
173 goto out;
174 }
175
176 /*
177 * The password prompt differs between users updating their
178 * own password, and users updating other an user's password
179 */
180 if (privileged) {
181 /*
182 * TRANSLATION_NOTE
183 * The following string has a single space at the end
184 */
185 (void) snprintf(prompt, sizeof (prompt),
186 dgettext(TEXT_DOMAIN, "Enter %s's password: "),
187 auth_user);
188 } else {
189 /*
190 * TRANSLATION_NOTE
191 * The following string has a single space at the end
192 */
193 (void) snprintf(prompt, sizeof (prompt),
194 dgettext(TEXT_DOMAIN, "Enter existing login password: "));
195 }
196
197 retval = __pam_get_authtok(pamh, PAM_PROMPT, PAM_AUTHTOK, prompt,
198 &password);
199 if (retval != PAM_SUCCESS)
200 goto out;
201
202 if (password == NULL) {
203 syslog(LOG_ERR, "pam_passwd_auth: pam_sm_authenticate: "
204 "got NULL password from get_authtok()");
205 retval = PAM_AUTH_ERR;
206 goto out;
207 }
208
209 /* Privileged users can skip the tests that follow */
210 if (privileged)
211 goto setitem;
212
213 /*
214 * Non privileged user: so we need to check the old password
215 * and possible restrictions on password changes.
216 */
217
218 /* Get password and it's age from the repository specified */
219 al[0].type = ATTR_PASSWD; al[0].next = &al[1];
220 al[1].type = ATTR_MIN; al[1].next = &al[2];
221 al[2].type = ATTR_MAX; al[2].next = &al[3];
222 al[3].type = ATTR_LSTCHG; al[3].next = &al[4];
223 al[4].type = ATTR_WARN; al[4].next = &al[5];
224 al[5].type = ATTR_INACT; al[5].next = &al[6];
225 al[6].type = ATTR_EXPIRE; al[6].next = &al[7];
226 al[7].type = ATTR_REP_NAME; al[7].next = NULL;
227
228 res = __get_authtoken_attr(auth_user, pwu_rep, al);
229
230 if (res != PWU_SUCCESS) {
231 retval = PAM_SYSTEM_ERR;
232 goto out;
233 }
234
235 repository_name = al[7].data.val_s;
236
237 /*
238 * if repository isn't files|nis, and user wants to follow server
239 * policy, return PAM_IGNORE
240 */
241 if (server_policy &&
242 strcmp(repository_name, "files") != 0 &&
243 strcmp(repository_name, "nis") != 0) {
244 retval = PAM_IGNORE;
245 goto out;
246 }
247
248 rep_passwd = al[0].data.val_s;
249
250 /*
251 * Chop off old SunOS-style password aging information.
252 *
253 * Note: old style password aging is only defined for UNIX-style
254 * crypt strings, hence the comma will always be at position 14.
255 * Note: This code is here because some other vendors might still
256 * support this style of password aging. If we don't remove
257 * the age field, users won't be able to change their password.
258 * XXX yank this code when we're certain this "compatibility"
259 * isn't needed anymore.
260 */
261 if (rep_passwd != NULL && rep_passwd[0] != '$' &&
262 strlen(rep_passwd) > 13 && rep_passwd[13] == ',')
263 rep_passwd[13] = '\0';
264
265 if (strcmp(crypt(password, rep_passwd), rep_passwd) != 0) {
266 retval = PAM_AUTH_ERR;
267 goto out;
268 }
269
270 /*
271 * Now check to see if the user is allowed to change
272 * the password.
273 */
274 min = al[1].data.val_i;
275 max = al[2].data.val_i;
276 lstchg = al[3].data.val_i;
277
278 if (max != -1 && lstchg != 0) {
279 /* aging is turned on, and a change is not forced */
280 time_t daynow = DAY_NOW_32;
281 if ((time_t)lstchg <= daynow) {
282 /* Aged enough? */
283 if (daynow < (time_t)(lstchg + min)) {
284 error(nowarn, pamh, dgettext(TEXT_DOMAIN,
285 "%s: Sorry: less than %d days "
286 "since the last change."),
287 service, min);
288 retval = PAM_PERM_DENIED;
289 goto out;
290 }
291
292 /*
293 * users with min>max are not allowed to
294 * change their password.
295 */
296 if (min > max) {
297 error(nowarn, pamh, dgettext(TEXT_DOMAIN,
298 "%s: You may not change "
299 "this password."), service);
300 retval = PAM_PERM_DENIED;
301 goto out;
302 }
303 }
304 }
305
306 setitem:
307
308 retval = pam_set_item(pamh, PAM_AUTHTOK, (void *)password);
309
310 out:
311 if (password) {
312 (void) memset(password, 0, strlen(password));
313 free(password);
314 }
315 if (rep_passwd) {
316 (void) memset(rep_passwd, 0, strlen(rep_passwd));
317 free(rep_passwd);
318 }
319 if (pwu_rep)
320 free(pwu_rep);
321 if (auth_user)
322 free(auth_user);
323 if (repository_name)
324 free(repository_name);
325
326 return (retval);
327 }
328
329 /*ARGSUSED*/
330 int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)331 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
332 {
333 return (PAM_IGNORE);
334 }
335