xref: /openbsd-src/usr.bin/passwd/local_passwd.c (revision ddc5974845058f5711a661fb582276f2489ba1bc)
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 
52 static uid_t uid;
53 extern int pwd_check(login_cap_t *, char *);
54 extern int pwd_gettries(login_cap_t *);
55 
56 int local_passwd(char *, int);
57 char *getnewpasswd(struct passwd *, login_cap_t *, int);
58 void kbintr(int);
59 
60 int
local_passwd(char * uname,int authenticated)61 local_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 
170 char *
getnewpasswd(struct passwd * pw,login_cap_t * lc,int authenticated)171 getnewpasswd(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 
248 void
kbintr(int signo)249 kbintr(int signo)
250 {
251 	dprintf(STDOUT_FILENO, "\n%s\n", UNCHANGED_MSG);
252 	_exit(0);
253 }
254