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