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