1/*	$OpenBSD: local_passwd.c,v 1.64 2023/05/08 17:15:43 tobias Exp $	*/
2
3/*-
4 * Copyright (c) 1990 The Regents of the University of California.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <sys/uio.h>
35
36#include <err.h>
37#include <errno.h>
38#include <fcntl.h>
39#include <paths.h>
40#include <pwd.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <signal.h>
44#include <string.h>
45#include <unistd.h>
46#include <util.h>
47#include <login_cap.h>
48#include <readpassphrase.h>
49
50#define UNCHANGED_MSG	"Password unchanged."
51
52static uid_t uid;
53extern int pwd_check(login_cap_t *, char *);
54extern int pwd_gettries(login_cap_t *);
55
56int local_passwd(char *, int);
57char *getnewpasswd(struct passwd *, login_cap_t *, int);
58void kbintr(int);
59
60int
61local_passwd(char *uname, int authenticated)
62{
63	struct passwd *pw, *opw;
64	login_cap_t *lc;
65	sigset_t fullset;
66	time_t period;
67	int i, pfd, tfd = -1;
68	int pwflags = _PASSWORD_OMITV7;
69
70	if (!(pw = getpwnam_shadow(uname))) {
71		warnx("unknown user %s.", uname);
72		return(1);
73	}
74
75	if (unveil(_PATH_MASTERPASSWD_LOCK, "rwc") == -1)
76		err(1, "unveil %s", _PATH_MASTERPASSWD_LOCK);
77	if (unveil(_PATH_MASTERPASSWD, "r") == -1)
78		err(1, "unveil %s", _PATH_MASTERPASSWD);
79	if (unveil(_PATH_LOGIN_CONF, "r") == -1)
80		err(1, "unveil %s", _PATH_LOGIN_CONF);
81	if (unveil(_PATH_LOGIN_CONF ".db", "r") == -1)
82		err(1, "unveil %s.db", _PATH_LOGIN_CONF);
83	if (unveil(_PATH_LOGIN_CONF_D, "r") == -1)
84		err(1, "unveil %s", _PATH_LOGIN_CONF_D);
85	if (unveil(_PATH_BSHELL, "x") == -1)
86		err(1, "unveil %s", _PATH_BSHELL);
87	if (unveil(_PATH_SHELLS, "r") == -1)
88		err(1, "unveil %s", _PATH_SHELLS);
89	if (unveil(_PATH_PWD_MKDB, "x") == -1)
90		err(1, "unveil %s", _PATH_PWD_MKDB);
91	if (pledge("stdio rpath wpath cpath getpw tty id proc exec", NULL) == -1)
92		err(1, "pledge");
93
94	if ((opw = pw_dup(pw)) == NULL) {
95		warn(NULL);
96		return(1);
97	}
98	if ((lc = login_getclass(pw->pw_class)) == NULL) {
99		warnx("unable to get login class for user %s.", uname);
100		free(opw);
101		return(1);
102	}
103
104	uid = authenticated ? pw->pw_uid : getuid();
105	if (uid && uid != pw->pw_uid) {
106		warnx("login/uid mismatch, username argument required.");
107		free(opw);
108		return(1);
109	}
110
111	/* Get the new password. */
112	pw->pw_passwd = getnewpasswd(pw, lc, authenticated);
113
114	if (pledge("stdio rpath wpath cpath getpw id proc exec", NULL) == -1)
115		err(1, "pledge");
116
117	/* Reset password change time based on login.conf. */
118	period = (time_t)login_getcaptime(lc, "passwordtime", 0, 0);
119	if (period > 0) {
120		pw->pw_change = time(NULL) + period;
121	} else {
122		/*
123		 * If the pw change time is the same we only need
124		 * to update the spwd.db file.
125		 */
126		if (pw->pw_change != 0)
127			pw->pw_change = 0;
128		else
129			pwflags = _PASSWORD_SECUREONLY;
130	}
131
132	/* Drop user's real uid and block all signals to avoid a DoS. */
133	setuid(0);
134	sigfillset(&fullset);
135	sigdelset(&fullset, SIGINT);
136	sigprocmask(SIG_BLOCK, &fullset, NULL);
137
138	if (pledge("stdio rpath wpath cpath proc exec", NULL) == -1)
139		err(1, "pledge");
140
141	/* Get a lock on the passwd file and open it. */
142	pw_init();
143	for (i = 1; (tfd = pw_lock(0)) == -1; i++) {
144		if (i == 4)
145			(void)fputs("Attempting to lock password file, "
146			    "please wait or press ^C to abort", stderr);
147		(void)signal(SIGINT, kbintr);
148		if (i % 16 == 0)
149			fputc('.', stderr);
150		usleep(250000);
151		(void)signal(SIGINT, SIG_IGN);
152	}
153	if (i >= 4)
154		fputc('\n', stderr);
155	pfd = open(_PATH_MASTERPASSWD, O_RDONLY | O_CLOEXEC);
156	if (pfd == -1)
157		pw_error(_PATH_MASTERPASSWD, 1, 1);
158
159	/* Update master.passwd file and rebuild spwd.db. */
160	pw_copy(pfd, tfd, pw, opw);
161	free(opw);
162	if (pw_mkdb(uname, pwflags) == -1)
163		pw_error(NULL, 0, 1);
164
165	fprintf(stderr, "passwd: password updated successfully\n");
166
167	return(0);
168}
169
170char *
171getnewpasswd(struct passwd *pw, login_cap_t *lc, int authenticated)
172{
173	static char hash[_PASSWORD_LEN];
174	char newpass[1024];
175	char *p, *pref;
176	int tries, pwd_tries;
177	sig_t saveint, savequit;
178
179	saveint = signal(SIGINT, kbintr);
180	savequit = signal(SIGQUIT, kbintr);
181
182	if (!authenticated) {
183		fprintf(stderr, "Changing password for %s.\n", pw->pw_name);
184		if (uid != 0 && pw->pw_passwd[0] != '\0') {
185			char oldpass[1024];
186
187			p = readpassphrase("Old password:", oldpass,
188			    sizeof(oldpass), RPP_ECHO_OFF);
189			if (p == NULL || *p == '\0') {
190				fprintf(stderr, "%s\n", UNCHANGED_MSG);
191				pw_abort();
192				exit(p == NULL ? 1 : 0);
193			}
194			if (crypt_checkpass(p, pw->pw_passwd) != 0) {
195				errno = EACCES;
196				explicit_bzero(oldpass, sizeof(oldpass));
197				pw_error(NULL, 1, 1);
198			}
199			explicit_bzero(oldpass, sizeof(oldpass));
200		}
201	}
202
203	pwd_tries = pwd_gettries(lc);
204
205	for (newpass[0] = '\0', tries = -1;;) {
206		char repeat[1024];
207
208		p = readpassphrase("New password:", newpass, sizeof(newpass),
209		    RPP_ECHO_OFF);
210		if (p == NULL || *p == '\0') {
211			fprintf(stderr, "%s\n", UNCHANGED_MSG);
212			pw_abort();
213			exit(p == NULL ? 1 : 0);
214		}
215		if (strcmp(p, "s/key") == 0) {
216			fprintf(stderr, "That password collides with a system feature. Choose another.\n");
217			continue;
218		}
219
220		if ((pwd_tries == 0 || ++tries < pwd_tries) &&
221		    pwd_check(lc, p) == 0)
222			continue;
223		p = readpassphrase("Retype new password:", repeat, sizeof(repeat),
224		    RPP_ECHO_OFF);
225		if (p != NULL && strcmp(newpass, p) == 0) {
226			explicit_bzero(repeat, sizeof(repeat));
227			break;
228		}
229		fprintf(stderr, "Mismatch; try again, EOF to quit.\n");
230		explicit_bzero(repeat, sizeof(repeat));
231		explicit_bzero(newpass, sizeof(newpass));
232	}
233
234	(void)signal(SIGINT, saveint);
235	(void)signal(SIGQUIT, savequit);
236
237	pref = login_getcapstr(lc, "localcipher", NULL, NULL);
238	if (crypt_newhash(newpass, pref, hash, sizeof(hash)) != 0) {
239		fprintf(stderr, "Couldn't generate hash.\n");
240		explicit_bzero(newpass, sizeof(newpass));
241		pw_error(NULL, 0, 0);
242	}
243	explicit_bzero(newpass, sizeof(newpass));
244	free(pref);
245	return hash;
246}
247
248void
249kbintr(int signo)
250{
251	dprintf(STDOUT_FILENO, "\n%s\n", UNCHANGED_MSG);
252	_exit(0);
253}
254