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