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