xref: /illumos-gate/usr/src/ucbcmd/vipw/vipw.c (revision cbab2b2687744cbfdc12fae90f8088127a0b266c)
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 
53 static int copyfile(char *, char *);
54 static int editfile(char *, char *, char *, time_t *);
55 static int sanity_check(char *, time_t *, char *);
56 static int validsh(char *);
57 
58 char	*ptemp = "/etc/ptmp";
59 char	*stemp = "/etc/stmp";
60 char	*passwd = "/etc/passwd";
61 char	*shadow = "/etc/shadow";
62 char	buf[BUFSIZ];
63 
64 int
65 main(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 	}
185 bad:
186 	(void) unlink(ptemp);
187 	(void) unlink(stemp);
188 	return (ok ? 0 : 1);
189 	/* NOTREACHED */
190 }
191 
192 
193 int
194 copyfile(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 
225 int
226 editfile(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 
236 int
237 validsh(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  */
258 int
259 sanity_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) {
344 bad_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