1/*-
2 * SPDX-License-Identifier: BSD-4-Clause
3 *
4 * Copyright (c) 1988, 1993, 1994
5 *	The Regents of the University of California.  All rights reserved.
6 * Copyright (c) 2002 Networks Associates Technology, Inc.
7 * All rights reserved.
8 *
9 * Portions of this software were developed for the FreeBSD Project by
10 * ThinkSec AS and NAI Labs, the Security Research Division of Network
11 * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
12 * ("CBOSS"), as part of the DARPA CHATS research program.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 * 1. Redistributions of source code must retain the above copyright
18 *    notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 *    notice, this list of conditions and the following disclaimer in the
21 *    documentation and/or other materials provided with the distribution.
22 * 3. All advertising materials mentioning features or use of this software
23 *    must display the following acknowledgement:
24 *	This product includes software developed by the University of
25 *	California, Berkeley and its contributors.
26 * 4. Neither the name of the University nor the names of its contributors
27 *    may be used to endorse or promote products derived from this software
28 *    without specific prior written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
41 */
42
43#include <sys/param.h>
44
45#include <err.h>
46#include <errno.h>
47#include <pwd.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <unistd.h>
52#ifdef YP
53#include <ypclnt.h>
54#endif
55
56#include <pw_scan.h>
57#include <libutil.h>
58
59#include "chpass.h"
60
61int master_mode;
62
63static void	baduser(void);
64static void	usage(void);
65
66int
67main(int argc, char *argv[])
68{
69	enum { NEWSH, LOADENTRY, EDITENTRY, NEWPW, NEWEXP } op;
70	struct passwd lpw, *old_pw, *pw;
71	int ch, pfd, tfd;
72	const char *password;
73	char *arg = NULL, *cryptpw;
74	uid_t uid;
75#ifdef YP
76	struct ypclnt *ypclnt;
77	const char *yp_domain = NULL, *yp_host = NULL;
78#endif
79
80	pw = old_pw = NULL;
81	op = EDITENTRY;
82#ifdef YP
83	while ((ch = getopt(argc, argv, "a:p:s:e:d:h:loy")) != -1)
84#else
85	while ((ch = getopt(argc, argv, "a:p:s:e:")) != -1)
86#endif
87		switch (ch) {
88		case 'a':
89			op = LOADENTRY;
90			arg = optarg;
91			break;
92		case 's':
93			op = NEWSH;
94			arg = optarg;
95			break;
96		case 'p':
97			op = NEWPW;
98			arg = optarg;
99			break;
100		case 'e':
101			op = NEWEXP;
102			arg = optarg;
103			break;
104#ifdef YP
105		case 'd':
106			yp_domain = optarg;
107			break;
108		case 'h':
109			yp_host = optarg;
110			break;
111		case 'l':
112		case 'o':
113		case 'y':
114			/* compatibility */
115			break;
116#endif
117		case '?':
118		default:
119			usage();
120		}
121
122	argc -= optind;
123	argv += optind;
124
125	if (argc > 1)
126		usage();
127
128	uid = getuid();
129
130	if (op == EDITENTRY || op == NEWSH || op == NEWPW || op == NEWEXP) {
131		if (argc == 0) {
132			if ((pw = getpwuid(uid)) == NULL)
133				errx(1, "unknown user: uid %lu",
134				    (unsigned long)uid);
135		} else {
136			if ((pw = getpwnam(*argv)) == NULL)
137				errx(1, "unknown user: %s", *argv);
138			if (uid != 0 && uid != pw->pw_uid)
139				baduser();
140		}
141
142		/* Make a copy for later verification */
143		if ((pw = pw_dup(pw)) == NULL ||
144		    (old_pw = pw_dup(pw)) == NULL)
145			err(1, "pw_dup");
146	}
147
148#ifdef YP
149	if (pw != NULL && (pw->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
150		ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_host);
151		master_mode = (ypclnt != NULL &&
152		    ypclnt_connect(ypclnt) != -1 &&
153		    ypclnt_havepasswdd(ypclnt) == 1);
154		ypclnt_free(ypclnt);
155	} else
156#endif
157	master_mode = (uid == 0);
158
159	if (op == NEWSH) {
160		/* protect p_shell -- it thinks NULL is /bin/sh */
161		if (!arg[0])
162			usage();
163		if (p_shell(arg, pw, (ENTRY *)NULL) == -1)
164			exit(1);
165	}
166
167	if (op == NEWEXP) {
168		if (uid)	/* only root can change expire */
169			baduser();
170		if (p_expire(arg, pw, (ENTRY *)NULL) == -1)
171			exit(1);
172	}
173
174	if (op == LOADENTRY) {
175		if (uid)
176			baduser();
177		pw = &lpw;
178		old_pw = NULL;
179		if (!__pw_scan(arg, pw, _PWSCAN_WARN|_PWSCAN_MASTER))
180			exit(1);
181	}
182
183	if (op == NEWPW) {
184		if (uid)
185			baduser();
186
187		if (strchr(arg, ':'))
188			errx(1, "invalid format for password");
189		pw->pw_passwd = arg;
190	}
191
192	if (op == EDITENTRY) {
193		/*
194		 * We don't really need pw_*() here, but pw_edit() (used
195		 * by edit()) is just too useful...
196		 */
197		if (pw_init(NULL, NULL))
198			err(1, "pw_init()");
199		if ((tfd = pw_tmp(-1)) == -1) {
200			pw_fini();
201			err(1, "pw_tmp()");
202		}
203		free(pw);
204		pw = edit(pw_tempname(), old_pw);
205		pw_fini();
206		if (pw == NULL)
207			err(1, "edit()");
208		/*
209		 * pw_equal does not check for crypted passwords, so we
210		 * should do it explicitly
211		 */
212		if (pw_equal(old_pw, pw) &&
213		    strcmp(old_pw->pw_passwd, pw->pw_passwd) == 0)
214			errx(0, "user information unchanged");
215	}
216
217	if (old_pw && !master_mode) {
218		password = getpass("Password: ");
219		cryptpw = crypt(password, old_pw->pw_passwd);
220		if (cryptpw == NULL || strcmp(cryptpw, old_pw->pw_passwd) != 0)
221			baduser();
222	} else {
223		password = "";
224	}
225
226	if (old_pw != NULL)
227		pw->pw_fields |= (old_pw->pw_fields & _PWF_SOURCE);
228	switch (pw->pw_fields & _PWF_SOURCE) {
229#ifdef YP
230	case _PWF_NIS:
231		ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_host);
232		if (ypclnt == NULL) {
233			warnx("ypclnt_new failed");
234			exit(1);
235		}
236		if (ypclnt_connect(ypclnt) == -1 ||
237		    ypclnt_passwd(ypclnt, pw, password) == -1) {
238			warnx("%s", ypclnt->error);
239			ypclnt_free(ypclnt);
240			exit(1);
241		}
242		ypclnt_free(ypclnt);
243		errx(0, "NIS user information updated");
244#endif /* YP */
245	case 0:
246	case _PWF_FILES:
247		if (pw_init(NULL, NULL))
248			err(1, "pw_init()");
249		if ((pfd = pw_lock()) == -1) {
250			pw_fini();
251			err(1, "pw_lock()");
252		}
253		if ((tfd = pw_tmp(-1)) == -1) {
254			pw_fini();
255			err(1, "pw_tmp()");
256		}
257		if (pw_copy(pfd, tfd, pw, old_pw) == -1) {
258			pw_fini();
259			err(1, "pw_copy");
260		}
261		if (pw_mkdb(pw->pw_name) == -1) {
262			pw_fini();
263			err(1, "pw_mkdb()");
264		}
265		pw_fini();
266		errx(0, "user information updated");
267		break;
268	default:
269		errx(1, "unsupported passwd source");
270	}
271}
272
273static void
274baduser(void)
275{
276
277	errx(1, "%s", strerror(EACCES));
278}
279
280static void
281usage(void)
282{
283
284	(void)fprintf(stderr,
285	    "usage: chpass%s %s [user]\n",
286#ifdef YP
287	    " [-d domain] [-h host]",
288#else
289	    "",
290#endif
291	    "[-a list] [-p encpass] [-s shell] [-e mmm dd yy]");
292	exit(1);
293}
294