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