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 *
local_getspnam(char * name)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
main(int argc,char ** argv)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
no_recover(void)477 no_recover(void)
478 {
479 DELPTMP();
480 DELSHWTMP();
481 (void) f_miss();
482 }
483
484 void
no_convert(void)485 no_convert(void)
486 {
487 DELPTMP();
488 DELSHWTMP();
489 (void) f_err();
490 }
491
492 void
f_err(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
f_miss(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
f_bdshw(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