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 2006 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
27/*	  All Rights Reserved  	*/
28
29/*
30 * Portions of this source code were derived from Berkeley 4.3 BSD
31 * under license from the Regents of the University of California.
32 */
33
34#pragma ident	"%Z%%M%	%I%	%E% SMI"
35
36#include <sys/types.h>
37#include <sys/stat.h>
38#include <sys/file.h>
39#include <sys/fcntl.h>
40
41#include <stdio.h>
42#include <errno.h>
43#include <signal.h>
44#include <stdlib.h>
45#include <strings.h>
46
47/*
48 * Password file editor with locking.
49 */
50
51#define	DEFAULT_EDITOR	"/usr/bin/vi"
52
53static int copyfile(char *, char *);
54static int editfile(char *, char *, char *, time_t *);
55static int sanity_check(char *, time_t *, char *);
56static int validsh(char *);
57
58char	*ptemp = "/etc/ptmp";
59char	*stemp = "/etc/stmp";
60char	*passwd = "/etc/passwd";
61char	*shadow = "/etc/shadow";
62char	buf[BUFSIZ];
63
64int
65main(void)
66{
67	int fd;
68	FILE *ft, *fp;
69	char *editor;
70	int ok = 0;
71	time_t o_mtime, n_mtime;
72	struct stat osbuf, sbuf, oshdbuf, shdbuf;
73	char c;
74
75	(void)signal(SIGINT, SIG_IGN);
76	(void)signal(SIGQUIT, SIG_IGN);
77	(void)signal(SIGHUP, SIG_IGN);
78	setbuf(stderr, (char *)NULL);
79
80	editor = getenv("VISUAL");
81	if (editor == 0)
82		editor = getenv("EDITOR");
83	if (editor == 0)
84		editor = DEFAULT_EDITOR;
85
86	(void)umask(0077);
87	if (stat(passwd, &osbuf) < 0) {
88                (void)fprintf(stderr,"vipw: can't stat passwd file.\n");
89                goto bad;
90        }
91
92	if (copyfile(passwd, ptemp))
93		goto bad;
94
95	if (stat(ptemp, &sbuf) < 0) {
96                (void)fprintf(stderr,
97			"vipw: can't stat ptemp file, %s unchanged\n",
98			passwd);
99		goto bad;
100	}
101
102	o_mtime = sbuf.st_mtime;
103
104	if (editfile(editor, ptemp, passwd, &n_mtime)) {
105		if (sanity_check(ptemp, &n_mtime, passwd))
106			goto bad;
107		if (o_mtime >= n_mtime)
108			goto bad;
109	}
110
111	ok++;
112	if (o_mtime < n_mtime) {
113		fprintf(stdout, "\nYou have modified the password file.\n");
114		fprintf(stdout,
115	"Press 'e' to edit the shadow file for consistency,\n 'q' to quit: ");
116		if ((c = getchar()) == 'q') {
117			if (chmod(ptemp, (osbuf.st_mode & 0644)) < 0) {
118				(void) fprintf(stderr, "vipw: %s: ", ptemp);
119				perror("chmod");
120				goto bad;
121			}
122			if (rename(ptemp, passwd) < 0) {
123				(void) fprintf(stderr, "vipw: %s: ", ptemp);
124				perror("rename");
125				goto bad;
126			}
127			if (((osbuf.st_gid != sbuf.st_gid) ||
128					(osbuf.st_uid != sbuf.st_uid)) &&
129			(chown(passwd, osbuf.st_uid, osbuf.st_gid) < 0)) {
130				(void) fprintf(stderr, "vipw: %s ", ptemp);
131				perror("chown");
132			}
133			goto bad;
134		} else if (c == 'e') {
135			if (stat(shadow, &oshdbuf) < 0) {
136				(void) fprintf(stderr,
137					"vipw: can't stat shadow file.\n");
138				goto bad;
139			}
140
141			if (copyfile(shadow, stemp))
142				goto bad;
143			if (stat(stemp, &shdbuf) < 0) {
144				(void) fprintf(stderr,
145					"vipw: can't stat stmp file.\n");
146				goto bad;
147			}
148
149			if (editfile(editor, stemp, shadow, &o_mtime))
150				goto bad;
151			ok++;
152			if (chmod(ptemp, (osbuf.st_mode & 0644)) < 0) {
153				(void) fprintf(stderr, "vipw: %s: ", ptemp);
154				perror("chmod");
155				goto bad;
156			}
157			if (chmod(stemp, (oshdbuf.st_mode & 0400)) < 0) {
158				(void) fprintf(stderr, "vipw: %s: ", stemp);
159				perror("chmod");
160				goto bad;
161			}
162			if (rename(ptemp, passwd) < 0) {
163				(void) fprintf(stderr, "vipw: %s: ", ptemp);
164				perror("rename");
165				goto bad;
166			}
167			if (((osbuf.st_gid != sbuf.st_gid) ||
168					(osbuf.st_uid != sbuf.st_uid)) &&
169			(chown(passwd, osbuf.st_uid, osbuf.st_gid) < 0)) {
170				(void) fprintf(stderr, "vipw: %s ", ptemp);
171				perror("chown");
172			}
173			if (rename(stemp, shadow) < 0) {
174				(void) fprintf(stderr, "vipw: %s: ", stemp);
175				perror("rename");
176				goto bad;
177			} else if (((oshdbuf.st_gid != shdbuf.st_gid) ||
178					(oshdbuf.st_uid != shdbuf.st_uid)) &&
179			(chown(shadow, oshdbuf.st_uid, oshdbuf.st_gid) < 0)) {
180				(void) fprintf(stderr, "vipw: %s ", stemp);
181				perror("chown");
182				}
183		}
184	}
185bad:
186	(void) unlink(ptemp);
187	(void) unlink(stemp);
188	return (ok ? 0 : 1);
189	/* NOTREACHED */
190}
191
192
193int
194copyfile(char *from, char *to)
195{
196	int fd;
197	FILE *fp, *ft;
198
199	fd = open(to, O_WRONLY|O_CREAT|O_EXCL, 0600);
200	if (fd < 0) {
201		if (errno == EEXIST) {
202			(void) fprintf(stderr, "vipw: %s file busy\n", from);
203			exit(1);
204		}
205		(void) fprintf(stderr, "vipw: "); perror(to);
206		exit(1);
207	}
208	ft = fdopen(fd, "w");
209	if (ft == NULL) {
210		(void) fprintf(stderr, "vipw: "); perror(to);
211		return( 1 );
212	}
213	fp = fopen(from, "r");
214	if (fp == NULL) {
215		(void) fprintf(stderr, "vipw: "); perror(from);
216		return( 1 );
217	}
218	while (fgets(buf, sizeof (buf) - 1, fp) != NULL)
219		fputs(buf, ft);
220	(void) fclose(ft);
221	(void) fclose(fp);
222	return( 0 );
223}
224
225int
226editfile(char *editor, char *temp, char *orig, time_t *mtime)
227{
228	(void)sprintf(buf, "%s %s", editor, temp);
229	if (system(buf) == 0) {
230		return (sanity_check(temp, mtime, orig));
231	}
232	return(1);
233}
234
235
236int
237validsh(char *rootsh)
238{
239
240	char	*sh, *getusershell();
241	int	ret = 0;
242
243	setusershell();
244	while((sh = getusershell()) != NULL ) {
245		if( strcmp( rootsh, sh) == 0 ) {
246			ret = 1;
247			break;
248		}
249	}
250	endusershell();
251	return(ret);
252}
253
254/*
255 * sanity checks
256 * return 0 if ok, 1 otherwise
257 */
258int
259sanity_check(char *temp, time_t *mtime, char *orig)
260{
261	int i, ok = 0;
262	FILE *ft;
263	struct stat sbuf, statbuf;
264	char *ldir;
265	int isshadow = 0;
266
267	if (!strcmp(orig, shadow))
268		isshadow = 1;
269
270	/* sanity checks */
271	if (stat(temp, &sbuf) < 0) {
272		(void)fprintf(stderr,
273		    "vipw: can't stat %s file, %s unchanged\n",
274		    temp, orig);
275		return(1);
276	}
277	*mtime = sbuf.st_mtime;
278	if (sbuf.st_size == 0) {
279		(void)fprintf(stderr, "vipw: bad %s file, %s unchanged\n",
280		    temp, orig);
281		return(1);
282	}
283	ft = fopen(temp, "r");
284	if (ft == NULL) {
285		(void)fprintf(stderr,
286		    "vipw: can't reopen %s file, %s unchanged\n",
287		    temp, orig);
288		return(1);
289	}
290
291	while (fgets(buf, sizeof (buf) - 1, ft) != NULL) {
292		char *cp;
293
294		cp = index(buf, '\n');
295		if (cp == 0)
296			continue;	/* ??? allow very long lines
297					 * and passwd files that do
298					 * not end in '\n' ???
299					 */
300		*cp = '\0';
301
302		cp = index(buf, ':');
303		if (cp == 0)		/* lines without colon
304					 * separated fields
305					 */
306			continue;
307		*cp = '\0';
308
309		if (strcmp(buf, "root"))
310			continue;
311
312		/* root password */
313		*cp = ':';
314		cp = index(cp + 1, ':');
315		if (cp == 0)
316			goto bad_root;
317
318		/* root uid for password */
319		if (!isshadow)
320			if (atoi(cp + 1) != 0) {
321
322				(void)fprintf(stderr, "root UID != 0:\n%s\n",
323				    buf);
324				break;
325			}
326		/* root uid for passwd and sp_lstchg for shadow */
327		cp = index(cp + 1, ':');
328		if (cp == 0)
329			goto bad_root;
330
331		/* root's gid for passwd and sp_min for shadow*/
332		cp = index(cp + 1, ':');
333		if (cp == 0)
334			goto bad_root;
335
336		/* root's gecos for passwd and sp_max for shadow*/
337		cp = index(cp + 1, ':');
338		if (isshadow) {
339			for (i=0; i<3; i++)
340				if ((cp = index(cp + 1, ':')) == 0)
341					goto bad_root;
342		} else {
343			if (cp == 0) {
344bad_root:		(void)fprintf(stderr,
345				    "Missing fields in root entry:\n%s\n", buf);
346				break;
347			}
348		}
349		if (!isshadow) {
350			/* root's login directory */
351			ldir = ++cp;
352			cp = index(cp, ':');
353			if (cp == 0)
354				goto bad_root;
355			*cp = '\0';
356			if (stat(ldir, &statbuf) < 0) {
357				*cp = ':';
358				(void) fprintf(stderr,
359				    "root login dir doesn't exist:\n%s\n",
360				    buf);
361				break;
362			} else if (!S_ISDIR(statbuf.st_mode)) {
363				*cp = ':';
364				(void) fprintf(stderr,
365				    "root login dir is not a directory:\n%s\n",
366				    buf);
367				break;
368			}
369
370			*cp = ':';
371			/* root's login shell */
372			++cp;
373			if (*cp && ! validsh(cp)) {
374				(void)fprintf(stderr,
375				    "Invalid root shell:\n%s\n", buf);
376				break;
377			}
378		}
379
380		ok++;
381	}
382	(void)fclose(ft);
383	if (ok)
384		return(0);
385	else {
386		(void)fprintf(stderr,
387		    "vipw: you mangled the %s file, %s unchanged\n",
388		    temp, orig);
389		return(1);
390	}
391}
392