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
main(void)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
copyfile(char * from,char * to)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
editfile(char * editor,char * temp,char * orig,time_t * mtime)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
validsh(char * rootsh)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
sanity_check(char * temp,time_t * mtime,char * orig)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