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