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