1 /*
2  * Copyright (c) 1987 Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Updated Thu Oct 12 09:56:55 1995 by faith@cs.unc.edu with security
34  * patches from Zefram <A.Main@dcs.warwick.ac.uk>
35  *
36  * Updated Thu Nov  9 21:58:53 1995 by Martin Schulze
37  * <joey@finlandia.infodrom.north.de>.  Support for vigr.
38  *
39  * Martin Schulze's patches adapted to Util-Linux by Nicolai Langfeldt.
40  *
41  * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
42  * - added Native Language Support
43  * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br>
44  * - fixed strerr(errno) in gettext calls
45  */
46 
47 /*
48  * This command is deprecated.  The utility is in maintenance mode,
49  * meaning we keep them in source tree for backward compatibility
50  * only.  Do not waste time making this command better, unless the
51  * fix is about security or other very critical issue.
52  *
53  * See Documentation/deprecated.txt for more information.
54  */
55 
56 #include <errno.h>
57 #include <fcntl.h>
58 #include <paths.h>
59 #include <pwd.h>
60 #include <shadow.h>
61 #include <signal.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <sys/file.h>
66 #include <sys/param.h>
67 #include <sys/resource.h>
68 #include <sys/stat.h>
69 #include <sys/time.h>
70 #include <sys/types.h>
71 #include <sys/wait.h>
72 #include <unistd.h>
73 
74 #include "c.h"
75 #include "fileutils.h"
76 #include "closestream.h"
77 #include "nls.h"
78 #include "setpwnam.h"
79 #include "strutils.h"
80 #include "xalloc.h"
81 #include "rpmatch.h"
82 
83 #ifdef HAVE_LIBSELINUX
84 # include <selinux/selinux.h>
85 #endif
86 
87 #define FILENAMELEN 67
88 
89 enum {
90 	VIPW,
91 	VIGR
92 };
93 int program;
94 char orig_file[FILENAMELEN];	/* original file /etc/passwd or /etc/group */
95 char *tmp_file;			/* tmp file */
96 
97 void pw_error __P((char *, int, int));
98 
copyfile(int from,int to)99 static void copyfile(int from, int to)
100 {
101 	int nr, nw, off;
102 	char buf[8 * 1024];
103 
104 	while ((nr = read(from, buf, sizeof(buf))) > 0)
105 		for (off = 0; off < nr; nr -= nw, off += nw)
106 			if ((nw = write(to, buf + off, nr)) < 0)
107 				pw_error(tmp_file, 1, 1);
108 
109 	if (nr < 0)
110 		pw_error(orig_file, 1, 1);
111 }
112 
pw_init(void)113 static void pw_init(void)
114 {
115 	struct rlimit rlim;
116 
117 	/* Unlimited resource limits. */
118 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
119 	(void)setrlimit(RLIMIT_CPU, &rlim);
120 	(void)setrlimit(RLIMIT_FSIZE, &rlim);
121 	(void)setrlimit(RLIMIT_STACK, &rlim);
122 	(void)setrlimit(RLIMIT_DATA, &rlim);
123 	(void)setrlimit(RLIMIT_RSS, &rlim);
124 
125 	/* Don't drop core (not really necessary, but GP's). */
126 	rlim.rlim_cur = rlim.rlim_max = 0;
127 	(void)setrlimit(RLIMIT_CORE, &rlim);
128 
129 	/* Turn off signals. */
130 	(void)signal(SIGALRM, SIG_IGN);
131 	(void)signal(SIGHUP, SIG_IGN);
132 	(void)signal(SIGINT, SIG_IGN);
133 	(void)signal(SIGPIPE, SIG_IGN);
134 	(void)signal(SIGQUIT, SIG_IGN);
135 	(void)signal(SIGTERM, SIG_IGN);
136 	(void)signal(SIGTSTP, SIG_IGN);
137 	(void)signal(SIGTTOU, SIG_IGN);
138 
139 	/* Create with exact permissions. */
140 	(void)umask(0);
141 }
142 
pw_tmpfile(int lockfd)143 static FILE * pw_tmpfile(int lockfd)
144 {
145 	FILE *fd;
146 	char *tmpname = NULL;
147 	char *dir = "/etc";
148 
149 	if ((fd = xfmkstemp(&tmpname, dir)) == NULL) {
150 		ulckpwdf();
151 		err(EXIT_FAILURE, _("can't open temporary file"));
152 	}
153 
154 	copyfile(lockfd, fileno(fd));
155 	tmp_file = tmpname;
156 	return fd;
157 }
158 
pw_write(void)159 static void pw_write(void)
160 {
161 	char tmp[FILENAMELEN + 4];
162 
163 	sprintf(tmp, "%s%s", orig_file, ".OLD");
164 	unlink(tmp);
165 
166 	if (link(orig_file, tmp))
167 		warn(_("%s: create a link to %s failed"), orig_file, tmp);
168 
169 #ifdef HAVE_LIBSELINUX
170 	if (is_selinux_enabled() > 0) {
171 		security_context_t passwd_context = NULL;
172 		int ret = 0;
173 		if (getfilecon(orig_file, &passwd_context) < 0) {
174 			warnx(_("Can't get context for %s"), orig_file);
175 			pw_error(orig_file, 1, 1);
176 		}
177 		ret = setfilecon(tmp_file, passwd_context);
178 		freecon(passwd_context);
179 		if (ret != 0) {
180 			warnx(_("Can't set context for %s"), tmp_file);
181 			pw_error(tmp_file, 1, 1);
182 		}
183 	}
184 #endif
185 
186 	if (rename(tmp_file, orig_file) == -1) {
187 		int errsv = errno;
188 		errx(EXIT_FAILURE,
189 		     ("cannot write %s: %s (your changes are still in %s)"),
190 		     orig_file, strerror(errsv), tmp_file);
191 	}
192 	unlink(tmp_file);
193 	free(tmp_file);
194 }
195 
pw_edit(void)196 static void pw_edit(void)
197 {
198 	int pstat;
199 	pid_t pid;
200 	char *p, *editor, *tk;
201 
202 	editor = getenv("EDITOR");
203 	editor = xstrdup(editor ? editor : _PATH_VI);
204 
205 	tk = strtok(editor, " \t");
206 	if (tk && (p = strrchr(tk, '/')) != NULL)
207 		++p;
208 	else
209 		p = editor;
210 
211 	pid = fork();
212 	if (pid < 0)
213 		err(EXIT_FAILURE, _("fork failed"));
214 
215 	if (!pid) {
216 		execlp(editor, p, tmp_file, NULL);
217 		/* Shouldn't get here */
218 		_exit(EXIT_FAILURE);
219 	}
220 	for (;;) {
221 		pid = waitpid(pid, &pstat, WUNTRACED);
222 		if (WIFSTOPPED(pstat)) {
223 			/* the editor suspended, so suspend us as well */
224 			kill(getpid(), SIGSTOP);
225 			kill(pid, SIGCONT);
226 		} else {
227 			break;
228 		}
229 	}
230 	if (pid == -1 || !WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0)
231 		pw_error(editor, 1, 1);
232 
233 	free(editor);
234 }
235 
236 void __attribute__((__noreturn__))
pw_error(char * name,int err,int eval)237 pw_error(char *name, int err, int eval)
238 {
239 	if (err) {
240 		if (name)
241 			warn("%s: ", name);
242 		else
243 			warn(NULL);
244 	}
245 	warnx(_("%s unchanged"), orig_file);
246 	unlink(tmp_file);
247 	ulckpwdf();
248 	exit(eval);
249 }
250 
edit_file(int is_shadow)251 static void edit_file(int is_shadow)
252 {
253 	struct stat begin, end;
254 	int passwd_file, ch_ret;
255 	FILE *tmp_fd;
256 
257 	pw_init();
258 
259 	/* acquire exclusive lock */
260 	if (lckpwdf() < 0)
261 		err(EXIT_FAILURE, _("cannot get lock"));
262 
263 	passwd_file = open(orig_file, O_RDONLY, 0);
264 	if (passwd_file < 0)
265 		err(EXIT_FAILURE, _("cannot open %s"), orig_file);
266 	tmp_fd = pw_tmpfile(passwd_file);
267 
268 	if (fstat(fileno(tmp_fd), &begin))
269 		pw_error(tmp_file, 1, 1);
270 
271 	pw_edit();
272 
273 	if (fstat(fileno(tmp_fd), &end))
274 		pw_error(tmp_file, 1, 1);
275 	/* Some editors, such as Vim with 'writebackup' mode enabled,
276 	 * use "atomic save" in which the old file is deleted and a new
277 	 * one with the same name created in its place.  */
278 	if (end.st_nlink == 0) {
279 		if (close_stream(tmp_fd) != 0)
280 			err(EXIT_FAILURE, _("write error"));
281 		tmp_fd = fopen(tmp_file, "r");
282 		if (!tmp_file)
283 			err(EXIT_FAILURE, _("cannot open %s"), tmp_file);
284 		if (fstat(fileno(tmp_fd), &end))
285 			pw_error(tmp_file, 1, 1);
286 	}
287 	if (begin.st_mtime == end.st_mtime) {
288 		warnx(_("no changes made"));
289 		pw_error((char *)NULL, 0, 0);
290 	}
291 	/* pw_tmpfile() will create the file with mode 600 */
292 	if (!is_shadow)
293 		ch_ret = fchmod(fileno(tmp_fd), 0644);
294 	else
295 		ch_ret = fchmod(fileno(tmp_fd), 0400);
296 	if (ch_ret < 0)
297 		err(EXIT_FAILURE, "%s: %s", _("cannot chmod file"), orig_file);
298 	if (close_stream(tmp_fd) != 0)
299 		err(EXIT_FAILURE, _("write error"));
300 	pw_write();
301 	close(passwd_file);
302 	ulckpwdf();
303 }
304 
usage(FILE * out)305 static void __attribute__((__noreturn__)) usage(FILE *out)
306 {
307 	fputs(USAGE_HEADER, out);
308 	fprintf(out, " %s\n", program_invocation_short_name);
309 	fputs(USAGE_OPTIONS, out);
310 	fputs(USAGE_HELP, out);
311 	fputs(USAGE_VERSION, out);
312 	fprintf(out, USAGE_MAN_TAIL("vipw(8)"));
313 	exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
314 }
315 
main(int argc,char * argv[])316 int main(int argc, char *argv[])
317 {
318 	setlocale(LC_ALL, "");
319 	bindtextdomain(PACKAGE, LOCALEDIR);
320 	textdomain(PACKAGE);
321 	atexit(close_stdout);
322 
323 	if (!strcmp(program_invocation_short_name, "vigr")) {
324 		program = VIGR;
325 		xstrncpy(orig_file, GROUP_FILE, sizeof(orig_file));
326 	} else {
327 		program = VIPW;
328 		xstrncpy(orig_file, PASSWD_FILE, sizeof(orig_file));
329 	}
330 
331 	if (1 < argc) {
332 		if (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version")) {
333 			printf(UTIL_LINUX_VERSION);
334 			exit(EXIT_SUCCESS);
335 		}
336 		if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
337 			usage(stdout);
338 		usage(stderr);
339 	}
340 
341 	edit_file(0);
342 
343 	if (program == VIGR) {
344 		strncpy(orig_file, SGROUP_FILE, FILENAMELEN - 1);
345 	} else {
346 		strncpy(orig_file, SHADOW_FILE, FILENAMELEN - 1);
347 	}
348 
349 	if (access(orig_file, F_OK) == 0) {
350 		char response[80];
351 
352 		printf((program == VIGR)
353 		       ? _("You are using shadow groups on this system.\n")
354 		       : _("You are using shadow passwords on this system.\n"));
355 		/* TRANSLATORS: this program uses for y and n rpmatch(3),
356 		 * which means they can be translated. */
357 		printf(_("Would you like to edit %s now [y/n]? "), orig_file);
358 
359 		if (fgets(response, sizeof(response), stdin)) {
360 			if (rpmatch(response) == 1)
361 				edit_file(1);
362 		}
363 	}
364 	exit(EXIT_SUCCESS);
365 }
366