xref: /illumos-gate/usr/src/cmd/pwconv/pwconv.c (revision 4e81fcfe0bcce62e29d80c1e391d9a241c0ee3b1)
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 /*
23  * Copyright 2006 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 /*  pwconv.c  */
31 /*  Conversion aid to copy appropriate fields from the	*/
32 /*  password file to the shadow file.			*/
33 
34 #include <pwd.h>
35 #include <fcntl.h>
36 #include <stdio.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <time.h>
40 #include <shadow.h>
41 #include <grp.h>
42 #include <signal.h>
43 #include <errno.h>
44 #include <unistd.h>
45 #include <stdlib.h>
46 #include <locale.h>
47 #include <string.h>
48 
49 #define	PRIVILEGED	0			/* privileged id */
50 
51 /* exit  code */
52 #define	SUCCESS	0	/* succeeded */
53 #define	NOPERM	1	/* No permission */
54 #define	BADSYN	2	/* Incorrect syntax */
55 #define	FMERR	3	/* File manipulation error */
56 #define	FATAL	4	/* Old file can not be recover */
57 #define	FBUSY	5	/* Lock file busy */
58 #define	BADSHW	6	/* Bad entry in shadow file  */
59 
60 #define	DELPTMP()	(void) unlink(PASSTEMP)
61 #define	DELSHWTMP()	(void) unlink(SHADTEMP)
62 
63 char pwdflr[]	= "x";				/* password filler */
64 char *prognamp;
65 void f_err(void), f_miss(void), f_bdshw(void);
66 
67 /*
68  * getspnan routine that ONLY looks at the local shadow file
69  */
70 struct spwd *
71 local_getspnam(char *name)
72 {
73 	FILE *shadf;
74 	struct spwd *sp;
75 
76 
77 	if ((shadf = fopen("/etc/shadow", "r")) == NULL)
78 		return (NULL);
79 
80 	while ((sp = fgetspent(shadf)) != NULL) {
81 		if (strcmp(sp->sp_namp, name) == 0)
82 			break;
83 	}
84 
85 	(void) fclose(shadf);
86 
87 	return (sp);
88 }
89 
90 int
91 main(int argc, char **argv)
92 {
93 	extern	int	errno;
94 	void  no_recover(void), no_convert(void);
95 	struct  passwd  *pwdp;
96 	struct	spwd	*sp, sp_pwd;		/* default entry */
97 	struct stat buf;
98 	FILE	*tp_fp, *tsp_fp;
99 	time_t	when, minweeks, maxweeks;
100 	int file_exist = 1;
101 	int end_of_file = 0;
102 	mode_t mode;
103 	mode_t pwd_mode;
104 	int pwerr = 0;
105 	ushort_t 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 		(void) fprintf(stderr, gettext("%s: Permission denied.\n"),
123 		    prognamp);
124 		exit(NOPERM);
125 	}
126 
127 	/* No argument can be passed to the command */
128 	if (argc > 1) {
129 		(void) fprintf(stderr,
130 		    gettext("%s: Invalid command syntax.\n"), prognamp);
131 		(void) 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 		(void) 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 	pwd_mode = buf.st_mode;
160 
161 	/* mode for the password file should be read-only or less */
162 	(void) umask(S_IAMB & ~(buf.st_mode & (S_IRUSR|S_IRGRP|S_IROTH)));
163 
164 	/* open temporary password file */
165 	if ((tp_fp = fopen(PASSTEMP, "w")) == NULL) {
166 		(void) f_err();
167 		exit(FMERR);
168 	}
169 
170 	if (chown(PASSTEMP, pwd_uid, pwd_gid) < 0) {
171 		DELPTMP();
172 		(void) f_err();
173 		exit(FMERR);
174 	}
175 	/* default mode mask of the shadow file */
176 	mode = S_IAMB & ~(S_IRUSR);
177 
178 	/* check the existence of  shadow file */
179 	/* if the shadow file exists, get mode mask and group id of the file */
180 	/* if file does not exist, the default group name will be the group  */
181 	/* name of the password file.  */
182 
183 	if (access(SHADOW, F_OK) == 0) {
184 		if (stat(SHADOW, &buf) == 0) {
185 			mode  = S_IAMB & ~(buf.st_mode & S_IRUSR);
186 			sp_gid = buf.st_gid;
187 			sp_uid = buf.st_uid;
188 		} else {
189 			DELPTMP();
190 			(void) f_err();
191 			exit(FMERR);
192 		}
193 	} else {
194 		sp_gid = pwd_gid;
195 		sp_uid = pwd_uid;
196 		file_exist = 0;
197 	}
198 	/*
199 	 * get the mode of shadow password file  -- mode of the file should
200 	 * be read-only for user or less.
201 	 */
202 	(void) umask(mode);
203 
204 	/* open temporary shadow file */
205 	if ((tsp_fp = fopen(SHADTEMP, "w")) == NULL) {
206 		DELPTMP();
207 		(void) f_err();
208 		exit(FMERR);
209 	}
210 
211 	/* change the group of the temporary shadow password file */
212 	if (chown(SHADTEMP, sp_uid, sp_gid) < 0) {
213 		(void) no_convert();
214 		exit(FMERR);
215 	}
216 
217 	/* Reads the password file.				*/
218 	/* If the shadow password file not exists, or		*/
219 	/* if an entry doesn't have a corresponding entry in    */
220 	/* the shadow file, entries/entry will be created.	*/
221 
222 	if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
223 		no_recover();
224 		exit(FATAL);
225 	}
226 
227 	count = 0;
228 	while (!end_of_file) {
229 		count++;
230 		if ((pwdp = fgetpwent(pwf)) != NULL) {
231 			if (!file_exist ||
232 			    (sp = local_getspnam(pwdp->pw_name)) == NULL) {
233 				if (errno == EINVAL) {
234 				/* Bad entry in shadow exit */
235 					DELSHWTMP();
236 					DELPTMP();
237 					(void) f_bdshw();
238 					exit(BADSHW);
239 				}
240 				sp = &sp_pwd;
241 				sp->sp_namp = pwdp->pw_name;
242 				if (!pwdp->pw_passwd ||
243 				    (pwdp->pw_passwd &&
244 				    *pwdp->pw_passwd == '\0')) {
245 					(void) fprintf(stderr, gettext(
246 					    "%s: WARNING user %s has no "
247 					    "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 != 0) {
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
292 				 * 'x', the entry will be written into the
293 				 * shadow file and the character 'x' is
294 				 * re-written as the passwd if !aging,
295 				 * last_changed as above
296 				 */
297 
298 				/*
299 				 * with NIS, only warn about password missing
300 				 * if entry is not a NIS-lookup entry
301 				 * ("+" or "-") black_magic from getpwnam_r.c
302 				 */
303 				black_magic = (*pwdp->pw_name == '+' ||
304 				    *pwdp->pw_name == '-');
305 				/*
306 				 * moan about absence of non "+/-" passwd
307 				 * we could do more, but what?
308 				 */
309 				if ((!pwdp->pw_passwd ||
310 				    (pwdp->pw_passwd &&
311 				    *pwdp->pw_passwd == '\0')) &&
312 				    !black_magic) {
313 					(void) fprintf(stderr, gettext(
314 					    "%s: WARNING user %s has no "
315 					    "password\n"),
316 					    prognamp, sp->sp_namp);
317 				}
318 				if (pwdp->pw_passwd && *pwdp->pw_passwd) {
319 					if (strcmp(pwdp->pw_passwd, pwdflr)) {
320 						sp->sp_pwdp = pwdp->pw_passwd;
321 						pwdp->pw_passwd = pwdflr;
322 						if (!pwdp->pw_age ||
323 						    (pwdp->pw_age &&
324 						    *pwdp->pw_age == 0)) {
325 							sp->sp_lstchg = DAY_NOW;
326 							sp->sp_min =  -1;
327 							sp->sp_max =  -1;
328 							sp->sp_warn = -1;
329 							sp->sp_inact = -1;
330 							sp->sp_expire = -1;
331 							sp->sp_flag = 0;
332 						}
333 					}
334 				} else {
335 					/*
336 					 * black_magic needs a non-null passwd
337 					 * and pwdflr seem appropriate here
338 					 * clear garbage if any
339 					 */
340 					sp->sp_pwdp = "";
341 					pwdp->pw_passwd = pwdflr;
342 					sp->sp_lstchg = sp->sp_min =
343 					    sp->sp_max = -1;
344 					sp->sp_warn = sp->sp_inact =
345 					    sp->sp_expire = -1;
346 					sp->sp_flag = 0;
347 				}
348 				/*
349 				 * if aging, split the aging info
350 				 * into age, max and min
351 				 * convert aging info from weeks to days
352 				 */
353 				if (pwdp->pw_age && *pwdp->pw_age != '\0') {
354 					when = (long)a64l(pwdp->pw_age);
355 					maxweeks = when & 077;
356 					minweeks = (when >> 6) & 077;
357 					when >>= 12;
358 					sp->sp_lstchg = when * 7;
359 					sp->sp_min = minweeks * 7;
360 					sp->sp_max = maxweeks * 7;
361 					sp->sp_warn = -1;
362 					sp->sp_inact = -1;
363 					sp->sp_expire = -1;
364 					sp->sp_flag = 0;
365 					pwdp->pw_age = ""; /* do we care? */
366 				}
367 			}
368 
369 			/* write an entry to temporary password file */
370 			if ((putpwent(pwdp, tp_fp)) != 0) {
371 				(void) no_convert();
372 				exit(FMERR);
373 			}
374 
375 			/* write an entry to temporary shadow password file */
376 			if (putspent(sp, tsp_fp) != 0) {
377 				(void) no_convert();
378 				exit(FMERR);
379 			}
380 		} else {
381 			if (feof(pwf)) {
382 				end_of_file = 1;
383 			} else {
384 				errno = 0;
385 				pwerr = 1;
386 				(void) fprintf(stderr,
387 				    gettext("%s: ERROR: bad entry or blank "
388 				    "line at line %d in /etc/passwd\n"),
389 				    prognamp, count);
390 			}
391 		}
392 	} /* end of while */
393 
394 	(void) fclose(pwf);
395 	(void) fclose(tsp_fp);
396 	(void) fclose(tp_fp);
397 	if (pwerr) {
398 		(void) no_convert();
399 		exit(FMERR);
400 	}
401 
402 	/* delete old password file if it exists */
403 	if (unlink(OPASSWD) && (access(OPASSWD, F_OK) == 0)) {
404 		(void) no_convert();
405 		exit(FMERR);
406 	}
407 
408 	/* rename the password file to old password file  */
409 	if (rename(PASSWD, OPASSWD) == -1) {
410 		(void) no_convert();
411 		exit(FMERR);
412 	}
413 
414 	/* rename temporary password file to password file */
415 	if (rename(PASSTEMP, PASSWD) == -1) {
416 		/* link old password file to password file */
417 		if (link(OPASSWD, PASSWD) < 0) {
418 			(void) no_recover();
419 			exit(FATAL);
420 		}
421 		(void) no_convert();
422 		exit(FMERR);
423 	}
424 
425 	/* delete old shadow password file if it exists */
426 	if (unlink(OSHADOW) && (access(OSHADOW, R_OK) == 0)) {
427 		/* link old password file to password file */
428 		if (unlink(PASSWD) || link(OPASSWD, PASSWD)) {
429 			(void) no_recover();
430 			exit(FATAL);
431 		}
432 		(void) no_convert();
433 		exit(FMERR);
434 	}
435 
436 	/* link shadow password file to old shadow password file */
437 	if (file_exist && rename(SHADOW, OSHADOW)) {
438 		/* link old password file to password file */
439 		if (unlink(PASSWD) || link(OPASSWD, PASSWD)) {
440 			(void) no_recover();
441 			exit(FATAL);
442 		}
443 		(void) no_convert();
444 		exit(FMERR);
445 	}
446 
447 
448 	/* link temporary shadow password file to shadow password file */
449 	if (rename(SHADTEMP, SHADOW) == -1) {
450 		/* link old shadow password file to shadow password file */
451 		if (file_exist && (link(OSHADOW, SHADOW))) {
452 			(void) no_recover();
453 			exit(FATAL);
454 		}
455 		if (unlink(PASSWD) || link(OPASSWD, PASSWD)) {
456 			(void) no_recover();
457 			exit(FATAL);
458 		}
459 		(void) no_convert();
460 		exit(FMERR);
461 	}
462 
463 	/* Make new mode same as old */
464 	(void) chmod(PASSWD, pwd_mode);
465 
466 	/* Change old password file to read only by owner   */
467 	/* If chmod fails, delete the old password file so that */
468 	/* the password fields can not be read by others */
469 	if (chmod(OPASSWD, S_IRUSR) < 0)
470 		(void) unlink(OPASSWD);
471 
472 	(void) ulckpwdf();
473 	return (0);
474 }
475 
476 void
477 no_recover(void)
478 {
479 	DELPTMP();
480 	DELSHWTMP();
481 	(void) f_miss();
482 }
483 
484 void
485 no_convert(void)
486 {
487 	DELPTMP();
488 	DELSHWTMP();
489 	(void) f_err();
490 }
491 
492 void
493 f_err(void)
494 {
495 	(void) fprintf(stderr,
496 	    gettext("%s: Unexpected failure. Conversion not done.\n"),
497 	    prognamp);
498 	(void) ulckpwdf();
499 }
500 
501 void
502 f_miss(void)
503 {
504 	(void) fprintf(stderr,
505 	    gettext("%s: Unexpected failure. Password file(s) missing.\n"),
506 	    prognamp);
507 	(void) ulckpwdf();
508 }
509 
510 void
511 f_bdshw(void)
512 {
513 	(void) fprintf(stderr,
514 	    gettext("%s: Bad entry in /etc/shadow. Conversion not done.\n"),
515 	    prognamp);
516 	(void) ulckpwdf();
517 }
518