xref: /illumos-gate/usr/src/lib/passwdutil/files_attr.c (revision 9164a50bf932130cbb5097a16f6986873ce0e6e5)
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  * Copyright (c) 2016 by Delphix. All rights reserved.
25  * Copyright 2024 OmniOS Community Edition (OmniOSce) Association.
26  */
27 
28 #include <sys/types.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <sys/stat.h>
33 #include <pwd.h>
34 #include <shadow.h>
35 #include <string.h>
36 #include <strings.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <nss_dbdefs.h>
40 #include <macros.h>
41 #include <syslog.h>
42 
43 #include <limits.h>		/* LOGNAME_MAX -- max Solaris user name */
44 
45 #include "passwdutil.h"
46 
47 int files_lock(void);
48 int files_unlock(void);
49 int files_checkhistory(const char *user, const char *passwd,
50     pwu_repository_t *rep);
51 int files_getattr(const char *name, attrlist *item, pwu_repository_t *rep);
52 int files_getpwnam(const char *name, attrlist *items, pwu_repository_t *rep,
53     void **buf);
54 int files_update(attrlist *items, pwu_repository_t *rep, void *buf);
55 int files_putpwnam(const char *name, const char *oldpw, pwu_repository_t *rep,
56     void *buf);
57 int files_user_to_authenticate(const char *name, pwu_repository_t *rep,
58 	char **auth_user, int *privileged);
59 
60 static int files_update_history(const char *name, struct spwd *spwd);
61 
62 /*
63  * files function pointer table, used by passwdutil_init to initialize
64  * the global Repository-OPerations table "rops"
65  */
66 struct repops files_repops = {
67 	files_checkhistory,
68 	files_getattr,
69 	files_getpwnam,
70 	files_update,
71 	files_putpwnam,
72 	files_user_to_authenticate,
73 	files_lock,
74 	files_unlock
75 };
76 
77 /*
78  * this structure defines the buffer used to keep state between
79  * get/update/put calls
80  */
81 struct pwbuf {
82 	int	update_history;
83 	struct passwd *pwd;
84 	char   *pwd_scratch;
85 	struct spwd *spwd;
86 	char   *spwd_scratch;
87 	char   *new_sp_pwdp;
88 };
89 
90 /*
91  * We should use sysconf, but there is no sysconf name for SHADOW
92  * so we use these from nss_dbdefs
93  */
94 #define	PWD_SCRATCH_SIZE NSS_LINELEN_PASSWD
95 #define	SPW_SCRATCH_SIZE NSS_LINELEN_SHADOW
96 
97 /*
98  * lock functions for files repository
99  */
100 int
101 files_lock(void)
102 {
103 	int res;
104 
105 	if (lckpwdf()) {
106 		switch (errno) {
107 		case EINTR:
108 			res = PWU_BUSY;
109 			break;
110 		case EACCES:
111 			res = PWU_DENIED;
112 			break;
113 		case 0:
114 			res = PWU_SUCCESS;
115 			break;
116 		}
117 	} else
118 		res = PWU_SUCCESS;
119 
120 	return (res);
121 }
122 
123 int
124 files_unlock(void)
125 {
126 	if (ulckpwdf())
127 		return (PWU_SYSTEM_ERROR);
128 
129 	return (PWU_SUCCESS);
130 }
131 
132 /*
133  * files_privileged
134  *
135  * Are we a privileged user with regard to the files repository?
136  */
137 int
138 files_privileged(void)
139 {
140 	return (getuid() == 0);
141 }
142 
143 /*
144  *
145  * private_getpwnam_r()
146  *
147  * A private implementation of getpwnam_r which does *not* fall back to
148  * other services possibly defined in nsswitch.conf
149  *
150  * behaves like getpwnam_r().
151  */
152 struct passwd *
153 private_getpwnam_r(const char *name, struct passwd *result, char *buffer,
154     int buflen)
155 {
156 	FILE *fp;
157 	int found;
158 
159 	if ((fp = fopen(PASSWD, "rF")) == NULL)
160 		return (NULL);
161 
162 	found = 0;
163 	while (!found && fgetpwent_r(fp, result, buffer, buflen) != NULL) {
164 		if (strcmp(name, result->pw_name) == 0)
165 			found = 1;
166 	}
167 
168 	(void) fclose(fp);
169 
170 	if (!found) {
171 		(void) memset(buffer, 0, buflen);
172 		(void) memset(result, 0, sizeof (*result));
173 		return (NULL);
174 	}
175 
176 	return (result);
177 }
178 
179 /*
180  * private_getspnam_r()
181  *
182  * A private implementation of getspnam_r which does *not* fall back to
183  * other services possibly defined in nsswitch.conf.
184  *
185  * Behaves like getspnam_r(). Since we use fgetspent_t(), all numeric
186  * fields that are undefined in /etc/shadow will be set to -1.
187  *
188  */
189 struct spwd *
190 private_getspnam_r(const char *name, struct spwd *result, char *buffer,
191     int buflen)
192 {
193 	FILE *fp;
194 	int found;
195 
196 	fp = fopen(SHADOW, "rF");
197 	if (fp == NULL)
198 		return (NULL);
199 
200 	found = 0;
201 	while (!found && fgetspent_r(fp, result, buffer, buflen) != NULL) {
202 		if (strcmp(name, result->sp_namp) == 0)
203 			found = 1;
204 	}
205 
206 	(void) fclose(fp);
207 
208 	if (!found) {
209 		(void) memset(buffer, 0, buflen);
210 		(void) memset(result, 0, sizeof (*result));
211 		return (NULL);
212 	}
213 	return (result);
214 }
215 
216 /*
217  * files_getpwnam(name, items, rep, buf)
218  *
219  */
220 /*ARGSUSED*/
221 int
222 files_getpwnam(const char *name, attrlist *items, pwu_repository_t *rep,
223     void **buf)
224 {
225 	attrlist *p;
226 	struct pwbuf *pwbuf;
227 	int err = PWU_SUCCESS;
228 
229 	*buf = calloc(1, sizeof (struct pwbuf));
230 	pwbuf = (struct pwbuf *)*buf;
231 	if (pwbuf == NULL)
232 		return (PWU_NOMEM);
233 
234 	/*
235 	 * determine which password structure (/etc/passwd or /etc/shadow)
236 	 * we need for the items we need to update
237 	 */
238 	for (p = items; p != NULL; p = p->next) {
239 		switch (p->type) {
240 		case ATTR_NAME:
241 		case ATTR_UID:
242 		case ATTR_GID:
243 		case ATTR_AGE:
244 		case ATTR_COMMENT:
245 		case ATTR_GECOS:
246 		case ATTR_HOMEDIR:
247 		case ATTR_SHELL:
248 			if (pwbuf->pwd == NULL) {
249 				pwbuf->pwd = malloc(sizeof (struct passwd));
250 				if (pwbuf->pwd == NULL) {
251 					err = PWU_NOMEM;
252 					goto error;
253 				}
254 			}
255 			break;
256 		case ATTR_PASSWD:
257 		case ATTR_PASSWD_SERVER_POLICY:
258 		case ATTR_LSTCHG:
259 		case ATTR_MIN:
260 		case ATTR_MAX:
261 		case ATTR_WARN:
262 		case ATTR_INACT:
263 		case ATTR_EXPIRE:
264 		case ATTR_FLAG:
265 		case ATTR_LOCK_ACCOUNT:
266 		case ATTR_EXPIRE_PASSWORD:
267 		case ATTR_FAILED_LOGINS:
268 		case ATTR_INCR_FAILED_LOGINS:
269 		case ATTR_RST_FAILED_LOGINS:
270 		case ATTR_NOLOGIN_ACCOUNT:
271 		case ATTR_UNLOCK_ACCOUNT:
272 			if (pwbuf->spwd == NULL) {
273 				pwbuf->spwd = malloc(sizeof (struct spwd));
274 				if (pwbuf->spwd == NULL) {
275 					err = PWU_NOMEM;
276 					goto error;
277 				}
278 			}
279 			break;
280 		default:
281 			/*
282 			 * Some other repository might have different values
283 			 * so we ignore those.
284 			 */
285 			break;
286 		}
287 	}
288 
289 	if (pwbuf->pwd) {
290 		if ((pwbuf->pwd_scratch = malloc(PWD_SCRATCH_SIZE)) == NULL) {
291 			err = PWU_NOMEM;
292 			goto error;
293 		}
294 		if (private_getpwnam_r(name, pwbuf->pwd, pwbuf->pwd_scratch,
295 		    PWD_SCRATCH_SIZE) == NULL) {
296 			err = PWU_NOT_FOUND;
297 			goto error;
298 		}
299 	}
300 
301 	if (pwbuf->spwd) {
302 		if ((pwbuf->spwd_scratch = malloc(SPW_SCRATCH_SIZE)) == NULL) {
303 			err = PWU_NOMEM;
304 			goto error;
305 		}
306 		if (private_getspnam_r(name, pwbuf->spwd, pwbuf->spwd_scratch,
307 		    SPW_SCRATCH_SIZE) == NULL) {
308 			err = PWU_NOT_FOUND;
309 			goto error;
310 		}
311 	}
312 
313 	return (PWU_SUCCESS);
314 error:
315 	if (pwbuf->pwd) free(pwbuf->pwd);
316 	if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch);
317 	if (pwbuf->spwd) free(pwbuf->spwd);
318 	if (pwbuf->spwd_scratch) free(pwbuf->spwd_scratch);
319 	free(pwbuf);
320 	*buf = NULL;
321 
322 	return (err);
323 }
324 
325 /*
326  * int files_user_to_authenticate(name, rep, auth_user, privileged)
327  * Determine which user needs to be authenticated. For files, the
328  * possible return values are:
329  * 	PWU_NOT_FOUND
330  *	PWU_SUCCESS	and (auth_user == NULL || auth_user = user)
331  *	PWU_DENIED
332  *	PWU_NOMEM
333  */
334 /*ARGSUSED*/
335 int
336 files_user_to_authenticate(const char *user, pwu_repository_t *rep,
337 	char **auth_user, int *privileged)
338 {
339 	struct pwbuf *pwbuf;
340 	int res;
341 	attrlist attr_tmp[1] = { { ATTR_UID, NULL, NULL } };
342 
343 	/* check to see if target user is present in files */
344 	res = files_getpwnam(user, &attr_tmp[0], rep, (void **)&pwbuf);
345 	if (res != PWU_SUCCESS)
346 		return (res);
347 
348 	if (files_privileged()) {
349 		*auth_user = NULL;
350 		*privileged = 1;
351 		res = PWU_SUCCESS;
352 	} else {
353 		*privileged = 0;
354 		if (getuid() == pwbuf->pwd->pw_uid) {
355 			if ((*auth_user = strdup(user)) == NULL) {
356 				res = PWU_NOMEM;
357 			} else {
358 				res = PWU_SUCCESS;
359 			}
360 		} else {
361 			res = PWU_DENIED;
362 		}
363 	}
364 
365 	if (pwbuf->pwd) free(pwbuf->pwd);
366 	if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch);
367 	if (pwbuf->spwd) free(pwbuf->spwd);
368 	if (pwbuf->spwd_scratch) free(pwbuf->spwd_scratch);
369 	free(pwbuf);
370 
371 	return (res);
372 }
373 
374 /*
375  *	Password history file format:
376  *		user:crypw1: ... crypwn: such that n <= MAXHISTORY
377  */
378 #define	HISTORY		"/etc/security/passhistory"
379 #define	HISTEMP		"/etc/security/pwhistemp"
380 #define	OHISTORY	"/etc/security/opwhistory"
381 #define	HISTMODE	S_IRUSR	/* mode to create history file */
382 /*
383  * XXX
384  *	3*LOGNAME_MAX just in case there are long user names.
385  *	Traditionally Solaris LOGNAME_MAX (_POSIX_LOGIN_NAME_MAX) is 13,
386  *	but some sites often user more.
387  *	If LOGNAME_MAX ever becomes reasonable (128) and actually enforced,
388  *	fix up here.
389  * XXX
390  */
391 #define	MAX_LOGNAME (3 * LOGNAME_MAX)
392 
393 /*
394  *	files_checkhistory - check if a user's new password is in the user's
395  *		old password history.
396  *
397  *	Entry
398  *		user = username.
399  *		passwd = new clear text password.
400  *
401  *	Exit
402  *		PWU_SUCCESS, passwd found in user's old password history.
403  *			The caller should only be interested and fail if
404  *			PWU_SUCCESS is returned.
405  *		PWU_NOT_FOUND, passwd not in user's old password history.
406  *		PWU_errors, PWU_ errors from other routines.
407  *
408  */
409 int
410 files_checkhistory(const char *user, const char *passwd, pwu_repository_t *rep)
411 {
412 	attrlist attr;
413 	int res;
414 
415 	attr.type = ATTR_HISTORY;
416 	attr.data.val_s = NULL;
417 	attr.next = NULL;
418 
419 	debug("files_checkhistory(user=%s)", user);
420 
421 	/*
422 	 * XXX
423 	 *	This depends on the underlying files_getattr implementation
424 	 *	treating user not found in backing store or no history as
425 	 *	an error.
426 	 * XXX
427 	 */
428 
429 	if ((res = files_getattr(user, &attr, rep)) == PWU_SUCCESS) {
430 		char	*s;
431 		char	*crypt_passwd;
432 		int	histsize;
433 		char	*last = attr.data.val_s;
434 
435 		if ((histsize = def_getint("HISTORY=", DEFHISTORY)) == 0) {
436 			debug("files_checkhistory: no history requested");
437 			res = PWU_NOT_FOUND;
438 			goto out;
439 		}
440 
441 		debug("files_checkhistory: histsize = %d", histsize);
442 		if (histsize > MAXHISTORY)
443 			histsize = MAXHISTORY;
444 
445 		debug("line to test\n\t%s", last);
446 
447 		/* compare crypt_passwd to attr.data.val_s strings. */
448 		res = PWU_NOT_FOUND;
449 		while ((histsize-- > 0) &&
450 		    (((s = strtok_r(NULL, ":", &last)) != NULL) &&
451 		    (*s != '\n'))) {
452 
453 			crypt_passwd = crypt(passwd, s);
454 			debug("files_checkhistory: user_pw=%s, history_pw=%s",
455 			    crypt_passwd, s);
456 			if (strcmp(crypt_passwd, s) == 0) {
457 				res = PWU_SUCCESS;
458 				break;
459 			}
460 		}
461 		debug("files_checkhistory(%s, %s) = %d", user, crypt_passwd,
462 		    res);
463 	}
464 out:
465 	if (attr.data.val_s != NULL)
466 		free(attr.data.val_s);
467 
468 	return (res);
469 }
470 
471 /*
472  * files_getattr(name, items, rep)
473  *
474  * Get attributes specified in list 'items'
475  */
476 int
477 files_getattr(const char *name, attrlist *items, pwu_repository_t *rep)
478 {
479 	struct pwbuf *pwbuf;
480 	struct passwd *pw;
481 	struct spwd *spw;
482 	attrlist *w;
483 	int res;
484 
485 	res = files_getpwnam(name, items, rep, (void **)&pwbuf);
486 	if (res != PWU_SUCCESS)
487 		return (res);
488 
489 	pw = pwbuf->pwd;
490 	spw = pwbuf->spwd;
491 
492 	for (w = items; res == PWU_SUCCESS && w != NULL; w = w->next) {
493 		switch (w->type) {
494 		case ATTR_NAME:
495 			if ((w->data.val_s = strdup(pw->pw_name)) == NULL)
496 				res = PWU_NOMEM;
497 			break;
498 		case ATTR_COMMENT:
499 			if ((w->data.val_s = strdup(pw->pw_comment)) == NULL)
500 				res = PWU_NOMEM;
501 			break;
502 		case ATTR_GECOS:
503 			if ((w->data.val_s = strdup(pw->pw_gecos)) == NULL)
504 				res = PWU_NOMEM;
505 			break;
506 		case ATTR_HOMEDIR:
507 			if ((w->data.val_s = strdup(pw->pw_dir)) == NULL)
508 				res = PWU_NOMEM;
509 			break;
510 		case ATTR_SHELL:
511 			if ((w->data.val_s = strdup(pw->pw_shell)) == NULL)
512 				res = PWU_NOMEM;
513 			break;
514 		/*
515 		 * Nothing special needs to be done for
516 		 * server policy
517 		 */
518 		case ATTR_PASSWD:
519 		case ATTR_PASSWD_SERVER_POLICY:
520 			if ((w->data.val_s = strdup(spw->sp_pwdp)) == NULL)
521 				res = PWU_NOMEM;
522 			break;
523 		case ATTR_AGE:
524 			if ((w->data.val_s = strdup(pw->pw_age)) == NULL)
525 				res = PWU_NOMEM;
526 			break;
527 		case ATTR_REP_NAME:
528 			if ((w->data.val_s = strdup("files")) == NULL)
529 				res = PWU_NOMEM;
530 			break;
531 		case ATTR_HISTORY: {
532 			FILE	*history;
533 			char	buf[MAX_LOGNAME + MAXHISTORY +
534 			    (MAXHISTORY * CRYPT_MAXCIPHERTEXTLEN)+1];
535 			char	*s, *s1;
536 
537 			debug("files_getattr: Get password history for %s ",
538 			    name);
539 
540 			if ((history = fopen(HISTORY, "rF")) == NULL) {
541 				debug("files_getattr: %s not found", HISTORY);
542 				res = PWU_OPEN_FAILED;
543 				goto getattr_exit;
544 			}
545 			res = PWU_NOT_FOUND;
546 			while ((s = fgets(buf, sizeof (buf), history)) !=
547 			    NULL) {
548 				s1 = strchr(s, ':');
549 				if (s1 != NULL) {
550 					*s1 = '\0';
551 				} else {
552 					res = PWU_NOT_FOUND;
553 					break;
554 				}
555 #ifdef	DEBUG
556 				debug("got history line for %s", s);
557 #endif	/* DEBUG */
558 				if (strcmp(s, name) == 0) {
559 					/* found user */
560 					if ((items->data.val_s =
561 					    strdup(s1+1)) == NULL)
562 						res = PWU_NOMEM;
563 					else
564 						res = PWU_SUCCESS;
565 					break;
566 				}
567 			}
568 			(void) fclose(history);
569 			break;
570 		}
571 
572 		/* integer values */
573 		case ATTR_UID:
574 			w->data.val_i = pw->pw_uid;
575 			break;
576 		case ATTR_GID:
577 			w->data.val_i = pw->pw_gid;
578 			break;
579 		case ATTR_LSTCHG:
580 			w->data.val_i = spw->sp_lstchg;
581 			break;
582 		case ATTR_MIN:
583 			w->data.val_i = spw->sp_min;
584 			break;
585 		case ATTR_MAX:
586 			w->data.val_i = spw->sp_max;
587 			break;
588 		case ATTR_WARN:
589 			w->data.val_i = spw->sp_warn;
590 			break;
591 		case ATTR_INACT:
592 			w->data.val_i = spw->sp_inact;
593 			break;
594 		case ATTR_EXPIRE:
595 			w->data.val_i = spw->sp_expire;
596 			break;
597 		case ATTR_FLAG:
598 			w->data.val_i = spw->sp_flag;
599 			break;
600 		case ATTR_FAILED_LOGINS:
601 			w->data.val_i = spw->sp_flag & FAILCOUNT_MASK;
602 			break;
603 		default:
604 			break;
605 		}
606 	}
607 
608 getattr_exit:
609 	if (pwbuf->pwd) free(pwbuf->pwd);
610 	if (pwbuf->pwd_scratch) free(pwbuf->pwd_scratch);
611 	if (pwbuf->spwd) free(pwbuf->spwd);
612 	if (pwbuf->spwd_scratch) free(pwbuf->spwd_scratch);
613 	free(pwbuf);
614 
615 	return (res);
616 }
617 
618 /*
619  * max_present(list)
620  *
621  * see if attribute ATTR_MAX, with value != -1, is present in
622  * attribute-list "list".
623  *
624  * returns 1 if present, 0 otherwise.
625  */
626 static int
627 max_present(attrlist *list)
628 {
629 	while (list != NULL)
630 		if (list->type == ATTR_MAX && list->data.val_i != -1)
631 			return (1);
632 		else
633 			list = list->next;
634 
635 	return (0);
636 }
637 
638 /*
639  * files_update(items, rep, buf)
640  *
641  * update the information in buf with the attributes specified in
642  * items.
643  */
644 /*ARGSUSED*/
645 int
646 files_update(attrlist *items, pwu_repository_t *rep, void *buf)
647 {
648 	struct pwbuf *pwbuf = (struct pwbuf *)buf;
649 	struct passwd *pw;
650 	struct spwd *spw;
651 	attrlist *p;
652 	int aging_needed = 0;
653 	int aging_set = 0;
654 	int disable_aging;
655 	char *pword;
656 	int len;
657 
658 	pw = pwbuf->pwd;
659 	spw = pwbuf->spwd;
660 	pwbuf->update_history = 0;
661 
662 	/*
663 	 * if sp_max==0 : disable passwd aging after updating the password
664 	 */
665 	disable_aging = (spw != NULL && spw->sp_max == 0);
666 
667 	for (p = items; p != NULL; p = p->next) {
668 		switch (p->type) {
669 		case ATTR_NAME:
670 			break;	/* We are able to handle this, but... */
671 		case ATTR_UID:
672 			pw->pw_uid = (uid_t)p->data.val_i;
673 			break;
674 		case ATTR_GID:
675 			pw->pw_gid = (gid_t)p->data.val_i;
676 			break;
677 		case ATTR_AGE:
678 			pw->pw_age = p->data.val_s;
679 			break;
680 		case ATTR_COMMENT:
681 			pw->pw_comment = p->data.val_s;
682 			break;
683 		case ATTR_GECOS:
684 			pw->pw_gecos = p->data.val_s;
685 			break;
686 		case ATTR_HOMEDIR:
687 			pw->pw_dir = p->data.val_s;
688 			break;
689 		case ATTR_SHELL:
690 			pw->pw_shell = p->data.val_s;
691 			break;
692 
693 		/*
694 		 * Nothing special needs to be done for
695 		 * server policy
696 		 */
697 		case ATTR_PASSWD:
698 		case ATTR_PASSWD_SERVER_POLICY:
699 			/*
700 			 * There is a special case only for files: if the
701 			 * password is to be deleted (-d to passwd),
702 			 * p->data.val_s will be NULL.
703 			 */
704 			if (p->data.val_s == NULL) {
705 				spw->sp_pwdp = "";
706 			} else {
707 				char *salt = NULL;
708 				char *hash = NULL;
709 
710 				salt = crypt_gensalt(spw->sp_pwdp, pw);
711 
712 				if (salt == NULL) {
713 					if (errno == ENOMEM)
714 						return (PWU_NOMEM);
715 					/* algorithm problem? */
716 					syslog(LOG_AUTH | LOG_ALERT,
717 					    "passwdutil: crypt_gensalt %m");
718 					return (PWU_UPDATE_FAILED);
719 				}
720 				hash = crypt(p->data.val_s, salt);
721 				free(salt);
722 				if (hash == NULL) {
723 					errno = ENOMEM;
724 					return (PWU_NOMEM);
725 				}
726 				pword = strdup(hash);
727 				if (pword == NULL) {
728 					errno = ENOMEM;
729 					return (PWU_NOMEM);
730 				}
731 
732 				if (pwbuf->new_sp_pwdp)
733 					free(pwbuf->new_sp_pwdp);
734 				pwbuf->new_sp_pwdp = pword;
735 				spw->sp_pwdp = pword;
736 				aging_needed = 1;
737 				pwbuf->update_history = 1;
738 			}
739 			spw->sp_flag &= ~FAILCOUNT_MASK; /* reset count */
740 			spw->sp_lstchg = DAY_NOW_32;
741 			break;
742 		case ATTR_LOCK_ACCOUNT:
743 			if (spw->sp_pwdp == NULL) {
744 				spw->sp_pwdp = LOCKSTRING;
745 			} else if ((strncmp(spw->sp_pwdp, LOCKSTRING,
746 			    sizeof (LOCKSTRING)-1) != 0) &&
747 			    (strcmp(spw->sp_pwdp, NOLOGINSTRING) != 0)) {
748 				len = sizeof (LOCKSTRING)-1 +
749 				    strlen(spw->sp_pwdp) + 1;
750 				pword = malloc(len);
751 				if (pword == NULL) {
752 					errno = ENOMEM;
753 					return (PWU_NOMEM);
754 				}
755 				(void) strlcpy(pword, LOCKSTRING, len);
756 				(void) strlcat(pword, spw->sp_pwdp, len);
757 				if (pwbuf->new_sp_pwdp)
758 					free(pwbuf->new_sp_pwdp);
759 				pwbuf->new_sp_pwdp = pword;
760 				spw->sp_pwdp = pword;
761 			}
762 			spw->sp_lstchg = DAY_NOW_32;
763 			break;
764 		case ATTR_UNLOCK_ACCOUNT:
765 			if (spw->sp_pwdp != NULL &&
766 			    strncmp(spw->sp_pwdp, LOCKSTRING,
767 			    sizeof (LOCKSTRING)-1) == 0) {
768 				(void) strcpy(spw->sp_pwdp, spw->sp_pwdp +
769 				    sizeof (LOCKSTRING)-1);
770 			}
771 			spw->sp_lstchg = DAY_NOW_32;
772 			break;
773 		case ATTR_NOLOGIN_ACCOUNT:
774 			spw->sp_pwdp = NOLOGINSTRING;
775 			if (pwbuf->new_sp_pwdp) {
776 				free(pwbuf->new_sp_pwdp);
777 				pwbuf->new_sp_pwdp = NULL;
778 			}
779 			spw->sp_lstchg = DAY_NOW_32;
780 			break;
781 		case ATTR_EXPIRE_PASSWORD:
782 			spw->sp_lstchg = 0;
783 			break;
784 		case ATTR_LSTCHG:
785 			spw->sp_lstchg = p->data.val_i;
786 			break;
787 		case ATTR_MIN:
788 			if (spw->sp_max == -1 &&
789 			    p->data.val_i != -1 && max_present(p->next) == 0)
790 				return (PWU_AGING_DISABLED);
791 			spw->sp_min = p->data.val_i;
792 			aging_set = 1;
793 			break;
794 		case ATTR_MAX:
795 			if (p->data.val_i == -1) {
796 				/* Turn aging off -> Reset min and warn too */
797 
798 				spw->sp_min = -1;
799 				spw->sp_warn = -1;
800 			} else {
801 				/* Turn aging on */
802 
803 				if (spw->sp_min == -1) {
804 					/*
805 					 * If minage has not been set with
806 					 * a command-line option, we set it
807 					 * to zero.
808 					 */
809 					spw->sp_min = 0;
810 				}
811 
812 				/*
813 				 * If aging was turned off, we update lstchg.
814 				 *
815 				 * We take care not to update lstchg if the
816 				 * user has no password, otherwise the user
817 				 * might not be required to provide a password
818 				 * the next time they log-in.
819 				 *
820 				 * Also, if lstchg != -1 (i.e., not set in
821 				 * /etc/shadow), we keep the old value.
822 				 */
823 				if (spw->sp_max == -1 &&
824 				    spw->sp_pwdp != NULL && *spw->sp_pwdp &&
825 				    spw->sp_lstchg == -1) {
826 					spw->sp_lstchg = DAY_NOW_32;
827 				}
828 			}
829 
830 			spw->sp_max = p->data.val_i;
831 
832 			aging_set = 1;
833 
834 			break;
835 		case ATTR_WARN:
836 			if (spw->sp_max == -1 && p->data.val_i != -1 &&
837 			    max_present(p->next) == 0)
838 				return (PWU_AGING_DISABLED);
839 			spw->sp_warn =  p->data.val_i;
840 			break;
841 		case ATTR_INACT:
842 			spw->sp_inact = p->data.val_i;
843 			break;
844 		case ATTR_EXPIRE:
845 			spw->sp_expire = p->data.val_i;
846 			break;
847 		case ATTR_FLAG:
848 			spw->sp_flag = p->data.val_i;
849 			break;
850 		case ATTR_INCR_FAILED_LOGINS:
851 			{
852 			int count = (spw->sp_flag & FAILCOUNT_MASK) + 1;
853 			spw->sp_flag &= ~FAILCOUNT_MASK;
854 			spw->sp_flag |= min(FAILCOUNT_MASK, count);
855 			p->data.val_i = count;
856 			}
857 			break;
858 		case ATTR_RST_FAILED_LOGINS:
859 			p->data.val_i = spw->sp_flag & FAILCOUNT_MASK;
860 			spw->sp_flag &= ~FAILCOUNT_MASK;
861 			break;
862 		default:
863 			break;
864 		}
865 	}
866 
867 	/*
868 	 * What should the new aging values look like?
869 	 *
870 	 * There are a number of different conditions
871 	 *
872 	 *  a) aging is already configured: don't touch it
873 	 *
874 	 *  b) disable_aging is set: disable aging
875 	 *
876 	 *  c) aging is not configured: turn on default aging;
877 	 *
878 	 *  b) and c) of course only if aging_needed and !aging_set.
879 	 *  (i.e., password changed, and aging values not changed)
880 	 */
881 
882 	if (spw != NULL && spw->sp_max <= 0) {
883 		/* a) aging not yet configured */
884 		if (aging_needed && !aging_set) {
885 			if (disable_aging) {
886 				/* b) turn off aging */
887 				spw->sp_min = spw->sp_max = spw->sp_warn = -1;
888 			} else {
889 				/* c) */
890 				turn_on_default_aging(spw);
891 			}
892 		}
893 	}
894 
895 	return (PWU_SUCCESS);
896 }
897 
898 /*
899  * files_update_shadow(char *name, struct spwd *spwd)
900  *
901  * update the shadow password file SHADOW to contain the spwd structure
902  * "spwd" for user "name"
903  */
904 int
905 files_update_shadow(const char *name, struct spwd *spwd)
906 {
907 	struct stat64 stbuf;
908 	FILE *dst;
909 	FILE *src;
910 	struct spwd cur;
911 	char buf[SPW_SCRATCH_SIZE];
912 	int tempfd;
913 	mode_t filemode;
914 	int result = -1;
915 	int err = PWU_SUCCESS;
916 
917 	/* Mode of the shadow file should be 400 or 000 */
918 	if (stat64(SHADOW, &stbuf) < 0) {
919 		err = PWU_STAT_FAILED;
920 		goto shadow_exit;
921 	}
922 
923 	/* copy mode from current shadow file (0400 or 0000) */
924 	filemode = stbuf.st_mode & S_IRUSR;
925 
926 	/*
927 	 * we can't specify filemodes to fopen(), and we SHOULD NOT
928 	 * set umask in multi-thread safe libraries, so we use
929 	 * a combination of open() and fdopen()
930 	 */
931 	tempfd = open(SHADTEMP, O_WRONLY|O_CREAT|O_TRUNC, filemode);
932 	if (tempfd < 0) {
933 		err = PWU_OPEN_FAILED;
934 		goto shadow_exit;
935 	}
936 	(void) fchown(tempfd, (uid_t)0, stbuf.st_gid);
937 
938 	if ((dst = fdopen(tempfd, "wF")) == NULL) {
939 		err = PWU_OPEN_FAILED;
940 		goto shadow_exit;
941 	}
942 
943 	if ((src = fopen(SHADOW, "rF")) == NULL) {
944 		err = PWU_OPEN_FAILED;
945 		(void) fclose(dst);
946 		(void) unlink(SHADTEMP);
947 		goto shadow_exit;
948 	}
949 
950 	/*
951 	 * copy old shadow to temporary file while replacing the entry
952 	 * that matches "name".
953 	 */
954 	while (fgetspent_r(src, &cur, buf, sizeof (buf)) != NULL) {
955 
956 		if (strcmp(cur.sp_namp, name) == 0)
957 			result = putspent(spwd, dst);
958 		else
959 			result = putspent(&cur, dst);
960 
961 		if (result != 0) {
962 			err = PWU_WRITE_FAILED;
963 			(void) fclose(src);
964 			(void) fclose(dst);
965 			goto shadow_exit;
966 		}
967 	}
968 
969 	(void) fclose(src);
970 
971 	if (fclose(dst) != 0) {
972 		/*
973 		 * Something went wrong (ENOSPC for example). Don't
974 		 * use the resulting temporary file!
975 		 */
976 		err = PWU_CLOSE_FAILED;
977 		(void) unlink(SHADTEMP);
978 		goto shadow_exit;
979 	}
980 
981 	/*
982 	 * Rename stmp to shadow:
983 	 *   1. make sure /etc/oshadow is gone
984 	 *   2. ln /etc/shadow /etc/oshadow
985 	 *   3. mv /etc/stmp /etc/shadow
986 	 */
987 	if (unlink(OSHADOW) && access(OSHADOW, 0) == 0) {
988 		err = PWU_UPDATE_FAILED;
989 		(void) unlink(SHADTEMP);
990 		goto shadow_exit;
991 	}
992 
993 	if (link(SHADOW, OSHADOW) == -1) {
994 		err = PWU_UPDATE_FAILED;
995 		(void) unlink(SHADTEMP);
996 		goto shadow_exit;
997 	}
998 
999 	if (rename(SHADTEMP, SHADOW) == -1) {
1000 		err = PWU_UPDATE_FAILED;
1001 		(void) unlink(SHADTEMP);
1002 		goto shadow_exit;
1003 	}
1004 	(void) unlink(OSHADOW);
1005 
1006 shadow_exit:
1007 	return (err);
1008 }
1009 
1010 int
1011 files_update_passwd(const char *name, struct passwd *pwd)
1012 {
1013 	struct stat64 stbuf;
1014 	FILE *src, *dst;
1015 	int tempfd;
1016 	struct passwd cur;
1017 	char buf[PWD_SCRATCH_SIZE];
1018 	int result;
1019 	int err = PWU_SUCCESS;
1020 
1021 	if (stat64(PASSWD, &stbuf) < 0) {
1022 		err = PWU_STAT_FAILED;
1023 		goto passwd_exit;
1024 	}
1025 
1026 	/* see files_update_shadow() for open()+fdopen() rationale */
1027 
1028 	if ((tempfd = open(PASSTEMP, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
1029 		err = PWU_OPEN_FAILED;
1030 		goto passwd_exit;
1031 	}
1032 	if ((dst = fdopen(tempfd, "wF")) == NULL) {
1033 		err = PWU_OPEN_FAILED;
1034 		goto passwd_exit;
1035 	}
1036 	if ((src = fopen(PASSWD, "rF")) == NULL) {
1037 		err = PWU_OPEN_FAILED;
1038 		(void) fclose(dst);
1039 		(void) unlink(PASSTEMP);
1040 		goto passwd_exit;
1041 	}
1042 
1043 	/*
1044 	 * copy old password entries to temporary file while replacing
1045 	 * the entry that matches "name"
1046 	 */
1047 	while (fgetpwent_r(src, &cur, buf, sizeof (buf)) != NULL) {
1048 		if (strcmp(cur.pw_name, name) == 0)
1049 			result = putpwent(pwd, dst);
1050 		else
1051 			result = putpwent(&cur, dst);
1052 		if (result != 0) {
1053 			err = PWU_WRITE_FAILED;
1054 			(void) fclose(src);
1055 			(void) fclose(dst);
1056 			goto passwd_exit;
1057 		}
1058 	}
1059 
1060 	(void) fclose(src);
1061 	if (fclose(dst) != 0) {
1062 		err = PWU_CLOSE_FAILED;
1063 		goto passwd_exit; /* Don't trust the temporary file */
1064 	}
1065 
1066 	/* Rename temp to passwd */
1067 	if (unlink(OPASSWD) && access(OPASSWD, 0) == 0) {
1068 		err = PWU_UPDATE_FAILED;
1069 		(void) unlink(PASSTEMP);
1070 		goto passwd_exit;
1071 	}
1072 
1073 	if (link(PASSWD, OPASSWD) == -1) {
1074 		err = PWU_UPDATE_FAILED;
1075 		(void) unlink(PASSTEMP);
1076 		goto passwd_exit;
1077 	}
1078 
1079 	if (rename(PASSTEMP, PASSWD) == -1) {
1080 		err = PWU_UPDATE_FAILED;
1081 		(void) unlink(PASSTEMP);
1082 		goto passwd_exit;
1083 	}
1084 
1085 	(void) chmod(PASSWD, 0644);
1086 
1087 passwd_exit:
1088 	return (err);
1089 
1090 }
1091 
1092 /*
1093  * files_putpwnam(name, oldpw, rep, buf)
1094  *
1095  * store the password attributes contained in "buf" in /etc/passwd and
1096  * /etc/shadow.
1097  */
1098 /*ARGSUSED*/
1099 int
1100 files_putpwnam(const char *name, const char *oldpw, pwu_repository_t *rep,
1101     void *buf)
1102 {
1103 	struct pwbuf *pwbuf = (struct pwbuf *)buf;
1104 	int result = PWU_SUCCESS;
1105 
1106 	if (pwbuf->pwd) {
1107 		result = files_update_passwd(name, pwbuf->pwd);
1108 	}
1109 
1110 	if (result == PWU_SUCCESS && pwbuf->spwd) {
1111 		if (pwbuf->update_history != 0) {
1112 			debug("update_history = %d", pwbuf->update_history);
1113 			result = files_update_history(name, pwbuf->spwd);
1114 		} else {
1115 			debug("no password change");
1116 		}
1117 		if (result == PWU_SUCCESS) {
1118 			result = files_update_shadow(name, pwbuf->spwd);
1119 		}
1120 	}
1121 
1122 	if (pwbuf->pwd) {
1123 		(void) memset(pwbuf->pwd, 0, sizeof (struct passwd));
1124 		(void) memset(pwbuf->pwd_scratch, 0, PWD_SCRATCH_SIZE);
1125 		free(pwbuf->pwd);
1126 		free(pwbuf->pwd_scratch);
1127 	}
1128 	if (pwbuf->spwd) {
1129 		(void) memset(pwbuf->spwd, 0, sizeof (struct spwd));
1130 		(void) memset(pwbuf->spwd_scratch, 0, SPW_SCRATCH_SIZE);
1131 		free(pwbuf->spwd);
1132 		free(pwbuf->spwd_scratch);
1133 	}
1134 	if (pwbuf->new_sp_pwdp) {
1135 		free(pwbuf->new_sp_pwdp);
1136 	}
1137 
1138 	return (result);
1139 }
1140 
1141 /*
1142  *	NOTE:  This is all covered under the repository lock held for updating
1143  *	passwd(5) and shadow(5).
1144  */
1145 int
1146 files_update_history(const char *name, struct spwd *spwd)
1147 {
1148 	int	histsize;
1149 	int	tmpfd;
1150 	FILE	*src;	/* history database file */
1151 	FILE	*dst;	/* temp history database being updated */
1152 	struct	stat64 statbuf;
1153 	char buf[MAX_LOGNAME + MAXHISTORY +
1154 	    (MAXHISTORY * CRYPT_MAXCIPHERTEXTLEN)+1];
1155 	int	found;
1156 
1157 	if ((histsize = def_getint("HISTORY=", DEFHISTORY)) == 0) {
1158 		debug("files_update_history(%s) no history, unlinking", name);
1159 		(void) unlink(HISTORY);
1160 		return (PWU_SUCCESS);	/* no history update defined */
1161 	}
1162 	debug("files_update_history(%s, %s) histsize = %d", name, spwd->sp_pwdp,
1163 	    histsize);
1164 
1165 	if (histsize > MAXHISTORY)
1166 		histsize = MAXHISTORY;
1167 	if ((tmpfd = open(HISTEMP, O_WRONLY|O_CREAT|O_TRUNC, HISTMODE)) < 0) {
1168 		return (PWU_OPEN_FAILED);
1169 	}
1170 	(void) fchown(tmpfd, (uid_t)0, (gid_t)0);
1171 
1172 	/* get ready to copy */
1173 	if (((src = fopen(HISTORY, "rF")) == NULL) &&
1174 	    (errno != ENOENT)) {
1175 		(void) unlink(HISTEMP);
1176 		return (PWU_OPEN_FAILED);
1177 	}
1178 	if ((dst = fdopen(tmpfd, "wF")) == NULL) {
1179 		(void) fclose(src);
1180 		(void) unlink(HISTEMP);
1181 		return (PWU_OPEN_FAILED);
1182 	}
1183 
1184 	/* Copy and update if found.  Add if not found. */
1185 
1186 	found = 0;
1187 
1188 	while ((src != NULL) &&
1189 	    (fgets(buf, sizeof (buf), src) != NULL)) {
1190 		char	*user;
1191 		char	*last;
1192 
1193 		/* get username field */
1194 		user = strtok_r(buf, ":", &last);
1195 
1196 #ifdef	DEBUG
1197 		debug("files_update_history: read=\"%s\"", user);
1198 #endif	/* DEBUG */
1199 
1200 		if (strcmp(user, name) == 0) {
1201 			char	*crypt;
1202 			int	i;
1203 
1204 			/* found user, update */
1205 			found++;
1206 			(void) fprintf(dst, "%s:%s:", name, spwd->sp_pwdp);
1207 			debug("files_update_history: update user\n"
1208 			    "\t%s:%s:", name, spwd->sp_pwdp);
1209 
1210 			/* get old crypted password history */
1211 			for (i = 0; i < MAXHISTORY-1; i++) {
1212 				crypt = strtok_r(NULL, ":", &last);
1213 				if (crypt == NULL ||
1214 				    *crypt == '\n') {
1215 					break;
1216 				}
1217 				(void) fprintf(dst, "%s:", crypt);
1218 				debug("\t%d = %s:", i+1, crypt);
1219 			}
1220 			(void) fprintf(dst, "\n");
1221 		} else {
1222 
1223 			/* copy other users to updated file */
1224 			(void) fprintf(dst, "%s:%s", user, last);
1225 #ifdef	DEBUG
1226 			debug("files_update_history: copy line %s",
1227 			    user);
1228 #endif	/* DEBUG */
1229 		}
1230 	}
1231 
1232 	if (found == 0) {
1233 
1234 		/* user not found, add to history file */
1235 		(void) fprintf(dst, "%s:%s:\n", name, spwd->sp_pwdp);
1236 		debug("files_update_history: add line\n"
1237 		    "\t%s:%s:", name, spwd->sp_pwdp);
1238 	}
1239 
1240 	(void) fclose(src);
1241 
1242 	/* If something messed up in file system, loose the update */
1243 	if (fclose(dst) != 0) {
1244 
1245 		debug("files_update_history: update file close failed %d",
1246 		    errno);
1247 		(void) unlink(HISTEMP);
1248 		return (PWU_CLOSE_FAILED);
1249 	}
1250 
1251 	/*
1252 	 * rename history to ohistory,
1253 	 * rename tmp to history,
1254 	 * unlink ohistory.
1255 	 */
1256 
1257 	(void) unlink(OHISTORY);
1258 
1259 	if (stat64(OHISTORY, &statbuf) == 0 ||
1260 	    ((src != NULL) && (link(HISTORY, OHISTORY) != 0)) ||
1261 	    rename(HISTEMP, HISTORY) != 0) {
1262 
1263 		/* old history won't go away, loose the update */
1264 		debug("files_update_history: update file rename failed %d",
1265 		    errno);
1266 		(void) unlink(HISTEMP);
1267 		return (PWU_UPDATE_FAILED);
1268 	}
1269 
1270 	(void) unlink(OHISTORY);
1271 	return (PWU_SUCCESS);
1272 }
1273