xref: /illumos-gate/usr/src/cmd/pwconv/pwconv.c (revision 8eea8e29)
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 1996 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 /*  pwconv.c  */
33 /*  Conversion aid to copy appropriate fields from the	*/
34 /*  password file to the shadow file.			*/
35 
36 #include <pwd.h>
37 #include <fcntl.h>
38 #include <stdio.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <time.h>
42 #include <shadow.h>
43 #include <grp.h>
44 #include <signal.h>
45 #include <errno.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <locale.h>
49 
50 #define	PRIVILEGED	0 			/* priviledged id */
51 
52 /* exit  code */
53 #define	SUCCESS	0	/* succeeded */
54 #define	NOPERM	1	/* No permission */
55 #define	BADSYN	2	/* Incorrect syntax */
56 #define	FMERR	3	/* File manipulation error */
57 #define	FATAL	4	/* Old file can not be recover */
58 #define	FBUSY	5	/* Lock file busy */
59 #define	BADSHW	6	/* Bad entry in shadow file  */
60 
61 #define	DELPTMP()	(void) unlink(PASSTEMP)
62 #define	DELSHWTMP()	(void) unlink(SHADTEMP)
63 
64 char pwdflr[]	= "x";				/* password filler */
65 char *prognamp;
66 void f_err(), f_miss(), f_bdshw();
67 
68 /*
69  * getspnan routine that ONLY looks at the local shadow file
70  */
71 struct spwd *
72 local_getspnam(name)
73 char *name;
74 {
75 	FILE *shadf;
76 	struct spwd * sp;
77 
78 
79 	if ((shadf = fopen("/etc/shadow", "r")) == NULL)
80 		return (NULL);
81 
82 	while ((sp = fgetspent(shadf)) != NULL) {
83 		if (strcmp(sp->sp_namp, name) == 0)
84 			break;
85 	}
86 
87 	fclose(shadf);
88 
89 	return (sp);
90 }
91 
92 main(argc, argv)
93 int argc;
94 char **argv;
95 {
96 	extern	int	errno;
97 	void  no_recover(), no_convert();
98 	struct  passwd  *pwdp;
99 	struct	spwd	*sp, sp_pwd;		/* default entry */
100 	struct stat buf;
101 	FILE	*tp_fp, *tsp_fp;
102 	register time_t	when, minweeks, maxweeks;
103 	int file_exist = 1;
104 	int end_of_file = 0;
105 	mode_t mode;
106 	int pwerr = 0;
107 	ushort i;
108 	gid_t pwd_gid, sp_gid;
109 	uid_t pwd_uid, sp_uid;
110 	FILE *pwf;
111 	int black_magic = 0;
112 	int count;
113 
114 	(void) setlocale(LC_ALL, "");
115 
116 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
117 #define	TEXT_DOMAIN "SYS_TEST"
118 #endif
119 	(void) textdomain(TEXT_DOMAIN);
120 
121 	prognamp = argv[0];
122 	/* only PRIVILEGED can execute this command */
123 	if (getuid() != PRIVILEGED) {
124 		fprintf(stderr, gettext("%s: Permission denied.\n"), prognamp);
125 		exit(NOPERM);
126 	}
127 
128 	/* No argument can be passed to the command */
129 	if (argc > 1) {
130 		(void) fprintf(stderr,
131 		gettext("%s: Invalid command syntax.\n"),
132 			prognamp);
133 		fprintf(stderr, gettext("Usage: pwconv\n"));
134 		exit(BADSYN);
135 	}
136 
137 	/* lock file so that only one process can read or write at a time */
138 	if (lckpwdf() < 0) {
139 		(void) fprintf(stderr,
140 		gettext("%s: Password file(s) busy.  Try again later.\n"),
141 			prognamp);
142 		exit(FBUSY);
143 	}
144 
145 	/* All signals will be ignored during the execution of pwconv */
146 	for (i = 1; i < NSIG; i++)
147 		sigset(i, SIG_IGN);
148 
149 	/* reset errno to avoid side effects of a failed */
150 	/* sigset (e.g., SIGKILL) */
151 	errno = 0;
152 
153 	/* check the file status of the password file */
154 	/* get the gid of the password file */
155 	if (stat(PASSWD, &buf) < 0) {
156 		(void) f_miss();
157 		exit(FATAL);
158 	}
159 	pwd_gid = buf.st_gid;
160 	pwd_uid = buf.st_uid;
161 
162 	/* mode for the password file should be read-only or less */
163 	umask(S_IAMB & ~(buf.st_mode & (S_IRUSR|S_IRGRP|S_IROTH)));
164 
165 	/* open temporary password file */
166 	if ((tp_fp = fopen(PASSTEMP, "w")) == NULL) {
167 		(void) f_err();
168 		exit(FMERR);
169 	}
170 
171 	if (chown(PASSTEMP, pwd_uid, pwd_gid) < 0) {
172 		DELPTMP();
173 		(void) f_err();
174 		exit(FMERR);
175 	}
176 	/* default mode mask of the shadow file */
177 	mode = S_IAMB & ~(S_IRUSR);
178 
179 	/* check the existence of  shadow file */
180 	/* if the shadow file exists, get mode mask and group id of the file */
181 	/* if file does not exist, the default group name will be the group  */
182 	/* name of the password file.  */
183 
184 	if (access(SHADOW, F_OK) == 0) {
185 		if (stat(SHADOW, &buf) == 0) {
186 			mode  = S_IAMB & ~(buf.st_mode & S_IRUSR);
187 			sp_gid = buf.st_gid;
188 			sp_uid = buf.st_uid;
189 		} else {
190 			DELPTMP();
191 			(void) f_err();
192 			exit(FMERR);
193 		}
194 	} else {
195 		sp_gid = pwd_gid;
196 		sp_uid = pwd_uid;
197 		file_exist = 0;
198 	}
199 	/*
200 	 * get the mode of shadow password file  -- mode of the file should
201 	 * be read-only for user or less.
202 	 */
203 	umask(mode);
204 
205 	/* open temporary shadow file */
206 	if ((tsp_fp = fopen(SHADTEMP, "w")) == NULL) {
207 		DELPTMP();
208 		(void) f_err();
209 		exit(FMERR);
210 	}
211 
212 	/* change the group of the temporary shadow password file */
213 	if (chown(SHADTEMP, sp_uid, sp_gid) < 0) {
214 		(void) no_convert();
215 		exit(FMERR);
216 	}
217 
218 	/* Reads the password file.  				*/
219 	/* If the shadow password file not exists, or		*/
220 	/* if an entry doesn't have a corresponding entry in    */
221 	/* the shadow file, entries/entry will be created.	*/
222 
223 	if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
224 		no_recover();
225 		exit(FATAL);
226 	}
227 
228 	count = 0;
229 	while (!end_of_file) {
230 		count++;
231 		if ((pwdp = fgetpwent(pwf)) != NULL) {
232 			if (!file_exist ||
233 			    (sp = local_getspnam(pwdp->pw_name)) == NULL) {
234 				if (errno == EINVAL) {
235 				/* Bad entry in shadow exit */
236 					DELSHWTMP();
237 					DELPTMP();
238 					(void) f_bdshw();
239 					exit(BADSHW);
240 				}
241 				sp = &sp_pwd;
242 				sp->sp_namp = pwdp->pw_name;
243 				if (!pwdp->pw_passwd ||
244 				    (pwdp->pw_passwd &&
245 					*pwdp->pw_passwd == NULL)) {
246 					(void) fprintf(stderr,
247 			gettext("%s: WARNING user %s has no password\n"),
248 					prognamp, sp->sp_namp);
249 				}
250 				/*
251 				 * copy the password field in the password
252 				 * file to the shadow file.
253 				 * replace the password field with an 'x'.
254 				 */
255 				sp->sp_pwdp = pwdp->pw_passwd;
256 				pwdp->pw_passwd = pwdflr;
257 				/*
258 				 * if aging, split the aging info
259 				 * into age, max and min
260 				 * convert aging info from weeks to days
261 				 */
262 				if (pwdp->pw_age && *pwdp->pw_age != NULL) {
263 					when = (long) a64l(pwdp->pw_age);
264 					maxweeks = when & 077;
265 					minweeks = (when >> 6) & 077;
266 					when >>= 12;
267 					sp->sp_lstchg = when * 7;
268 					sp->sp_min = minweeks * 7;
269 					sp->sp_max = maxweeks * 7;
270 					sp->sp_warn = -1;
271 					sp->sp_inact = -1;
272 					sp->sp_expire = -1;
273 					sp->sp_flag = 0;
274 					pwdp->pw_age = "";  /* do we care? */
275 				} else {
276 				/*
277 				 * if !aging, last_changed will be the day the
278 				 * conversion is done, min and max fields will
279 				 * be null - use timezone to get local time
280 				 */
281 					sp->sp_lstchg = DAY_NOW;
282 					sp->sp_min =  -1;
283 					sp->sp_max =  -1;
284 					sp->sp_warn = -1;
285 					sp->sp_inact = -1;
286 					sp->sp_expire = -1;
287 					sp->sp_flag = 0;
288 				}
289 	    } else {
290 			/*
291 			 * if the passwd field has a string other than 'x',
292 			 * the entry will be written into the shadow file
293 			 * and the character 'x' is re-written as the passwd
294 			 * if !aging, last_changed as above
295 			 */
296 
297 			/*
298 			 * with NIS, only warn about password missing if entry
299 			 * is not a NIS-lookup entry ("+" or "-")
300 			 * black_magic from getpwnam_r.c
301 			 */
302 			black_magic = (*pwdp->pw_name == '+' ||
303 					*pwdp->pw_name == '-');
304 			/*
305 			 * moan about absence of non "+/-" passwd
306 			 * we could do more, but what?
307 			 */
308 			if ((!pwdp->pw_passwd ||
309 			    (pwdp->pw_passwd && *pwdp->pw_passwd == NULL)) &&
310 			    !black_magic) {
311 				(void) fprintf(stderr,
312 			gettext("%s: WARNING user %s has no password\n"),
313 					prognamp, sp->sp_namp);
314 			}
315 			if (pwdp->pw_passwd && *pwdp->pw_passwd) {
316 				if (strcmp(pwdp->pw_passwd, pwdflr)) {
317 					sp->sp_pwdp = pwdp->pw_passwd;
318 					pwdp->pw_passwd = pwdflr;
319 					if (!pwdp->pw_age ||
320 					    (pwdp->pw_age &&
321 						*pwdp->pw_age == NULL)) {
322 						sp->sp_lstchg = DAY_NOW;
323 						sp->sp_min =  -1;
324 						sp->sp_max =  -1;
325 						sp->sp_warn = -1;
326 						sp->sp_inact = -1;
327 						sp->sp_expire = -1;
328 						sp->sp_flag = 0;
329 					}
330 				}
331 			} else {
332 				/*
333 				 * black_magic needs a non-null passwd
334 				 * and pwdflr seem appropriate here
335 				 * clear garbage if any
336 				 */
337 				sp->sp_pwdp = "";
338 				pwdp->pw_passwd = pwdflr;
339 				sp->sp_lstchg = sp->sp_min = sp->sp_max = -1;
340 				sp->sp_warn = sp->sp_inact = sp->sp_expire = -1;
341 				sp->sp_flag = 0;
342 			}
343 			/*
344 			 * if aging, split the aging info
345 			 * into age, max and min
346 			 * convert aging info from weeks to days
347 			 */
348 			if (pwdp->pw_age && *pwdp->pw_age != NULL) {
349 				when = (long) a64l(pwdp->pw_age);
350 				maxweeks = when & 077;
351 				minweeks = (when >> 6) & 077;
352 				when >>= 12;
353 				sp->sp_lstchg = when * 7;
354 				sp->sp_min = minweeks * 7;
355 				sp->sp_max = maxweeks * 7;
356 				sp->sp_warn = -1;
357 				sp->sp_inact = -1;
358 				sp->sp_expire = -1;
359 				sp->sp_flag = 0;
360 				pwdp->pw_age = ""; /* do we care? */
361 			}
362 		}
363 
364 		/* write an entry to temporary password file */
365 		if ((putpwent(pwdp, tp_fp)) != 0) {
366 			(void) no_convert();
367 			exit(FMERR);
368 		}
369 
370 		/* write an entry to temporary shadow password file */
371 		if (putspent(sp, tsp_fp) != 0) {
372 			(void) no_convert();
373 			exit(FMERR);
374 		}
375 	    } else {
376 		if (feof(pwf)) {
377 			end_of_file = 1;
378 		} else {
379 			errno = 0;
380 			pwerr = 1;
381 			(void) fprintf(stderr,
382 			gettext("%s: ERROR: bad entry or blank line at line %d in /etc/passwd\n"),
383 					prognamp, count);
384 		}
385 	    }
386 	} /* end of while */
387 
388 	(void) fclose(pwf);
389 	(void) fclose(tsp_fp);
390 	(void) fclose(tp_fp);
391 	if (pwerr) {
392 		(void) no_convert();
393 		exit(FMERR);
394 	}
395 
396 	/* delete old password file if it exists */
397 	if (unlink(OPASSWD) && (access(OPASSWD, F_OK) == 0)) {
398 		(void) no_convert();
399 		exit(FMERR);
400 	}
401 
402 	/* rename the password file to old password file  */
403 	if (rename(PASSWD, OPASSWD) == -1) {
404 		(void) no_convert();
405 		exit(FMERR);
406 	}
407 
408 	/* rename temporary password file to password file */
409 	if (rename(PASSTEMP, PASSWD) == -1) {
410 		/* link old password file to password file */
411 		if (link(OPASSWD, PASSWD) < 0) {
412 			(void) no_recover();
413 			exit(FATAL);
414 		}
415 		(void) no_convert();
416 		exit(FMERR);
417 	}
418 
419 	/* delete old shadow password file if it exists */
420 	if (unlink(OSHADOW) && (access(OSHADOW, R_OK) == 0)) {
421 		/* link old password file to password file */
422 		if (unlink(PASSWD) || link(OPASSWD, PASSWD)) {
423 			(void) no_recover();
424 			exit(FATAL);
425 		}
426 		(void) no_convert();
427 		exit(FMERR);
428 	}
429 
430 	/* link shadow password file to old shadow password file */
431 	if (file_exist && rename(SHADOW, OSHADOW)) {
432 		/* link old password file to password file */
433 		if (unlink(PASSWD) || link(OPASSWD, PASSWD)) {
434 			(void) no_recover();
435 			exit(FATAL);
436 		}
437 		(void) no_convert();
438 		exit(FMERR);
439 	}
440 
441 
442 	/* link temporary shadow password file to shadow password file */
443 	if (rename(SHADTEMP, SHADOW) == -1) {
444 		/* link old shadow password file to shadow password file */
445 		if (file_exist && (link(OSHADOW, SHADOW))) {
446 			(void) no_recover();
447 			exit(FATAL);
448 		}
449 		if (unlink(PASSWD) || link(OPASSWD, PASSWD)) {
450 			(void) no_recover();
451 			exit(FATAL);
452 		}
453 		(void) no_convert();
454 		exit(FMERR);
455 	}
456 
457 	/* Change old password file to read only by owner   */
458 	/* If chmod fails, delete the old password file so that */
459 	/* the password fields can not be read by others */
460 	if (chmod(OPASSWD, S_IRUSR) < 0)
461 		(void) unlink(OPASSWD);
462 
463 	(void) ulckpwdf();
464 	exit(0);
465 }
466 
467 void
468 no_recover()
469 {
470 	DELPTMP();
471 	DELSHWTMP();
472 	(void) f_miss();
473 }
474 
475 void
476 no_convert()
477 {
478 	DELPTMP();
479 	DELSHWTMP();
480 	(void) f_err();
481 }
482 
483 void
484 f_err()
485 {
486 	fprintf(stderr,
487 		gettext("%s: Unexpected failure. Conversion not done.\n"),
488 			prognamp);
489 	(void) ulckpwdf();
490 }
491 
492 void
493 f_miss()
494 {
495 	fprintf(stderr,
496 		gettext("%s: Unexpected failure. Password file(s) missing.\n"),
497 			prognamp);
498 	(void) ulckpwdf();
499 }
500 
501 void
502 f_bdshw()
503 {
504 	fprintf(stderr,
505 		gettext("%s: Bad entry in /etc/shadow. Conversion not done.\n"),
506 			prognamp);
507 	(void) ulckpwdf();
508 }
509