1 /* $NetBSD: user.c,v 1.131 2012/11/28 11:31:27 blymn Exp $ */
4 * Copyright (c) 1999 Alistair G. Crooks. All rights reserved.
5 * Copyright (c) 2005 Liam J. Foy. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote
16 * products derived from this software without specific prior written
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
20 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include <sys/cdefs.h>
34 __COPYRIGHT("@(#) Copyright (c) 1999\
35 The NetBSD Foundation, Inc. All rights reserved.");
36 __RCSID("$NetBSD: user.c,v 1.131 2012/11/28 11:31:27 blymn Exp $");
39 #include <sys/types.h>
40 #include <sys/param.h>
50 #include <login_cap.h>
64 #include "pathnames.h"
69 /* this struct describes a uid range */
70 typedef struct range_t
{
71 int r_from
; /* low uid */
72 int r_to
; /* high uid */
75 typedef struct rangelist_t
{
76 unsigned rl_rsize
; /* size of range array */
77 unsigned rl_rc
; /* # of ranges */
78 range_t
*rl_rv
; /* the ranges */
79 unsigned rl_defrc
; /* # of ranges in defaults */
82 /* this struct encapsulates the user and group information */
83 typedef struct user_t
{
84 int u_flags
; /* see below */
85 int u_uid
; /* uid of user */
86 char *u_password
; /* encrypted password */
87 char *u_comment
; /* comment field */
88 char *u_home
; /* home directory */
89 mode_t u_homeperm
; /* permissions of home dir */
90 char *u_primgrp
; /* primary group */
91 int u_groupc
; /* # of secondary groups */
92 const char *u_groupv
[NGROUPS_MAX
]; /* secondary groups */
93 char *u_shell
; /* user's shell */
94 char *u_basedir
; /* base directory for home */
95 char *u_expire
; /* when password will expire */
96 char *u_inactive
; /* when account will expire */
97 char *u_skeldir
; /* directory for startup files */
98 char *u_class
; /* login class */
99 rangelist_t u_r
; /* list of ranges */
100 unsigned u_defrc
; /* # of ranges in defaults */
101 int u_preserve
; /* preserve uids on deletion */
102 int u_allow_samba
; /* allow trailing '$' for samba login names */
103 int u_locked
; /* user account lock */
105 #define u_rsize u_r.rl_rsize
106 #define u_rc u_r.rl_rc
107 #define u_rv u_r.rl_rv
108 #define u_defrc u_r.rl_defrc
110 /* this struct encapsulates the user and group information */
111 typedef struct group_t
{
112 rangelist_t g_r
; /* list of ranges */
114 #define g_rsize g_r.rl_rsize
115 #define g_rc g_r.rl_rc
116 #define g_rv g_r.rl_rv
117 #define g_defrc g_r.rl_defrc
119 typedef struct def_t
{
124 /* flags for which fields of the user_t replace the passwd entry */
143 #define LOCKED "*LOCKED*"
146 #define DEF_GROUP "users"
150 #define DEF_BASEDIR "/home"
154 #define DEF_SKELDIR "/etc/skel"
158 #define DEF_SHELL _PATH_BSHELL
162 #define DEF_COMMENT ""
166 #define DEF_LOWUID 1000
170 #define DEF_HIGHUID 60000
174 #define DEF_INACTIVE 0
178 #define DEF_EXPIRE NULL
190 #define NOBODY_UID 32767
194 #define DEF_HOMEPERM 0755
197 /* some useful constants */
199 MaxShellNameLen
= 256,
200 MaxFileNameLen
= MAXPATHLEN
,
201 MaxUserNameLen
= LOGIN_NAME_MAX
- 1,
202 MaxCommandLen
= 2048,
204 PasswordLength
= 2048,
209 #define UNSET_INACTIVE "Null (unset)"
210 #define UNSET_EXPIRY "Null (unset)"
212 static int asystem(const char *fmt
, ...) __printflike(1, 2);
213 static int is_number(const char *);
214 static struct group
*find_group_info(const char *);
220 for (; *s
&& isspace((unsigned char)*s
) ; s
++) {
226 check_numeric(const char *val
, const char *name
)
228 if (!is_number(val
)) {
229 errx(EXIT_FAILURE
, "When using [-%c %s], "
230 "the %s must be numeric", *name
, name
, name
);
235 /* resize *cpp appropriately then assign `n' chars of `s' to it */
237 memsave(char **cpp
, const char *s
, size_t n
)
239 RENEW(char, *cpp
, n
+ 1, exit(1));
240 (void)memcpy(*cpp
, s
, n
);
244 /* a replacement for system(3) */
246 asystem(const char *fmt
, ...)
249 char buf
[MaxCommandLen
];
253 (void)vsnprintf(buf
, sizeof(buf
), fmt
, vp
);
256 (void)printf("Command: %s\n", buf
);
260 warn("Error running `%s'", buf
);
261 } else if (WIFSIGNALED(ret
)) {
262 warnx("Error running `%s': Signal %d", buf
, WTERMSIG(ret
));
263 } else if (WIFEXITED(ret
) && WEXITSTATUS(ret
) != 0) {
264 warnx("Error running `%s': Exit %d", buf
, WEXITSTATUS(ret
));
269 /* remove a users home directory, returning 1 for success (ie, no problems encountered) */
271 removehomedir(struct passwd
*pwp
)
275 /* userid not root? */
276 if (pwp
->pw_uid
== 0) {
277 warnx("Not deleting home directory `%s'; userid is 0", pwp
->pw_dir
);
281 /* directory exists (and is a directory!) */
282 if (stat(pwp
->pw_dir
, &st
) < 0) {
283 warn("Cannot access home directory `%s'", pwp
->pw_dir
);
286 if (!S_ISDIR(st
.st_mode
)) {
287 warnx("Home directory `%s' is not a directory", pwp
->pw_dir
);
291 /* userid matches directory owner? */
292 if (st
.st_uid
!= pwp
->pw_uid
) {
293 warnx("User `%s' doesn't own directory `%s', not removed",
294 pwp
->pw_name
, pwp
->pw_dir
);
298 (void)seteuid(pwp
->pw_uid
);
299 /* we add the "|| true" to keep asystem() quiet if there is a non-zero exit status. */
300 (void)asystem("%s -rf %s > /dev/null 2>&1 || true", _PATH_RM
,
303 if (rmdir(pwp
->pw_dir
) < 0) {
304 warn("Unable to remove all files in `%s'", pwp
->pw_dir
);
310 /* return 1 if all of `s' is numeric */
312 is_number(const char *s
)
315 if (!isdigit((unsigned char) *s
)) {
323 * check that the effective uid is 0 - called from funcs which will
324 * modify data and config files.
329 if (geteuid() != 0) {
330 errx(EXIT_FAILURE
, "Program must be run as root");
334 /* copy any dot files into the user's home directory */
336 copydotfiles(char *skeldir
, int uid
, int gid
, char *dir
, mode_t homeperm
)
342 if ((dirp
= opendir(skeldir
)) == NULL
) {
343 warn("Can't open source . files dir `%s'", skeldir
);
346 for (n
= 0; (dp
= readdir(dirp
)) != NULL
&& n
== 0 ; ) {
347 if (strcmp(dp
->d_name
, ".") == 0 ||
348 strcmp(dp
->d_name
, "..") == 0) {
353 (void)closedir(dirp
);
355 warnx("No \"dot\" initialisation files found");
357 (void)asystem("cd %s && %s -rw -pe %s . %s",
358 skeldir
, _PATH_PAX
, (verbose
) ? "-v" : "", dir
);
360 (void)asystem("%s -R -h %d:%d %s", _PATH_CHOWN
, uid
, gid
, dir
);
361 (void)asystem("%s -R u+w %s", _PATH_CHMOD
, dir
);
363 (void)asystem("%s 0%o %s", _PATH_CHMOD
, homeperm
, dir
);
368 /* create a group entry with gid `gid' */
370 creategid(char *group
, int gid
, const char *name
)
375 char buf
[MaxEntryLen
];
376 char f
[MaxFileNameLen
];
380 if (getgrnam(group
) != NULL
) {
381 warnx("Can't create group `%s': already exists", group
);
385 /* LSC: Minix flock implementation is a wrapper around fctl, which
386 * requires writeable fds for LOCK_EX to succeed. */
387 if ((from
= fopen(_PATH_GROUP
, "r+")) == NULL
) {
389 if ((from
= fopen(_PATH_GROUP
, "r")) == NULL
) {
390 #endif /* defined(__minix) */
391 warn("Can't create group `%s': can't open `%s'", name
,
395 if (flock(fileno(from
), LOCK_EX
| LOCK_NB
) < 0) {
396 warn("Can't lock `%s'", _PATH_GROUP
);
400 (void)fstat(fileno(from
), &st
);
401 (void)snprintf(f
, sizeof(f
), "%s.XXXXXX", _PATH_GROUP
);
402 if ((fd
= mkstemp(f
)) < 0) {
403 warn("Can't create group `%s': mkstemp failed", group
);
407 if ((to
= fdopen(fd
, "w")) == NULL
) {
408 warn("Can't create group `%s': fdopen `%s' failed",
415 while ((cc
= fread(buf
, sizeof(char), sizeof(buf
), from
)) > 0) {
416 if (fwrite(buf
, sizeof(char), (unsigned) cc
, to
) != cc
) {
417 warn("Can't create group `%s': short write to `%s'",
425 (void)fprintf(to
, "%s:*:%d:%s\n", group
, gid
, name
);
428 if (rename(f
, _PATH_GROUP
) < 0) {
429 warn("Can't create group `%s': can't rename `%s' to `%s'",
430 group
, f
, _PATH_GROUP
);
434 (void)chmod(_PATH_GROUP
, st
.st_mode
& 07777);
435 syslog(LOG_INFO
, "New group added: name=%s, gid=%d", group
, gid
);
439 /* modify the group entry with name `group' to be newent */
441 modify_gid(char *group
, char *newent
)
446 char buf
[MaxEntryLen
];
447 char f
[MaxFileNameLen
];
455 /* LSC: Minix flock implementation is a wrapper around fctl, which
456 * requires writeable fds for LOCK_EX to succeed. */
457 if ((from
= fopen(_PATH_GROUP
, "r+")) == NULL
) {
459 if ((from
= fopen(_PATH_GROUP
, "r")) == NULL
) {
460 #endif /* defined(__minix) */
461 warn("Can't modify group `%s': can't open `%s'",
465 if (flock(fileno(from
), LOCK_EX
| LOCK_NB
) < 0) {
466 warn("Can't modify group `%s': can't lock `%s'",
471 (void)fstat(fileno(from
), &st
);
472 (void)snprintf(f
, sizeof(f
), "%s.XXXXXX", _PATH_GROUP
);
473 if ((fd
= mkstemp(f
)) < 0) {
474 warn("Can't modify group `%s': mkstemp failed", group
);
478 if ((to
= fdopen(fd
, "w")) == NULL
) {
479 warn("Can't modify group `%s': fdopen `%s' failed", group
, f
);
485 groupc
= strlen(group
);
486 while (fgets(buf
, sizeof(buf
), from
) != NULL
) {
488 if ((colon
= strchr(buf
, ':')) == NULL
) {
489 warnx("Badly formed entry `%s'", buf
);
492 entc
= (int)(colon
- buf
);
493 if (entc
== groupc
&&
494 strncmp(group
, buf
, (unsigned) entc
) == 0) {
495 if (newent
== NULL
) {
496 struct group
*grp_rm
;
497 struct passwd
*user_pwd
;
500 * Check that the group being removed
501 * isn't any user's Primary group. Just
502 * warn if it is. This could cause problems
503 * if the group GID was reused for a
507 grp_rm
= find_group_info(group
);
508 while ((user_pwd
= getpwent()) != NULL
) {
509 if (user_pwd
->pw_gid
== grp_rm
->gr_gid
) {
510 warnx("Warning: group `%s'(%d)"
511 " is the primary group of"
512 " `%s'. Use caution if you"
513 " later add this GID.",
515 grp_rm
->gr_gid
, user_pwd
->pw_name
);
522 (void)strlcpy(buf
, newent
, sizeof(buf
));
525 if (fwrite(buf
, sizeof(char), (unsigned) cc
, to
) != cc
) {
526 warn("Can't modify group `%s': short write to `%s'",
536 if (rename(f
, _PATH_GROUP
) < 0) {
537 warn("Can't modify group `%s': can't rename `%s' to `%s'",
538 group
, f
, _PATH_GROUP
);
542 (void)chmod(_PATH_GROUP
, st
.st_mode
& 07777);
543 if (newent
== NULL
) {
544 syslog(LOG_INFO
, "group deleted: name=%s", group
);
546 syslog(LOG_INFO
, "group information modified: name=%s", group
);
551 /* modify the group entries for all `groups', by adding `user' */
553 append_group(char *user
, int ngroups
, const char **groups
)
559 char buf
[MaxEntryLen
];
560 char f
[MaxFileNameLen
];
570 for (i
= 0 ; i
< ngroups
; i
++) {
571 if ((grp
= getgrnam(groups
[i
])) == NULL
) {
572 warnx("Can't append group `%s' for user `%s'",
575 for (j
= 0 ; grp
->gr_mem
[j
] ; j
++) {
576 if (strcmp(user
, grp
->gr_mem
[j
]) == 0) {
584 /* LSC: Minix flock implementation is a wrapper around fctl, which
585 * requires writeable fds for LOCK_EX to succeed. */
586 if ((from
= fopen(_PATH_GROUP
, "r+")) == NULL
) {
588 if ((from
= fopen(_PATH_GROUP
, "r")) == NULL
) {
589 #endif /* defined(__minix) */
590 warn("Can't append group(s) for `%s': can't open `%s'",
594 if (flock(fileno(from
), LOCK_EX
| LOCK_NB
) < 0) {
595 warn("Can't append group(s) for `%s': can't lock `%s'",
600 (void)fstat(fileno(from
), &st
);
601 (void)snprintf(f
, sizeof(f
), "%s.XXXXXX", _PATH_GROUP
);
602 if ((fd
= mkstemp(f
)) < 0) {
603 warn("Can't append group(s) for `%s': mkstemp failed",
608 if ((to
= fdopen(fd
, "w")) == NULL
) {
609 warn("Can't append group(s) for `%s': fdopen `%s' failed",
616 while (fgets(buf
, sizeof(buf
), from
) != NULL
) {
618 if ((colon
= strchr(buf
, ':')) == NULL
) {
619 warnx("Badly formed entry `%s'", buf
);
622 entc
= (int)(colon
- buf
);
623 for (i
= 0 ; i
< ngroups
; i
++) {
624 if ((groupc
= strlen(groups
[i
])) == 0) {
627 if (entc
== groupc
&&
628 strncmp(groups
[i
], buf
, (unsigned) entc
) == 0) {
629 if ((nc
= snprintf(&buf
[cc
- 1],
630 sizeof(buf
) - cc
+ 1, "%s%s\n",
631 (buf
[cc
- 2] == ':') ? "" : ",", user
)) < 0) {
632 warnx("Warning: group `%s' "
633 "entry too long", groups
[i
]);
638 if (fwrite(buf
, sizeof(char), (unsigned) cc
, to
) != cc
) {
639 warn("Can't append group(s) for `%s':"
640 " short write to `%s'", user
, f
);
649 if (rename(f
, _PATH_GROUP
) < 0) {
650 warn("Can't append group(s) for `%s': "
651 "can't rename `%s' to `%s'", user
, f
, _PATH_GROUP
);
655 (void)chmod(_PATH_GROUP
, st
.st_mode
& 07777);
659 /* the valid characters for login and group names */
660 #define VALID_CHAR(c) (isalnum(c) || (c) == '.' || (c) == '_' || (c) == '-')
662 /* return 1 if `login' is a valid login name */
664 valid_login(char *login_name
, int allow_samba
)
668 /* First character of a login name cannot be '-'. */
669 if (*login_name
== '-') {
672 if (strlen(login_name
) >= LOGIN_NAME_MAX
) {
675 for (cp
= (unsigned char *)login_name
; *cp
; cp
++) {
676 if (!VALID_CHAR(*cp
)) {
678 /* check for a trailing '$' in a Samba user name */
679 if (allow_samba
&& *cp
== '$' && *(cp
+ 1) == 0x0) {
689 /* return 1 if `group' is a valid group name */
691 valid_group(char *group
)
695 for (cp
= (unsigned char *)group
; *cp
; cp
++) {
696 if (!VALID_CHAR(*cp
)) {
703 /* find the next gid in the range lo .. hi */
705 getnextgid(int *gidp
, int lo
, int hi
)
707 for (*gidp
= lo
; *gidp
< hi
; *gidp
+= 1) {
708 if (getgrgid((gid_t
)*gidp
) == NULL
) {
716 /* save a range of uids */
718 save_range(rangelist_t
*rlp
, char *cp
)
724 if (rlp
->rl_rsize
== 0) {
726 NEWARRAY(range_t
, rlp
->rl_rv
, rlp
->rl_rsize
, return(0));
727 } else if (rlp
->rl_rc
== rlp
->rl_rsize
) {
729 RENEW(range_t
, rlp
->rl_rv
, rlp
->rl_rsize
, return(0));
731 if (rlp
->rl_rv
&& sscanf(cp
, "%d..%d", &from
, &to
) == 2) {
732 for (i
= rlp
->rl_defrc
; i
< rlp
->rl_rc
; i
++) {
733 if (rlp
->rl_rv
[i
].r_from
== from
&&
734 rlp
->rl_rv
[i
].r_to
== to
) {
738 if (i
== rlp
->rl_rc
) {
739 rlp
->rl_rv
[rlp
->rl_rc
].r_from
= from
;
740 rlp
->rl_rv
[rlp
->rl_rc
].r_to
= to
;
744 warnx("Bad range `%s'", cp
);
751 /* set the defaults in the defaults file */
753 setdefaults(user_t
*up
)
755 char template[MaxFileNameLen
];
763 (void)snprintf(template, sizeof(template), "%s.XXXXXX",
764 _PATH_USERMGMT_CONF
);
765 if ((fd
= mkstemp(template)) < 0) {
766 warn("Can't set defaults: can't mkstemp `%s' for writing",
767 _PATH_USERMGMT_CONF
);
770 if ((fp
= fdopen(fd
, "w")) == NULL
) {
771 warn("Can't set defaults: can't fdopen `%s' for writing",
772 _PATH_USERMGMT_CONF
);
776 if (fprintf(fp
, "group\t\t%s\n", up
->u_primgrp
) <= 0 ||
777 fprintf(fp
, "base_dir\t%s\n", up
->u_basedir
) <= 0 ||
778 fprintf(fp
, "skel_dir\t%s\n", up
->u_skeldir
) <= 0 ||
779 fprintf(fp
, "shell\t\t%s\n", up
->u_shell
) <= 0 ||
781 fprintf(fp
, "class\t\t%s\n", up
->u_class
) <= 0 ||
782 fprintf(fp
, "homeperm\t0%o\n", up
->u_homeperm
) <= 0 ||
784 fprintf(fp
, "inactive\t%s\n", (up
->u_inactive
== NULL
) ?
785 UNSET_INACTIVE
: up
->u_inactive
) <= 0 ||
786 fprintf(fp
, "expire\t\t%s\n", (up
->u_expire
== NULL
) ?
787 UNSET_EXPIRY
: up
->u_expire
) <= 0 ||
788 fprintf(fp
, "preserve\t%s\n", (up
->u_preserve
== 0) ?
789 "false" : "true") <= 0) {
790 warn("Can't write to `%s'", _PATH_USERMGMT_CONF
);
794 for (i
= (up
->u_defrc
!= up
->u_rc
) ? up
->u_defrc
: 0;
795 i
< up
->u_rc
; i
++) {
796 if (fprintf(fp
, "range\t\t%d..%d\n", up
->u_rv
[i
].r_from
,
797 up
->u_rv
[i
].r_to
) <= 0) {
798 warn("Can't set defaults: can't write to `%s'",
799 _PATH_USERMGMT_CONF
);
806 ret
= ((rename(template, _PATH_USERMGMT_CONF
) == 0) &&
807 (chmod(_PATH_USERMGMT_CONF
, 0644) == 0));
812 /* read the defaults file */
814 read_defaults(def_t
*dp
)
822 user_t
*up
= &dp
->user
;
823 group_t
*gp
= &dp
->group
;
825 (void)memset(dp
, 0, sizeof(*dp
));
827 memsave(&up
->u_primgrp
, DEF_GROUP
, strlen(DEF_GROUP
));
828 memsave(&up
->u_basedir
, DEF_BASEDIR
, strlen(DEF_BASEDIR
));
829 memsave(&up
->u_skeldir
, DEF_SKELDIR
, strlen(DEF_SKELDIR
));
830 memsave(&up
->u_shell
, DEF_SHELL
, strlen(DEF_SHELL
));
831 memsave(&up
->u_comment
, DEF_COMMENT
, strlen(DEF_COMMENT
));
833 memsave(&up
->u_class
, DEF_CLASS
, strlen(DEF_CLASS
));
837 NEWARRAY(range_t
, up
->u_rv
, up
->u_rsize
, exit(1));
838 up
->u_inactive
= DEF_INACTIVE
;
839 up
->u_expire
= DEF_EXPIRE
;
842 NEWARRAY(range_t
, gp
->g_rv
, gp
->g_rsize
, exit(1));
843 if ((fp
= fopen(_PATH_USERMGMT_CONF
, "r")) == NULL
) {
844 if (stat(_PATH_USERMGMT_CONF
, &st
) < 0 && !setdefaults(up
)) {
845 warn("Can't create `%s' defaults file",
846 _PATH_USERMGMT_CONF
);
848 fp
= fopen(_PATH_USERMGMT_CONF
, "r");
851 while ((s
= fparseln(fp
, &len
, &lineno
, NULL
, 0)) != NULL
) {
852 if (strncmp(s
, "group", 5) == 0) {
853 cp
= skipspace(s
+ 5);
854 memsave(&up
->u_primgrp
, (char *)cp
, strlen(cp
));
855 } else if (strncmp(s
, "base_dir", 8) == 0) {
856 cp
= skipspace(s
+ 8);
857 memsave(&up
->u_basedir
, (char *)cp
, strlen(cp
));
858 } else if (strncmp(s
, "skel_dir", 8) == 0) {
859 cp
= skipspace(s
+ 8);
860 memsave(&up
->u_skeldir
, (char *)cp
, strlen(cp
));
861 } else if (strncmp(s
, "shell", 5) == 0) {
862 cp
= skipspace(s
+ 5);
863 memsave(&up
->u_shell
, cp
, strlen(cp
));
865 } else if (strncmp((char *)s
, "class", 5) == 0) {
866 cp
= skipspace(s
+ 5);
867 memsave(&up
->u_class
, cp
, strlen(cp
));
870 } else if (strncmp(s
, "homeperm", 8) == 0) {
871 for (cp
= s
+ 8; *cp
&&
872 isspace((unsigned char)*cp
); cp
++)
874 up
->u_homeperm
= strtoul(cp
, NULL
, 8);
876 } else if (strncmp(s
, "inactive", 8) == 0) {
877 cp
= skipspace(s
+ 8);
878 if (strcmp(cp
, UNSET_INACTIVE
) == 0) {
879 if (up
->u_inactive
) {
880 FREE(up
->u_inactive
);
882 up
->u_inactive
= NULL
;
884 memsave(&up
->u_inactive
, cp
, strlen(cp
));
887 } else if (strncmp(s
, "range", 5) == 0) {
888 cp
= skipspace(s
+ 5);
889 (void)save_range(&up
->u_r
, cp
);
892 } else if (strncmp(s
, "preserve", 8) == 0) {
893 cp
= skipspace(s
+ 8);
895 (strncmp(cp
, "true", 4) == 0) ? 1 :
896 (strncmp(cp
, "yes", 3) == 0) ? 1 : atoi(cp
);
898 } else if (strncmp(s
, "expire", 6) == 0) {
899 cp
= skipspace(s
+ 6);
900 if (strcmp(cp
, UNSET_EXPIRY
) == 0) {
906 memsave(&up
->u_expire
, cp
, strlen(cp
));
909 } else if (strncmp(s
, "gid_range", 9) == 0) {
910 cp
= skipspace(s
+ 9);
911 (void)save_range(&gp
->g_r
, cp
);
919 up
->u_rv
[up
->u_rc
].r_from
= DEF_LOWUID
;
920 up
->u_rv
[up
->u_rc
].r_to
= DEF_HIGHUID
;
923 up
->u_defrc
= up
->u_rc
;
924 up
->u_homeperm
= DEF_HOMEPERM
;
927 /* return the next valid unused uid */
929 getnextuid(int sync_uid_gid
, int *uid
, int low_uid
, int high_uid
)
931 for (*uid
= low_uid
; *uid
<= high_uid
; (*uid
)++) {
932 if (getpwuid((uid_t
)(*uid
)) == NULL
&& *uid
!= NOBODY_UID
) {
934 if (getgrgid((gid_t
)(*uid
)) == NULL
) {
945 /* structure which defines a password type */
946 typedef struct passwd_type_t
{
947 const char *type
; /* optional type descriptor */
948 size_t desc_length
; /* length of type descriptor */
949 size_t length
; /* length of password */
950 const char *regex
; /* regexp to output the password */
951 size_t re_sub
; /* subscript of regexp to use */
954 static passwd_type_t passwd_types
[] = {
955 { "$sha1", 5, 28, "\\$[^$]+\\$[^$]+\\$[^$]+\\$(.*)", 1 }, /* SHA1 */
956 { "$2a", 3, 53, "\\$[^$]+\\$[^$]+\\$(.*)", 1 }, /* Blowfish */
957 { "$1", 2, 34, NULL
, 0 }, /* MD5 */
958 { "", 0, DES_Len
,NULL
, 0 }, /* standard DES */
959 { NULL
, (size_t)~0, (size_t)~0, NULL
, 0 }
960 /* none - terminate search */
963 /* return non-zero if it's a valid password - check length for cipher type */
965 valid_password_length(char *newpasswd
)
968 regmatch_t matchv
[10];
971 for (pwtp
= passwd_types
; pwtp
->desc_length
!= (size_t)~0; pwtp
++) {
972 if (strncmp(newpasswd
, pwtp
->type
, pwtp
->desc_length
) == 0) {
973 if (pwtp
->regex
== NULL
) {
974 return strlen(newpasswd
) == pwtp
->length
;
976 (void)regcomp(&r
, pwtp
->regex
, REG_EXTENDED
);
977 if (regexec(&r
, newpasswd
, 10, matchv
, 0) == 0) {
979 return (int)(matchv
[pwtp
->re_sub
].rm_eo
-
980 matchv
[pwtp
->re_sub
].rm_so
) ==
990 /* return 1 if `class' is a valid login class */
992 valid_class(char *class)
996 if (class == NULL
|| *class == '\0') {
1000 * Check if /etc/login.conf exists. login_getclass() will
1001 * return 1 due to it not existing, so not informing the
1002 * user the actual login class does not exist.
1005 if (access(_PATH_LOGINCONF
, R_OK
) == -1) {
1006 warn("Access failed for `%s'; will not validate class `%s'",
1007 _PATH_LOGINCONF
, class);
1011 if ((lc
= login_getclass(class)) != NULL
) {
1018 /* return 1 if the `shellname' is a valid user shell */
1020 valid_shell(const char *shellname
)
1024 if (access(_PATH_SHELLS
, R_OK
) == -1) {
1026 warn("Access failed for `%s'; will not validate shell `%s'",
1027 _PATH_SHELLS
, shellname
);
1031 /* if nologin is used as a shell, consider it a valid shell */
1032 if (strcmp(shellname
, _PATH_SBIN_NOLOGIN
) == 0)
1035 while ((shellp
= getusershell()) != NULL
)
1036 if (strcmp(shellp
, shellname
) == 0)
1039 warnx("Shell `%s' not found in `%s'", shellname
, _PATH_SHELLS
);
1041 return access(shellname
, X_OK
) != -1;
1045 /* look for a valid time, return 0 if it was specified but bad */
1047 scantime(time_t *tp
, char *s
)
1055 (void)memset(&tm
, 0, sizeof(tm
));
1056 if (strptime(s
, "%c", &tm
) != NULL
) {
1058 return (*tp
== -1) ? 0 : 1;
1059 } else if (strptime(s
, "%B %d %Y", &tm
) != NULL
) {
1061 return (*tp
== -1) ? 0 : 1;
1064 *tp
= val
= strtol(s
, &ep
, 10);
1065 if (*ep
!= '\0' || *tp
< -1 || errno
== ERANGE
) {
1079 adduser(char *login_name
, user_t
*up
)
1085 char password
[PasswordLength
+ 1];
1086 char home
[MaxFileNameLen
];
1087 char buf
[MaxFileNameLen
];
1095 if (!valid_login(login_name
, up
->u_allow_samba
)) {
1096 errx(EXIT_FAILURE
, "Can't add user `%s': invalid login name", login_name
);
1099 if (!valid_class(up
->u_class
)) {
1100 errx(EXIT_FAILURE
, "Can't add user `%s': no such login class `%s'",
1101 login_name
, up
->u_class
);
1104 #if defined(__minix)
1105 /* LSC: Minix flock implementation is a wrapper around fctl, which
1106 * requires writeable fds for LOCK_EX to succeed. */
1107 if ((masterfd
= open(_PATH_MASTERPASSWD
, O_RDWR
)) < 0) {
1109 if ((masterfd
= open(_PATH_MASTERPASSWD
, O_RDONLY
)) < 0) {
1110 #endif /* defined(__minix) */
1111 err(EXIT_FAILURE
, "Can't add user `%s': can't open `%s'",
1112 login_name
, _PATH_MASTERPASSWD
);
1114 if (flock(masterfd
, LOCK_EX
| LOCK_NB
) < 0) {
1115 err(EXIT_FAILURE
, "Can't add user `%s': can't lock `%s'",
1116 login_name
, _PATH_MASTERPASSWD
);
1119 if ((ptmpfd
= pw_lock(WAITSECS
)) < 0) {
1121 (void)close(masterfd
);
1123 err(EXIT_FAILURE
, "Can't add user `%s': can't obtain pw_lock",
1126 while ((cc
= read(masterfd
, buf
, sizeof(buf
))) > 0) {
1127 if (write(ptmpfd
, buf
, (size_t)(cc
)) != cc
) {
1129 (void)close(masterfd
);
1130 (void)close(ptmpfd
);
1133 err(EXIT_FAILURE
, "Can't add user `%s': "
1134 "short write to /etc/ptmp", login_name
);
1137 (void)close(masterfd
);
1138 /* if no uid was specified, get next one in [low_uid..high_uid] range */
1139 sync_uid_gid
= (strcmp(up
->u_primgrp
, "=uid") == 0);
1140 if (up
->u_uid
== -1) {
1144 * Look for a free UID in the command line ranges (if any).
1145 * These start after the ranges specified in the config file.
1147 for (i
= up
->u_defrc
; !got_id
&& i
< up
->u_rc
; i
++) {
1148 got_id
= getnextuid(sync_uid_gid
, &up
->u_uid
,
1149 up
->u_rv
[i
].r_from
, up
->u_rv
[i
].r_to
);
1152 * If there were no free UIDs in the command line ranges,
1153 * try the ranges from the config file (there will always
1154 * be at least one default).
1156 for (i
= 0; !got_id
&& i
< up
->u_defrc
; i
++) {
1157 got_id
= getnextuid(sync_uid_gid
, &up
->u_uid
,
1158 up
->u_rv
[i
].r_from
, up
->u_rv
[i
].r_to
);
1161 (void)close(ptmpfd
);
1163 errx(EXIT_FAILURE
, "Can't add user `%s': "
1164 "can't get next uid for %d", login_name
,
1168 /* check uid isn't already allocated */
1169 if (!(up
->u_flags
& F_DUPUID
) && getpwuid((uid_t
)(up
->u_uid
)) != NULL
) {
1170 (void)close(ptmpfd
);
1172 errx(EXIT_FAILURE
, "Can't add user `%s': "
1173 "uid %d is already in use", login_name
, up
->u_uid
);
1175 /* if -g=uid was specified, check gid is unused */
1177 if (getgrgid((gid_t
)(up
->u_uid
)) != NULL
) {
1178 (void)close(ptmpfd
);
1180 errx(EXIT_FAILURE
, "Can't add user `%s': "
1181 "gid %d is already in use", login_name
,
1185 } else if ((grp
= getgrnam(up
->u_primgrp
)) != NULL
) {
1187 } else if (is_number(up
->u_primgrp
) &&
1188 (grp
= getgrgid((gid_t
)atoi(up
->u_primgrp
))) != NULL
) {
1191 (void)close(ptmpfd
);
1193 errx(EXIT_FAILURE
, "Can't add user `%s': group %s not found",
1194 login_name
, up
->u_primgrp
);
1196 /* check name isn't already in use */
1197 if (!(up
->u_flags
& F_DUPUID
) && getpwnam(login_name
) != NULL
) {
1198 (void)close(ptmpfd
);
1200 errx(EXIT_FAILURE
, "Can't add user `%s': "
1201 "`%s' is already a user", login_name
, login_name
);
1203 if (up
->u_flags
& F_HOMEDIR
) {
1204 (void)strlcpy(home
, up
->u_home
, sizeof(home
));
1206 /* if home directory hasn't been given, make it up */
1207 (void)snprintf(home
, sizeof(home
), "%s/%s", up
->u_basedir
,
1210 if (up
->u_flags
& F_SHELL
) {
1212 if (!valid_shell(up
->u_shell
)) {
1214 (void)close(ptmpfd
);
1217 errx(EXIT_FAILURE
, "Can't add user `%s': "
1218 "Cannot access shell `%s'",
1219 login_name
, up
->u_shell
);
1224 if (!scantime(&inactive
, up
->u_inactive
)) {
1225 warnx("Warning: inactive time `%s' invalid, password expiry off",
1228 if (!scantime(&expire
, up
->u_expire
) || expire
== -1) {
1229 warnx("Warning: expire time `%s' invalid, account expiry off",
1231 expire
= 0; /* Just in case. */
1233 if (lstat(home
, &st
) < 0 && !(up
->u_flags
& F_MKDIR
)) {
1234 warnx("Warning: home directory `%s' doesn't exist, "
1235 "and -m was not specified", home
);
1237 password
[sizeof(password
) - 1] = '\0';
1238 if (up
->u_password
!= NULL
&& valid_password_length(up
->u_password
)) {
1239 (void)strlcpy(password
, up
->u_password
, sizeof(password
));
1241 (void)memset(password
, '*', DES_Len
);
1242 password
[DES_Len
] = 0;
1243 if (up
->u_password
!= NULL
) {
1244 warnx("Password `%s' is invalid: setting it to `%s'",
1245 up
->u_password
, password
);
1248 cc
= snprintf(buf
, sizeof(buf
), "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n",
1263 if (write(ptmpfd
, buf
, (size_t) cc
) != cc
) {
1265 (void)close(ptmpfd
);
1268 err(EXIT_FAILURE
, "Can't add user `%s': write failed",
1271 if (up
->u_flags
& F_MKDIR
) {
1272 if (lstat(home
, &st
) == 0) {
1273 (void)close(ptmpfd
);
1276 "Can't add user `%s': home directory `%s' "
1277 "already exists", login_name
, home
);
1279 if (asystem("%s -p %s", _PATH_MKDIR
, home
) != 0) {
1280 (void)close(ptmpfd
);
1282 errx(EXIT_FAILURE
, "Can't add user `%s': "
1283 "can't mkdir `%s'", login_name
, home
);
1285 (void)copydotfiles(up
->u_skeldir
, up
->u_uid
, gid
, home
,
1289 if (strcmp(up
->u_primgrp
, "=uid") == 0 &&
1290 getgrnam(login_name
) == NULL
&&
1291 !creategid(login_name
, gid
, login_name
)) {
1292 (void)close(ptmpfd
);
1294 errx(EXIT_FAILURE
, "Can't add user `%s': can't create gid %d ",
1297 if (up
->u_groupc
> 0 &&
1298 !append_group(login_name
, up
->u_groupc
, up
->u_groupv
)) {
1299 (void)close(ptmpfd
);
1301 errx(EXIT_FAILURE
, "Can't add user `%s': can't append "
1302 "to new groups", login_name
);
1304 (void)close(ptmpfd
);
1305 #if PW_MKDB_ARGC == 2
1306 if (pw_mkdb(login_name
, 0) < 0)
1312 errx(EXIT_FAILURE
, "Can't add user `%s': pw_mkdb failed",
1315 syslog(LOG_INFO
, "New user added: name=%s, uid=%d, gid=%d, home=%s, "
1316 "shell=%s", login_name
, up
->u_uid
, gid
, home
, up
->u_shell
);
1320 /* remove a user from the groups file */
1322 rm_user_from_groups(char *login_name
)
1325 regmatch_t matchv
[10];
1329 char line
[MaxEntryLen
];
1330 char buf
[MaxEntryLen
];
1331 char f
[MaxFileNameLen
];
1336 (void)snprintf(line
, sizeof(line
), "(:|,)(%s)(,|$)", login_name
);
1337 if ((sc
= regcomp(&r
, line
, REG_EXTENDED
|REG_NEWLINE
)) != 0) {
1338 (void)regerror(sc
, &r
, buf
, sizeof(buf
));
1339 warnx("Can't compile regular expression `%s' (%s)", line
,
1343 #if defined(__minix)
1344 /* LSC: Minix flock implementation is a wrapper around fctl, which
1345 * requires writeable fds for LOCK_EX to succeed. */
1346 if ((from
= fopen(_PATH_GROUP
, "r+")) == NULL
) {
1348 if ((from
= fopen(_PATH_GROUP
, "r")) == NULL
) {
1349 #endif /* defined(__minix) */
1350 warn("Can't remove user `%s' from `%s': can't open `%s'",
1351 login_name
, _PATH_GROUP
, _PATH_GROUP
);
1354 if (flock(fileno(from
), LOCK_EX
| LOCK_NB
) < 0) {
1355 warn("Can't remove user `%s' from `%s': can't lock `%s'",
1356 login_name
, _PATH_GROUP
, _PATH_GROUP
);
1360 (void)fstat(fileno(from
), &st
);
1361 (void)snprintf(f
, sizeof(f
), "%s.XXXXXX", _PATH_GROUP
);
1362 if ((fd
= mkstemp(f
)) < 0) {
1363 warn("Can't remove user `%s' from `%s': mkstemp failed",
1364 login_name
, _PATH_GROUP
);
1368 if ((to
= fdopen(fd
, "w")) == NULL
) {
1369 warn("Can't remove user `%s' from `%s': fdopen `%s' failed",
1370 login_name
, _PATH_GROUP
, f
);
1376 while (fgets(buf
, sizeof(buf
), from
) != NULL
) {
1378 if (regexec(&r
, buf
, 10, matchv
, 0) == 0) {
1379 if (buf
[(int)matchv
[1].rm_so
] == ',') {
1380 matchv
[2].rm_so
= matchv
[1].rm_so
;
1381 } else if (matchv
[2].rm_eo
!= matchv
[3].rm_eo
) {
1382 matchv
[2].rm_eo
= matchv
[3].rm_eo
;
1384 cc
-= (int) matchv
[2].rm_eo
;
1385 sc
= (int) matchv
[2].rm_so
;
1386 if (fwrite(buf
, sizeof(char), (size_t)sc
, to
) != sc
||
1387 fwrite(&buf
[(int)matchv
[2].rm_eo
], sizeof(char),
1388 (size_t)cc
, to
) != cc
) {
1389 warn("Can't remove user `%s' from `%s': "
1390 "short write to `%s'", login_name
,
1397 } else if (fwrite(buf
, sizeof(char), (unsigned) cc
, to
) != cc
) {
1398 warn("Can't remove user `%s' from `%s': "
1399 "short write to `%s'", login_name
, _PATH_GROUP
, f
);
1408 if (rename(f
, _PATH_GROUP
) < 0) {
1409 warn("Can't remove user `%s' from `%s': "
1410 "can't rename `%s' to `%s'",
1411 login_name
, _PATH_GROUP
, f
, _PATH_GROUP
);
1415 (void)chmod(_PATH_GROUP
, st
.st_mode
& 07777);
1419 /* check that the user or group is local, not from YP/NIS */
1421 is_local(char *name
, const char *file
)
1424 char buf
[MaxEntryLen
];
1428 if ((fp
= fopen(file
, "r")) == NULL
) {
1429 err(EXIT_FAILURE
, "Can't open `%s'", file
);
1432 for (ret
= 0 ; fgets(buf
, sizeof(buf
), fp
) != NULL
; ) {
1433 if (strncmp(buf
, name
, len
) == 0 && buf
[len
] == ':') {
1444 moduser(char *login_name
, char *newlogin
, user_t
*up
, int allow_samba
)
1446 struct passwd
*pwp
, pw
;
1448 const char *homedir
;
1454 char newdir
[MaxFileNameLen
];
1455 char buf
[MaxEntryLen
];
1456 char pwbuf
[MaxEntryLen
];
1462 if (!valid_login(newlogin
, allow_samba
)) {
1463 errx(EXIT_FAILURE
, "Can't modify user `%s': invalid login name",
1466 if (getpwnam_r(login_name
, &pw
, pwbuf
, sizeof(pwbuf
), &pwp
) != 0
1468 errx(EXIT_FAILURE
, "Can't modify user `%s': no such user",
1471 if (!is_local(login_name
, _PATH_MASTERPASSWD
)) {
1472 errx(EXIT_FAILURE
, "Can't modify user `%s': must be a local user",
1475 /* keep dir name in case we need it for '-m' */
1476 homedir
= pwp
->pw_dir
;
1478 #if defined(__minix)
1479 /* LSC: Minix flock implementation is a wrapper around fctl, which
1480 * requires writeable fds for LOCK_EX to succeed. */
1481 if ((masterfd
= open(_PATH_MASTERPASSWD
, O_RDWR
)) < 0) {
1483 if ((masterfd
= open(_PATH_MASTERPASSWD
, O_RDONLY
)) < 0) {
1484 #endif /* defined(__minix) */
1485 err(EXIT_FAILURE
, "Can't modify user `%s': can't open `%s'",
1486 login_name
, _PATH_MASTERPASSWD
);
1488 if (flock(masterfd
, LOCK_EX
| LOCK_NB
) < 0) {
1489 err(EXIT_FAILURE
, "Can't modify user `%s': can't lock `%s'",
1490 login_name
, _PATH_MASTERPASSWD
);
1493 if ((ptmpfd
= pw_lock(WAITSECS
)) < 0) {
1495 (void)close(masterfd
);
1497 err(EXIT_FAILURE
, "Can't modify user `%s': "
1498 "can't obtain pw_lock", login_name
);
1500 if ((master
= fdopen(masterfd
, "r")) == NULL
) {
1502 (void)close(masterfd
);
1503 (void)close(ptmpfd
);
1506 err(EXIT_FAILURE
, "Can't modify user `%s': "
1507 "fdopen fd for %s", login_name
, _PATH_MASTERPASSWD
);
1510 if (up
->u_flags
& F_USERNAME
) {
1513 * check new name isn't already in use
1515 if (strcmp(login_name
, newlogin
) != 0 &&
1516 getpwnam(newlogin
) != NULL
) {
1517 (void)close(ptmpfd
);
1519 errx(EXIT_FAILURE
, "Can't modify user `%s': "
1520 "`%s' is already a user", login_name
,
1523 pwp
->pw_name
= newlogin
;
1526 * Provide a new directory name in case the
1527 * home directory is to be moved.
1529 if (up
->u_flags
& F_MKDIR
) {
1530 (void)snprintf(newdir
, sizeof(newdir
), "%s/%s",
1531 up
->u_basedir
, newlogin
);
1532 pwp
->pw_dir
= newdir
;
1535 if (up
->u_flags
& F_PASSWORD
) {
1536 if (up
->u_password
!= NULL
) {
1537 if (!valid_password_length(up
->u_password
)) {
1538 (void)close(ptmpfd
);
1541 "Can't modify user `%s': "
1542 "invalid password: `%s'",
1543 login_name
, up
->u_password
);
1546 strstr(pwp
->pw_passwd
, LOCKED
)) != NULL
) {
1548 * account is locked - keep it locked
1549 * and just change the password.
1551 if (asprintf(&locked_pwd
, "%s%s",
1552 LOCKED
, up
->u_password
) == -1) {
1553 (void)close(ptmpfd
);
1556 "Can't modify user `%s': "
1560 pwp
->pw_passwd
= locked_pwd
;
1562 pwp
->pw_passwd
= up
->u_password
;
1567 /* check whether we should lock the account. */
1568 if (up
->u_locked
== LOCK
) {
1569 /* check to see account if already locked. */
1570 if ((locked_pwd
= strstr(pwp
->pw_passwd
, LOCKED
))
1572 warnx("Account is already locked");
1574 if (asprintf(&locked_pwd
, "%s%s", LOCKED
,
1575 pwp
->pw_passwd
) == -1) {
1576 (void)close(ptmpfd
);
1579 "Can't modify user `%s': "
1580 "asprintf failed", login_name
);
1582 pwp
->pw_passwd
= locked_pwd
;
1584 } else if (up
->u_locked
== UNLOCK
) {
1585 if ((locked_pwd
= strstr(pwp
->pw_passwd
, LOCKED
))
1587 warnx("Can't modify user `%s': "
1588 "account is not locked", login_name
);
1590 pwp
->pw_passwd
= locked_pwd
+ strlen(LOCKED
);
1594 if (up
->u_flags
& F_UID
) {
1595 /* check uid isn't already allocated */
1596 if (!(up
->u_flags
& F_DUPUID
) &&
1597 getpwuid((uid_t
)(up
->u_uid
)) != NULL
) {
1598 (void)close(ptmpfd
);
1600 errx(EXIT_FAILURE
, "Can't modify user `%s': "
1601 "uid `%d' is already in use", login_name
,
1604 pwp
->pw_uid
= up
->u_uid
;
1606 if (up
->u_flags
& F_GROUP
) {
1607 /* if -g=uid was specified, check gid is unused */
1608 if (strcmp(up
->u_primgrp
, "=uid") == 0) {
1609 if (getgrgid((gid_t
)(pwp
->pw_uid
)) != NULL
) {
1610 (void)close(ptmpfd
);
1613 "Can't modify user `%s': "
1614 "gid %d is already in use",
1615 login_name
, pwp
->pw_uid
);
1617 pwp
->pw_gid
= pwp
->pw_uid
;
1618 if (!creategid(newlogin
, pwp
->pw_uid
, "")) {
1620 "Could not create group %s "
1621 "with uid %d", newlogin
,
1624 } else if ((grp
= getgrnam(up
->u_primgrp
)) != NULL
) {
1625 pwp
->pw_gid
= grp
->gr_gid
;
1626 } else if (is_number(up
->u_primgrp
) &&
1628 (gid_t
)atoi(up
->u_primgrp
))) != NULL
) {
1629 pwp
->pw_gid
= grp
->gr_gid
;
1631 (void)close(ptmpfd
);
1633 errx(EXIT_FAILURE
, "Can't modify user `%s': "
1634 "group %s not found", login_name
,
1638 if (up
->u_flags
& F_INACTIVE
) {
1639 if (!scantime(&pwp
->pw_change
, up
->u_inactive
)) {
1640 warnx("Warning: inactive time `%s' invalid, "
1641 "password expiry off",
1645 if (up
->u_flags
& F_EXPIRE
) {
1646 if (!scantime(&pwp
->pw_expire
, up
->u_expire
) ||
1647 pwp
->pw_expire
== -1) {
1648 warnx("Warning: expire time `%s' invalid, "
1649 "account expiry off",
1654 if (up
->u_flags
& F_COMMENT
) {
1655 pwp
->pw_gecos
= up
->u_comment
;
1657 if (up
->u_flags
& F_HOMEDIR
) {
1658 pwp
->pw_dir
= up
->u_home
;
1660 if (up
->u_flags
& F_SHELL
) {
1662 if (!valid_shell(up
->u_shell
)) {
1664 (void)close(ptmpfd
);
1667 errx(EXIT_FAILURE
, "Can't modify user `%s': "
1668 "Cannot access shell `%s'",
1669 login_name
, up
->u_shell
);
1671 pwp
->pw_shell
= up
->u_shell
;
1673 pwp
->pw_shell
= up
->u_shell
;
1677 if (up
->u_flags
& F_CLASS
) {
1678 if (!valid_class(up
->u_class
)) {
1679 (void)close(ptmpfd
);
1681 errx(EXIT_FAILURE
, "Can't modify user `%s': "
1682 "no such login class `%s'", login_name
,
1685 pwp
->pw_class
= up
->u_class
;
1689 loginc
= strlen(login_name
);
1690 while (fgets(buf
, sizeof(buf
), master
) != NULL
) {
1691 if ((colon
= strchr(buf
, ':')) == NULL
) {
1692 warnx("Malformed entry `%s'. Skipping", buf
);
1695 colonc
= (size_t)(colon
- buf
);
1696 if (strncmp(login_name
, buf
, loginc
) == 0 && loginc
== colonc
) {
1698 len
= snprintf(buf
, sizeof(buf
), "%s:%s:%d:%d:"
1702 ":%ld:%ld:%s:%s:%s\n",
1710 (long)pwp
->pw_change
,
1711 (long)pwp
->pw_expire
,
1715 if (write(ptmpfd
, buf
, len
) != len
) {
1717 (void)close(ptmpfd
);
1720 err(EXIT_FAILURE
, "Can't modify user "
1721 "`%s': write", login_name
);
1726 if (write(ptmpfd
, buf
, len
) != len
) {
1728 (void)close(masterfd
);
1729 (void)close(ptmpfd
);
1732 err(EXIT_FAILURE
, "Can't modify `%s': "
1733 "write", login_name
);
1738 if ((up
->u_flags
& F_MKDIR
) &&
1739 asystem("%s %s %s", _PATH_MV
, homedir
, pwp
->pw_dir
) != 0) {
1740 (void)close(ptmpfd
);
1742 errx(EXIT_FAILURE
, "Can't modify user `%s': "
1743 "can't move `%s' to `%s'",
1744 login_name
, homedir
, pwp
->pw_dir
);
1746 if (up
->u_groupc
> 0 &&
1747 !append_group(newlogin
, up
->u_groupc
, up
->u_groupv
)) {
1748 (void)close(ptmpfd
);
1750 errx(EXIT_FAILURE
, "Can't modify user `%s': "
1751 "can't append `%s' to new groups",
1752 login_name
, newlogin
);
1755 (void)close(ptmpfd
);
1756 (void)fclose(master
);
1757 #if PW_MKDB_ARGC == 2
1758 if (up
!= NULL
&& strcmp(login_name
, newlogin
) == 0) {
1759 error
= pw_mkdb(login_name
, 0);
1761 error
= pw_mkdb(NULL
, 0);
1768 errx(EXIT_FAILURE
, "Can't modify user `%s': pw_mkdb failed",
1772 syslog(LOG_INFO
, "User removed: name=%s", login_name
);
1773 } else if (strcmp(login_name
, newlogin
) == 0) {
1774 syslog(LOG_INFO
, "User information modified: name=%s, uid=%d, "
1775 "gid=%d, home=%s, shell=%s",
1776 login_name
, pwp
->pw_uid
, pwp
->pw_gid
, pwp
->pw_dir
,
1779 syslog(LOG_INFO
, "User information modified: name=%s, "
1780 "new name=%s, uid=%d, gid=%d, home=%s, shell=%s",
1781 login_name
, newlogin
, pwp
->pw_uid
, pwp
->pw_gid
,
1782 pwp
->pw_dir
, pwp
->pw_shell
);
1788 /* see if we can find out the user struct */
1789 static struct passwd
*
1790 find_user_info(const char *name
)
1794 if ((pwp
= getpwnam(name
)) != NULL
) {
1797 if (is_number(name
) && (pwp
= getpwuid((uid_t
)atoi(name
))) != NULL
) {
1804 /* see if we can find out the group struct */
1805 static struct group
*
1806 find_group_info(const char *name
)
1810 if ((grp
= getgrnam(name
)) != NULL
) {
1813 if (is_number(name
) && (grp
= getgrgid((gid_t
)atoi(name
))) != NULL
) {
1819 /* print out usage message, and then exit */
1821 usermgmt_usage(const char *prog
)
1823 if (strcmp(prog
, "useradd") == 0) {
1824 (void)fprintf(stderr
, "usage: %s -D [-F] [-b base-dir] "
1825 "[-e expiry-time] [-f inactive-time]\n"
1826 "\t[-g gid | name | =uid] [-k skel-dir] [-L login-class]\n"
1827 "\t[-M homeperm] [-r lowuid..highuid] [-s shell]\n", prog
);
1828 (void)fprintf(stderr
, "usage: %s [-moSv] [-b base-dir] "
1829 "[-c comment] [-d home-dir] [-e expiry-time]\n"
1830 "\t[-f inactive-time] [-G secondary-group] "
1831 "[-g gid | name | =uid]\n"
1832 "\t[-k skeletondir] [-L login-class] [-M homeperm] "
1834 "\t[-r lowuid..highuid] [-s shell] [-u uid] user\n",
1836 } else if (strcmp(prog
, "usermod") == 0) {
1837 (void)fprintf(stderr
, "usage: %s [-FmoSv] [-C yes/no] "
1838 "[-c comment] [-d home-dir] [-e expiry-time]\n"
1839 "\t[-f inactive] [-G secondary-group] "
1840 "[-g gid | name | =uid]\n"
1841 "\t[-L login-class] [-l new-login] [-p password] "
1842 "[-s shell] [-u uid]\n"
1844 } else if (strcmp(prog
, "userdel") == 0) {
1845 (void)fprintf(stderr
, "usage: %s -D [-p preserve-value]\n", prog
);
1846 (void)fprintf(stderr
,
1847 "usage: %s [-rSv] [-p preserve-value] user\n", prog
);
1849 } else if (strcmp(prog
, "userinfo") == 0) {
1850 (void)fprintf(stderr
, "usage: %s [-e] user\n", prog
);
1852 } else if (strcmp(prog
, "groupadd") == 0) {
1853 (void)fprintf(stderr
, "usage: %s [-ov] [-g gid]"
1854 " [-r lowgid..highgid] group\n", prog
);
1855 } else if (strcmp(prog
, "groupdel") == 0) {
1856 (void)fprintf(stderr
, "usage: %s [-v] group\n", prog
);
1857 } else if (strcmp(prog
, "groupmod") == 0) {
1858 (void)fprintf(stderr
,
1859 "usage: %s [-ov] [-g gid] [-n newname] group\n", prog
);
1860 } else if (strcmp(prog
, "user") == 0 || strcmp(prog
, "group") == 0) {
1861 (void)fprintf(stderr
,
1862 "usage: %s ( add | del | mod | info ) ...\n", prog
);
1864 } else if (strcmp(prog
, "groupinfo") == 0) {
1865 (void)fprintf(stderr
, "usage: %s [-ev] group\n", prog
);
1873 #define ADD_OPT_EXTENSIONS "M:p:r:vL:S"
1875 #define ADD_OPT_EXTENSIONS
1879 useradd(int argc
, char **argv
)
1882 user_t
*up
= &def
.user
;
1890 read_defaults(&def
);
1892 defaultfield
= bigD
= 0;
1893 while ((c
= getopt(argc
, argv
, "DFG:b:c:d:e:f:g:k:mou:s:"
1894 ADD_OPT_EXTENSIONS
)) != -1) {
1901 * Setting -1 will force the new user to
1902 * change their password as soon as they
1903 * next log in - passwd(5).
1906 memsave(&up
->u_inactive
, "-1", strlen("-1"));
1909 while (up
->u_groupc
< NGROUPS_MAX
&&
1910 (up
->u_groupv
[up
->u_groupc
] = strsep(&optarg
, ",")) != NULL
) {
1911 if (up
->u_groupv
[up
->u_groupc
][0] != 0) {
1915 if (optarg
!= NULL
) {
1916 warnx("Truncated list of secondary groups "
1917 "to %d entries", NGROUPS_MAX
);
1922 up
->u_allow_samba
= 1;
1927 memsave(&up
->u_basedir
, optarg
, strlen(optarg
));
1930 memsave(&up
->u_comment
, optarg
, strlen(optarg
));
1933 memsave(&up
->u_home
, optarg
, strlen(optarg
));
1934 up
->u_flags
|= F_HOMEDIR
;
1938 memsave(&up
->u_expire
, optarg
, strlen(optarg
));
1942 memsave(&up
->u_inactive
, optarg
, strlen(optarg
));
1946 memsave(&up
->u_primgrp
, optarg
, strlen(optarg
));
1950 memsave(&up
->u_skeldir
, optarg
, strlen(optarg
));
1955 memsave(&up
->u_class
, optarg
, strlen(optarg
));
1959 up
->u_flags
|= F_MKDIR
;
1964 up
->u_homeperm
= strtoul(optarg
, NULL
, 8);
1968 up
->u_flags
|= F_DUPUID
;
1972 memsave(&up
->u_password
, optarg
, strlen(optarg
));
1978 (void)save_range(&up
->u_r
, optarg
);
1982 up
->u_flags
|= F_SHELL
;
1984 memsave(&up
->u_shell
, optarg
, strlen(optarg
));
1987 up
->u_uid
= check_numeric(optarg
, "uid");
1995 usermgmt_usage("useradd");
2002 return setdefaults(up
) ? EXIT_SUCCESS
: EXIT_FAILURE
;
2004 (void)printf("group\t\t%s\n", up
->u_primgrp
);
2005 (void)printf("base_dir\t%s\n", up
->u_basedir
);
2006 (void)printf("skel_dir\t%s\n", up
->u_skeldir
);
2007 (void)printf("shell\t\t%s\n", up
->u_shell
);
2009 (void)printf("class\t\t%s\n", up
->u_class
);
2010 (void)printf("homeperm\t0%o\n", up
->u_homeperm
);
2012 (void)printf("inactive\t%s\n", (up
->u_inactive
== NULL
) ?
2013 UNSET_INACTIVE
: up
->u_inactive
);
2014 (void)printf("expire\t\t%s\n", (up
->u_expire
== NULL
) ?
2015 UNSET_EXPIRY
: up
->u_expire
);
2017 for (i
= 0 ; i
< up
->u_rc
; i
++) {
2018 (void)printf("range\t\t%d..%d\n",
2019 up
->u_rv
[i
].r_from
, up
->u_rv
[i
].r_to
);
2022 return EXIT_SUCCESS
;
2027 usermgmt_usage("useradd");
2030 openlog("useradd", LOG_PID
, LOG_USER
);
2031 return adduser(*argv
, up
) ? EXIT_SUCCESS
: EXIT_FAILURE
;
2035 #define MOD_OPT_EXTENSIONS "p:vL:S"
2037 #define MOD_OPT_EXTENSIONS
2041 usermod(int argc
, char **argv
)
2044 user_t
*up
= &def
.user
;
2045 char newuser
[MaxUserNameLen
+ 1];
2046 int c
, have_new_user
;
2048 (void)memset(newuser
, 0, sizeof(newuser
));
2049 read_defaults(&def
);
2052 while ((c
= getopt(argc
, argv
, "C:FG:c:d:e:f:g:l:mos:u:"
2053 MOD_OPT_EXTENSIONS
)) != -1) {
2056 while (up
->u_groupc
< NGROUPS_MAX
&&
2057 (up
->u_groupv
[up
->u_groupc
] =
2058 strsep(&optarg
, ",")) != NULL
) {
2059 if (up
->u_groupv
[up
->u_groupc
][0] != 0) {
2063 if (optarg
!= NULL
) {
2064 warnx("Truncated list of secondary groups "
2065 "to %d entries", NGROUPS_MAX
);
2067 up
->u_flags
|= F_SECGROUP
;
2071 up
->u_allow_samba
= 1;
2075 memsave(&up
->u_comment
, optarg
, strlen(optarg
));
2076 up
->u_flags
|= F_COMMENT
;
2079 if (strcasecmp(optarg
, "yes") == 0) {
2080 up
->u_locked
= LOCK
;
2081 } else if (strcasecmp(optarg
, "no") == 0) {
2082 up
->u_locked
= UNLOCK
;
2086 "Please type 'yes' or 'no'");
2090 memsave(&up
->u_inactive
, "-1", strlen("-1"));
2091 up
->u_flags
|= F_INACTIVE
;
2094 memsave(&up
->u_home
, optarg
, strlen(optarg
));
2095 up
->u_flags
|= F_HOMEDIR
;
2098 memsave(&up
->u_expire
, optarg
, strlen(optarg
));
2099 up
->u_flags
|= F_EXPIRE
;
2102 memsave(&up
->u_inactive
, optarg
, strlen(optarg
));
2103 up
->u_flags
|= F_INACTIVE
;
2106 memsave(&up
->u_primgrp
, optarg
, strlen(optarg
));
2107 up
->u_flags
|= F_GROUP
;
2110 (void)strlcpy(newuser
, optarg
, sizeof(newuser
));
2112 up
->u_flags
|= F_USERNAME
;
2116 memsave(&up
->u_class
, optarg
, strlen(optarg
));
2117 up
->u_flags
|= F_CLASS
;
2121 up
->u_flags
|= F_MKDIR
;
2124 up
->u_flags
|= F_DUPUID
;
2128 memsave(&up
->u_password
, optarg
, strlen(optarg
));
2129 up
->u_flags
|= F_PASSWORD
;
2133 memsave(&up
->u_shell
, optarg
, strlen(optarg
));
2134 up
->u_flags
|= F_SHELL
;
2137 up
->u_uid
= check_numeric(optarg
, "uid");
2138 up
->u_flags
|= F_UID
;
2146 usermgmt_usage("usermod");
2150 if ((up
->u_flags
& F_MKDIR
) && !(up
->u_flags
& F_HOMEDIR
) &&
2151 !(up
->u_flags
& F_USERNAME
)) {
2152 warnx("Option 'm' useless without 'd' or 'l' -- ignored");
2153 up
->u_flags
&= ~F_MKDIR
;
2158 usermgmt_usage("usermod");
2161 openlog("usermod", LOG_PID
, LOG_USER
);
2162 return moduser(*argv
, (have_new_user
) ? newuser
: *argv
, up
,
2163 up
->u_allow_samba
) ? EXIT_SUCCESS
: EXIT_FAILURE
;
2167 #define DEL_OPT_EXTENSIONS "Dp:vS"
2169 #define DEL_OPT_EXTENSIONS
2173 userdel(int argc
, char **argv
)
2177 user_t
*up
= &def
.user
;
2178 char password
[PasswordLength
+ 1];
2184 read_defaults(&def
);
2185 defaultfield
= bigD
= rmhome
= 0;
2186 while ((c
= getopt(argc
, argv
, "r" DEL_OPT_EXTENSIONS
)) != -1) {
2195 up
->u_allow_samba
= 1;
2201 up
->u_preserve
= (strcmp(optarg
, "true") == 0) ? 1 :
2202 (strcmp(optarg
, "yes") == 0) ? 1 :
2215 usermgmt_usage("userdel");
2223 return setdefaults(up
) ? EXIT_SUCCESS
: EXIT_FAILURE
;
2225 (void)printf("preserve\t%s\n", (up
->u_preserve
) ? "true" :
2227 return EXIT_SUCCESS
;
2233 usermgmt_usage("userdel");
2236 if ((pwp
= getpwnam(*argv
)) == NULL
) {
2237 warnx("No such user `%s'", *argv
);
2238 return EXIT_FAILURE
;
2241 (void)removehomedir(pwp
);
2243 if (up
->u_preserve
) {
2244 up
->u_flags
|= F_SHELL
;
2245 memsave(&up
->u_shell
, _PATH_SBIN_NOLOGIN
,
2246 strlen(_PATH_SBIN_NOLOGIN
));
2247 (void)memset(password
, '*', DES_Len
);
2248 password
[DES_Len
] = 0;
2249 memsave(&up
->u_password
, password
, strlen(password
));
2250 up
->u_flags
|= F_PASSWORD
;
2251 openlog("userdel", LOG_PID
, LOG_USER
);
2252 return moduser(*argv
, *argv
, up
, up
->u_allow_samba
) ?
2253 EXIT_SUCCESS
: EXIT_FAILURE
;
2255 if (!rm_user_from_groups(*argv
)) {
2258 openlog("userdel", LOG_PID
, LOG_USER
);
2259 return moduser(*argv
, *argv
, NULL
, up
->u_allow_samba
) ?
2260 EXIT_SUCCESS
: EXIT_FAILURE
;
2264 #define GROUP_ADD_OPT_EXTENSIONS "r:v"
2266 #define GROUP_ADD_OPT_EXTENSIONS
2271 groupadd(int argc
, char **argv
)
2274 group_t
*gp
= &def
.group
;
2281 read_defaults(&def
);
2282 while ((c
= getopt(argc
, argv
, "g:o" GROUP_ADD_OPT_EXTENSIONS
)) != -1) {
2285 gid
= check_numeric(optarg
, "gid");
2292 (void)save_range(&gp
->g_r
, optarg
);
2299 usermgmt_usage("groupadd");
2306 usermgmt_usage("groupadd");
2308 if (gp
->g_rc
== 0) {
2309 gp
->g_rv
[gp
->g_rc
].r_from
= DEF_LOWUID
;
2310 gp
->g_rv
[gp
->g_rc
].r_to
= DEF_HIGHUID
;
2313 gp
->g_defrc
= gp
->g_rc
;
2320 * Look for a free GID in the command line ranges (if any).
2321 * These start after the ranges specified in the config file.
2323 for (i
= gp
->g_defrc
; !got_id
&& i
< gp
->g_rc
; i
++) {
2324 got_id
= getnextgid(&gid
,
2325 gp
->g_rv
[i
].r_from
, gp
->g_rv
[i
].r_to
);
2328 * If there were no free GIDs in the command line ranges,
2329 * try the ranges from the config file (there will always
2330 * be at least one default).
2332 for (i
= 0; !got_id
&& i
< gp
->g_defrc
; i
++) {
2333 got_id
= getnextgid(&gid
,
2334 gp
->g_rv
[i
].r_from
, gp
->g_rv
[i
].r_to
);
2337 errx(EXIT_FAILURE
, "Can't add group: can't get next gid");
2339 if (!dupgid
&& getgrgid((gid_t
) gid
) != NULL
) {
2340 errx(EXIT_FAILURE
, "Can't add group: gid %d is a duplicate",
2343 if (!valid_group(*argv
)) {
2344 warnx("Invalid group name `%s'", *argv
);
2346 openlog("groupadd", LOG_PID
, LOG_USER
);
2347 if (!creategid(*argv
, gid
, ""))
2350 return EXIT_SUCCESS
;
2354 #define GROUP_DEL_OPT_EXTENSIONS "v"
2356 #define GROUP_DEL_OPT_EXTENSIONS
2359 /* remove a group */
2361 groupdel(int argc
, char **argv
)
2365 while ((c
= getopt(argc
, argv
, "" GROUP_DEL_OPT_EXTENSIONS
)) != -1) {
2373 usermgmt_usage("groupdel");
2380 usermgmt_usage("groupdel");
2382 if (getgrnam(*argv
) == NULL
) {
2383 errx(EXIT_FAILURE
, "No such group `%s'", *argv
);
2386 openlog("groupdel", LOG_PID
, LOG_USER
);
2387 if (!modify_gid(*argv
, NULL
))
2390 return EXIT_SUCCESS
;
2394 #define GROUP_MOD_OPT_EXTENSIONS "v"
2396 #define GROUP_MOD_OPT_EXTENSIONS
2399 /* modify a group */
2401 groupmod(int argc
, char **argv
)
2404 char buf
[MaxEntryLen
];
2415 while ((c
= getopt(argc
, argv
, "g:on:" GROUP_MOD_OPT_EXTENSIONS
)) != -1) {
2418 gid
= check_numeric(optarg
, "gid");
2424 memsave(&newname
, optarg
, strlen(optarg
));
2432 usermgmt_usage("groupmod");
2439 usermgmt_usage("groupmod");
2442 if (gid
< 0 && newname
== NULL
) {
2443 errx(EXIT_FAILURE
, "Nothing to change");
2445 if (dupgid
&& gid
< 0) {
2446 errx(EXIT_FAILURE
, "Duplicate which gid?");
2448 if (!dupgid
&& getgrgid((gid_t
) gid
) != NULL
) {
2449 errx(EXIT_FAILURE
, "Can't modify group: gid %d is a duplicate",
2452 if ((grp
= find_group_info(*argv
)) == NULL
) {
2453 errx(EXIT_FAILURE
, "Can't find group `%s' to modify", *argv
);
2455 if (!is_local(*argv
, _PATH_GROUP
)) {
2456 errx(EXIT_FAILURE
, "Group `%s' must be a local group", *argv
);
2458 if (newname
!= NULL
&& !valid_group(newname
)) {
2459 warnx("Invalid group name `%s'", newname
);
2461 cc
= snprintf(buf
, sizeof(buf
), "%s:%s:%d:",
2462 (newname
) ? newname
: grp
->gr_name
,
2464 (gid
< 0) ? grp
->gr_gid
: gid
);
2465 for (cpp
= grp
->gr_mem
; *cpp
&& cc
< sizeof(buf
) ; cpp
++) {
2466 cc
+= snprintf(&buf
[cc
], sizeof(buf
) - cc
, "%s%s", *cpp
,
2467 (cpp
[1] == NULL
) ? "" : ",");
2469 cc
+= snprintf(&buf
[cc
], sizeof(buf
) - cc
, "\n");
2470 if (newname
!= NULL
)
2472 openlog("groupmod", LOG_PID
, LOG_USER
);
2473 if (!modify_gid(*argv
, buf
))
2476 return EXIT_SUCCESS
;
2480 /* display user information */
2482 userinfo(int argc
, char **argv
)
2486 char buf
[MaxEntryLen
];
2494 while ((i
= getopt(argc
, argv
, "e")) != -1) {
2500 usermgmt_usage("userinfo");
2507 usermgmt_usage("userinfo");
2509 pwp
= find_user_info(*argv
);
2511 exit((pwp
) ? EXIT_SUCCESS
: EXIT_FAILURE
);
2514 errx(EXIT_FAILURE
, "Can't find user `%s'", *argv
);
2516 (void)printf("login\t%s\n", pwp
->pw_name
);
2517 (void)printf("passwd\t%s\n", pwp
->pw_passwd
);
2518 (void)printf("uid\t%d\n", pwp
->pw_uid
);
2519 for (cc
= 0 ; (grp
= getgrent()) != NULL
; ) {
2520 for (cpp
= grp
->gr_mem
; *cpp
; cpp
++) {
2521 if (strcmp(*cpp
, *argv
) == 0 &&
2522 grp
->gr_gid
!= pwp
->pw_gid
) {
2523 cc
+= snprintf(&buf
[cc
], sizeof(buf
) - cc
,
2524 "%s ", grp
->gr_name
);
2528 if ((grp
= getgrgid(pwp
->pw_gid
)) == NULL
) {
2529 (void)printf("groups\t%d %s\n", pwp
->pw_gid
, buf
);
2531 (void)printf("groups\t%s %s\n", grp
->gr_name
, buf
);
2533 (void)printf("change\t%s", pwp
->pw_change
> 0 ?
2534 ctime(&pwp
->pw_change
) : pwp
->pw_change
== -1 ?
2535 "NEXT LOGIN\n" : "NEVER\n");
2536 (void)printf("class\t%s\n", pwp
->pw_class
);
2537 (void)printf("gecos\t%s\n", pwp
->pw_gecos
);
2538 (void)printf("dir\t%s\n", pwp
->pw_dir
);
2539 (void)printf("shell\t%s\n", pwp
->pw_shell
);
2540 (void)printf("expire\t%s", pwp
->pw_expire
?
2541 ctime(&pwp
->pw_expire
) : "NEVER\n");
2542 return EXIT_SUCCESS
;
2547 /* display user information */
2549 groupinfo(int argc
, char **argv
)
2557 while ((i
= getopt(argc
, argv
, "ev")) != -1) {
2566 usermgmt_usage("groupinfo");
2573 usermgmt_usage("groupinfo");
2575 grp
= find_group_info(*argv
);
2577 exit((grp
) ? EXIT_SUCCESS
: EXIT_FAILURE
);
2580 errx(EXIT_FAILURE
, "Can't find group `%s'", *argv
);
2582 (void)printf("name\t%s\n", grp
->gr_name
);
2583 (void)printf("passwd\t%s\n", grp
->gr_passwd
);
2584 (void)printf("gid\t%d\n", grp
->gr_gid
);
2585 (void)printf("members\t");
2586 for (cpp
= grp
->gr_mem
; *cpp
; cpp
++) {
2587 (void)printf("%s", *cpp
);
2589 (void) printf(", ");
2592 (void)fputc('\n', stdout
);
2593 return EXIT_SUCCESS
;