summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--adduser/adduser.8479
-rw-r--r--adduser/rmuser.8210
-rw-r--r--libutil/gr_util.c2
-rw-r--r--libutil/pw_util.c19
-rw-r--r--pw/Makefile14
-rw-r--r--pw/fileupd.c68
-rw-r--r--pw/grupd.c129
-rw-r--r--pw/pw.88
-rw-r--r--pw/pw.c452
-rw-r--r--pw/pw.h124
-rw-r--r--pw/pw_group.c424
-rw-r--r--pw/pw_log.c68
-rw-r--r--pw/pw_nis.c94
-rw-r--r--pw/pw_user.c60
-rw-r--r--pw/pw_vpw.c302
-rw-r--r--pw/pwupd.c180
-rw-r--r--pw/pwupd.h121
17 files changed, 2733 insertions, 21 deletions
diff --git a/adduser/adduser.8 b/adduser/adduser.8
new file mode 100644
index 0000000..f23ecff
--- /dev/null
+++ b/adduser/adduser.8
@@ -0,0 +1,479 @@
+.\" Copyright (c) 1995-1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+.\" All rights reserved.
+.\" Copyright (c) 2002-2004 Michael Telahun Makonnen <mtm@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 15, 2012
+.Dt ADDUSER 8
+.Os
+.Sh NAME
+.Nm adduser
+.Nd command for adding new users
+.Sh SYNOPSIS
+.Nm
+.Op Fl CDENShq
+.Op Fl G Ar groups
+.Op Fl L Ar login_class
+.Op Fl M Ar mode
+.Op Fl d Ar partition
+.Op Fl f Ar file
+.Op Fl g Ar login_group
+.Op Fl k Ar dotdir
+.Op Fl m Ar message_file
+.Op Fl s Ar shell
+.Op Fl u Ar uid_start
+.Op Fl w Ar type
+.Sh DESCRIPTION
+The
+.Nm
+utility is a shell script, implemented around the
+.Xr pw 8
+command, for adding new users.
+It creates passwd/group entries, a home directory,
+copies dotfiles and sends the new user a welcome message.
+It supports two modes of operation.
+It may be used interactively
+at the command line to add one user at a time, or it may be directed
+to get the list of new users from a file and operate in batch mode
+without requiring any user interaction.
+.Sh RESTRICTIONS
+.Bl -tag -width indent
+.It username
+Login name.
+The user name is restricted to whatever
+.Xr pw 8
+will accept.
+Generally this means it
+may contain only lowercase characters or digits but cannot begin with the
+.Ql -
+character.
+Maximum length
+is 16 characters.
+The reasons for this limit are historical.
+Given that people have traditionally wanted to break this
+limit for aesthetic reasons, it has never been of great importance to break
+such a basic fundamental parameter in
+.Ux .
+You can change
+.Dv UT_NAMESIZE
+in
+.In utmp.h
+and recompile the
+world; people have done this and it works, but you will have problems
+with any precompiled programs, or source that assumes the 8-character
+name limit, such as NIS.
+The NIS protocol mandates an 8-character username.
+If you need a longer login name for e-mail addresses,
+you can define an alias in
+.Pa /etc/mail/aliases .
+.It "full name"
+This is typically known as the gecos field and usually contains
+the user's full name.
+Additionally, it may contain a comma separated
+list of values such as office number and work and home phones.
+If the
+name contains an ampersand it will be replaced by the capitalized
+login name when displayed by other programs.
+The
+.Ql \&:
+character is not allowed.
+.It shell
+Unless the
+.Fl S
+argument is supplied only valid shells from the shell database
+.Pq Pa /etc/shells
+are allowed.
+In addition,
+either the base name or the full path of the shell may be supplied.
+.It UID
+Automatically generated or your choice.
+It must be less than 32000.
+.It "GID/login group"
+Automatically generated or your choice.
+It must be less than 32000.
+.It password
+You may choose an empty password, disable the password, use a
+randomly generated password or specify your own plaintext password,
+which will be encrypted before being stored in the user database.
+.El
+.Sh UNIQUE GROUPS
+Perhaps you are missing what
+.Em can
+be done with this scheme that falls apart
+with most other schemes.
+With each user in their own group,
+they can safely run with a umask of 002 instead of the usual 022
+and create files in their home directory
+without worrying about others being able to change them.
+.Pp
+For a shared area you create a separate UID/GID, you place each person
+that should be able to access this area into that new group.
+.Pp
+This model of UID/GID administration allows far greater flexibility than lumping
+users into groups and having to muck with the umask when working in a shared
+area.
+.Pp
+I have been using this model for almost 10 years and found that it works
+for most situations, and has never gotten in the way.
+(Rod Grimes)
+.Sh CONFIGURATION
+The
+.Nm
+utility reads its configuration information from
+.Pa /etc/adduser.conf .
+If this file does not exist, it will use predefined defaults.
+While this file may be edited by hand,
+the safer option is to use the
+.Fl C
+command line argument.
+With this argument,
+.Nm
+will start interactive input, save the answers to its prompts in
+.Pa /etc/adduser.conf ,
+and promptly exit without modifying the user
+database.
+Options specified on the command line will take precedence over
+any values saved in this file.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl C
+Create new configuration file and exit.
+This option is mutually exclusive with the
+.Fl f
+option.
+.It Fl d Ar partition
+Home partition.
+Default partition, under which all user directories
+will be located.
+The
+.Pa /nonexistent
+partition is considered special.
+The
+.Nm
+script will not create and populate a home directory by that name.
+Otherwise,
+by default it attempts to create a home directory.
+.It Fl D
+Do not attempt to create the home directory.
+.It Fl E
+Disable the account.
+This option will lock the account by prepending the string
+.Dq Li *LOCKED*
+to the password field.
+The account may be unlocked
+by the super-user with the
+.Xr pw 8
+command:
+.Pp
+.D1 Nm pw Cm unlock Op Ar name | uid
+.It Fl f Ar file
+Get the list of accounts to create from
+.Ar file .
+If
+.Ar file
+is
+.Dq Fl ,
+then get the list from standard input.
+If this option is specified,
+.Nm
+will operate in batch mode and will not seek any user input.
+If an error is encountered while processing an account, it will write a
+message to standard error and move to the next account.
+The format
+of the input file is described below.
+.It Fl g Ar login_group
+Normally,
+if no login group is specified,
+it is assumed to be the same as the username.
+This option makes
+.Ar login_group
+the default.
+.It Fl G Ar groups
+Space-separated list of additional groups.
+This option allows the user to specify additional groups to add users to.
+The user is a member of these groups in addition to their login group.
+.It Fl h
+Print a summary of options and exit.
+.It Fl k Ar directory
+Copy files from
+.Ar directory
+into the home
+directory of new users;
+.Pa dot.foo
+will be renamed to
+.Pa .foo .
+.It Fl L Ar login_class
+Set default login class.
+.It Fl m Ar file
+Send new users a welcome message from
+.Ar file .
+Specifying a value of
+.Cm no
+for
+.Ar file
+causes no message to be sent to new users.
+Please note that the message
+file can reference the internal variables of the
+.Nm
+script.
+.It Fl M Ar mode
+Create the home directory with permissions set to
+.Ar mode .
+.It Fl N
+Do not read the default configuration file.
+.It Fl q
+Minimal user feedback.
+In particular, the random password will not be echoed to
+standard output.
+.It Fl s Ar shell
+Default shell for new users.
+The
+.Ar shell
+argument may be the base name of the shell or the full path.
+Unless the
+.Fl S
+argument is supplied the shell must exist in
+.Pa /etc/shells
+or be the special shell
+.Em nologin
+to be considered a valid shell.
+.It Fl S
+The existence or validity of the specified shell will not be checked.
+.It Fl u Ar uid
+Use UIDs from
+.Ar uid
+on up.
+.It Fl w Ar type
+Password type.
+The
+.Nm
+utility allows the user to specify what type of password to create.
+The
+.Ar type
+argument may have one of the following values:
+.Bl -tag -width ".Cm random"
+.It Cm no
+Disable the password.
+Instead of an encrypted string, the password field will contain a single
+.Ql *
+character.
+The user may not log in until the super-user
+manually enables the password.
+.It Cm none
+Use an empty string as the password.
+.It Cm yes
+Use a user-supplied string as the password.
+In interactive mode,
+the user will be prompted for the password.
+In batch mode, the
+last (10th) field in the line is assumed to be the password.
+.It Cm random
+Generate a random string and use it as a password.
+The password will be echoed to standard output.
+In addition, it will be available for inclusion in the message file in the
+.Va randompass
+variable.
+.El
+.El
+.Sh FORMAT
+When the
+.Fl f
+option is used, the account information must be stored in a specific
+format.
+All empty lines or lines beginning with a
+.Ql #
+will be ignored.
+All other lines must contain ten colon
+.Pq Ql \&:
+separated fields as described below.
+Command line options do not take precedence
+over values in the fields.
+Only the password field may contain a
+.Ql \&:
+character as part of the string.
+.Pp
+.Sm off
+.D1 Ar name : uid : gid : class : change : expire : gecos : home_dir : shell : password
+.Sm on
+.Bl -tag -width ".Ar password"
+.It Ar name
+Login name.
+This field may not be empty.
+.It Ar uid
+Numeric login user ID.
+If this field is left empty, it will be automatically generated.
+.It Ar gid
+Numeric primary group ID.
+If this field is left empty, a group with the
+same name as the user name will be created and its GID will be used
+instead.
+.It Ar class
+Login class.
+This field may be left empty.
+.It Ar change
+Password ageing.
+This field denotes the password change date for the account.
+The format of this field is the same as the format of the
+.Fl p
+argument to
+.Xr pw 8 .
+It may be
+.Ar dd Ns - Ns Ar mmm Ns - Ns Ar yy Ns Op Ar yy ,
+where
+.Ar dd
+is for the day,
+.Ar mmm
+is for the month in numeric or alphabetical format:
+.Dq Li 10
+or
+.Dq Li Oct ,
+and
+.Ar yy Ns Op Ar yy
+is the four or two digit year.
+To denote a time relative to the current date the format is:
+.No + Ns Ar n Ns Op Ar mhdwoy ,
+where
+.Ar n
+denotes a number, followed by the minutes, hours, days, weeks,
+months or years after which the password must be changed.
+This field may be left empty to turn it off.
+.It Ar expire
+Account expiration.
+This field denotes the expiry date of the account.
+The account may not be used after the specified date.
+The format of this field is the same as that for password ageing.
+This field may be left empty to turn it off.
+.It Ar gecos
+Full name and other extra information about the user.
+.It Ar home_dir
+Home directory.
+If this field is left empty, it will be automatically
+created by appending the username to the home partition.
+The
+.Pa /nonexistent
+home directory is considered special and
+is understood to mean that no home directory is to be
+created for the user.
+.It Ar shell
+Login shell.
+This field should contain either the base name or
+the full path to a valid login shell.
+.It Ar password
+User password.
+This field should contain a plaintext string, which will
+be encrypted before being placed in the user database.
+If the password type is
+.Cm yes
+and this field is empty, it is assumed the account will have an empty password.
+If the password type is
+.Cm random
+and this field is
+.Em not
+empty, its contents will be used
+as a password.
+This field will be ignored if the
+.Fl w
+option is used with a
+.Cm no
+or
+.Cm none
+argument.
+Be careful not to terminate this field with a closing
+.Ql \&:
+because it will be treated as part of the password.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /etc/adduser.message" -compact
+.It Pa /etc/master.passwd
+user database
+.It Pa /etc/group
+group database
+.It Pa /etc/shells
+shell database
+.It Pa /etc/login.conf
+login classes database
+.It Pa /etc/adduser.conf
+configuration file for
+.Nm
+.It Pa /etc/adduser.message
+message file for
+.Nm
+.It Pa /usr/share/skel
+skeletal login directory
+.It Pa /var/log/adduser
+logfile for
+.Nm
+.El
+.Sh SEE ALSO
+.Xr chpass 1 ,
+.Xr passwd 1 ,
+.Xr adduser.conf 5 ,
+.Xr aliases 5 ,
+.Xr group 5 ,
+.Xr login.conf 5 ,
+.Xr passwd 5 ,
+.Xr shells 5 ,
+.Xr adding_user 8 ,
+.Xr pw 8 ,
+.Xr pwd_mkdb 8 ,
+.Xr rmuser 8 ,
+.Xr vipw 8 ,
+.Xr yp 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 2.1 .
+.Sh AUTHORS
+.An -nosplit
+This manual page and the original script, in Perl, was written by
+.An Wolfram Schneider Aq wosch@FreeBSD.org .
+The replacement script, written as a Bourne
+shell script with some enhancements, and the man page modification that
+came with it were done by
+.An Mike Makonnen Aq mtm@identd.net .
+.Sh BUGS
+In order for
+.Nm
+to correctly expand variables such as
+.Va $username
+and
+.Va $randompass
+in the message sent to new users, it must let the shell evaluate
+each line of the message file.
+This means that shell commands can also be embedded in the message file.
+The
+.Nm
+utility attempts to mitigate the possibility of an attacker using this
+feature by refusing to evaluate the file if it is not owned and writable
+only by the root user.
+In addition, shell special characters and operators will have to be
+escaped when used in the message file.
+.Pp
+Also, password ageing and account expiry times are currently settable
+only in batch mode or when specified in
+.Pa /etc/adduser.conf .
+The user should be able to set them in interactive mode as well.
diff --git a/adduser/rmuser.8 b/adduser/rmuser.8
new file mode 100644
index 0000000..e24d5ee
--- /dev/null
+++ b/adduser/rmuser.8
@@ -0,0 +1,210 @@
+.\" Copyright 1995, 1996, 1997
+.\" Guy Helmer, Ames, Iowa 50014. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer as
+.\" the first lines of this file unmodified.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY GUY HELMER ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL GUY HELMER BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd May 10, 2002
+.Dt RMUSER 8
+.Os
+.Sh NAME
+.Nm rmuser
+.Nd remove users from the system
+.Sh SYNOPSIS
+.Nm
+.Op Fl yv
+.Op Fl f Ar file
+.Op Ar username ...
+.Sh DESCRIPTION
+The
+.Nm
+utility removes one or more users submitted on the command line
+or from a file.
+In removing a user from the system, this utility:
+.Bl -enum
+.It
+Removes the user's
+.Xr crontab 1
+entry (if any).
+.It
+Removes any
+.Xr at 1
+jobs belonging to the user.
+.It
+Sends a
+.Dv SIGKILL
+signal to all processes owned by the user.
+.It
+Removes the user from the system's local password file.
+.It
+Removes the user's home directory (if it is owned by the user),
+including handling of symbolic links in the path to the actual home
+directory.
+.It
+Removes the incoming mail and POP daemon mail files belonging to the
+user from
+.Pa /var/mail .
+.It
+Removes all files owned by the user from
+.Pa /tmp , /var/tmp ,
+and
+.Pa /var/tmp/vi.recover .
+.It
+Removes the username from all groups to which it belongs in
+.Pa /etc/group .
+(If a group becomes empty and the group name is the same as the username,
+the group is removed; this complements
+.Xr adduser 8 Ns 's
+per-user unique groups.)
+.It
+Removes all message queues, shared memory segments and
+semaphores owned by the user.
+.El
+.Pp
+The
+.Nm
+utility refuses to remove users whose UID is 0 (typically root), since
+certain actions (namely, killing all the user's processes, and perhaps
+removing the user's home directory) would cause damage to a running system.
+If it is necessary to remove a user whose UID is 0, see
+.Xr vipw 8
+for information on directly editing the password file.
+.Pp
+If
+.Nm
+was not invoked with the
+.Fl y
+option, it will
+show the selected user's password file entry and ask for confirmation
+that the user be removed.
+It will then ask for confirmation to delete
+the user's home directory.
+If the answer is in the affirmative, the home
+directory and any files and subdirectories under it will be deleted only if
+they are owned by the user.
+See
+.Xr pw 8
+for more details.
+.Pp
+As
+.Nm
+operates, it informs the user regarding the current activity.
+If any
+errors occur, they are posted to standard error and, if it is possible for
+.Nm
+to continue, it will.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Ar username"
+.It Fl f Ar file
+The
+.Nm
+utility will get a list of users to be removed from
+.Ar file ,
+which will contain one user per line.
+Anything following a hash mark
+.Pq Ql # ,
+including the hash mark itself, is considered a comment and will not
+be processed.
+If the file is owned by anyone other than a user with
+UID 0, or is writable by anyone other than the owner,
+.Nm
+will refuse to continue.
+.It Fl y
+Implicitly answer
+.Dq Li yes
+to any and all prompts.
+Currently, this includes
+prompts on whether to remove the specified user and whether to remove
+the home directory.
+This option requires that either the
+.Fl f
+option be used, or one or more user names be given as command line
+arguments.
+.It Fl v
+Enable verbose mode.
+Normally,
+the output includes one line per removed user;
+however,
+with this option
+.Nm
+will be much more chatty about the steps taken.
+.It Ar username
+Identifies one or more users to be removed; if not present,
+.Nm
+interactively asks for one or more users to be removed.
+.El
+.Sh FILES
+.Bl -tag -width "Pa /etc/master.passwd" -compact
+.It Pa /etc/master.passwd
+.It Pa /etc/passwd
+.It Pa /etc/group
+.It Pa /etc/spwd.db
+.It Pa /etc/pwd.db
+.El
+.Sh SEE ALSO
+.Xr at 1 ,
+.Xr chpass 1 ,
+.Xr crontab 1 ,
+.Xr finger 1 ,
+.Xr passwd 1 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr adduser 8 ,
+.Xr pw 8 ,
+.Xr pwd_mkdb 8 ,
+.Xr vipw 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 2.2 .
+.Sh BUGS
+The
+.Nm
+utility does not comprehensively search the file system for all files
+owned by the removed user and remove them; to do so on a system
+of any size is prohibitively slow and I/O intensive.
+It is also unable to remove symbolic links that were created by the
+user in
+.Pa /tmp
+or
+.Pa /var/tmp ,
+as symbolic links on
+.Bx 4.4
+file systems do not contain information
+as to who created them.
+Also, there may be other files created in
+.Pa /var/mail
+other than
+.Pa /var/mail/ Ns Ar username
+and
+.Pa /var/mail/.pop. Ns Ar username
+that are not owned by the removed user but should be removed.
+.Pp
+The
+.Nm
+utility has no knowledge of YP/NIS, and it operates only on the
+local password file.
diff --git a/libutil/gr_util.c b/libutil/gr_util.c
index 0173595..6d96d5e 100644
--- a/libutil/gr_util.c
+++ b/libutil/gr_util.c
@@ -63,6 +63,7 @@ static const char group_line_format[] = "%s:%s:%ju:";
int
gr_init(const char *dir, const char *group)
{
+
if (dir == NULL) {
strcpy(group_dir, _PATH_ETC);
} else {
@@ -88,6 +89,7 @@ gr_init(const char *dir, const char *group)
}
strcpy(group_file, group);
}
+
initialized = 1;
return (0);
}
diff --git a/libutil/pw_util.c b/libutil/pw_util.c
index 63c63de..4bf3001 100644
--- a/libutil/pw_util.c
+++ b/libutil/pw_util.c
@@ -437,14 +437,21 @@ pw_copy(int ffd, int tfd, const struct passwd *pw, struct passwd *old_pw)
size_t len;
int eof, readlen;
- spw = pw;
+ if (old_pw == NULL && pw == NULL)
+ return (-1);
+
+ spw = old_pw;
+ /* deleting a user */
if (pw == NULL) {
line = NULL;
- if (old_pw == NULL)
+ } else {
+ if ((line = pw_make(pw)) == NULL)
return (-1);
- spw = old_pw;
- } else if ((line = pw_make(pw)) == NULL)
- return (-1);
+ }
+
+ /* adding a user */
+ if (spw == NULL)
+ spw = pw;
eof = 0;
len = 0;
@@ -511,7 +518,7 @@ pw_copy(int ffd, int tfd, const struct passwd *pw, struct passwd *old_pw)
*/
*q = t;
- if (fpw == NULL || fpw->pw_uid != spw->pw_uid) {
+ if (fpw == NULL || strcmp(fpw->pw_name, spw->pw_name) != 0) {
/* nope */
if (fpw != NULL)
free(fpw);
diff --git a/pw/Makefile b/pw/Makefile
new file mode 100644
index 0000000..eae0b87
--- /dev/null
+++ b/pw/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+PROG= pw
+MAN= pw.conf.5 pw.8
+SRCS= pw.c pw_conf.c pw_user.c pw_group.c pw_log.c pw_nis.c pw_vpw.c \
+ grupd.c pwupd.c fileupd.c psdate.c \
+ bitmap.c cpdir.c rm_r.c
+
+WARNS?= 2
+
+DPADD= ${LIBCRYPT} ${LIBUTIL}
+LDADD= -lcrypt -lutil
+
+.include <bsd.prog.mk>
diff --git a/pw/fileupd.c b/pw/fileupd.c
new file mode 100644
index 0000000..7df4bb1
--- /dev/null
+++ b/pw/fileupd.c
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "pwupd.h"
+
+int
+extendline(char **buf, int * buflen, int needed)
+{
+ if (needed > *buflen) {
+ char *tmp = realloc(*buf, needed);
+ if (tmp == NULL)
+ return -1;
+ *buf = tmp;
+ *buflen = needed;
+ }
+ return *buflen;
+}
+
+int
+extendarray(char ***buf, int * buflen, int needed)
+{
+ if (needed > *buflen) {
+ char **tmp = realloc(*buf, needed * sizeof(char *));
+ if (tmp == NULL)
+ return -1;
+ *buf = tmp;
+ *buflen = needed;
+ }
+ return *buflen;
+}
diff --git a/pw/grupd.c b/pw/grupd.c
new file mode 100644
index 0000000..e9f6b5e
--- /dev/null
+++ b/pw/grupd.c
@@ -0,0 +1,129 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <grp.h>
+#include <libutil.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include "pwupd.h"
+
+static char * grpath = _PATH_PWD;
+
+int
+setgrdir(const char * dir)
+{
+ if (dir == NULL)
+ return -1;
+ else {
+ char * d = malloc(strlen(dir)+1);
+ if (d == NULL)
+ return -1;
+ grpath = strcpy(d, dir);
+ }
+ return 0;
+}
+
+char *
+getgrpath(const char * file)
+{
+ static char pathbuf[MAXPATHLEN];
+
+ snprintf(pathbuf, sizeof pathbuf, "%s/%s", grpath, file);
+ return pathbuf;
+}
+
+static int
+gr_update(struct group * grp, char const * group)
+{
+ int pfd, tfd;
+ struct group *gr = NULL;
+ struct group *old_gr = NULL;
+
+ if (grp != NULL)
+ gr = gr_dup(grp);
+
+ if (group != NULL)
+ old_gr = GETGRNAM(group);
+
+ if (gr_init(grpath, NULL))
+ err(1, "gr_init()");
+
+ if ((pfd = gr_lock()) == -1) {
+ gr_fini();
+ err(1, "gr_lock()");
+ }
+ if ((tfd = gr_tmp(-1)) == -1) {
+ gr_fini();
+ err(1, "gr_tmp()");
+ }
+ if (gr_copy(pfd, tfd, gr, old_gr) == -1) {
+ gr_fini();
+ err(1, "gr_copy()");
+ }
+ if (gr_mkdb() == -1) {
+ gr_fini();
+ err(1, "gr_mkdb()");
+ }
+ free(gr);
+ gr_fini();
+ return 0;
+}
+
+
+int
+addgrent(struct group * grp)
+{
+ return gr_update(grp, NULL);
+}
+
+int
+chggrent(char const * login, struct group * grp)
+{
+ return gr_update(grp, login);
+}
+
+int
+delgrent(struct group * grp)
+{
+ char group[MAXLOGNAME];
+
+ strlcpy(group, grp->gr_name, MAXLOGNAME);
+
+ return gr_update(NULL, group);
+}
diff --git a/pw/pw.8 b/pw/pw.8
index 8b21107..076f2eb 100644
--- a/pw/pw.8
+++ b/pw/pw.8
@@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
-.Dd December 21, 2011
+.Dd October 29, 2012
.Dt PW 8
.Os
.Sh NAME
@@ -904,12 +904,6 @@ A Version 7 format password file
The user capabilities database
.It Pa /etc/group
The group database
-.It Pa /etc/master.passwd.new
-Temporary copy of the master password file
-.It Pa /etc/passwd.new
-Temporary copy of the Version 7 password file
-.It Pa /etc/group.new
-Temporary copy of the group file
.It Pa /etc/pw.conf
Pw default options file
.It Pa /var/log/userlog
diff --git a/pw/pw.c b/pw/pw.c
new file mode 100644
index 0000000..b0ac728
--- /dev/null
+++ b/pw/pw.c
@@ -0,0 +1,452 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <err.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <paths.h>
+#include <sys/wait.h>
+#include "pw.h"
+
+#if !defined(_PATH_YP)
+#define _PATH_YP "/var/yp/"
+#endif
+const char *Modes[] = {
+ "add", "del", "mod", "show", "next",
+ NULL};
+const char *Which[] = {"user", "group", NULL};
+static const char *Combo1[] = {
+ "useradd", "userdel", "usermod", "usershow", "usernext",
+ "lock", "unlock",
+ "groupadd", "groupdel", "groupmod", "groupshow", "groupnext",
+ NULL};
+static const char *Combo2[] = {
+ "adduser", "deluser", "moduser", "showuser", "nextuser",
+ "lock", "unlock",
+ "addgroup", "delgroup", "modgroup", "showgroup", "nextgroup",
+ NULL};
+
+struct pwf PWF =
+{
+ 0,
+ setpwent,
+ endpwent,
+ getpwent,
+ getpwuid,
+ getpwnam,
+ setgrent,
+ endgrent,
+ getgrent,
+ getgrgid,
+ getgrnam,
+
+};
+struct pwf VPWF =
+{
+ 1,
+ vsetpwent,
+ vendpwent,
+ vgetpwent,
+ vgetpwuid,
+ vgetpwnam,
+ vsetgrent,
+ vendgrent,
+ vgetgrent,
+ vgetgrgid,
+ vgetgrnam,
+};
+
+static struct cargs arglist;
+
+static int getindex(const char *words[], const char *word);
+static void cmdhelp(int mode, int which);
+
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int mode = -1;
+ int which = -1;
+ char *config = NULL;
+ struct userconf *cnf;
+
+ static const char *opts[W_NUM][M_NUM] =
+ {
+ { /* user */
+ "V:C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y",
+ "V:C:qn:u:rY",
+ "V:C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:FNPY",
+ "V:C:qn:u:FPa7",
+ "V:C:q",
+ "V:C:q",
+ "V:C:q"
+ },
+ { /* grp */
+ "V:C:qn:g:h:H:M:opNPY",
+ "V:C:qn:g:Y",
+ "V:C:qn:d:g:l:h:H:FM:m:NPY",
+ "V:C:qn:g:FPa",
+ "V:C:q"
+ }
+ };
+
+ static int (*funcs[W_NUM]) (struct userconf * _cnf, int _mode, struct cargs * _args) =
+ { /* Request handlers */
+ pw_user,
+ pw_group
+ };
+
+ LIST_INIT(&arglist);
+
+ (void)setlocale(LC_ALL, "");
+
+ /*
+ * Break off the first couple of words to determine what exactly
+ * we're being asked to do
+ */
+ while (argc > 1) {
+ int tmp;
+
+ if (*argv[1] == '-') {
+ /*
+ * Special case, allow pw -V<dir> <operation> [args] for scripts etc.
+ */
+ if (argv[1][1] == 'V') {
+ optarg = &argv[1][2];
+ if (*optarg == '\0') {
+ optarg = argv[2];
+ ++argv;
+ --argc;
+ }
+ addarg(&arglist, 'V', optarg);
+ } else
+ break;
+ }
+ else if (mode == -1 && (tmp = getindex(Modes, argv[1])) != -1)
+ mode = tmp;
+ else if (which == -1 && (tmp = getindex(Which, argv[1])) != -1)
+ which = tmp;
+ else if ((mode == -1 && which == -1) &&
+ ((tmp = getindex(Combo1, argv[1])) != -1 ||
+ (tmp = getindex(Combo2, argv[1])) != -1)) {
+ which = tmp / M_NUM;
+ mode = tmp % M_NUM;
+ } else if (strcmp(argv[1], "help") == 0 && argv[2] == NULL)
+ cmdhelp(mode, which);
+ else if (which != -1 && mode != -1)
+ addarg(&arglist, 'n', argv[1]);
+ else
+ errx(EX_USAGE, "unknown keyword `%s'", argv[1]);
+ ++argv;
+ --argc;
+ }
+
+ /*
+ * Bail out unless the user is specific!
+ */
+ if (mode == -1 || which == -1)
+ cmdhelp(mode, which);
+
+ /*
+ * We know which mode we're in and what we're about to do, so now
+ * let's dispatch the remaining command line args in a genric way.
+ */
+ optarg = NULL;
+
+ while ((ch = getopt(argc, argv, opts[which][mode])) != -1) {
+ if (ch == '?')
+ errx(EX_USAGE, "unknown switch");
+ else
+ addarg(&arglist, ch, optarg);
+ optarg = NULL;
+ }
+
+ /*
+ * Must be root to attempt an update
+ */
+ if (geteuid() != 0 && mode != M_PRINT && mode != M_NEXT && getarg(&arglist, 'N')==NULL)
+ errx(EX_NOPERM, "you must be root to run this program");
+
+ /*
+ * We should immediately look for the -q 'quiet' switch so that we
+ * don't bother with extraneous errors
+ */
+ if (getarg(&arglist, 'q') != NULL)
+ freopen(_PATH_DEVNULL, "w", stderr);
+
+ /*
+ * Set our base working path if not overridden
+ */
+
+ config = getarg(&arglist, 'C') ? getarg(&arglist, 'C')->val : NULL;
+
+ if (getarg(&arglist, 'V') != NULL) {
+ char * etcpath = getarg(&arglist, 'V')->val;
+ if (*etcpath) {
+ if (config == NULL) { /* Only override config location if -C not specified */
+ config = malloc(MAXPATHLEN);
+ snprintf(config, MAXPATHLEN, "%s/pw.conf", etcpath);
+ }
+ memcpy(&PWF, &VPWF, sizeof PWF);
+ setpwdir(etcpath);
+ setgrdir(etcpath);
+ }
+ }
+
+ /*
+ * Now, let's do the common initialisation
+ */
+ cnf = read_userconfig(config);
+
+ ch = funcs[which] (cnf, mode, &arglist);
+
+ /*
+ * If everything went ok, and we've been asked to update
+ * the NIS maps, then do it now
+ */
+ if (ch == EXIT_SUCCESS && getarg(&arglist, 'Y') != NULL) {
+ pid_t pid;
+
+ fflush(NULL);
+ if (chdir(_PATH_YP) == -1)
+ warn("chdir(" _PATH_YP ")");
+ else if ((pid = fork()) == -1)
+ warn("fork()");
+ else if (pid == 0) {
+ /* Is make anywhere else? */
+ execlp("/usr/bin/make", "make", (char *)NULL);
+ _exit(1);
+ } else {
+ int i;
+ waitpid(pid, &i, 0);
+ if ((i = WEXITSTATUS(i)) != 0)
+ errx(ch, "make exited with status %d", i);
+ else
+ pw_log(cnf, mode, which, "NIS maps updated");
+ }
+ }
+ return ch;
+}
+
+
+static int
+getindex(const char *words[], const char *word)
+{
+ int i = 0;
+
+ while (words[i]) {
+ if (strcmp(words[i], word) == 0)
+ return i;
+ i++;
+ }
+ return -1;
+}
+
+
+/*
+ * This is probably an overkill for a cmdline help system, but it reflects
+ * the complexity of the command line.
+ */
+
+static void
+cmdhelp(int mode, int which)
+{
+ if (which == -1)
+ fprintf(stderr, "usage:\n pw [user|group|lock|unlock] [add|del|mod|show|next] [help|switches/values]\n");
+ else if (mode == -1)
+ fprintf(stderr, "usage:\n pw %s [add|del|mod|show|next] [help|switches/values]\n", Which[which]);
+ else {
+
+ /*
+ * We need to give mode specific help
+ */
+ static const char *help[W_NUM][M_NUM] =
+ {
+ {
+ "usage: pw useradd [name] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ " Adding users:\n"
+ "\t-n name login name\n"
+ "\t-u uid user id\n"
+ "\t-c comment user name/comment\n"
+ "\t-d directory home directory\n"
+ "\t-e date account expiry date\n"
+ "\t-p date password expiry date\n"
+ "\t-g grp initial group\n"
+ "\t-G grp1,grp2 additional groups\n"
+ "\t-m [ -k dir ] create and set up home\n"
+ "\t-M mode home directory permissions\n"
+ "\t-s shell name of login shell\n"
+ "\t-o duplicate uid ok\n"
+ "\t-L class user class\n"
+ "\t-h fd read password on fd\n"
+ "\t-H fd read encrypted password on fd\n"
+ "\t-Y update NIS maps\n"
+ "\t-N no update\n"
+ " Setting defaults:\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-D set user defaults\n"
+ "\t-b dir default home root dir\n"
+ "\t-e period default expiry period\n"
+ "\t-p period default password change period\n"
+ "\t-g group default group\n"
+ "\t-G grp1,grp2 additional groups\n"
+ "\t-L class default user class\n"
+ "\t-k dir default home skeleton\n"
+ "\t-M mode home directory permissions\n"
+ "\t-u min,max set min,max uids\n"
+ "\t-i min,max set min,max gids\n"
+ "\t-w method set default password method\n"
+ "\t-s shell default shell\n"
+ "\t-y path set NIS passwd file path\n",
+ "usage: pw userdel [uid|name] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-n name login name\n"
+ "\t-u uid user id\n"
+ "\t-Y update NIS maps\n"
+ "\t-r remove home & contents\n",
+ "usage: pw usermod [uid|name] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ "\t-F force add if no user\n"
+ "\t-n name login name\n"
+ "\t-u uid user id\n"
+ "\t-c comment user name/comment\n"
+ "\t-d directory home directory\n"
+ "\t-e date account expiry date\n"
+ "\t-p date password expiry date\n"
+ "\t-g grp initial group\n"
+ "\t-G grp1,grp2 additional groups\n"
+ "\t-l name new login name\n"
+ "\t-L class user class\n"
+ "\t-m [ -k dir ] create and set up home\n"
+ "\t-M mode home directory permissions\n"
+ "\t-s shell name of login shell\n"
+ "\t-w method set new password using method\n"
+ "\t-h fd read password on fd\n"
+ "\t-H fd read encrypted password on fd\n"
+ "\t-Y update NIS maps\n"
+ "\t-N no update\n",
+ "usage: pw usershow [uid|name] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-n name login name\n"
+ "\t-u uid user id\n"
+ "\t-F force print\n"
+ "\t-P prettier format\n"
+ "\t-a print all users\n"
+ "\t-7 print in v7 format\n",
+ "usage: pw usernext [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n",
+ "usage pw: lock [switches]\n"
+ "\t-V etcdir alternate /etc locations\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n",
+ "usage pw: unlock [switches]\n"
+ "\t-V etcdir alternate /etc locations\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ },
+ {
+ "usage: pw groupadd [group|gid] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ "\t-n group group name\n"
+ "\t-g gid group id\n"
+ "\t-M usr1,usr2 add users as group members\n"
+ "\t-o duplicate gid ok\n"
+ "\t-Y update NIS maps\n"
+ "\t-N no update\n",
+ "usage: pw groupdel [group|gid] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-n name group name\n"
+ "\t-g gid group id\n"
+ "\t-Y update NIS maps\n",
+ "usage: pw groupmod [group|gid] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ "\t-F force add if not exists\n"
+ "\t-n name group name\n"
+ "\t-g gid group id\n"
+ "\t-M usr1,usr2 replaces users as group members\n"
+ "\t-m usr1,usr2 add users as group members\n"
+ "\t-d usr1,usr2 delete users as group members\n"
+ "\t-l name new group name\n"
+ "\t-Y update NIS maps\n"
+ "\t-N no update\n",
+ "usage: pw groupshow [group|gid] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-n name group name\n"
+ "\t-g gid group id\n"
+ "\t-F force print\n"
+ "\t-P prettier format\n"
+ "\t-a print all accounting groups\n",
+ "usage: pw groupnext [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ }
+ };
+
+ fprintf(stderr, "%s", help[which][mode]);
+ }
+ exit(EXIT_FAILURE);
+}
+
+struct carg *
+getarg(struct cargs * _args, int ch)
+{
+ struct carg *c = LIST_FIRST(_args);
+
+ while (c != NULL && c->ch != ch)
+ c = LIST_NEXT(c, list);
+ return c;
+}
+
+struct carg *
+addarg(struct cargs * _args, int ch, char *argstr)
+{
+ struct carg *ca = malloc(sizeof(struct carg));
+
+ if (ca == NULL)
+ errx(EX_OSERR, "out of memory");
+ ca->ch = ch;
+ ca->val = argstr;
+ LIST_INSERT_HEAD(_args, ca, list);
+ return ca;
+}
diff --git a/pw/pw.h b/pw/pw.h
new file mode 100644
index 0000000..1ff69a6
--- /dev/null
+++ b/pw/pw.h
@@ -0,0 +1,124 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/queue.h>
+#include <sysexits.h>
+
+#include "psdate.h"
+#include "pwupd.h"
+
+enum _mode
+{
+ M_ADD,
+ M_DELETE,
+ M_UPDATE,
+ M_PRINT,
+ M_NEXT,
+ M_LOCK,
+ M_UNLOCK,
+ M_NUM
+};
+
+enum _which
+{
+ W_USER,
+ W_GROUP,
+ W_NUM
+};
+
+struct carg
+{
+ int ch;
+ char *val;
+ LIST_ENTRY(carg) list;
+};
+
+LIST_HEAD(cargs, carg);
+
+struct userconf
+{
+ int default_password; /* Default password for new users? */
+ int reuse_uids; /* Reuse uids? */
+ int reuse_gids; /* Reuse gids? */
+ char *nispasswd; /* Path to NIS version of the passwd file */
+ char *dotdir; /* Where to obtain skeleton files */
+ char *newmail; /* Mail to send to new accounts */
+ char *logfile; /* Where to log changes */
+ char *home; /* Where to create home directory */
+ mode_t homemode; /* Home directory permissions */
+ char *shelldir; /* Where shells are located */
+ char **shells; /* List of shells */
+ char *shell_default; /* Default shell */
+ char *default_group; /* Default group number */
+ char **groups; /* Default (additional) groups */
+ char *default_class; /* Default user class */
+ uid_t min_uid, max_uid; /* Allowed range of uids */
+ gid_t min_gid, max_gid; /* Allowed range of gids */
+ int expire_days; /* Days to expiry */
+ int password_days; /* Days to password expiry */
+ int numgroups; /* (internal) size of default_group array */
+};
+
+#define _DEF_DIRMODE (S_IRWXU | S_IRWXG | S_IRWXO)
+#define _PATH_PW_CONF "/etc/pw.conf"
+#define _UC_MAXLINE 1024
+#define _UC_MAXSHELLS 32
+
+struct userconf *read_userconfig(char const * file);
+int write_userconfig(char const * file);
+struct carg *addarg(struct cargs * _args, int ch, char *argstr);
+struct carg *getarg(struct cargs * _args, int ch);
+
+int pw_user(struct userconf * cnf, int mode, struct cargs * _args);
+int pw_group(struct userconf * cnf, int mode, struct cargs * _args);
+char *pw_checkname(u_char *name, int gecos);
+
+int addnispwent(const char *path, struct passwd *pwd);
+int delnispwent(const char *path, const char *login);
+int chgnispwent(const char *path, const char *login, struct passwd *pwd);
+
+int boolean_val(char const * str, int dflt);
+char const *boolean_str(int val);
+char *newstr(char const * p);
+
+void pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...) __printflike(4, 5);
+char *pw_pwcrypt(char *password);
+
+extern const char *Modes[];
+extern const char *Which[];
diff --git a/pw/pw_group.c b/pw/pw_group.c
new file mode 100644
index 0000000..f4f2116
--- /dev/null
+++ b/pw/pw_group.c
@@ -0,0 +1,424 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <ctype.h>
+#include <err.h>
+#include <termios.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <grp.h>
+#include <libutil.h>
+
+#include "pw.h"
+#include "bitmap.h"
+
+
+static struct passwd *lookup_pwent(const char *user);
+static void delete_members(char ***members, int *grmembers, int *i,
+ struct carg *arg, struct group *grp);
+static int print_group(struct group * grp, int pretty);
+static gid_t gr_gidpolicy(struct userconf * cnf, struct cargs * args);
+
+int
+pw_group(struct userconf * cnf, int mode, struct cargs * args)
+{
+ int rc;
+ struct carg *a_name = getarg(args, 'n');
+ struct carg *a_gid = getarg(args, 'g');
+ struct carg *arg;
+ struct group *grp = NULL;
+ int grmembers = 0;
+ char **members = NULL;
+
+ static struct group fakegroup =
+ {
+ "nogroup",
+ "*",
+ -1,
+ NULL
+ };
+
+ if (mode == M_LOCK || mode == M_UNLOCK)
+ errx(EX_USAGE, "'lock' command is not available for groups");
+
+ /*
+ * With M_NEXT, we only need to return the
+ * next gid to stdout
+ */
+ if (mode == M_NEXT) {
+ gid_t next = gr_gidpolicy(cnf, args);
+ if (getarg(args, 'q'))
+ return next;
+ printf("%ld\n", (long)next);
+ return EXIT_SUCCESS;
+ }
+
+ if (mode == M_PRINT && getarg(args, 'a')) {
+ int pretty = getarg(args, 'P') != NULL;
+
+ SETGRENT();
+ while ((grp = GETGRENT()) != NULL)
+ print_group(grp, pretty);
+ ENDGRENT();
+ return EXIT_SUCCESS;
+ }
+ if (a_gid == NULL) {
+ if (a_name == NULL)
+ errx(EX_DATAERR, "group name or id required");
+
+ if (mode != M_ADD && grp == NULL && isdigit((unsigned char)*a_name->val)) {
+ (a_gid = a_name)->ch = 'g';
+ a_name = NULL;
+ }
+ }
+ grp = (a_name != NULL) ? GETGRNAM(a_name->val) : GETGRGID((gid_t) atoi(a_gid->val));
+
+ if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) {
+ if (a_name == NULL && grp == NULL) /* Try harder */
+ grp = GETGRGID(atoi(a_gid->val));
+
+ if (grp == NULL) {
+ if (mode == M_PRINT && getarg(args, 'F')) {
+ char *fmems[1];
+ fmems[0] = NULL;
+ fakegroup.gr_name = a_name ? a_name->val : "nogroup";
+ fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1;
+ fakegroup.gr_mem = fmems;
+ return print_group(&fakegroup, getarg(args, 'P') != NULL);
+ }
+ errx(EX_DATAERR, "unknown group `%s'", a_name ? a_name->val : a_gid->val);
+ }
+ if (a_name == NULL) /* Needed later */
+ a_name = addarg(args, 'n', grp->gr_name);
+
+ /*
+ * Handle deletions now
+ */
+ if (mode == M_DELETE) {
+ gid_t gid = grp->gr_gid;
+
+ rc = delgrent(grp);
+ if (rc == -1)
+ err(EX_IOERR, "group '%s' not available (NIS?)", grp->gr_name);
+ else if (rc != 0) {
+ warn("group update");
+ return EX_IOERR;
+ }
+ pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid);
+ return EXIT_SUCCESS;
+ } else if (mode == M_PRINT)
+ return print_group(grp, getarg(args, 'P') != NULL);
+
+ if (a_gid)
+ grp->gr_gid = (gid_t) atoi(a_gid->val);
+
+ if ((arg = getarg(args, 'l')) != NULL)
+ grp->gr_name = pw_checkname((u_char *)arg->val, 0);
+ } else {
+ if (a_name == NULL) /* Required */
+ errx(EX_DATAERR, "group name required");
+ else if (grp != NULL) /* Exists */
+ errx(EX_DATAERR, "group name `%s' already exists", a_name->val);
+
+ extendarray(&members, &grmembers, 200);
+ members[0] = NULL;
+ grp = &fakegroup;
+ grp->gr_name = pw_checkname((u_char *)a_name->val, 0);
+ grp->gr_passwd = "*";
+ grp->gr_gid = gr_gidpolicy(cnf, args);
+ grp->gr_mem = members;
+ }
+
+ /*
+ * This allows us to set a group password Group passwords is an
+ * antique idea, rarely used and insecure (no secure database) Should
+ * be discouraged, but it is apparently still supported by some
+ * software.
+ */
+
+ if ((arg = getarg(args, 'h')) != NULL ||
+ (arg = getarg(args, 'H')) != NULL) {
+ if (strcmp(arg->val, "-") == 0)
+ grp->gr_passwd = "*"; /* No access */
+ else {
+ int fd = atoi(arg->val);
+ int precrypt = (arg->ch == 'H');
+ int b;
+ int istty = isatty(fd);
+ struct termios t;
+ char *p, line[256];
+
+ if (istty) {
+ if (tcgetattr(fd, &t) == -1)
+ istty = 0;
+ else {
+ struct termios n = t;
+
+ /* Disable echo */
+ n.c_lflag &= ~(ECHO);
+ tcsetattr(fd, TCSANOW, &n);
+ printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name);
+ fflush(stdout);
+ }
+ }
+ b = read(fd, line, sizeof(line) - 1);
+ if (istty) { /* Restore state */
+ tcsetattr(fd, TCSANOW, &t);
+ fputc('\n', stdout);
+ fflush(stdout);
+ }
+ if (b < 0) {
+ warn("-h file descriptor");
+ return EX_OSERR;
+ }
+ line[b] = '\0';
+ if ((p = strpbrk(line, " \t\r\n")) != NULL)
+ *p = '\0';
+ if (!*line)
+ errx(EX_DATAERR, "empty password read on file descriptor %d", fd);
+ if (precrypt) {
+ if (strchr(line, ':') != NULL)
+ return EX_DATAERR;
+ grp->gr_passwd = line;
+ } else
+ grp->gr_passwd = pw_pwcrypt(line);
+ }
+ }
+
+ if (((arg = getarg(args, 'M')) != NULL ||
+ (arg = getarg(args, 'd')) != NULL ||
+ (arg = getarg(args, 'm')) != NULL) && arg->val) {
+ int i = 0;
+ char *p;
+ struct passwd *pwd;
+
+ /* Make sure this is not stay NULL with -M "" */
+ extendarray(&members, &grmembers, 200);
+ if (arg->ch == 'd')
+ delete_members(&members, &grmembers, &i, arg, grp);
+ else if (arg->ch == 'm') {
+ int k = 0;
+
+ while (grp->gr_mem[k] != NULL) {
+ if (extendarray(&members, &grmembers, i + 2) != -1)
+ members[i++] = grp->gr_mem[k];
+ k++;
+ }
+ }
+
+ if (arg->ch != 'd')
+ for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
+ int j;
+
+ /*
+ * Check for duplicates
+ */
+ pwd = lookup_pwent(p);
+ for (j = 0; j < i && strcmp(members[j], pwd->pw_name) != 0; j++)
+ ;
+ if (j == i && extendarray(&members, &grmembers, i + 2) != -1)
+ members[i++] = newstr(pwd->pw_name);
+ }
+ while (i < grmembers)
+ members[i++] = NULL;
+ grp->gr_mem = members;
+ }
+
+ if (getarg(args, 'N') != NULL)
+ return print_group(grp, getarg(args, 'P') != NULL);
+
+ if (mode == M_ADD && (rc = addgrent(grp)) != 0) {
+ if (rc == -1)
+ warnx("group '%s' already exists", grp->gr_name);
+ else
+ warn("group update");
+ return EX_IOERR;
+ } else if (mode == M_UPDATE && (rc = chggrent(a_name->val, grp)) != 0) {
+ if (rc == -1)
+ warnx("group '%s' not available (NIS?)", grp->gr_name);
+ else
+ warn("group update");
+ return EX_IOERR;
+ }
+ /* grp may have been invalidated */
+ if ((grp = GETGRNAM(a_name->val)) == NULL)
+ errx(EX_SOFTWARE, "group disappeared during update");
+
+ pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid);
+
+ if (members)
+ free(members);
+
+ return EXIT_SUCCESS;
+}
+
+
+/*
+ * Lookup a passwd entry using a name or UID.
+ */
+static struct passwd *
+lookup_pwent(const char *user)
+{
+ struct passwd *pwd;
+
+ if ((pwd = GETPWNAM(user)) == NULL &&
+ (!isdigit((unsigned char)*user) ||
+ (pwd = getpwuid((uid_t) atoi(user))) == NULL))
+ errx(EX_NOUSER, "user `%s' does not exist", user);
+
+ return (pwd);
+}
+
+
+/*
+ * Delete requested members from a group.
+ */
+static void
+delete_members(char ***members, int *grmembers, int *i, struct carg *arg,
+ struct group *grp)
+{
+ bool matchFound;
+ char *user;
+ char *valueCopy;
+ char *valuePtr;
+ int k;
+ struct passwd *pwd;
+
+ k = 0;
+ while (grp->gr_mem[k] != NULL) {
+ matchFound = false;
+ if ((valueCopy = strdup(arg->val)) == NULL)
+ errx(EX_UNAVAILABLE, "out of memory");
+ valuePtr = valueCopy;
+ while ((user = strsep(&valuePtr, ", \t")) != NULL) {
+ pwd = lookup_pwent(user);
+ if (strcmp(grp->gr_mem[k], pwd->pw_name) == 0) {
+ matchFound = true;
+ break;
+ }
+ }
+ free(valueCopy);
+
+ if (!matchFound &&
+ extendarray(members, grmembers, *i + 2) != -1)
+ (*members)[(*i)++] = grp->gr_mem[k];
+
+ k++;
+ }
+
+ return;
+}
+
+
+static gid_t
+gr_gidpolicy(struct userconf * cnf, struct cargs * args)
+{
+ struct group *grp;
+ gid_t gid = (gid_t) - 1;
+ struct carg *a_gid = getarg(args, 'g');
+
+ /*
+ * Check the given gid, if any
+ */
+ if (a_gid != NULL) {
+ gid = (gid_t) atol(a_gid->val);
+
+ if ((grp = GETGRGID(gid)) != NULL && getarg(args, 'o') == NULL)
+ errx(EX_DATAERR, "gid `%ld' has already been allocated", (long) grp->gr_gid);
+ } else {
+ struct bitmap bm;
+
+ /*
+ * We need to allocate the next available gid under one of
+ * two policies a) Grab the first unused gid b) Grab the
+ * highest possible unused gid
+ */
+ if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */
+ cnf->min_gid = 1000;
+ cnf->max_gid = 32000;
+ }
+ bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
+
+ /*
+ * Now, let's fill the bitmap from the password file
+ */
+ SETGRENT();
+ while ((grp = GETGRENT()) != NULL)
+ if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
+ (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
+ bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
+ ENDGRENT();
+
+ /*
+ * Then apply the policy, with fallback to reuse if necessary
+ */
+ if (cnf->reuse_gids)
+ gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
+ else {
+ gid = (gid_t) (bm_lastset(&bm) + 1);
+ if (!bm_isset(&bm, gid))
+ gid += cnf->min_gid;
+ else
+ gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
+ }
+
+ /*
+ * Another sanity check
+ */
+ if (gid < cnf->min_gid || gid > cnf->max_gid)
+ errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used");
+ bm_dealloc(&bm);
+ }
+ return gid;
+}
+
+
+static int
+print_group(struct group * grp, int pretty)
+{
+ if (!pretty) {
+ char *buf = NULL;
+
+ buf = gr_make(grp);
+ fputs(buf, stdout);
+ free(buf);
+ } else {
+ int i;
+
+ printf("Group Name: %-15s #%lu\n"
+ " Members: ",
+ grp->gr_name, (long) grp->gr_gid);
+ for (i = 0; grp->gr_mem[i]; i++)
+ printf("%s%s", i ? "," : "", grp->gr_mem[i]);
+ fputs("\n\n", stdout);
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/pw/pw_log.c b/pw/pw_log.c
new file mode 100644
index 0000000..f16274f
--- /dev/null
+++ b/pw/pw_log.c
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <fcntl.h>
+
+#include "pw.h"
+
+static FILE *logfile = NULL;
+
+void
+pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...)
+{
+ if (cnf->logfile && *cnf->logfile) {
+ if (logfile == NULL) { /* With umask==0 we need to control file access modes on create */
+ int fd = open(cnf->logfile, O_WRONLY | O_CREAT | O_APPEND, 0600);
+
+ if (fd != -1)
+ logfile = fdopen(fd, "a");
+ }
+ if (logfile != NULL) {
+ va_list argp;
+ int l;
+ time_t now = time(NULL);
+ struct tm *t = localtime(&now);
+ char nfmt[256];
+ const char *name;
+
+ if ((name = getenv("LOGNAME")) == NULL && (name = getenv("USER")) == NULL)
+ name = "unknown";
+ /* ISO 8601 International Standard Date format */
+ strftime(nfmt, sizeof nfmt, "%Y-%m-%d %T ", t);
+ l = strlen(nfmt);
+ sprintf(nfmt + strlen(nfmt), "[%s:%s%s] %s\n", name, Which[which], Modes[mode], fmt);
+ va_start(argp, fmt);
+ vfprintf(logfile, nfmt, argp);
+ va_end(argp);
+ fflush(logfile);
+ }
+ }
+}
diff --git a/pw/pw_nis.c b/pw/pw_nis.c
new file mode 100644
index 0000000..af5901a
--- /dev/null
+++ b/pw/pw_nis.c
@@ -0,0 +1,94 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <err.h>
+#include <pwd.h>
+#include <libutil.h>
+
+#include "pw.h"
+
+static int
+pw_nisupdate(const char * path, struct passwd * pwd, char const * user)
+{
+ int pfd, tfd;
+ struct passwd *pw = NULL;
+ struct passwd *old_pw = NULL;
+
+ if (pwd != NULL)
+ pw = pw_dup(pwd);
+
+ if (user != NULL)
+ old_pw = GETPWNAM(user);
+
+ if (pw_init(NULL, path))
+ err(1,"pw_init()");
+ if ((pfd = pw_lock()) == -1) {
+ pw_fini();
+ err(1, "pw_lock()");
+ }
+ if ((tfd = pw_tmp(-1)) == -1) {
+ pw_fini();
+ err(1, "pw_tmp()");
+ }
+ if (pw_copy(pfd, tfd, pw, old_pw) == -1) {
+ pw_fini();
+ err(1, "pw_copy()");
+ }
+ if (rename(pw_tempname(), path) == -1)
+ err(1, "rename()");
+
+ free(pw);
+ pw_fini();
+
+ return (0);
+}
+
+int
+addnispwent(const char *path, struct passwd * pwd)
+{
+ return pw_nisupdate(path, pwd, NULL);
+}
+
+int
+chgnispwent(const char *path, char const * login, struct passwd * pwd)
+{
+ return pw_nisupdate(path, pwd, login);
+}
+
+int
+delnispwent(const char *path, const char *login)
+{
+ return pw_nisupdate(path, NULL, login);
+}
diff --git a/pw/pw_user.c b/pw/pw_user.c
index b59789c..abf1c35 100644
--- a/pw/pw_user.c
+++ b/pw/pw_user.c
@@ -42,6 +42,9 @@ static const char rcsid[] =
#include <sys/resource.h>
#include <unistd.h>
#include <login_cap.h>
+#include <pwd.h>
+#include <grp.h>
+#include <libutil.h>
#include "pw.h"
#include "bitmap.h"
@@ -292,7 +295,6 @@ pw_user(struct userconf * cnf, int mode, struct cargs * args)
if (mode == M_PRINT && getarg(args, 'a')) {
int pretty = getarg(args, 'P') != NULL;
int v7 = getarg(args, '7') != NULL;
-
SETPWENT();
while ((pwd = GETPWENT()) != NULL)
print_user(pwd, pretty, v7);
@@ -315,7 +317,7 @@ pw_user(struct userconf * cnf, int mode, struct cargs * args)
*/
if (mode != M_ADD && pwd == NULL
&& strspn(a_name->val, "0123456789") == strlen(a_name->val)
- && atoi(a_name->val) > 0) { /* Assume uid */
+ && *a_name->val) {
(a_uid = a_name)->ch = 'u';
a_name = NULL;
}
@@ -422,7 +424,24 @@ pw_user(struct userconf * cnf, int mode, struct cargs * args)
/* non-fatal */
}
- editgroups(a_name->val, NULL);
+ grp = GETGRNAM(a_name->val);
+ if (*grp->gr_mem == NULL)
+ delgrent(GETGRNAM(a_name->val));
+ SETGRENT();
+ while ((grp = GETGRENT()) != NULL) {
+ int i;
+ char group[MAXLOGNAME];
+ for (i = 0; grp->gr_mem[i] != NULL; i++) {
+ if (!strcmp(grp->gr_mem[i], a_name->val)) {
+ while (grp->gr_mem[i] != NULL) {
+ grp->gr_mem[i] = grp->gr_mem[i+1];
+ }
+ strlcpy(group, grp->gr_name, MAXLOGNAME);
+ chggrent(group, grp);
+ }
+ }
+ }
+ ENDGRENT();
pw_log(cnf, mode, W_USER, "%s(%ld) account removed", a_name->val, (long) uid);
@@ -725,8 +744,29 @@ pw_user(struct userconf * cnf, int mode, struct cargs * args)
* Ok, user is created or changed - now edit group file
*/
- if (mode == M_ADD || getarg(args, 'G') != NULL)
- editgroups(pwd->pw_name, cnf->groups);
+ if (mode == M_ADD || getarg(args, 'G') != NULL) {
+ int i, j;
+ for (i = 0; cnf->groups[i] != NULL; i++) {
+ grp = GETGRNAM(cnf->groups[i]);
+ for (j = 0; grp->gr_mem[j] != NULL; j++) {
+ if (!strcmp(grp->gr_mem[j], pwd->pw_name))
+ break;
+ }
+ if (grp->gr_mem[j] != NULL) /* user already member of group */
+ continue;
+
+ if (j == 0)
+ grp->gr_mem = NULL;
+
+ grp->gr_mem = reallocf(grp->gr_mem, sizeof(*grp->gr_mem) *
+ (j + 2));
+
+ grp->gr_mem[j] = pwd->pw_name;
+ grp->gr_mem[j+1] = NULL;
+ chggrent(cnf->groups[i], grp);
+ }
+ }
+
/* go get a current version of pwd */
pwd = GETPWNAM(a_name->val);
@@ -1090,10 +1130,14 @@ static int
print_user(struct passwd * pwd, int pretty, int v7)
{
if (!pretty) {
- char buf[_UC_MAXLINE];
+ char *buf;
+
+ if (!v7)
+ pwd->pw_passwd = (pwd->pw_passwd == NULL) ? "" : "*";
- fmtpwentry(buf, pwd, v7 ? PWF_PASSWD : PWF_STANDARD);
- fputs(buf, stdout);
+ buf = v7 ? pw_make_v7(pwd) : pw_make(pwd);
+ printf("%s\n", buf);
+ free(buf);
} else {
int j;
char *p;
diff --git a/pw/pw_vpw.c b/pw/pw_vpw.c
new file mode 100644
index 0000000..674b64f
--- /dev/null
+++ b/pw/pw_vpw.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/param.h>
+
+#include "pwupd.h"
+
+static FILE * pwd_fp = NULL;
+
+void
+vendpwent(void)
+{
+ if (pwd_fp != NULL) {
+ fclose(pwd_fp);
+ pwd_fp = NULL;
+ }
+}
+
+void
+vsetpwent(void)
+{
+ vendpwent();
+}
+
+static struct passwd *
+vnextpwent(char const * nam, uid_t uid, int doclose)
+{
+ struct passwd * pw = NULL;
+ static char pwtmp[1024];
+
+ strlcpy(pwtmp, getpwpath(_MASTERPASSWD), sizeof(pwtmp));
+
+ if (pwd_fp != NULL || (pwd_fp = fopen(pwtmp, "r")) != NULL) {
+ int done = 0;
+
+ static struct passwd pwd;
+
+ while (!done && fgets(pwtmp, sizeof pwtmp, pwd_fp) != NULL)
+ {
+ int i, quickout = 0;
+ char * q;
+ char * p = strchr(pwtmp, '\n');
+
+ if (p == NULL) {
+ while (fgets(pwtmp, sizeof pwtmp, pwd_fp) != NULL && strchr(pwtmp, '\n')==NULL)
+ ; /* Skip long lines */
+ continue;
+ }
+
+ /* skip comments & empty lines */
+ if (*pwtmp =='\n' || *pwtmp == '#')
+ continue;
+
+ i = 0;
+ q = p = pwtmp;
+ bzero(&pwd, sizeof pwd);
+ while (!quickout && (p = strsep(&q, ":\n")) != NULL) {
+ switch (i++)
+ {
+ case 0: /* username */
+ pwd.pw_name = p;
+ if (nam) {
+ if (strcmp(nam, p) == 0)
+ done = 1;
+ else
+ quickout = 1;
+ }
+ break;
+ case 1: /* password */
+ pwd.pw_passwd = p;
+ break;
+ case 2: /* uid */
+ pwd.pw_uid = atoi(p);
+ if (uid != (uid_t)-1) {
+ if (uid == pwd.pw_uid)
+ done = 1;
+ else
+ quickout = 1;
+ }
+ break;
+ case 3: /* gid */
+ pwd.pw_gid = atoi(p);
+ break;
+ case 4: /* class */
+ if (nam == NULL && uid == (uid_t)-1)
+ done = 1;
+ pwd.pw_class = p;
+ break;
+ case 5: /* change */
+ pwd.pw_change = (time_t)atol(p);
+ break;
+ case 6: /* expire */
+ pwd.pw_expire = (time_t)atol(p);
+ break;
+ case 7: /* gecos */
+ pwd.pw_gecos = p;
+ break;
+ case 8: /* directory */
+ pwd.pw_dir = p;
+ break;
+ case 9: /* shell */
+ pwd.pw_shell = p;
+ break;
+ }
+ }
+ }
+ if (doclose)
+ vendpwent();
+ if (done && pwd.pw_name) {
+ pw = &pwd;
+
+ #define CKNULL(s) s = s ? s : ""
+ CKNULL(pwd.pw_passwd);
+ CKNULL(pwd.pw_class);
+ CKNULL(pwd.pw_gecos);
+ CKNULL(pwd.pw_dir);
+ CKNULL(pwd.pw_shell);
+ }
+ }
+ return pw;
+}
+
+struct passwd *
+vgetpwent(void)
+{
+ return vnextpwent(NULL, -1, 0);
+}
+
+struct passwd *
+vgetpwuid(uid_t uid)
+{
+ return vnextpwent(NULL, uid, 1);
+}
+
+struct passwd *
+vgetpwnam(const char * nam)
+{
+ return vnextpwent(nam, -1, 1);
+}
+
+
+static FILE * grp_fp = NULL;
+
+void
+vendgrent(void)
+{
+ if (grp_fp != NULL) {
+ fclose(grp_fp);
+ grp_fp = NULL;
+ }
+}
+
+RET_SETGRENT
+vsetgrent(void)
+{
+ vendgrent();
+#if defined(__FreeBSD__)
+ return 0;
+#endif
+}
+
+static struct group *
+vnextgrent(char const * nam, gid_t gid, int doclose)
+{
+ struct group * gr = NULL;
+
+ static char * grtmp = NULL;
+ static int grlen = 0;
+ static char ** mems = NULL;
+ static int memlen = 0;
+
+ extendline(&grtmp, &grlen, MAXPATHLEN);
+ strlcpy(grtmp, getgrpath(_GROUP), MAXPATHLEN);
+
+ if (grp_fp != NULL || (grp_fp = fopen(grtmp, "r")) != NULL) {
+ int done = 0;
+
+ static struct group grp;
+
+ while (!done && fgets(grtmp, grlen, grp_fp) != NULL)
+ {
+ int i, quickout = 0;
+ int mno = 0;
+ char * q, * p;
+ const char * sep = ":\n";
+
+ if ((p = strchr(grtmp, '\n')) == NULL) {
+ int l;
+ extendline(&grtmp, &grlen, grlen + PWBUFSZ);
+ l = strlen(grtmp);
+ if (fgets(grtmp + l, grlen - l, grp_fp) == NULL)
+ break; /* No newline terminator on last line */
+ }
+ /* Skip comments and empty lines */
+ if (*grtmp == '\n' || *grtmp == '#')
+ continue;
+ i = 0;
+ q = p = grtmp;
+ bzero(&grp, sizeof grp);
+ extendarray(&mems, &memlen, 200);
+ while (!quickout && (p = strsep(&q, sep)) != NULL) {
+ switch (i++)
+ {
+ case 0: /* groupname */
+ grp.gr_name = p;
+ if (nam) {
+ if (strcmp(nam, p) == 0)
+ done = 1;
+ else
+ quickout = 1;
+ }
+ break;
+ case 1: /* password */
+ grp.gr_passwd = p;
+ break;
+ case 2: /* gid */
+ grp.gr_gid = atoi(p);
+ if (gid != (gid_t)-1) {
+ if (gid == (gid_t)grp.gr_gid)
+ done = 1;
+ else
+ quickout = 1;
+ } else if (nam == NULL)
+ done = 1;
+ break;
+ case 3:
+ q = p;
+ sep = ",\n";
+ break;
+ default:
+ if (*p) {
+ extendarray(&mems, &memlen, mno + 2);
+ mems[mno++] = p;
+ }
+ break;
+ }
+ }
+ grp.gr_mem = mems;
+ mems[mno] = NULL;
+ }
+ if (doclose)
+ vendgrent();
+ if (done && grp.gr_name) {
+ gr = &grp;
+
+ CKNULL(grp.gr_passwd);
+ }
+ }
+ return gr;
+}
+
+struct group *
+vgetgrent(void)
+{
+ return vnextgrent(NULL, -1, 0);
+}
+
+
+struct group *
+vgetgrgid(gid_t gid)
+{
+ return vnextgrent(NULL, gid, 1);
+}
+
+struct group *
+vgetgrnam(const char * nam)
+{
+ return vnextgrent(nam, -1, 1);
+}
+
diff --git a/pw/pwupd.c b/pw/pwupd.c
new file mode 100644
index 0000000..4ab0f01
--- /dev/null
+++ b/pw/pwupd.c
@@ -0,0 +1,180 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <pwd.h>
+#include <libutil.h>
+#include <errno.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+
+#include "pwupd.h"
+
+#define HAVE_PWDB_C 1
+#define HAVE_PWDB_U 1
+
+static char pathpwd[] = _PATH_PWD;
+static char * pwpath = pathpwd;
+
+int
+setpwdir(const char * dir)
+{
+ if (dir == NULL)
+ return -1;
+ else {
+ char * d = malloc(strlen(dir)+1);
+ if (d == NULL)
+ return -1;
+ pwpath = strcpy(d, dir);
+ }
+ return 0;
+}
+
+char *
+getpwpath(char const * file)
+{
+ static char pathbuf[MAXPATHLEN];
+
+ snprintf(pathbuf, sizeof pathbuf, "%s/%s", pwpath, file);
+ return pathbuf;
+}
+
+static int
+pwdb(char *arg,...)
+{
+ int i = 0;
+ pid_t pid;
+ va_list ap;
+ char *args[10];
+
+ args[i++] = _PATH_PWD_MKDB;
+ va_start(ap, arg);
+ while (i < 6 && arg != NULL) {
+ args[i++] = arg;
+ arg = va_arg(ap, char *);
+ }
+ if (pwpath != pathpwd) {
+ args[i++] = "-d";
+ args[i++] = pwpath;
+ }
+ args[i++] = getpwpath(_MASTERPASSWD);
+ args[i] = NULL;
+
+ if ((pid = fork()) == -1) /* Error (errno set) */
+ i = errno;
+ else if (pid == 0) { /* Child */
+ execv(args[0], args);
+ _exit(1);
+ } else { /* Parent */
+ waitpid(pid, &i, 0);
+ if (WEXITSTATUS(i))
+ i = EIO;
+ }
+ va_end(ap);
+ return i;
+}
+
+static int
+pw_update(struct passwd * pwd, char const * user)
+{
+ int rc = 0;
+
+ /*
+ * First, let's check the see if the database is alright
+ * Note: -C is only available in FreeBSD 2.2 and above
+ */
+#ifdef HAVE_PWDB_C
+ rc = pwdb("-C", (char *)NULL); /* Check only */
+ if (rc == 0) {
+#else
+ { /* No -C */
+#endif
+ int pfd, tfd;
+ struct passwd *pw = NULL;
+ struct passwd *old_pw = NULL;
+
+ if (pwd != NULL)
+ pw = pw_dup(pwd);
+
+ if (user != NULL)
+ old_pw = GETPWNAM(user);
+
+ if (pw_init(pwpath, NULL))
+ err(1, "pw_init()");
+ if ((pfd = pw_lock()) == -1) {
+ pw_fini();
+ err(1, "pw_lock()");
+ }
+ if ((tfd = pw_tmp(-1)) == -1) {
+ pw_fini();
+ err(1, "pw_tmp()");
+ }
+ if (pw_copy(pfd, tfd, pw, old_pw) == -1) {
+ pw_fini();
+ err(1, "pw_copy()");
+ }
+ if (pw_mkdb(user) == -1) {
+ pw_fini();
+ err(1, "pw_mkdb()");
+ }
+ free(pw);
+ pw_fini();
+ }
+ return 0;
+}
+
+int
+addpwent(struct passwd * pwd)
+{
+ return pw_update(pwd, NULL);
+}
+
+int
+chgpwent(char const * login, struct passwd * pwd)
+{
+ return pw_update(pwd, login);
+}
+
+int
+delpwent(struct passwd * pwd)
+{
+ char login[MAXLOGNAME];
+
+ strlcpy(login, pwd->pw_name, MAXLOGNAME);
+ return pw_update(NULL, login);
+}
diff --git a/pw/pwupd.h b/pw/pwupd.h
new file mode 100644
index 0000000..200ffee
--- /dev/null
+++ b/pw/pwupd.h
@@ -0,0 +1,121 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _PWUPD_H_
+#define _PWUPD_H_
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <sys/cdefs.h>
+
+#if defined(__FreeBSD__)
+#define RET_SETGRENT int
+#else
+#define RET_SETGRENT void
+#endif
+
+struct pwf
+{
+ int _altdir;
+ void (*_setpwent)(void);
+ void (*_endpwent)(void);
+ struct passwd * (*_getpwent)(void);
+ struct passwd * (*_getpwuid)(uid_t uid);
+ struct passwd * (*_getpwnam)(const char * nam);
+ RET_SETGRENT (*_setgrent)(void);
+ void (*_endgrent)(void);
+ struct group * (*_getgrent)(void);
+ struct group * (*_getgrgid)(gid_t gid);
+ struct group * (*_getgrnam)(const char * nam);
+};
+
+extern struct pwf PWF;
+extern struct pwf VPWF;
+
+#define SETPWENT() PWF._setpwent()
+#define ENDPWENT() PWF._endpwent()
+#define GETPWENT() PWF._getpwent()
+#define GETPWUID(uid) PWF._getpwuid(uid)
+#define GETPWNAM(nam) PWF._getpwnam(nam)
+
+#define SETGRENT() PWF._setgrent()
+#define ENDGRENT() PWF._endgrent()
+#define GETGRENT() PWF._getgrent()
+#define GETGRGID(gid) PWF._getgrgid(gid)
+#define GETGRNAM(nam) PWF._getgrnam(nam)
+
+#define PWALTDIR() PWF._altdir
+#ifndef _PATH_PWD
+#define _PATH_PWD "/etc"
+#endif
+#ifndef _GROUP
+#define _GROUP "group"
+#endif
+#ifndef _MASTERPASSWD
+#define _MASTERPASSWD "master.passwd"
+#endif
+
+__BEGIN_DECLS
+int addpwent(struct passwd * pwd);
+int delpwent(struct passwd * pwd);
+int chgpwent(char const * login, struct passwd * pwd);
+
+int setpwdir(const char * dir);
+char * getpwpath(char const * file);
+
+int addgrent(struct group * grp);
+int delgrent(struct group * grp);
+int chggrent(char const * name, struct group * grp);
+int editgroups(char *name, char **groups);
+
+int setgrdir(const char * dir);
+char * getgrpath(const char *file);
+
+void vsetpwent(void);
+void vendpwent(void);
+struct passwd * vgetpwent(void);
+struct passwd * vgetpwuid(uid_t uid);
+struct passwd * vgetpwnam(const char * nam);
+
+struct group * vgetgrent(void);
+struct group * vgetgrgid(gid_t gid);
+struct group * vgetgrnam(const char * nam);
+RET_SETGRENT vsetgrent(void);
+void vendgrent(void);
+
+void copymkdir(char const * dir, char const * skel, mode_t mode, uid_t uid, gid_t gid);
+void rm_r(char const * dir, uid_t uid);
+int extendline(char **buf, int *buflen, int needed);
+int extendarray(char ***buf, int *buflen, int needed);
+__END_DECLS
+
+#define PWBUFSZ 1024
+
+#endif /* !_PWUPD_H */