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