Merge commit '00f1a4f432b3d8aad1aa270e91c44c57f03ef407'
[unleashed.git] / usr / src / cmd / passmgmt / passmgmt.c
blobeec76c2bd9d86724c34600b916a09261eef63c3e
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
25 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
26 /* All Rights Reserved */
28 #include <stdio.h>
29 #include <sys/types.h>
30 #include <shadow.h>
31 #include <pwd.h>
32 #include <string.h>
33 #include <signal.h>
34 #include <sys/stat.h>
35 #include <errno.h>
36 #include <time.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <locale.h>
40 #include <fcntl.h>
41 #include <secdb.h>
42 #include <user_attr.h>
43 #include <nss.h>
45 #define CMT_SIZE (128+1) /* Argument sizes + 1 (for '\0') */
46 #define DIR_SIZE (256+1)
47 #define SHL_SIZE (256+1)
48 #define ENTRY_LENGTH 512 /* Max length of an /etc/passwd entry */
49 #define UID_MIN 100 /* Lower bound of default UID */
51 #define M_MASK 01 /* Masks for the optn_mask variable */
52 #define L_MASK 02 /* It keeps track of which options */
53 #define C_MASK 04 /* have been entered */
54 #define H_MASK 010
55 #define U_MASK 020
56 #define G_MASK 040
57 #define S_MASK 0100
58 #define O_MASK 0200
59 #define A_MASK 0400
60 #define D_MASK 01000
61 #define F_MASK 02000
62 #define E_MASK 04000
64 #define UATTR_MASK 010000
66 /* flags for info_mask */
67 #define LOGNAME_EXIST 01 /* logname exists */
68 #define BOTH_FILES 02 /* touch both password files */
69 #define WRITE_P_ENTRY 04 /* write out password entry */
70 #define WRITE_S_ENTRY 010 /* write out shadow entry */
71 #define NEED_DEF_UID 020 /* need default uid */
72 #define FOUND 040 /* found the entry in password file */
73 #define LOCKED 0100 /* did we lock the password file */
74 #define UATTR_FILE 0200 /* touch user_attr file */
75 #define BAD_ENT_MESSAGE "%s: Bad entry found in /etc/passwd.\n"
77 typedef struct kvopts {
78 const char option;
79 const char *key;
80 char *newvalue;
81 } kvopts_t;
83 /* mapping of extensible keywords and options */
84 kvopts_t ua_opts[] = {
85 { 'A', USERATTR_AUTHS_KW },
86 { 'P', USERATTR_PROFILES_KW },
87 { 'R', USERATTR_ROLES_KW },
88 { 'T', USERATTR_TYPE_KW },
89 { '\0', USERATTR_DEFAULTPROJ_KW },
90 { '\0', USERATTR_LIMPRIV_KW },
91 { '\0', USERATTR_DFLTPRIV_KW },
92 { '\0', USERATTR_LOCK_AFTER_RETRIES_KW },
93 { '\0', USERATTR_AUDIT_FLAGS_KW },
96 #define UA_KEYS (sizeof (ua_opts)/sizeof (kvopts_t))
99 char defdir[] = "/home/"; /* default home directory for new user */
100 char pwdflr[] = "x"; /* password string for /etc/passwd */
101 char lkstring[] = "*LK*"; /* lock string for shadow password */
102 char nullstr[] = ""; /* null string */
103 char *msg; /* pointer to error message */
105 #define DATMSK "DATEMSK=/etc/datemsk"
107 #define OUSERATTR_FILENAME "/etc/ouser_attr"
108 #define USERATTR_TEMP "/etc/uatmp"
110 struct uid_blk {
111 struct uid_blk *link;
112 uid_t low; /* low bound for this uid block */
113 uid_t high; /* high bound for this uid block */
116 extern userattr_t *fgetuserattr(FILE *);
117 extern struct passwd *fgetpwent(FILE *);
120 * Declare all functions that do not return integers. This is here
121 * to get rid of some lint messages
124 void uid_bcom(struct uid_blk *), add_ublk(uid_t, struct uid_blk *),
125 bad_perm(void),
126 bad_usage(char *), bad_arg(char *), bad_uid(void), bad_pasf(void),
127 file_error(void), bad_news(void), no_lock(void), add_uid(uid_t),
128 rid_tmpf(void), ck_p_sz(struct passwd *), ck_s_sz(struct spwd *),
129 bad_name(char *), bad_uattr(void);
131 static FILE *fp_ptemp, *fp_stemp, *fp_uatemp;
132 static int fd_ptemp, fd_stemp, fd_uatemp;
135 * The uid_blk structure is used in the search for the default
136 * uid. Each uid_blk represent a range of uid(s) that are currently
137 * used on the system.
141 #ifndef att
143 * getspnan routine that ONLY looks at the local shadow file
145 struct spwd *
146 local_getspnam(char *name)
148 FILE *shadf;
149 struct spwd *sp;
151 if ((shadf = fopen("/etc/shadow", "r")) == NULL)
152 return (NULL);
154 while ((sp = fgetspent(shadf)) != NULL) {
155 if (strcmp(sp->sp_namp, name) == 0)
156 break;
159 fclose(shadf);
161 return (sp);
163 #endif
165 static void
166 putuserattrent(userattr_t *user, FILE *f)
168 int i, j;
169 char *key;
170 char *val;
171 kv_t *kv_pair;
174 * Avoid trivial entries. Those with no attributes or with
175 * only "type=normal". This retains backward compatibility.
177 if (user->attr == NULL)
178 return;
180 kv_pair = user->attr->data;
182 for (i = j = 0; i < user->attr->length; i++) {
183 key = kv_pair[i].key;
184 val = kv_pair[i].value;
185 if ((key == NULL) || (val == NULL))
186 break;
187 if (strlen(val) == 0 ||
188 (strcmp(key, USERATTR_TYPE_KW) == 0 &&
189 strcmp(val, USERATTR_TYPE_NORMAL_KW) == 0))
190 continue;
191 j++;
193 if (j == 0)
194 return;
196 (void) fprintf(f, "%s:%s:%s:%s:", user->name, user->qualifier,
197 user->res1, user->res2);
199 for (i = j = 0; i < user->attr->length; i++) {
200 key = kv_pair[i].key;
201 val = _escape(kv_pair[i].value, KV_SPECIAL);
202 if ((key == NULL) || (val == NULL))
203 break;
204 if (strlen(val) == 0)
205 continue;
206 if (j > 0)
207 (void) fprintf(f, KV_DELIMITER);
208 (void) fprintf(f, "%s=%s", key, val);
209 j++;
211 (void) fprintf(f, "\n");
214 static void
215 assign_attr(userattr_t *user, const char *newkey, char *val)
218 int i;
219 char *key;
220 kv_t *kv_pair;
221 int avail = -1;
223 if (user->attr != NULL) {
224 kv_pair = user->attr->data;
225 for (i = 0; i < user->attr->length; i++) {
226 key = kv_pair[i].key;
227 if (key == NULL) {
228 avail = i;
229 continue;
230 } else if (strcmp(key, newkey) == 0) {
231 kv_pair[i].value = strdup(val);
232 return;
236 if (avail == -1)
237 avail = user->attr->length++;
238 kv_pair[avail].key = strdup(newkey);
239 kv_pair[avail].value = strdup(val);
243 static void
244 unassign_role(userattr_t *user, char *rolelist, char *role)
247 char *roleptr;
248 char *templist;
249 char *temprole;
250 int length;
252 roleptr = rolelist;
253 templist = strdup(roleptr);
254 temprole = strtok(templist, ",");
255 while (temprole) {
256 if (strcmp(temprole, role) == 0) {
258 length = strlen(role);
259 roleptr += temprole - templist;
261 if (*(roleptr + length) == ',')
262 length++;
263 strcpy(roleptr, roleptr + length);
264 length = strlen(roleptr) - 1;
265 if (*(roleptr + length) == ',')
266 *(roleptr + length) = '\0';
267 assign_attr(user, USERATTR_ROLES_KW, rolelist);
268 break;
269 } else {
270 temprole = strtok(NULL, ",");
275 struct uid_blk *uid_sp;
276 char *prognamp; /* program name */
277 extern int errno;
278 int optn_mask = 0, info_mask = 0;
281 main(int argc, char **argv)
283 int c, i;
284 char *lognamp, *char_p;
285 int end_of_file = 0;
286 int error;
287 long date = 0;
288 FILE *pwf, *spf, *uaf;
290 struct passwd *pw_ptr1p, passwd_st;
291 struct spwd *sp_ptr1p, shadow_st;
292 userattr_t *ua_ptr1p, userattr_st;
293 static kv_t ua_kv[KV_ADD_KEYS];
294 kva_t ua_kva;
295 struct stat statbuf;
296 struct tm *tm_ptr;
298 (void) setlocale(LC_ALL, "");
300 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
301 #define TEXT_DOMAIN "SYS_TEST"
302 #endif
303 (void) textdomain(TEXT_DOMAIN);
305 tzset();
306 /* Get program name */
307 prognamp = argv[0];
309 /* Check identity */
310 if (geteuid() != 0)
311 bad_perm();
313 /* Lock the password file(s) */
315 if (lckpwdf() != 0)
316 no_lock();
317 info_mask |= LOCKED; /* remember we locked */
319 /* initialize the two structures */
321 passwd_st.pw_passwd = pwdflr; /* bogus password */
322 passwd_st.pw_name = nullstr; /* login name */
323 passwd_st.pw_uid = -1; /* no uid */
324 passwd_st.pw_gid = 1; /* default gid */
325 passwd_st.pw_age = nullstr; /* no aging info. */
326 passwd_st.pw_comment = nullstr; /* no comments */
327 passwd_st.pw_gecos = nullstr; /* no comments */
328 passwd_st.pw_dir = nullstr; /* no default directory */
329 passwd_st.pw_shell = nullstr; /* no default shell */
331 shadow_st.sp_namp = nullstr; /* no name */
332 shadow_st.sp_pwdp = lkstring; /* locked password */
333 shadow_st.sp_lstchg = -1; /* no lastchanged date */
334 shadow_st.sp_min = -1; /* no min */
335 shadow_st.sp_max = -1; /* no max */
336 shadow_st.sp_warn = -1; /* no warn */
337 shadow_st.sp_inact = -1; /* no inactive */
338 shadow_st.sp_expire = -1; /* no expire */
339 shadow_st.sp_flag = 0; /* no flag */
341 userattr_st.name = nullstr;
342 userattr_st.qualifier = nullstr;
343 userattr_st.res1 = nullstr;
344 userattr_st.res2 = nullstr;
346 ua_kva.length = 1;
347 ua_kv[0].key = USERATTR_TYPE_KW;
348 ua_kv[0].value = USERATTR_TYPE_NORMAL_KW;
349 ua_kva.data = ua_kv;
350 userattr_st.attr = &ua_kva;
352 /* parse the command line */
354 while ((c = getopt(argc, argv,
355 "ml:c:h:u:g:s:f:e:k:A:P:R:T:oadK:")) != -1) {
357 switch (c) {
358 case 'm':
359 /* Modify */
361 if ((A_MASK|D_MASK|M_MASK) & optn_mask)
362 bad_usage("Invalid combination of options");
364 optn_mask |= M_MASK;
365 break;
367 case 'l' :
368 /* Change logname */
370 if ((A_MASK|D_MASK|L_MASK) & optn_mask)
371 bad_usage("Invalid combination of options");
373 if (strpbrk(optarg, ":\n") ||
374 strlen(optarg) == 0)
375 bad_arg("Invalid argument to option -l");
377 optn_mask |= L_MASK;
378 passwd_st.pw_name = optarg;
379 shadow_st.sp_namp = optarg;
380 userattr_st.name = optarg;
381 break;
383 case 'f' :
384 /* set inactive */
386 if ((D_MASK|F_MASK) & optn_mask)
387 bad_usage("Invalid combination of options");
388 if (((shadow_st.sp_inact =
389 strtol(optarg, &char_p, 10)) < (long)0) ||
390 (*char_p != '\0') ||
391 strlen(optarg) == 0)
392 bad_arg("Invalid argument to option -f");
393 if (shadow_st.sp_inact == 0)
394 shadow_st.sp_inact = -1;
395 optn_mask |= F_MASK;
396 break;
398 case 'e' :
399 /* set expire date */
401 if ((D_MASK|E_MASK) & optn_mask)
402 bad_usage("Invalid combination of options");
404 if ((strlen(optarg)) < (size_t)2)
405 shadow_st.sp_expire = -1;
406 else {
407 putenv(DATMSK);
408 if ((tm_ptr = getdate(optarg)) == NULL) {
409 msg = "Invalid argument to option -e";
410 bad_arg(msg);
412 if ((date = mktime(tm_ptr)) < 0) {
413 msg = "Invalid argument to option -e";
414 bad_arg(msg);
416 shadow_st.sp_expire = (date / DAY);
417 if (shadow_st.sp_expire <= DAY_NOW) {
418 msg = "Invalid argument to option -e";
419 bad_arg(msg);
423 optn_mask |= E_MASK;
424 break;
426 case 'c' :
427 /* The comment */
429 if ((D_MASK|C_MASK) & optn_mask)
430 bad_usage("Invalid combination of options");
432 if (strlen(optarg) > (size_t)CMT_SIZE ||
433 strpbrk(optarg, ":\n"))
434 bad_arg("Invalid argument to option -c");
436 optn_mask |= C_MASK;
437 passwd_st.pw_comment = optarg;
438 passwd_st.pw_gecos = optarg;
439 break;
441 case 'h' :
442 /* The home directory */
444 if ((D_MASK|H_MASK) & optn_mask)
445 bad_usage("Invalid combination of options");
447 if (strlen(optarg) > (size_t)DIR_SIZE ||
448 strpbrk(optarg, ":\n"))
449 bad_arg("Invalid argument to option -h");
451 optn_mask |= H_MASK;
452 passwd_st.pw_dir = optarg;
453 break;
455 case 'u' :
456 /* The uid */
458 if ((D_MASK|U_MASK) & optn_mask)
459 bad_usage("Invalid combination of options");
461 optn_mask |= U_MASK;
462 passwd_st.pw_uid = (uid_t)strtol(optarg, &char_p, 10);
463 if ((*char_p != '\0') ||
464 (passwd_st.pw_uid < 0) ||
465 (strlen(optarg) == 0))
466 bad_arg("Invalid argument to option -u");
468 break;
470 case 'g' :
471 /* The gid */
473 if ((D_MASK|G_MASK) & optn_mask)
474 bad_usage("Invalid combination of options");
476 optn_mask |= G_MASK;
477 passwd_st.pw_gid = (gid_t)strtol(optarg, &char_p, 10);
479 if ((*char_p != '\0') || (passwd_st.pw_gid < 0) ||
480 (strlen(optarg) == 0))
481 bad_arg("Invalid argument to option -g");
482 break;
484 case 's' :
485 /* The shell */
487 if ((D_MASK|S_MASK) & optn_mask)
488 bad_usage("Invalid combination of options");
490 if (strlen(optarg) > (size_t)SHL_SIZE ||
491 strpbrk(optarg, ":\n"))
492 bad_arg("Invalid argument to option -s");
494 optn_mask |= S_MASK;
495 passwd_st.pw_shell = optarg;
496 break;
498 case 'o' :
499 /* Override unique uid */
501 if ((D_MASK|O_MASK) & optn_mask)
502 bad_usage("Invalid combination of options");
504 optn_mask |= O_MASK;
505 break;
507 case 'a' :
508 /* Add */
510 if ((A_MASK|M_MASK|D_MASK|L_MASK) & optn_mask)
511 bad_usage("Invalid combination of options");
513 optn_mask |= A_MASK;
514 break;
516 case 'd' :
517 /* Delete */
519 if ((D_MASK|M_MASK|L_MASK|C_MASK|
520 H_MASK|U_MASK|G_MASK|S_MASK|
521 O_MASK|A_MASK) & optn_mask)
522 bad_usage("Invalid combination of options");
524 optn_mask |= D_MASK;
525 break;
527 case 'K':
528 if (D_MASK & optn_mask)
529 bad_usage("Invalid combination of options");
531 char_p = strchr(optarg, '=');
532 if (char_p == NULL)
533 bad_usage("Missing value in -K option");
535 *char_p++ = '\0';
537 for (i = 0; i < UA_KEYS; i++) {
538 if (strcmp(optarg, ua_opts[i].key) == 0) {
539 ua_opts[i].newvalue =
540 _escape(char_p, KV_SPECIAL);
541 assign_attr(&userattr_st, optarg,
542 char_p);
543 break;
546 if (i == UA_KEYS)
547 bad_usage("bad key");
548 optn_mask |= UATTR_MASK;
549 break;
551 case '?' :
553 bad_usage("");
554 break;
556 default :
557 /* Extended User Attributes */
559 int j;
561 for (j = 0; j < UA_KEYS; j++) {
562 if (ua_opts[j].option == (char)c) {
563 if ((D_MASK) & optn_mask)
564 bad_usage("Invalid "
565 "combination of "
566 " options");
567 optn_mask |= UATTR_MASK;
568 assign_attr(&userattr_st,
569 ua_opts[j].key,
570 _escape(optarg,
571 KV_SPECIAL));
572 ua_opts[j].newvalue =
573 _escape(optarg, KV_SPECIAL);
574 break;
577 break;
582 /* check command syntax for the following errors */
583 /* too few or too many arguments */
584 /* no -a -m or -d option */
585 /* -o without -u */
586 /* -m with no other option */
588 if (optind == argc || argc > (optind+1) ||
589 !((A_MASK|M_MASK|D_MASK) & optn_mask) ||
590 ((optn_mask & O_MASK) && !(optn_mask & U_MASK)) ||
591 ((optn_mask & M_MASK) &&
592 !(optn_mask &
593 (L_MASK|C_MASK|H_MASK|U_MASK|G_MASK|S_MASK|F_MASK|
594 E_MASK|UATTR_MASK))))
595 bad_usage("Invalid command syntax");
597 /* null string argument or bad characters ? */
598 if ((strlen(argv[optind]) == 0) || strpbrk(argv[optind], ":\n"))
599 bad_arg("Invalid name");
601 lognamp = argv [optind];
604 * if we are adding a new user or modifying an existing user
605 * (not the logname), then copy logname into the two data
606 * structures
609 if ((A_MASK & optn_mask) ||
610 ((M_MASK & optn_mask) && !(optn_mask & L_MASK))) {
611 passwd_st.pw_name = argv [optind];
612 shadow_st.sp_namp = argv [optind];
613 userattr_st.name = argv [optind];
616 /* Put in directory if we are adding and we need a default */
618 if (!(optn_mask & H_MASK) && (optn_mask & A_MASK)) {
619 if ((passwd_st.pw_dir = malloc((size_t)DIR_SIZE)) == NULL)
620 file_error();
622 *passwd_st.pw_dir = '\0';
623 (void) strcat(passwd_st.pw_dir, defdir);
624 (void) strcat(passwd_st.pw_dir, lognamp);
627 /* Check the number of password files we are touching */
629 if ((!((M_MASK & optn_mask) && !(L_MASK & optn_mask))) ||
630 ((M_MASK & optn_mask) && ((E_MASK & optn_mask) ||
631 (F_MASK & optn_mask))))
632 info_mask |= BOTH_FILES;
634 if ((D_MASK|L_MASK|UATTR_MASK) & optn_mask)
635 info_mask |= UATTR_FILE;
637 /* Open the temporary file(s) with appropriate permission mask */
638 /* and the appropriate owner */
640 if (stat(PASSWD, &statbuf) < 0)
641 file_error();
643 fd_ptemp = open(PASSTEMP, O_CREAT|O_EXCL|O_WRONLY, statbuf.st_mode);
644 if (fd_ptemp == -1) {
645 if (errno == EEXIST) {
646 if (unlink(PASSTEMP)) {
647 msg = "%s: warning: cannot unlink %s\n";
648 (void) fprintf(stderr, gettext(msg), prognamp,
649 PASSTEMP);
651 fd_ptemp = open(PASSTEMP, O_CREAT|O_EXCL|O_WRONLY,
652 statbuf.st_mode);
653 if (fd_ptemp == -1) {
654 file_error();
657 } else
658 file_error();
660 fp_ptemp = fdopen(fd_ptemp, "w");
661 if (fp_ptemp == NULL)
662 file_error();
663 error = fchown(fd_ptemp, statbuf.st_uid, statbuf.st_gid);
664 if (error == 0)
665 error = fchmod(fd_ptemp, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
666 if (error != 0) {
667 (void) fclose(fp_ptemp);
668 if (unlink(PASSTEMP)) {
669 msg = "%s: warning: cannot unlink %s\n";
670 (void) fprintf(stderr, gettext(msg), prognamp,
671 PASSTEMP);
673 file_error();
676 if (info_mask & BOTH_FILES) {
677 if (stat(SHADOW, &statbuf) < 0) {
678 rid_tmpf();
679 file_error();
681 fd_stemp = open(SHADTEMP, O_CREAT|O_EXCL|O_WRONLY,
682 statbuf.st_mode);
683 if (fd_stemp == -1) {
684 if (errno == EEXIST) {
685 if (unlink(SHADTEMP)) {
686 msg = "%s: warning: cannot unlink %s\n";
687 (void) fprintf(stderr, gettext(msg),
688 prognamp, SHADTEMP);
690 fd_stemp = open(SHADTEMP,
691 O_CREAT|O_EXCL|O_WRONLY, statbuf.st_mode);
692 if (fd_stemp == -1) {
693 rid_tmpf();
694 file_error();
697 } else {
698 rid_tmpf();
699 file_error();
702 fp_stemp = fdopen(fd_stemp, "w");
703 if (fp_stemp == NULL) {
704 rid_tmpf();
705 file_error();
707 error = fchown(fd_stemp, statbuf.st_uid, statbuf.st_gid);
708 if (error == 0)
709 error = fchmod(fd_stemp, S_IRUSR);
710 if (error != 0) {
711 rid_tmpf();
712 file_error();
716 if (info_mask & UATTR_FILE) {
717 if (stat(USERATTR_FILENAME, &statbuf) < 0) {
718 rid_tmpf();
719 file_error();
721 fd_uatemp = open(USERATTR_TEMP, O_CREAT|O_EXCL|O_WRONLY,
722 statbuf.st_mode);
723 if (fd_uatemp == -1) {
724 if (errno == EEXIST) {
725 if (unlink(USERATTR_TEMP)) {
726 msg = "%s: warning: cannot unlink %s\n";
727 (void) fprintf(stderr, gettext(msg),
728 prognamp, USERATTR_TEMP);
730 fd_uatemp = open(USERATTR_TEMP,
731 O_CREAT|O_EXCL|O_WRONLY, statbuf.st_mode);
732 if (fd_uatemp == -1) {
733 rid_tmpf();
734 file_error();
737 } else {
738 rid_tmpf();
739 file_error();
742 fp_uatemp = fdopen(fd_uatemp, "w");
743 if (fp_uatemp == NULL) {
744 rid_tmpf();
745 file_error();
747 error = fchown(fd_uatemp, statbuf.st_uid, statbuf.st_gid);
748 if (error == 0)
749 error = fchmod(fd_uatemp,
750 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
751 if (error != 0) {
752 rid_tmpf();
753 file_error();
756 /* Default uid needed ? */
758 if (!(optn_mask & U_MASK) && (optn_mask & A_MASK)) {
759 /* mark it in the information mask */
760 info_mask |= NEED_DEF_UID;
762 /* create the head of the uid number list */
763 uid_sp = malloc(sizeof (struct uid_blk));
764 if (uid_sp == NULL) {
765 rid_tmpf();
766 file_error();
769 uid_sp->link = NULL;
770 uid_sp->low = (UID_MIN -1);
771 uid_sp->high = (UID_MIN -1);
774 error = 0;
776 if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
777 rid_tmpf();
778 if (errno == ENOENT)
779 bad_news();
780 else
781 file_error();
784 /* The while loop for reading PASSWD entries */
785 info_mask |= WRITE_P_ENTRY;
787 while (!end_of_file) {
788 pw_ptr1p = fgetpwent(pwf);
789 if (pw_ptr1p == NULL) {
790 if (!feof(pwf)) {
791 /* A real error - report it and exit */
792 rid_tmpf();
793 bad_pasf();
795 else
796 break;
799 info_mask |= WRITE_P_ENTRY;
802 * Set up the uid usage blocks to find the first
803 * available uid above UID_MIN, if needed
806 if (info_mask & NEED_DEF_UID)
807 add_uid(pw_ptr1p->pw_uid);
809 /* Check for unique UID */
811 if (strcmp(lognamp, pw_ptr1p->pw_name) &&
812 (pw_ptr1p->pw_uid == passwd_st.pw_uid) &&
813 ((optn_mask & U_MASK) && !(optn_mask & O_MASK))) {
814 rid_tmpf(); /* get rid of temp files */
815 bad_uid();
818 /* Check for unique new logname */
820 if (strcmp(lognamp, pw_ptr1p->pw_name) == 0 &&
821 optn_mask & L_MASK &&
822 strcmp(pw_ptr1p->pw_name, passwd_st.pw_name) == 0) {
823 rid_tmpf();
824 #ifdef att
825 if (!getspnam(pw_ptr1p->pw_name))
826 #else
827 if (!local_getspnam(pw_ptr1p->pw_name))
828 #endif
829 bad_pasf();
830 else
831 bad_name("logname already exists");
834 if (strcmp(lognamp, pw_ptr1p->pw_name) == 0) {
836 /* no good if we want to add an existing logname */
837 if (optn_mask & A_MASK) {
838 rid_tmpf();
839 #ifdef att
840 if (!getspnam(lognamp))
841 #else
842 if (!local_getspnam(lognamp))
843 #endif
844 bad_pasf();
845 else
846 bad_name("name already exists");
849 /* remember we found it */
850 info_mask |= FOUND;
852 /* Do not write it out on the fly */
853 if (optn_mask & D_MASK)
854 info_mask &= ~WRITE_P_ENTRY;
856 if (optn_mask & M_MASK) {
858 #ifdef att
859 if (!getspnam(lognamp))
860 #else
861 if (!local_getspnam(lognamp))
862 #endif
864 rid_tmpf();
865 bad_pasf();
867 if (optn_mask & L_MASK)
868 pw_ptr1p->pw_name = passwd_st.pw_name;
870 if (optn_mask & U_MASK)
871 pw_ptr1p->pw_uid = passwd_st.pw_uid;
873 if (optn_mask & G_MASK)
874 pw_ptr1p->pw_gid = passwd_st.pw_gid;
876 if (optn_mask & C_MASK) {
877 pw_ptr1p->pw_comment =
878 passwd_st.pw_comment;
880 pw_ptr1p->pw_gecos =
881 passwd_st.pw_comment;
884 if (optn_mask & H_MASK)
885 pw_ptr1p->pw_dir = passwd_st.pw_dir;
887 if (optn_mask & S_MASK)
888 pw_ptr1p->pw_shell = passwd_st.pw_shell;
889 ck_p_sz(pw_ptr1p); /* check entry size */
893 if (info_mask & WRITE_P_ENTRY) {
894 if (putpwent(pw_ptr1p, fp_ptemp)) {
895 rid_tmpf();
896 file_error();
899 } /* end-of-while-loop */
901 if (error >= 1) {
902 msg = BAD_ENT_MESSAGE;
903 fprintf(stderr, gettext(msg), prognamp);
906 /* Cannot find the target entry and we are deleting or modifying */
908 if (!(info_mask & FOUND) && (optn_mask & (D_MASK|M_MASK))) {
909 rid_tmpf();
910 #ifdef att
911 if (getspnam(lognamp) != NULL)
912 #else
913 if (local_getspnam(lognamp) != NULL)
914 #endif
915 bad_pasf();
916 else
917 bad_name("name does not exist");
920 /* First available uid above UID_MIN is ... */
922 if (info_mask & NEED_DEF_UID)
923 passwd_st.pw_uid = uid_sp->high + 1;
925 /* Write out the added entry now */
927 if (optn_mask & A_MASK) {
928 ck_p_sz(&passwd_st); /* Check entry size */
929 if (putpwent(&passwd_st, fp_ptemp)) {
930 rid_tmpf();
931 file_error();
935 (void) fclose(pwf);
937 /* flush and sync the file before closing it */
938 if (fflush(fp_ptemp) != 0 || fsync(fd_ptemp) != 0)
939 file_error();
941 /* Now we are done with PASSWD */
942 (void) fclose(fp_ptemp);
944 /* Do this if we are touching both password files */
947 if (info_mask & BOTH_FILES) {
948 info_mask &= ~FOUND; /* Reset FOUND flag */
950 /* The while loop for reading SHADOW entries */
951 info_mask |= WRITE_S_ENTRY;
953 end_of_file = 0;
954 errno = 0;
955 error = 0;
957 if ((spf = fopen("/etc/shadow", "r")) == NULL) {
958 rid_tmpf();
959 file_error();
962 while (!end_of_file) {
963 sp_ptr1p = fgetspent(spf);
964 if (sp_ptr1p == NULL) {
965 if (!feof(spf)) {
966 rid_tmpf();
967 bad_pasf();
969 else
970 break;
973 info_mask |= WRITE_S_ENTRY;
976 * See if the new logname already exist in the
977 * shadow passwd file
979 if ((optn_mask & M_MASK) &&
980 strcmp(lognamp, shadow_st.sp_namp) != 0 &&
981 strcmp(sp_ptr1p->sp_namp, shadow_st.sp_namp) == 0) {
982 rid_tmpf();
983 bad_pasf();
986 if (strcmp(lognamp, sp_ptr1p->sp_namp) == 0) {
987 info_mask |= FOUND;
988 if (optn_mask & A_MASK) {
989 /* password file inconsistent */
990 rid_tmpf();
991 bad_pasf();
994 if (optn_mask & M_MASK) {
995 sp_ptr1p->sp_namp = shadow_st.sp_namp;
996 if (F_MASK & optn_mask)
997 sp_ptr1p->sp_inact =
998 shadow_st.sp_inact;
999 if (E_MASK & optn_mask)
1000 sp_ptr1p->sp_expire =
1001 shadow_st.sp_expire;
1003 ck_s_sz(sp_ptr1p);
1006 if (optn_mask & D_MASK)
1007 info_mask &= ~WRITE_S_ENTRY;
1010 if (info_mask & WRITE_S_ENTRY) {
1011 if (putspent(sp_ptr1p, fp_stemp)) {
1012 rid_tmpf();
1013 file_error();
1017 } /* end-of-while-loop */
1019 if (error >= 1) {
1021 msg = BAD_ENT_MESSAGE;
1022 fprintf(stderr, gettext(msg), prognamp);
1026 * If we cannot find the entry and we are deleting or
1027 * modifying
1030 if (!(info_mask & FOUND) && (optn_mask & (D_MASK|M_MASK))) {
1031 rid_tmpf();
1032 bad_pasf();
1035 if (optn_mask & A_MASK) {
1036 ck_s_sz(&shadow_st);
1037 if (putspent(&shadow_st, fp_stemp)) {
1038 rid_tmpf();
1039 file_error();
1043 /* flush and sync the file before closing it */
1044 if (fflush(fp_stemp) != 0 || fsync(fd_stemp) != 0)
1045 file_error();
1046 (void) fclose(fp_stemp);
1048 /* Done with SHADOW */
1049 (void) fclose(spf);
1051 } /* End of if info_mask */
1053 if (info_mask & UATTR_FILE) {
1054 info_mask &= ~FOUND; /* Reset FOUND flag */
1056 /* The while loop for reading USER_ATTR entries */
1057 info_mask |= WRITE_S_ENTRY;
1059 end_of_file = 0;
1060 errno = 0;
1061 error = 0;
1063 if ((uaf = fopen(USERATTR_FILENAME, "r")) == NULL) {
1064 rid_tmpf();
1065 file_error();
1068 while (!end_of_file) {
1069 ua_ptr1p = fgetuserattr(uaf);
1070 if (ua_ptr1p == NULL) {
1071 if (!feof(uaf)) {
1072 rid_tmpf();
1073 bad_uattr();
1075 else
1076 break;
1079 if (ua_ptr1p->name[0] == '#') {
1081 * If this is a comment, write it back as it
1082 * is.
1084 if (ua_ptr1p->qualifier[0] == '\0' &&
1085 ua_ptr1p->res1[0] == '\0' &&
1086 ua_ptr1p->res2[0] == '\0' &&
1087 (ua_ptr1p->attr == NULL ||
1088 ua_ptr1p->attr->length == 0))
1089 (void) fprintf(fp_uatemp, "%s\n",
1090 ua_ptr1p->name);
1091 else
1093 * This is a commented user_attr entry;
1094 * reformat it, and write it back.
1096 putuserattrent(ua_ptr1p, fp_uatemp);
1097 free_userattr(ua_ptr1p);
1098 continue;
1101 info_mask |= WRITE_S_ENTRY;
1104 * See if the new logname already exist in the
1105 * user_attr file
1107 if ((optn_mask & M_MASK) &&
1108 strcmp(lognamp, userattr_st.name) != 0 &&
1109 strcmp(ua_ptr1p->name, userattr_st.name) == 0) {
1110 rid_tmpf();
1111 bad_pasf();
1114 if (strcmp(lognamp, ua_ptr1p->name) == 0) {
1115 info_mask |= FOUND;
1116 if (optn_mask & A_MASK) {
1117 /* password file inconsistent */
1118 rid_tmpf();
1119 bad_pasf();
1122 if (optn_mask & M_MASK) {
1123 int j;
1124 char *value;
1126 for (j = 0; j < UA_KEYS; j++) {
1127 if (ua_opts[j].newvalue != NULL)
1128 continue;
1129 value =
1130 kva_match(ua_ptr1p->attr,
1131 (char *)ua_opts[j].key);
1132 if (value == NULL)
1133 continue;
1134 assign_attr(&userattr_st,
1135 ua_opts[j].key,
1136 value);
1138 free_userattr(ua_ptr1p);
1139 ua_ptr1p = &userattr_st;
1142 if (optn_mask & D_MASK)
1143 info_mask &= ~WRITE_S_ENTRY;
1144 } else if (optn_mask & D_MASK) {
1145 char *rolelist;
1147 rolelist = kva_match(ua_ptr1p->attr,
1148 USERATTR_ROLES_KW);
1149 if (rolelist) {
1150 unassign_role(ua_ptr1p,
1151 rolelist, lognamp);
1155 if (info_mask & WRITE_S_ENTRY) {
1156 putuserattrent(ua_ptr1p, fp_uatemp);
1159 if (!(optn_mask & M_MASK))
1160 free_userattr(ua_ptr1p);
1161 } /* end-of-while-loop */
1163 if (error >= 1) {
1165 msg = BAD_ENT_MESSAGE;
1166 fprintf(stderr, gettext(msg), prognamp);
1170 * Add entry in user_attr if masks is UATTR_MASK
1171 * We don't need to do anything for L_MASK if there's
1172 * no user_attr entry for the user being modified.
1174 if (!(info_mask & FOUND) && !(L_MASK & optn_mask) &&
1175 !(D_MASK & optn_mask)) {
1176 putuserattrent(&userattr_st, fp_uatemp);
1179 /* flush and sync the file before closing it */
1180 if (fflush(fp_uatemp) != 0 || fsync(fd_uatemp) != 0)
1181 file_error();
1182 (void) fclose(fp_uatemp);
1184 /* Done with USERATTR */
1185 (void) fclose(uaf);
1187 } /* End of if info_mask */
1188 /* ignore all signals */
1190 for (i = 1; i < NSIG; i++)
1191 (void) sigset(i, SIG_IGN);
1193 errno = 0; /* For correcting sigset to SIGKILL */
1195 if (unlink(OPASSWD) && access(OPASSWD, 0) == 0)
1196 file_error();
1198 if (link(PASSWD, OPASSWD) == -1)
1199 file_error();
1202 if (rename(PASSTEMP, PASSWD) == -1) {
1203 if (link(OPASSWD, PASSWD))
1204 bad_news();
1205 file_error();
1209 if (info_mask & BOTH_FILES) {
1211 if (unlink(OSHADOW) && access(OSHADOW, 0) == 0) {
1212 if (rec_pwd())
1213 bad_news();
1214 else
1215 file_error();
1218 if (link(SHADOW, OSHADOW) == -1) {
1219 if (rec_pwd())
1220 bad_news();
1221 else
1222 file_error();
1226 if (rename(SHADTEMP, SHADOW) == -1) {
1227 if (rename(OSHADOW, SHADOW) == -1)
1228 bad_news();
1230 if (rec_pwd())
1231 bad_news();
1232 else
1233 file_error();
1237 if (info_mask & UATTR_FILE) {
1238 if (unlink(OUSERATTR_FILENAME) &&
1239 access(OUSERATTR_FILENAME, 0) == 0) {
1240 if (rec_pwd())
1241 bad_news();
1242 else
1243 file_error();
1246 if (link(USERATTR_FILENAME, OUSERATTR_FILENAME) == -1) {
1247 if (rec_pwd())
1248 bad_news();
1249 else
1250 file_error();
1254 if (rename(USERATTR_TEMP, USERATTR_FILENAME) == -1) {
1255 if (rename(OUSERATTR_FILENAME, USERATTR_FILENAME) == -1)
1256 bad_news();
1258 if (rec_pwd())
1259 bad_news();
1260 else
1261 file_error();
1266 ulckpwdf();
1269 * Return 0 status, indicating success
1271 return (0);
1273 } /* end of main */
1275 /* Try to recover the old password file */
1278 rec_pwd(void)
1280 if (unlink(PASSWD) || link(OPASSWD, PASSWD))
1281 return (-1);
1283 return (0);
1286 /* combine two uid_blk's */
1288 void
1289 uid_bcom(struct uid_blk *uid_p)
1291 struct uid_blk *uid_tp;
1293 uid_tp = uid_p->link;
1294 uid_p->high = uid_tp->high;
1295 uid_p->link = uid_tp->link;
1297 free(uid_tp);
1300 /* add a new uid_blk */
1302 void
1303 add_ublk(uid_t num, struct uid_blk *uid_p)
1305 struct uid_blk *uid_tp;
1307 uid_tp = malloc(sizeof (struct uid_blk));
1308 if (uid_tp == NULL) {
1309 rid_tmpf();
1310 file_error();
1313 uid_tp->high = uid_tp->low = num;
1314 uid_tp->link = uid_p->link;
1315 uid_p->link = uid_tp;
1319 * Here we are using a linked list of uid_blk to keep track of all
1320 * the used uids. Each uid_blk represents a range of used uid,
1321 * with low represents the low inclusive end and high represents
1322 * the high inclusive end. In the beginning, we initialize a linked
1323 * list of one uid_blk with low = high = (UID_MIN-1). This was
1324 * done in main().
1325 * Each time we read in another used uid, we add it onto the linked
1326 * list by either making a new uid_blk, decrementing the low of
1327 * an existing uid_blk, incrementing the high of an existing
1328 * uid_blk, or combining two existing uid_blks. After we finished
1329 * building this linked list, the first available uid above or
1330 * equal to UID_MIN is the high of the first uid_blk in the linked
1331 * list + 1.
1333 /* add_uid() adds uid to the link list of used uids */
1334 void
1335 add_uid(uid_t uid)
1337 struct uid_blk *uid_p;
1338 /* Only keep track of the ones above UID_MIN */
1340 if (uid >= UID_MIN) {
1341 uid_p = uid_sp;
1343 while (uid_p != NULL) {
1345 if (uid_p->link != NULL) {
1347 if (uid >= uid_p->link->low)
1348 uid_p = uid_p->link;
1350 else if (uid >= uid_p->low &&
1351 uid <= uid_p->high) {
1352 uid_p = NULL;
1355 else if (uid == (uid_p->high+1)) {
1357 if (++uid_p->high ==
1358 (uid_p->link->low - 1)) {
1359 uid_bcom(uid_p);
1361 uid_p = NULL;
1364 else if (uid == (uid_p->link->low - 1)) {
1365 uid_p->link->low --;
1366 uid_p = NULL;
1369 else if (uid < uid_p->link->low) {
1370 add_ublk(uid, uid_p);
1371 uid_p = NULL;
1373 } /* if uid_p->link */
1375 else {
1377 if (uid == (uid_p->high + 1)) {
1378 uid_p->high++;
1379 uid_p = NULL;
1380 } else if (uid >= uid_p->low &&
1381 uid <= uid_p->high) {
1382 uid_p = NULL;
1383 } else {
1384 add_ublk(uid, uid_p);
1385 uid_p = NULL;
1387 } /* else */
1388 } /* while uid_p */
1390 } /* if uid */
1393 void
1394 bad_perm(void)
1396 (void) fprintf(stderr, gettext("%s: Permission denied\n"), prognamp);
1397 exit(1);
1400 void
1401 bad_usage(char *sp)
1403 if (strlen(sp) != 0)
1404 (void) fprintf(stderr, "%s: %s\n", prognamp, gettext(sp));
1405 (void) fprintf(stderr, gettext("Usage:\n\
1406 %s -a [-c comment] [-h homedir] [-u uid [-o]] [-g gid] \n\
1407 [-s shell] [-f inactive] [-e expire] name\n\
1408 %s -m -c comment | -h homedir | -u uid [-o] | -g gid |\n\
1409 -s shell | -f inactive | -e expire | -l logname name\n\
1410 %s -d name\n"), prognamp, prognamp, prognamp);
1411 if (info_mask & LOCKED)
1412 ulckpwdf();
1413 exit(2);
1416 void
1417 bad_arg(char *s)
1419 (void) fprintf(stderr, "%s: %s\n", prognamp, gettext(s));
1421 if (info_mask & LOCKED)
1422 ulckpwdf();
1423 exit(3);
1426 void
1427 bad_name(char *s)
1429 (void) fprintf(stderr, "%s: %s\n", prognamp, gettext(s));
1430 ulckpwdf();
1431 exit(9);
1434 void
1435 bad_uid(void)
1437 (void) fprintf(stderr, gettext("%s: UID in use\n"), prognamp);
1439 ulckpwdf();
1440 exit(4);
1443 void
1444 bad_pasf(void)
1446 msg = "%s: Inconsistent password files\n";
1447 (void) fprintf(stderr, gettext(msg), prognamp);
1449 ulckpwdf();
1450 exit(5);
1453 void
1454 bad_uattr(void)
1456 msg = "%s: Bad user_attr database\n";
1457 (void) fprintf(stderr, gettext(msg), prognamp);
1459 ulckpwdf();
1460 exit(5);
1463 void
1464 file_error(void)
1466 msg = "%s: Unexpected failure. Password files unchanged\n";
1467 (void) fprintf(stderr, gettext(msg), prognamp);
1469 ulckpwdf();
1470 exit(6);
1473 void
1474 bad_news(void)
1476 msg = "%s: Unexpected failure. Password file(s) missing\n";
1477 (void) fprintf(stderr, gettext(msg), prognamp);
1479 ulckpwdf();
1480 exit(7);
1483 void
1484 no_lock(void)
1486 msg = "%s: Password file(s) busy. Try again later\n";
1487 (void) fprintf(stderr, gettext(msg), prognamp);
1489 exit(8);
1492 /* Check for the size of the whole passwd entry */
1493 void
1494 ck_p_sz(struct passwd *pwp)
1496 char ctp[128];
1498 /* Ensure that the combined length of the individual */
1499 /* fields will fit in a passwd entry. The 1 accounts for the */
1500 /* newline and the 6 accounts for the colons (:'s) */
1501 if (((int)strlen(pwp->pw_name) + 1 +
1502 sprintf(ctp, "%d", pwp->pw_uid) +
1503 sprintf(ctp, "%d", pwp->pw_gid) +
1504 (int)strlen(pwp->pw_comment) +
1505 (int)strlen(pwp->pw_dir) +
1506 (int)strlen(pwp->pw_shell) + 6) > (ENTRY_LENGTH-1)) {
1507 rid_tmpf();
1508 bad_arg("New password entry too long");
1512 /* Check for the size of the whole passwd entry */
1513 void
1514 ck_s_sz(struct spwd *ssp)
1516 char ctp[128];
1518 /* Ensure that the combined length of the individual */
1519 /* fields will fit in a shadow entry. The 1 accounts for the */
1520 /* newline and the 7 accounts for the colons (:'s) */
1521 if (((int)strlen(ssp->sp_namp) + 1 +
1522 (int)strlen(ssp->sp_pwdp) +
1523 sprintf(ctp, "%d", ssp->sp_lstchg) +
1524 sprintf(ctp, "%d", ssp->sp_min) +
1525 sprintf(ctp, "%d", ssp->sp_max) +
1526 sprintf(ctp, "%d", ssp->sp_warn) +
1527 sprintf(ctp, "%d", ssp->sp_inact) +
1528 sprintf(ctp, "%d", ssp->sp_expire) + 7) > (ENTRY_LENGTH - 1)) {
1529 rid_tmpf();
1530 bad_arg("New password entry too long");
1534 /* Get rid of the temp files */
1535 void
1536 rid_tmpf(void)
1538 (void) fclose(fp_ptemp);
1540 if (unlink(PASSTEMP)) {
1541 msg = "%s: warning: cannot unlink %s\n";
1542 (void) fprintf(stderr, gettext(msg), prognamp, PASSTEMP);
1545 if (info_mask & BOTH_FILES) {
1546 (void) fclose(fp_stemp);
1548 if (unlink(SHADTEMP)) {
1549 msg = "%s: warning: cannot unlink %s\n";
1550 (void) fprintf(stderr, gettext(msg), prognamp,
1551 SHADTEMP);
1555 if (info_mask & UATTR_FILE) {
1556 (void) fclose(fp_uatemp);
1558 if (unlink(USERATTR_TEMP)) {
1559 msg = "%s: warning: cannot unlink %s\n";
1560 (void) fprintf(stderr, gettext(msg), prognamp,
1561 USERATTR_TEMP);