summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--adduser/Makefile6
-rw-r--r--adduser/adduser.8480
-rw-r--r--adduser/adduser.conf.5221
-rw-r--r--adduser/adduser.sh1052
-rw-r--r--adduser/rmuser.8210
-rw-r--r--adduser/rmuser.sh361
-rw-r--r--chpass/Makefile49
-rw-r--r--chpass/chpass.1492
-rw-r--r--chpass/chpass.c302
-rw-r--r--chpass/chpass.h79
-rw-r--r--chpass/edit.c296
-rw-r--r--chpass/field.c261
-rw-r--r--chpass/table.c69
-rw-r--r--chpass/util.c182
-rw-r--r--libc/gen/pw_scan.c201
-rw-r--r--libc/gen/pw_scan.h36
-rw-r--r--libc/stdlib/strtonum.c68
-rw-r--r--libutil/_secure_path.c74
-rw-r--r--libutil/flopen.c108
-rw-r--r--libutil/gr_util.c250
-rw-r--r--libutil/libutil.h188
-rw-r--r--libutil/login_cap.c819
-rw-r--r--libutil/login_cap.h166
-rw-r--r--libutil/login_crypt.c50
-rw-r--r--libutil/pw_util.c621
-rw-r--r--pw/Makefile12
-rw-r--r--pw/README22
-rw-r--r--pw/bitmap.c132
-rw-r--r--pw/bitmap.h50
-rw-r--r--pw/cpdir.c130
-rw-r--r--pw/edgroup.c229
-rw-r--r--pw/fileupd.c203
-rw-r--r--pw/grupd.c171
-rw-r--r--pw/psdate.c295
-rw-r--r--pw/psdate.h40
-rw-r--r--pw/pw.81001
-rw-r--r--pw/pw.c456
-rw-r--r--pw/pw.conf.5318
-rw-r--r--pw/pw.h132
-rw-r--r--pw/pw_conf.c516
-rw-r--r--pw/pw_group.c423
-rw-r--r--pw/pw_log.c68
-rw-r--r--pw/pw_nis.c72
-rw-r--r--pw/pw_user.c1279
-rw-r--r--pw/pw_vpw.c316
-rw-r--r--pw/pwupd.c213
-rw-r--r--pw/pwupd.h160
-rw-r--r--pw/rm_r.c75
48 files changed, 12954 insertions, 0 deletions
diff --git a/adduser/Makefile b/adduser/Makefile
new file mode 100644
index 0000000..0ca2dae
--- /dev/null
+++ b/adduser/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=adduser.sh rmuser.sh
+MAN= adduser.conf.5 adduser.8 rmuser.8
+
+.include <bsd.prog.mk>
diff --git a/adduser/adduser.8 b/adduser/adduser.8
new file mode 100644
index 0000000..03f7e34
--- /dev/null
+++ b/adduser/adduser.8
@@ -0,0 +1,480 @@
+.\" 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 March 16, 2008
+.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 (like cvs or ncvs on freefall),
+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/adduser.conf.5 b/adduser/adduser.conf.5
new file mode 100644
index 0000000..a78aeea
--- /dev/null
+++ b/adduser/adduser.conf.5
@@ -0,0 +1,221 @@
+.\"
+.\" Copyright (c) 2004 Tom Rhodes
+.\" 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 April 12, 2007
+.Dt ADDUSER.CONF 5
+.Os
+.Sh NAME
+.Nm adduser.conf
+.Nd
+.Xr adduser 8
+configuration file
+.Sh DESCRIPTION
+The
+.Pa /etc/adduser.conf
+file is automatically generated by the
+.Xr adduser 8
+utility when invoked with the
+.Fl C
+command-line option.
+It is not meant to be edited by hand.
+.Pp
+The
+.Pa /etc/adduser.conf
+file is used to pre-set certain configuration options for
+the
+.Xr adduser 8
+utility.
+When
+.Xr adduser 8
+is invoked, it will check to see if this file exists, and
+if so, the configuration will be used or offered as the
+default settings.
+The
+.Nm
+file offers three types of configuration:
+.Bl -bullet
+.It
+Default settings offered by
+.Xr adduser 8 .
+These options are specified in the configuration file and offered
+as the default during every invocation of the
+.Xr adduser 8
+utility.
+.It
+Configuration options which can be set in
+.Nm ,
+but overridden by passing a flag to
+.Xr adduser 8 .
+.It
+Configuration supported by
+.Xr adduser 8
+but not offered by a flag or during initial invocation.
+.El
+.Pp
+In the first case, these options can be set in
+.Nm
+but will still be offered when
+.Xr adduser 8
+is invoked.
+In the second case,
+.Xr adduser 8
+will read the configuration data unless a flag
+has been passed to override it.
+For example, the
+.Va defaultshell
+option.
+In the third case, the configuration will be utilized, but the
+user will never be prompted to modify the default setting by
+either a flag or an
+.Xr adduser 8
+prompt.
+For example, the
+.Va upwexpire
+setting.
+.Pp
+The following configuration options can be set in
+.Nm :
+.Bl -tag -width ".Va defaultgroups" -offset indent
+.It Va defaultLgroup
+The default group new users will be added to.
+.It Va defaultclass
+The default class to place users in as described in
+.Xr login.conf 5 .
+.It Va defaultgroups
+This option is used to specify what other groups the new account
+should be added to.
+.It Va passwdtype
+May be one of
+.Cm no , none , random ,
+or
+.Cm yes ,
+as described in
+.Xr adduser 8 .
+As such, the text is not duplicated here and may be
+read in
+.Xr adduser 8 .
+.It Va homeprefix
+The default home directory prefix, usually
+.Pa /home .
+.It Va defaultshell
+The user's default shell which may be any of the shells listed in
+.Xr shells 5 .
+.It Va udotdir
+Defines the location of the default shell and environment
+configuration files.
+.It Va msgfile
+Location of the default new user message file.
+This message will be sent to all new users if specified
+here or at the
+.Xr adduser 8
+prompt.
+.It Va disableflag
+The default message enclosed in brackets for the
+lock account prompt.
+.It Va upwexpire
+The default password expiration time.
+Format of the date is either a
+.Ux
+time in decimal, or a date in
+.Sm off
+.Ar dd No - Ar mmm No - Ar yy Op Ar yy
+.Sm on
+format, where
+.Ar dd
+is the day,
+.Ar mmm
+is the month in either numeric or
+alphabetic format, and
+.Ar yy Ns Op Ar yy
+is either a two or four digit year.
+This option also accepts a relative date in the form of
+.Sm off
+.Ar n Op Ar m h d w o y
+.Sm on
+where
+.Ar n
+is a decimal, octal (leading 0) or hexadecimal (leading 0x) digit
+followed by the number of Minutes, Hours, Days, Weeks, Months or
+Years from the current date at
+which the expiration time is to be set.
+.It Va uexpire
+The default account expire time.
+The format is similar to the
+.Va upwexpire
+option.
+.It Va ugecos
+The default information to be held in the GECOS field of
+.Pa /etc/master.passwd .
+.It Va uidstart
+The default user ID setting.
+This must be a number above 1000 and fewer than 65534.
+.El
+.Sh EXAMPLES
+The following is an example
+.Nm
+file created with the
+.Fl C
+.Xr adduser 8
+flag and modified.
+.Bd -literal -offset indent
+# Configuration file for adduser(8).
+# NOTE: only *some* variables are saved.
+# Last Modified on Fri Mar 30 14:04:05 EST 2004.
+
+defaultLgroup=
+defaultclass=
+defaultgroups=
+passwdtype=yes
+homeprefix=/home
+defaultshell=/bin/csh
+udotdir=/usr/share/skel
+msgfile=/etc/adduser.msg
+disableflag=
+upwexpire=91d # Expire passwords 91 days after creation.
+.Ed
+.Sh SEE ALSO
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr adduser 8 ,
+.Xr pw 8 ,
+.Xr rmuser 8
+.Sh HISTORY
+The
+.Nm
+manual page first appeared in
+.Fx 5.3 .
+.Sh BUGS
+The internal variables documented here may change without notice.
+Do not rely on them.
+To modify this file invoke
+.Xr adduser 8
+with the
+.Fl C
+option instead.
+.Sh AUTHORS
+This manual page was written by
+.An Tom Rhodes Aq trhodes@FreeBSD.org .
diff --git a/adduser/adduser.sh b/adduser/adduser.sh
new file mode 100644
index 0000000..3d1d6f8
--- /dev/null
+++ b/adduser/adduser.sh
@@ -0,0 +1,1052 @@
+#!/bin/sh
+#
+# Copyright (c) 2002-2004 Michael Telahun Makonnen. 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 ``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 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.
+#
+# Email: Mike Makonnen <mtm@FreeBSD.Org>
+#
+# $FreeBSD$
+#
+
+# err msg
+# Display $msg on stderr, unless we're being quiet.
+#
+err() {
+ if [ -z "$quietflag" ]; then
+ echo 1>&2 ${THISCMD}: ERROR: $*
+ fi
+}
+
+# info msg
+# Display $msg on stdout, unless we're being quiet.
+#
+info() {
+ if [ -z "$quietflag" ]; then
+ echo ${THISCMD}: INFO: $*
+ fi
+}
+
+# get_nextuid
+# Output the value of $_uid if it is available for use. If it
+# is not, output the value of the next higher uid that is available.
+# If a uid is not specified, output the first available uid, as indicated
+# by pw(8).
+#
+get_nextuid () {
+ _uid=$1
+ _nextuid=
+
+ if [ -z "$_uid" ]; then
+ _nextuid="`${PWCMD} usernext | cut -f1 -d:`"
+ else
+ while : ; do
+ ${PWCMD} usershow $_uid > /dev/null 2>&1
+ if [ ! "$?" -eq 0 ]; then
+ _nextuid=$_uid
+ break
+ fi
+ _uid=$(($_uid + 1))
+ done
+ fi
+ echo $_nextuid
+}
+
+# show_usage
+# Display usage information for this utility.
+#
+show_usage() {
+ echo "usage: ${THISCMD} [options]"
+ echo " options may include:"
+ echo " -C save to the configuration file only"
+ echo " -D do not attempt to create the home directory"
+ echo " -E disable this account after creation"
+ echo " -G additional groups to add accounts to"
+ echo " -L login class of the user"
+ echo " -M file permission for home directory"
+ echo " -N do not read configuration file"
+ echo " -S a nonexistent shell is not an error"
+ echo " -d home directory"
+ echo " -f file from which input will be received"
+ echo " -g default login group"
+ echo " -h display this usage message"
+ echo " -k path to skeleton home directory"
+ echo " -m user welcome message file"
+ echo " -q absolute minimal user feedback"
+ echo " -s shell"
+ echo " -u uid to start at"
+ echo " -w password type: no, none, yes or random"
+}
+
+# valid_shells
+# Outputs a list of valid shells from /etc/shells. Only the
+# basename of the shell is output.
+#
+valid_shells() {
+ _prefix=
+ cat ${ETCSHELLS} |
+ while read _path _junk ; do
+ case $_path in
+ \#*|'')
+ ;;
+ *)
+ echo -n "${_prefix}`basename $_path`"
+ _prefix=' '
+ ;;
+ esac
+ done
+
+ # /usr/sbin/nologin is a special case
+ [ -x "${NOLOGIN_PATH}" ] && echo -n " ${NOLOGIN}"
+}
+
+# fullpath_from_shell shell
+# Given $shell, which is either the full path to a shell or
+# the basename component of a valid shell, get the
+# full path to the shell from the /etc/shells file.
+#
+fullpath_from_shell() {
+ _shell=$1
+ [ -z "$_shell" ] && return 1
+
+ # /usr/sbin/nologin is a special case; it needs to be handled
+ # before the cat | while loop, since a 'return' from within
+ # a subshell will not terminate the function's execution, and
+ # the path to the nologin shell might be printed out twice.
+ #
+ if [ "$_shell" = "${NOLOGIN}" -o \
+ "$_shell" = "${NOLOGIN_PATH}" ]; then
+ echo ${NOLOGIN_PATH}
+ return 0;
+ fi
+
+ cat ${ETCSHELLS} |
+ while read _path _junk ; do
+ case "$_path" in
+ \#*|'')
+ ;;
+ *)
+ if [ "$_path" = "$_shell" -o \
+ "`basename $_path`" = "$_shell" ]; then
+ echo $_path
+ return 0
+ fi
+ ;;
+ esac
+ done
+
+ return 1
+}
+
+# shell_exists shell
+# If the given shell is listed in ${ETCSHELLS} or it is
+# the nologin shell this function will return 0.
+# Otherwise, it will return 1. If shell is valid but
+# the path is invalid or it is not executable it
+# will emit an informational message saying so.
+#
+shell_exists()
+{
+ _sh="$1"
+ _shellchk="${GREPCMD} '^$_sh$' ${ETCSHELLS} > /dev/null 2>&1"
+
+ if ! eval $_shellchk; then
+ # The nologin shell is not listed in /etc/shells.
+ if [ "$_sh" != "${NOLOGIN_PATH}" ]; then
+ err "Invalid shell ($_sh) for user $username."
+ return 1
+ fi
+ fi
+ ! [ -x "$_sh" ] &&
+ info "The shell ($_sh) does not exist or is not executable."
+
+ return 0
+}
+
+# save_config
+# Save some variables to a configuration file.
+# Note: not all script variables are saved, only those that
+# it makes sense to save.
+#
+save_config() {
+ echo "# Configuration file for adduser(8)." > ${ADDUSERCONF}
+ echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF}
+ echo "# Last Modified on `${DATECMD}`." >> ${ADDUSERCONF}
+ echo '' >> ${ADDUSERCONF}
+ echo "defaultHomePerm=$uhomeperm" >> ${ADDUSERCONF}
+ echo "defaultLgroup=$ulogingroup" >> ${ADDUSERCONF}
+ echo "defaultclass=$uclass" >> ${ADDUSERCONF}
+ echo "defaultgroups=$ugroups" >> ${ADDUSERCONF}
+ echo "passwdtype=$passwdtype" >> ${ADDUSERCONF}
+ echo "homeprefix=$homeprefix" >> ${ADDUSERCONF}
+ echo "defaultshell=$ushell" >> ${ADDUSERCONF}
+ echo "udotdir=$udotdir" >> ${ADDUSERCONF}
+ echo "msgfile=$msgfile" >> ${ADDUSERCONF}
+ echo "disableflag=$disableflag" >> ${ADDUSERCONF}
+ echo "uidstart=$uidstart" >> ${ADDUSERCONF}
+}
+
+# add_user
+# Add a user to the user database. If the user chose to send a welcome
+# message or lock the account, do so.
+#
+add_user() {
+
+ # Is this a configuration run? If so, don't modify user database.
+ #
+ if [ -n "$configflag" ]; then
+ save_config
+ return
+ fi
+
+ _uid=
+ _name=
+ _comment=
+ _gecos=
+ _home=
+ _group=
+ _grouplist=
+ _shell=
+ _class=
+ _dotdir=
+ _expire=
+ _pwexpire=
+ _passwd=
+ _upasswd=
+ _passwdmethod=
+
+ _name="-n '$username'"
+ [ -n "$uuid" ] && _uid='-u "$uuid"'
+ [ -n "$ulogingroup" ] && _group='-g "$ulogingroup"'
+ [ -n "$ugroups" ] && _grouplist='-G "$ugroups"'
+ [ -n "$ushell" ] && _shell='-s "$ushell"'
+ [ -n "$uclass" ] && _class='-L "$uclass"'
+ [ -n "$ugecos" ] && _comment='-c "$ugecos"'
+ [ -n "$udotdir" ] && _dotdir='-k "$udotdir"'
+ [ -n "$uexpire" ] && _expire='-e "$uexpire"'
+ [ -n "$upwexpire" ] && _pwexpire='-p "$upwexpire"'
+ if [ -z "$Dflag" -a -n "$uhome" ]; then
+ # The /nonexistent home directory is special. It
+ # means the user has no home directory.
+ if [ "$uhome" = "$NOHOME" ]; then
+ _home='-d "$uhome"'
+ else
+ # Use home directory permissions if specified
+ if [ -n "$uhomeperm" ]; then
+ _home='-m -d "$uhome" -M "$uhomeperm"'
+ else
+ _home='-m -d "$uhome"'
+ fi
+ fi
+ elif [ -n "$Dflag" -a -n "$uhome" ]; then
+ _home='-d "$uhome"'
+ fi
+ case $passwdtype in
+ no)
+ _passwdmethod="-w no"
+ _passwd="-h -"
+ ;;
+ yes)
+ # Note on processing the password: The outer double quotes
+ # make literal everything except ` and \ and $.
+ # The outer single quotes make literal ` and $.
+ # We can ensure the \ isn't treated specially by specifying
+ # the -r switch to the read command used to obtain the input.
+ #
+ _passwdmethod="-w yes"
+ _passwd="-h 0"
+ _upasswd='echo "$upass" |'
+ ;;
+ none)
+ _passwdmethod="-w none"
+ ;;
+ random)
+ _passwdmethod="-w random"
+ ;;
+ esac
+
+ _pwcmd="$_upasswd ${PWCMD} useradd $_uid $_name $_group $_grouplist $_comment"
+ _pwcmd="$_pwcmd $_shell $_class $_home $_dotdir $_passwdmethod $_passwd"
+ _pwcmd="$_pwcmd $_expire $_pwexpire"
+
+ if ! _output=`eval $_pwcmd` ; then
+ err "There was an error adding user ($username)."
+ return 1
+ else
+ info "Successfully added ($username) to the user database."
+ if [ "random" = "$passwdtype" ]; then
+ randompass="$_output"
+ info "Password for ($username) is: $randompass"
+ fi
+ fi
+
+ if [ -n "$disableflag" ]; then
+ if ${PWCMD} lock $username ; then
+ info "Account ($username) is locked."
+ else
+ info "Account ($username) could NOT be locked."
+ fi
+ fi
+
+ _line=
+ _owner=
+ _perms=
+ if [ -n "$msgflag" ]; then
+ [ -r "$msgfile" ] && {
+ # We're evaluating the contents of an external file.
+ # Let's not open ourselves up for attack. _perms will
+ # be empty if it's writeable only by the owner. _owner
+ # will *NOT* be empty if the file is owned by root.
+ #
+ _dir="`dirname $msgfile`"
+ _file="`basename $msgfile`"
+ _perms=`/usr/bin/find $_dir -name $_file -perm +07022 -prune`
+ _owner=`/usr/bin/find $_dir -name $_file -user 0 -prune`
+ if [ -z "$_owner" -o -n "$_perms" ]; then
+ err "The message file ($msgfile) may be writeable only by root."
+ return 1
+ fi
+ cat "$msgfile" |
+ while read _line ; do
+ eval echo "$_line"
+ done | ${MAILCMD} -s"Welcome" ${username}
+ info "Sent welcome message to ($username)."
+ }
+ fi
+}
+
+# get_user
+# Reads username of the account from standard input or from a global
+# variable containing an account line from a file. The username is
+# required. If this is an interactive session it will prompt in
+# a loop until a username is entered. If it is batch processing from
+# a file it will output an error message and return to the caller.
+#
+get_user() {
+ _input=
+
+ # No need to take down user names if this is a configuration saving run.
+ [ -n "$configflag" ] && return
+
+ while : ; do
+ if [ -z "$fflag" ]; then
+ echo -n "Username: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f1 -d:`"
+ fi
+
+ # There *must* be a username, and it must not exist. If
+ # this is an interactive session give the user an
+ # opportunity to retry.
+ #
+ if [ -z "$_input" ]; then
+ err "You must enter a username!"
+ [ -z "$fflag" ] && continue
+ fi
+ ${PWCMD} usershow $_input > /dev/null 2>&1
+ if [ "$?" -eq 0 ]; then
+ err "User exists!"
+ [ -z "$fflag" ] && continue
+ fi
+ break
+ done
+ username="$_input"
+}
+
+# get_gecos
+# Reads extra information about the user. Can be used both in interactive
+# and batch (from file) mode.
+#
+get_gecos() {
+ _input=
+
+ # No need to take down additional user information for a configuration run.
+ [ -n "$configflag" ] && return
+
+ if [ -z "$fflag" ]; then
+ echo -n "Full name: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f7 -d:`"
+ fi
+ ugecos="$_input"
+}
+
+# get_shell
+# Get the account's shell. Works in interactive and batch mode. It
+# accepts either the base name of the shell or the full path.
+# If an invalid shell is entered it will simply use the default shell.
+#
+get_shell() {
+ _input=
+ _fullpath=
+ ushell="$defaultshell"
+
+ # Make sure the current value of the shell is a valid one
+ if [ -z "$Sflag" ]; then
+ if ! shell_exists $ushell ; then
+ info "Using default shell ${defaultshell}."
+ ushell="$defaultshell"
+ fi
+ fi
+
+ if [ -z "$fflag" ]; then
+ echo -n "Shell ($shells) [`basename $ushell`]: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f9 -d:`"
+ fi
+ if [ -n "$_input" ]; then
+ if [ -n "$Sflag" ]; then
+ ushell="$_input"
+ else
+ _fullpath=`fullpath_from_shell $_input`
+ if [ -n "$_fullpath" ]; then
+ ushell="$_fullpath"
+ else
+ err "Invalid shell ($_input) for user $username."
+ info "Using default shell ${defaultshell}."
+ ushell="$defaultshell"
+ fi
+ fi
+ fi
+}
+
+# get_homedir
+# Reads the account's home directory. Used both with interactive input
+# and batch input.
+#
+get_homedir() {
+ _input=
+ if [ -z "$fflag" ]; then
+ echo -n "Home directory [${homeprefix}/${username}]: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f8 -d:`"
+ fi
+
+ if [ -n "$_input" ]; then
+ uhome="$_input"
+ # if this is a configuration run, then user input is the home
+ # directory prefix. Otherwise it is understood to
+ # be $prefix/$user
+ #
+ [ -z "$configflag" ] && homeprefix="`dirname $uhome`" || homeprefix="$uhome"
+ else
+ uhome="${homeprefix}/${username}"
+ fi
+}
+
+# get_homeperm
+# Reads the account's home directory permissions.
+#
+get_homeperm() {
+ uhomeperm=$defaultHomePerm
+ _input=
+ _prompt=
+
+ if [ -n "$uhomeperm" ]; then
+ _prompt="Home directory permissions [${uhomeperm}]: "
+ else
+ _prompt="Home directory permissions (Leave empty for default): "
+ fi
+ if [ -z "$fflag" ]; then
+ echo -n "$_prompt"
+ read _input
+ fi
+
+ if [ -n "$_input" ]; then
+ uhomeperm="$_input"
+ fi
+}
+
+# get_uid
+# Reads a numeric userid in an interactive or batch session. Automatically
+# allocates one if it is not specified.
+#
+get_uid() {
+ uuid=${uidstart}
+ _input=
+ _prompt=
+
+ if [ -n "$uuid" ]; then
+ _prompt="Uid [$uuid]: "
+ else
+ _prompt="Uid (Leave empty for default): "
+ fi
+ if [ -z "$fflag" ]; then
+ echo -n "$_prompt"
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f2 -d:`"
+ fi
+
+ [ -n "$_input" ] && uuid=$_input
+ uuid=`get_nextuid $uuid`
+ uidstart=$uuid
+}
+
+# get_class
+# Reads login class of account. Can be used in interactive or batch mode.
+#
+get_class() {
+ uclass="$defaultclass"
+ _input=
+ _class=${uclass:-"default"}
+
+ if [ -z "$fflag" ]; then
+ echo -n "Login class [$_class]: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f4 -d:`"
+ fi
+
+ [ -n "$_input" ] && uclass="$_input"
+}
+
+# get_logingroup
+# Reads user's login group. Can be used in both interactive and batch
+# modes. The specified value can be a group name or its numeric id.
+# This routine leaves the field blank if nothing is provided and
+# a default login group has not been set. The pw(8) command
+# will then provide a login group with the same name as the username.
+#
+get_logingroup() {
+ ulogingroup="$defaultLgroup"
+ _input=
+
+ if [ -z "$fflag" ]; then
+ echo -n "Login group [${ulogingroup:-$username}]: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f3 -d:`"
+ fi
+
+ # Pw(8) will use the username as login group if it's left empty
+ [ -n "$_input" ] && ulogingroup="$_input"
+}
+
+# get_groups
+# Read additional groups for the user. It can be used in both interactive
+# and batch modes.
+#
+get_groups() {
+ ugroups="$defaultgroups"
+ _input=
+ _group=${ulogingroup:-"${username}"}
+
+ if [ -z "$configflag" ]; then
+ [ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username"
+ [ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: "
+ else
+ [ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: "
+ fi
+ read _input
+
+ [ -n "$_input" ] && ugroups="$_input"
+}
+
+# get_expire_dates
+# Read expiry information for the account and also for the password. This
+# routine is used only from batch processing mode.
+#
+get_expire_dates() {
+ upwexpire="`echo "$fileline" | cut -f5 -d:`"
+ uexpire="`echo "$fileline" | cut -f6 -d:`"
+}
+
+# get_password
+# Read the password in batch processing mode. The password field matters
+# only when the password type is "yes" or "random". If the field is empty and the
+# password type is "yes", then it assumes the account has an empty passsword
+# and changes the password type accordingly. If the password type is "random"
+# and the password field is NOT empty, then it assumes the account will NOT
+# have a random password and set passwdtype to "yes."
+#
+get_password() {
+ # We may temporarily change a password type. Make sure it's changed
+ # back to whatever it was before we process the next account.
+ #
+ [ -n "$savedpwtype" ] && {
+ passwdtype=$savedpwtype
+ savedpwtype=
+ }
+
+ # There may be a ':' in the password
+ upass=${fileline#*:*:*:*:*:*:*:*:*:}
+
+ if [ -z "$upass" ]; then
+ case $passwdtype in
+ yes)
+ # if it's empty, assume an empty password
+ passwdtype=none
+ savedpwtype=yes
+ ;;
+ esac
+ else
+ case $passwdtype in
+ random)
+ passwdtype=yes
+ savedpwtype=random
+ ;;
+ esac
+ fi
+}
+
+# input_from_file
+# Reads a line of account information from standard input and
+# adds it to the user database.
+#
+input_from_file() {
+ _field=
+
+ while read -r fileline ; do
+ case "$fileline" in
+ \#*|'')
+ ;;
+ *)
+ get_user || continue
+ get_gecos
+ get_uid
+ get_logingroup
+ get_class
+ get_shell
+ get_homedir
+ get_homeperm
+ get_password
+ get_expire_dates
+ ugroups="$defaultgroups"
+
+ add_user
+ ;;
+ esac
+ done
+}
+
+# input_interactive
+# Prompts for user information interactively, and commits to
+# the user database.
+#
+input_interactive() {
+
+ _disable=
+ _pass=
+ _passconfirm=
+ _random="no"
+ _emptypass="no"
+ _usepass="yes"
+ _logingroup_ok="no"
+ _groups_ok="no"
+ case $passwdtype in
+ none)
+ _emptypass="yes"
+ _usepass="yes"
+ ;;
+ no)
+ _usepass="no"
+ ;;
+ random)
+ _random="yes"
+ ;;
+ esac
+
+ get_user
+ get_gecos
+ get_uid
+
+ # The case where group = user is handled elsewhere, so
+ # validate any other groups the user is invited to.
+ until [ "$_logingroup_ok" = yes ]; do
+ get_logingroup
+ _logingroup_ok=yes
+ if [ -n "$ulogingroup" -a "$username" != "$ulogingroup" ]; then
+ if ! ${PWCMD} show group $ulogingroup > /dev/null 2>&1; then
+ echo "Group $ulogingroup does not exist!"
+ _logingroup_ok=no
+ fi
+ fi
+ done
+ until [ "$_groups_ok" = yes ]; do
+ get_groups
+ _groups_ok=yes
+ for i in $ugroups; do
+ if [ "$username" != "$i" ]; then
+ if ! ${PWCMD} show group $i > /dev/null 2>&1; then
+ echo "Group $i does not exist!"
+ _groups_ok=no
+ fi
+ fi
+ done
+ done
+
+ get_class
+ get_shell
+ get_homedir
+ get_homeperm
+
+ while : ; do
+ echo -n "Use password-based authentication? [$_usepass]: "
+ read _input
+ [ -z "$_input" ] && _input=$_usepass
+ case $_input in
+ [Nn][Oo]|[Nn])
+ passwdtype="no"
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ while : ; do
+ echo -n "Use an empty password? (yes/no) [$_emptypass]: "
+ read _input
+ [ -n "$_input" ] && _emptypass=$_input
+ case $_emptypass in
+ [Nn][Oo]|[Nn])
+ echo -n "Use a random password? (yes/no) [$_random]: "
+ read _input
+ [ -n "$_input" ] && _random="$_input"
+ case $_random in
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ passwdtype="random"
+ break
+ ;;
+ esac
+ passwdtype="yes"
+ [ -n "$configflag" ] && break
+ trap 'stty echo; exit' 0 1 2 3 15
+ stty -echo
+ echo -n "Enter password: "
+ read -r upass
+ echo''
+ echo -n "Enter password again: "
+ read -r _passconfirm
+ echo ''
+ stty echo
+ # if user entered a blank password
+ # explicitly ask again.
+ [ -z "$upass" -a -z "$_passconfirm" ] \
+ && continue
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ passwdtype="none"
+ break;
+ ;;
+ *)
+ # invalid answer; repeat the loop
+ continue
+ ;;
+ esac
+ if [ "$upass" != "$_passconfirm" ]; then
+ echo "Passwords did not match!"
+ continue
+ fi
+ break
+ done
+ ;;
+ *)
+ # invalid answer; repeat loop
+ continue
+ ;;
+ esac
+ break;
+ done
+ _disable=${disableflag:-"no"}
+ while : ; do
+ echo -n "Lock out the account after creation? [$_disable]: "
+ read _input
+ [ -z "$_input" ] && _input=$_disable
+ case $_input in
+ [Nn][Oo]|[Nn])
+ disableflag=
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ disableflag=yes
+ ;;
+ *)
+ # invalid answer; repeat loop
+ continue
+ ;;
+ esac
+ break
+ done
+
+ # Display the information we have so far and prompt to
+ # commit it.
+ #
+ _disable=${disableflag:-"no"}
+ [ -z "$configflag" ] && printf "%-10s : %s\n" Username $username
+ case $passwdtype in
+ yes)
+ _pass='*****'
+ ;;
+ no)
+ _pass='<disabled>'
+ ;;
+ none)
+ _pass='<blank>'
+ ;;
+ random)
+ _pass='<random>'
+ ;;
+ esac
+ [ -z "$configflag" ] && printf "%-10s : %s\n" "Password" "$_pass"
+ [ -n "$configflag" ] && printf "%-10s : %s\n" "Pass Type" "$passwdtype"
+ [ -z "$configflag" ] && printf "%-10s : %s\n" "Full Name" "$ugecos"
+ [ -z "$configflag" ] && printf "%-10s : %s\n" "Uid" "$uuid"
+ printf "%-10s : %s\n" "Class" "$uclass"
+ printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
+ printf "%-10s : %s\n" "Home" "$uhome"
+ printf "%-10s : %s\n" "Home Mode" "$uhomeperm"
+ printf "%-10s : %s\n" "Shell" "$ushell"
+ printf "%-10s : %s\n" "Locked" "$_disable"
+ while : ; do
+ echo -n "OK? (yes/no): "
+ read _input
+ case $_input in
+ [Nn][Oo]|[Nn])
+ return 1
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ add_user
+ ;;
+ *)
+ continue
+ ;;
+ esac
+ break
+ done
+ return 0
+}
+
+#### END SUBROUTINE DEFINITION ####
+
+THISCMD=`/usr/bin/basename $0`
+DEFAULTSHELL=/bin/sh
+ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}"
+PWCMD="${PWCMD:-/usr/sbin/pw}"
+MAILCMD="${MAILCMD:-mail}"
+ETCSHELLS="${ETCSHELLS:-/etc/shells}"
+NOHOME="/nonexistent"
+NOLOGIN="nologin"
+NOLOGIN_PATH="/usr/sbin/nologin"
+GREPCMD="/usr/bin/grep"
+DATECMD="/bin/date"
+
+# Set default values
+#
+username=
+uuid=
+uidstart=
+ugecos=
+ulogingroup=
+uclass=
+uhome=
+uhomeperm=
+upass=
+ushell=
+udotdir=/usr/share/skel
+ugroups=
+uexpire=
+upwexpire=
+shells="`valid_shells`"
+passwdtype="yes"
+msgfile=/etc/adduser.msg
+msgflag=
+quietflag=
+configflag=
+fflag=
+infile=
+disableflag=
+Dflag=
+Sflag=
+readconfig="yes"
+homeprefix="/home"
+randompass=
+fileline=
+savedpwtype=
+defaultclass=
+defaultLgroup=
+defaultgroups=
+defaultshell="${DEFAULTSHELL}"
+defaultHomePerm=
+
+# Make sure the user running this program is root. This isn't a security
+# measure as much as it is a usefull method of reminding the user to
+# 'su -' before he/she wastes time entering data that won't be saved.
+#
+procowner=${procowner:-`/usr/bin/id -u`}
+if [ "$procowner" != "0" ]; then
+ err 'you must be the super-user (uid 0) to use this utility.'
+ exit 1
+fi
+
+# Overide from our conf file
+# Quickly go through the commandline line to see if we should read
+# from our configuration file. The actual parsing of the commandline
+# arguments happens after we read in our configuration file (commandline
+# should override configuration file).
+#
+for _i in $* ; do
+ if [ "$_i" = "-N" ]; then
+ readconfig=
+ break;
+ fi
+done
+if [ -n "$readconfig" ]; then
+ # On a long-lived system, the first time this script is run it
+ # will barf upon reading the configuration file for its perl predecessor.
+ if ( . ${ADDUSERCONF} > /dev/null 2>&1 ); then
+ [ -r ${ADDUSERCONF} ] && . ${ADDUSERCONF} > /dev/null 2>&1
+ fi
+fi
+
+# Proccess command-line options
+#
+for _switch ; do
+ case $_switch in
+ -L)
+ defaultclass="$2"
+ shift; shift
+ ;;
+ -C)
+ configflag=yes
+ shift
+ ;;
+ -D)
+ Dflag=yes
+ shift
+ ;;
+ -E)
+ disableflag=yes
+ shift
+ ;;
+ -k)
+ udotdir="$2"
+ shift; shift
+ ;;
+ -f)
+ [ "$2" != "-" ] && infile="$2"
+ fflag=yes
+ shift; shift
+ ;;
+ -g)
+ defaultLgroup="$2"
+ shift; shift
+ ;;
+ -G)
+ defaultgroups="$2"
+ shift; shift
+ ;;
+ -h)
+ show_usage
+ exit 0
+ ;;
+ -d)
+ homeprefix="$2"
+ shift; shift
+ ;;
+ -m)
+ case "$2" in
+ [Nn][Oo])
+ msgflag=
+ ;;
+ *)
+ msgflag=yes
+ msgfile="$2"
+ ;;
+ esac
+ shift; shift
+ ;;
+ -M)
+ defaultHomePerm=$2
+ shift; shift
+ ;;
+ -N)
+ readconfig=
+ shift
+ ;;
+ -w)
+ case "$2" in
+ no|none|random|yes)
+ passwdtype=$2
+ ;;
+ *)
+ show_usage
+ exit 1
+ ;;
+ esac
+ shift; shift
+ ;;
+ -q)
+ quietflag=yes
+ shift
+ ;;
+ -s)
+ defaultshell="`fullpath_from_shell $2`"
+ shift; shift
+ ;;
+ -S)
+ Sflag=yes
+ shift
+ ;;
+ -u)
+ uidstart=$2
+ shift; shift
+ ;;
+ esac
+done
+
+# If the -f switch was used, get input from a file. Otherwise,
+# this is an interactive session.
+#
+if [ -n "$fflag" ]; then
+ if [ -z "$infile" ]; then
+ input_from_file
+ elif [ -n "$infile" ]; then
+ if [ -r "$infile" ]; then
+ input_from_file < $infile
+ else
+ err "File ($infile) is unreadable or does not exist."
+ fi
+ fi
+else
+ input_interactive
+ while : ; do
+ if [ -z "$configflag" ]; then
+ echo -n "Add another user? (yes/no): "
+ else
+ echo -n "Re-edit the default configuration? (yes/no): "
+ fi
+ read _input
+ case $_input in
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ uidstart=`get_nextuid $uidstart`
+ input_interactive
+ continue
+ ;;
+ [Nn][Oo]|[Nn])
+ echo "Goodbye!"
+ ;;
+ *)
+ continue
+ ;;
+ esac
+ break
+ done
+fi
diff --git a/adduser/rmuser.8 b/adduser/rmuser.8
new file mode 100644
index 0000000..68a99b5
--- /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 -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/adduser/rmuser.sh b/adduser/rmuser.sh
new file mode 100644
index 0000000..6b09225
--- /dev/null
+++ b/adduser/rmuser.sh
@@ -0,0 +1,361 @@
+#!/bin/sh
+#
+# Copyright (c) 2002, 2003 Michael Telahun Makonnen. 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 ``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 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.
+#
+# Email: Mike Makonnen <mtm@FreeBSD.Org>
+#
+# $FreeBSD$
+#
+
+ATJOBDIR="/var/at/jobs"
+CRONJOBDIR="/var/cron/tabs"
+MAILSPOOL="/var/mail"
+SIGKILL="-KILL"
+TEMPDIRS="/tmp /var/tmp"
+THISCMD=`/usr/bin/basename $0`
+PWCMD="${PWCMD:-/usr/sbin/pw}"
+
+# err msg
+# Display $msg on stderr.
+#
+err() {
+ echo 1>&2 ${THISCMD}: $*
+}
+
+# verbose
+# Returns 0 if verbose mode is set, 1 if it is not.
+#
+verbose() {
+ [ -n "$vflag" ] && return 0 || return 1
+}
+
+# rm_files login
+# Removes files or empty directories belonging to $login from various
+# temporary directories.
+#
+rm_files() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ totalcount=0
+ for _dir in ${TEMPDIRS} ; do
+ filecount=0
+ if [ ! -d $_dir ]; then
+ err "$_dir is not a valid directory."
+ continue
+ fi
+ verbose && echo -n "Removing files owned by ($login) in $_dir:"
+ filecount=`find 2>/dev/null "$_dir" -user "$login" -delete -print |
+ wc -l | sed 's/ *//'`
+ verbose && echo " $filecount removed."
+ totalcount=$(($totalcount + $filecount))
+ done
+ ! verbose && [ $totalcount -ne 0 ] && echo -n " files($totalcount)"
+}
+
+# rm_mail login
+# Removes unix mail and pop daemon files belonging to the user
+# specified in the $login argument.
+#
+rm_mail() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ verbose && echo -n "Removing mail spool(s) for ($login):"
+ if [ -f ${MAILSPOOL}/$login ]; then
+ verbose && echo -n " ${MAILSPOOL}/$login" ||
+ echo -n " mailspool"
+ rm ${MAILSPOOL}/$login
+ fi
+ if [ -f ${MAILSPOOL}/.${login}.pop ]; then
+ verbose && echo -n " ${MAILSPOOL}/.${login}.pop" ||
+ echo -n " pop3"
+ rm ${MAILSPOOL}/.${login}.pop
+ fi
+ verbose && echo '.'
+}
+
+# kill_procs login
+# Send a SIGKILL to all processes owned by $login.
+#
+kill_procs() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ verbose && echo -n "Terminating all processes owned by ($login):"
+ killcount=0
+ proclist=`ps 2>/dev/null -U $login | grep -v '^\ *PID' | awk '{print $1}'`
+ for _pid in $proclist ; do
+ kill 2>/dev/null ${SIGKILL} $_pid
+ killcount=$(($killcount + 1))
+ done
+ verbose && echo " ${SIGKILL} signal sent to $killcount processes."
+ ! verbose && [ $killcount -ne 0 ] && echo -n " processes(${killcount})"
+}
+
+# rm_at_jobs login
+# Remove at (1) jobs belonging to $login.
+#
+rm_at_jobs() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ atjoblist=`find 2>/dev/null ${ATJOBDIR} -maxdepth 1 -user $login -print`
+ jobcount=0
+ verbose && echo -n "Removing at(1) jobs owned by ($login):"
+ for _atjob in $atjoblist ; do
+ rm -f $_atjob
+ jobcount=$(($jobcount + 1))
+ done
+ verbose && echo " $jobcount removed."
+ ! verbose && [ $jobcount -ne 0 ] && echo -n " at($jobcount)"
+}
+
+# rm_crontab login
+# Removes crontab file belonging to user $login.
+#
+rm_crontab() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ verbose && echo -n "Removing crontab for ($login):"
+ if [ -f ${CRONJOBDIR}/$login ]; then
+ verbose && echo -n " ${CRONJOBDIR}/$login" || echo -n " crontab"
+ rm -f ${CRONJOBDIR}/$login
+ fi
+ verbose && echo '.'
+}
+
+# rm_ipc login
+# Remove all IPC mechanisms which are owned by $login.
+#
+rm_ipc() {
+ verbose && echo -n "Removing IPC mechanisms"
+ for i in s m q; do
+ ipcs -$i |
+ awk -v i=$i -v login=$1 '$1 == i && $5 == login { print $2 }' |
+ xargs -n 1 ipcrm -$i
+ done
+ verbose && echo '.'
+}
+
+# rm_user login
+# Remove user $login from the system. This subroutine makes use
+# of the pw(8) command to remove a user from the system. The pw(8)
+# command will remove the specified user from the user database
+# and group file and remove any crontabs. His home
+# directory will be removed if it is owned by him and contains no
+# files or subdirectories owned by other users. Mail spool files will
+# also be removed.
+#
+rm_user() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ verbose && echo -n "Removing user ($login)"
+ [ -n "$pw_rswitch" ] && {
+ verbose && echo -n " (including home directory)"
+ ! verbose && echo -n " home"
+ }
+ ! verbose && echo -n " passwd"
+ verbose && echo -n " from the system:"
+ ${PWCMD} userdel -n $login $pw_rswitch
+ verbose && echo ' Done.'
+}
+
+# prompt_yesno msg
+# Prompts the user with a $msg. The answer is expected to be
+# yes, no, or some variation thereof. This subroutine returns 0
+# if the answer was yes, 1 if it was not.
+#
+prompt_yesno() {
+ # The argument is required
+ [ -n "$1" ] && msg="$1" || return
+
+ while : ; do
+ echo -n "$msg"
+ read _ans
+ case $_ans in
+ [Nn][Oo]|[Nn])
+ return 1
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ return 0
+ ;;
+ *)
+ ;;
+ esac
+ done
+}
+
+# show_usage
+# (no arguments)
+# Display usage message.
+#
+show_usage() {
+ echo "usage: ${THISCMD} [-yv] [-f file] [user ...]"
+ echo " if the -y switch is used, either the -f switch or"
+ echo " one or more user names must be given"
+}
+
+#### END SUBROUTINE DEFENITION ####
+
+ffile=
+fflag=
+procowner=
+pw_rswitch=
+userlist=
+yflag=
+vflag=
+
+procowner=`/usr/bin/id -u`
+if [ "$procowner" != "0" ]; then
+ err 'you must be root (0) to use this utility.'
+ exit 1
+fi
+
+args=`getopt 2>/dev/null yvf: $*`
+if [ "$?" != "0" ]; then
+ show_usage
+ exit 1
+fi
+set -- $args
+for _switch ; do
+ case $_switch in
+ -y)
+ yflag=1
+ shift
+ ;;
+ -v)
+ vflag=1
+ shift
+ ;;
+ -f)
+ fflag=1
+ ffile="$2"
+ shift; shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ esac
+done
+
+# Get user names from a file if the -f switch was used. Otherwise,
+# get them from the commandline arguments. If we're getting it
+# from a file, the file must be owned by and writable only by root.
+#
+if [ $fflag ]; then
+ _insecure=`find $ffile ! -user 0 -or -perm +0022`
+ if [ -n "$_insecure" ]; then
+ err "file ($ffile) must be owned by and writeable only by root."
+ exit 1
+ fi
+ if [ -r "$ffile" ]; then
+ userlist=`cat $ffile | while read _user _junk ; do
+ case $_user in
+ \#*|'')
+ ;;
+ *)
+ echo -n "$userlist $_user"
+ ;;
+ esac
+ done`
+ fi
+else
+ while [ $1 ] ; do
+ userlist="$userlist $1"
+ shift
+ done
+fi
+
+# If the -y or -f switch has been used and the list of users to remove
+# is empty it is a fatal error. Otherwise, prompt the user for a list
+# of one or more user names.
+#
+if [ ! "$userlist" ]; then
+ if [ $fflag ]; then
+ err "($ffile) does not exist or does not contain any user names."
+ exit 1
+ elif [ $yflag ]; then
+ show_usage
+ exit 1
+ else
+ echo -n "Please enter one or more usernames: "
+ read userlist
+ fi
+fi
+
+_user=
+_uid=
+for _user in $userlist ; do
+ # Make sure the name exists in the passwd database and that it
+ # does not have a uid of 0
+ #
+ userrec=`pw 2>/dev/null usershow -n $_user`
+ if [ "$?" != "0" ]; then
+ err "user ($_user) does not exist in the password database."
+ continue
+ fi
+ _uid=`echo $userrec | awk -F: '{print $3}'`
+ if [ "$_uid" = "0" ]; then
+ err "user ($_user) has uid 0. You may not remove this user."
+ continue
+ fi
+
+ # If the -y switch was not used ask for confirmation to remove the
+ # user and home directory.
+ #
+ if [ -z "$yflag" ]; then
+ echo "Matching password entry:"
+ echo
+ echo $userrec
+ echo
+ if ! prompt_yesno "Is this the entry you wish to remove? " ; then
+ continue
+ fi
+ _homedir=`echo $userrec | awk -F: '{print $9}'`
+ if prompt_yesno "Remove user's home directory ($_homedir)? "; then
+ pw_rswitch="-r"
+ fi
+ else
+ pw_rswitch="-r"
+ fi
+
+ # Disable any further attempts to log into this account
+ ${PWCMD} 2>/dev/null lock $_user
+
+ # Remove crontab, mail spool, etc. Then obliterate the user from
+ # the passwd and group database.
+ #
+ ! verbose && echo -n "Removing user ($_user):"
+ rm_crontab $_user
+ rm_at_jobs $_user
+ rm_ipc $_user
+ kill_procs $_user
+ rm_files $_user
+ rm_mail $_user
+ rm_user $_user
+ ! verbose && echo "."
+done
diff --git a/chpass/Makefile b/chpass/Makefile
new file mode 100644
index 0000000..2bbdc08
--- /dev/null
+++ b/chpass/Makefile
@@ -0,0 +1,49 @@
+# @(#)Makefile 8.2 (Berkeley) 4/2/94
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+.PATH: ${.CURDIR}/../../usr.sbin/pwd_mkdb ${.CURDIR}/../../lib/libc/gen
+
+PROG= chpass
+SRCS= chpass.c edit.c field.c pw_scan.c table.c util.c
+BINOWN= root
+BINMODE=4555
+WARNS?= 5
+.if ${MK_NIS} != "no"
+CFLAGS+= -DYP
+.endif
+#Some people need this, uncomment to activate
+#CFLAGS+=-DRESTRICT_FULLNAME_CHANGE
+CFLAGS+=-I${.CURDIR}/../../usr.sbin/pwd_mkdb -I${.CURDIR}/../../lib/libc/gen -I.
+
+DPADD= ${LIBCRYPT} ${LIBUTIL}
+LDADD= -lcrypt -lutil
+.if ${MK_NIS} != "no"
+DPADD+= ${LIBYPCLNT}
+LDADD+= -lypclnt
+.endif
+
+LINKS= ${BINDIR}/chpass ${BINDIR}/chfn
+LINKS+= ${BINDIR}/chpass ${BINDIR}/chsh
+.if ${MK_NIS} != "no"
+LINKS+= ${BINDIR}/chpass ${BINDIR}/ypchpass
+LINKS+= ${BINDIR}/chpass ${BINDIR}/ypchfn
+LINKS+= ${BINDIR}/chpass ${BINDIR}/ypchsh
+.endif
+
+MLINKS= chpass.1 chfn.1 chpass.1 chsh.1
+.if ${MK_NIS} != "no"
+MLINKS+= chpass.1 ypchpass.1 chpass.1 ypchfn.1 chpass.1 ypchsh.1
+.endif
+
+beforeinstall:
+.for i in chpass chfn chsh ypchpass ypchfn ypchsh
+ [ ! -e ${DESTDIR}${BINDIR}/$i ] || \
+ chflags noschg ${DESTDIR}${BINDIR}/$i || true
+.endfor
+
+afterinstall:
+ -chflags schg ${DESTDIR}${BINDIR}/chpass
+
+.include <bsd.prog.mk>
diff --git a/chpass/chpass.1 b/chpass/chpass.1
new file mode 100644
index 0000000..24b1759
--- /dev/null
+++ b/chpass/chpass.1
@@ -0,0 +1,492 @@
+.\" Copyright (c) 1988, 1990, 1993
+.\" The Regents of the University of California. 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.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+.\"
+.\" @(#)chpass.1 8.2 (Berkeley) 12/30/93
+.\" $FreeBSD$
+.\"
+.Dd December 30, 1993
+.Dt CHPASS 1
+.Os
+.Sh NAME
+.Nm chpass ,
+.Nm chfn ,
+.Nm chsh ,
+.Nm ypchpass ,
+.Nm ypchfn ,
+.Nm ypchsh
+.Nd add or change user database information
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar list
+.Op Fl p Ar encpass
+.Op Fl e Ar expiretime
+.Op Fl s Ar newshell
+.Op user
+.Nm
+.Op Fl oly
+.Op Fl a Ar list
+.Op Fl p Ar encpass
+.Op Fl e Ar expiretime
+.Op Fl s Ar newshell
+.Op Fl d Ar domain
+.Op Fl h Ar host
+.Op user
+.Sh DESCRIPTION
+The
+.Nm
+utility
+allows editing of the user database information associated
+with
+.Ar user
+or, by default, the current user.
+.Pp
+The
+.Nm chfn ,
+.Nm chsh ,
+.Nm ypchpass ,
+.Nm ypchfn
+and
+.Nm ypchsh
+utilities behave identically to
+.Nm .
+(There is only one program.)
+.Pp
+The information is formatted and supplied to an editor for changes.
+.Pp
+Only the information that the user is allowed to change is displayed.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a
+The super-user is allowed to directly supply a user database
+entry, in the format specified by
+.Xr passwd 5 ,
+as an argument.
+This argument must be a colon
+.Pq Dq \&:
+separated list of all the
+user database fields, although they may be empty.
+.It Fl p
+The super-user is allowed to directly supply an encrypted password field,
+in the format used by
+.Xr crypt 3 ,
+as an argument.
+.It Fl e Ar expiretime
+Change the account expire time.
+This option is used to set the expire time
+from a script as if it was done in the interactive editor.
+.It Fl s Ar newshell
+Attempt to change the user's shell to
+.Ar newshell .
+.El
+.Pp
+Possible display items are as follows:
+.Pp
+.Bl -tag -width "Other Information:" -compact -offset indent
+.It Login:
+user's login name
+.It Password:
+user's encrypted password
+.It Uid:
+user's login
+.It Gid:
+user's login group
+.It Class:
+user's general classification
+.It Change:
+password change time
+.It Expire:
+account expiration time
+.It Full Name:
+user's real name
+.It Office Location:
+user's office location (1)
+.It Office Phone:
+user's office phone (1)
+.It Home Phone:
+user's home phone (1)
+.It Other Information:
+any locally defined parameters for user (1)
+.It Home Directory:
+user's home directory
+.It Shell:
+user's login shell
+.Pp
+.It NOTE(1) -
+In the actual master.passwd file, these fields are comma-delimited
+fields embedded in the FullName field.
+.El
+.Pp
+The
+.Ar login
+field is the user name used to access the computer account.
+.Pp
+The
+.Ar password
+field contains the encrypted form of the user's password.
+.Pp
+The
+.Ar uid
+field is the number associated with the
+.Ar login
+field.
+Both of these fields should be unique across the system (and often
+across a group of systems) as they control file access.
+.Pp
+While it is possible to have multiple entries with identical login names
+and/or identical user id's, it is usually a mistake to do so.
+Routines
+that manipulate these files will often return only one of the multiple
+entries, and that one by random selection.
+.Pp
+The
+.Ar group
+field is the group that the user will be placed in at login.
+Since
+.Bx
+supports multiple groups (see
+.Xr groups 1 )
+this field currently has little special meaning.
+This field may be filled in with either a number or a group name (see
+.Xr group 5 ) .
+.Pp
+The
+.Ar class
+field references class descriptions in
+.Pa /etc/login.conf
+and is typically used to initialize the user's system resource limits
+when they login.
+.Pp
+The
+.Ar change
+field is the date by which the password must be changed.
+.Pp
+The
+.Ar expire
+field is the date on which the account expires.
+.Pp
+Both the
+.Ar change
+and
+.Ar expire
+fields should be entered in the form
+.Dq month day year
+where
+.Ar month
+is the month name (the first three characters are sufficient),
+.Ar day
+is the day of the month, and
+.Ar year
+is the year.
+.Pp
+Five fields are available for storing the user's
+.Ar full name , office location ,
+.Ar work
+and
+.Ar home telephone
+numbers and finally
+.Ar other information
+which is a single comma delimited string to represent any additional
+gecos fields (typically used for site specific user information).
+Note that
+.Xr finger 1
+will display the office location and office phone together under the
+heading
+.Ar Office: .
+.Pp
+The user's
+.Ar home directory
+is the full
+.Ux
+path name where the user
+will be placed at login.
+.Pp
+The
+.Ar shell
+field is the command interpreter the user prefers.
+If the
+.Ar shell
+field is empty, the Bourne shell,
+.Pa /bin/sh ,
+is assumed.
+When altering a login shell, and not the super-user, the user
+may not change from a non-standard shell or to a non-standard
+shell.
+Non-standard is defined as a shell not found in
+.Pa /etc/shells .
+.Pp
+Once the information has been verified,
+.Nm
+uses
+.Xr pwd_mkdb 8
+to update the user database.
+.Sh ENVIRONMENT
+The
+.Xr vi 1
+editor will be used unless the environment variable
+.Ev EDITOR
+is set to
+an alternate editor.
+When the editor terminates, the information is re-read and used to
+update the user database itself.
+Only the user, or the super-user, may edit the information associated
+with the user.
+.Pp
+See
+.Xr pwd_mkdb 8
+for an explanation of the impact of setting the
+.Ev PW_SCAN_BIG_IDS
+environment variable.
+.Sh NIS INTERACTION
+The
+.Nm
+utility can also be used in conjunction with NIS, however some restrictions
+apply.
+Currently,
+.Nm
+can only make changes to the NIS passwd maps through
+.Xr rpc.yppasswdd 8 ,
+which normally only permits changes to a user's password, shell and GECOS
+fields.
+Except when invoked by the super-user on the NIS master server,
+.Nm
+(and, similarly,
+.Xr passwd 1 )
+cannot use the
+.Xr rpc.yppasswdd 8
+server to change other user information or
+add new records to the NIS passwd maps.
+Furthermore,
+.Xr rpc.yppasswdd 8
+requires password authentication before it will make any
+changes.
+The only user allowed to submit changes without supplying
+a password is the super-user on the NIS master server; all other users,
+including those with root privileges on NIS clients (and NIS slave
+servers) must enter a password.
+(The super-user on the NIS master is allowed to bypass these restrictions
+largely for convenience: a user with root access
+to the NIS master server already has the privileges required to make
+updates to the NIS maps, but editing the map source files by hand can
+be cumbersome.
+.Pp
+Note: these exceptions only apply when the NIS master server is a
+.Fx
+system).
+.Pp
+Consequently, except where noted, the following restrictions apply when
+.Nm
+is used with NIS:
+.Bl -enum -offset indent
+.It
+.Em "Only the shell and GECOS information may be changed" .
+All other
+fields are restricted, even when
+.Nm
+is invoked by the super-user.
+While support for
+changing other fields could be added, this would lead to
+compatibility problems with other NIS-capable systems.
+Even though the super-user may supply data for other fields
+while editing an entry, the extra information (other than the
+password -- see below) will be silently discarded.
+.Pp
+Exception: the super-user on the NIS master server is permitted to
+change any field.
+.Pp
+.It
+.Em "Password authentication is required" .
+The
+.Nm
+utility will prompt for the user's NIS password before effecting
+any changes.
+If the password is invalid, all changes will be
+discarded.
+.Pp
+Exception: the super-user on the NIS master server is allowed to
+submit changes without supplying a password.
+(The super-user may
+choose to turn off this feature using the
+.Fl o
+flag, described below.)
+.It
+.Em "Adding new records to the local password database is discouraged" .
+The
+.Nm
+utility will allow the administrator to add new records to the
+local password database while NIS is enabled, but this can lead to
+some confusion since the new records are appended to the end of
+the master password file, usually after the special NIS '+' entries.
+The administrator should use
+.Xr vipw 8
+to modify the local password
+file when NIS is running.
+.Pp
+The super-user on the NIS master server is permitted to add new records
+to the NIS password maps, provided the
+.Xr rpc.yppasswdd 8
+server has been started with the
+.Fl a
+flag to permitted additions (it refuses them by default).
+The
+.Nm
+utility tries to update the local password database by default; to update the
+NIS maps instead, invoke chpass with the
+.Fl y
+flag.
+.It
+.Em "Password changes are not permitted".
+Users should use
+.Xr passwd 1
+or
+.Xr yppasswd 1
+to change their NIS passwords.
+The super-user is allowed to specify
+a new password (even though the
+.Dq Password:
+field does not show
+up in the editor template, the super-user may add it back by hand),
+but even the super-user must supply the user's original password
+otherwise
+.Xr rpc.yppasswdd 8
+will refuse to update the NIS maps.
+.Pp
+Exception: the super-user on the NIS master server is permitted to
+change a user's NIS password with
+.Nm .
+.El
+.Pp
+There are also a few extra option flags that are available when
+.Nm
+is compiled with NIS support:
+.Bl -tag -width indent
+.It Fl l
+Force
+.Nm
+to modify the local copy of a user's password
+information in the event that a user exists in both
+the local and NIS databases.
+.It Fl y
+Opposite effect of
+.Fl l .
+This flag is largely redundant since
+.Nm
+operates on NIS entries by default if NIS is enabled.
+.It Fl d Ar domain
+Specify a particular NIS domain.
+The
+.Nm
+utility uses the system domain name by default, as set by the
+.Xr domainname 1
+utility.
+The
+.Fl d
+option can be used to override a default, or to specify a domain
+when the system domain name is not set.
+.It Fl h Ar host
+Specify the name or address of an NIS server to query.
+Normally,
+.Nm
+will communicate with the NIS master host specified in the
+.Pa master.passwd
+or
+.Pa passwd
+maps.
+On hosts that have not been configured as NIS clients, there is
+no way for the program to determine this information unless the user
+provides the hostname of a server.
+Note that the specified hostname need
+not be that of the NIS master server; the name of any server, master or
+slave, in a given NIS domain will do.
+.Pp
+When using the
+.Fl d
+option, the hostname defaults to
+.Dq localhost .
+The
+.Fl h
+option can be used in conjunction with the
+.Fl d
+option, in which case the user-specified hostname will override
+the default.
+.Pp
+.It Fl o
+Force the use of RPC-based updates when communicating with
+.Xr rpc.yppasswdd 8
+.Pq Dq old-mode .
+When invoked by the super-user on the NIS master server,
+.Nm
+allows unrestricted changes to the NIS passwd maps using dedicated,
+non-RPC-based mechanism (in this case, a
+.Ux
+domain socket).
+The
+.Fl o
+flag can be used to force
+.Nm
+to use the standard update mechanism instead.
+This option is provided
+mainly for testing purposes.
+.El
+.Sh FILES
+.Bl -tag -width /etc/master.passwd -compact
+.It Pa /etc/master.passwd
+the user database
+.It Pa /etc/passwd
+a Version 7 format password file
+.It Pa /etc/chpass.XXXXXX
+temporary copy of the password file
+.It Pa /etc/shells
+the list of approved shells
+.El
+.Sh SEE ALSO
+.Xr finger 1 ,
+.Xr login 1 ,
+.Xr passwd 1 ,
+.Xr getusershell 3 ,
+.Xr login.conf 5 ,
+.Xr passwd 5 ,
+.Xr pw 8 ,
+.Xr pwd_mkdb 8 ,
+.Xr vipw 8
+.Rs
+.%A Robert Morris
+and
+.%A Ken Thompson
+.%T "UNIX Password security"
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 Reno .
+.Sh BUGS
+User information should (and eventually will) be stored elsewhere.
diff --git a/chpass/chpass.c b/chpass/chpass.c
new file mode 100644
index 0000000..2504e68
--- /dev/null
+++ b/chpass/chpass.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS and NAI Labs, the Security Research Division of Network
+ * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1988, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)chpass.c 8.4 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef YP
+#include <ypclnt.h>
+#endif
+
+#include <pw_scan.h>
+#include <libutil.h>
+
+#include "chpass.h"
+
+int master_mode;
+
+static void baduser(void);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ enum { NEWSH, LOADENTRY, EDITENTRY, NEWPW, NEWEXP } op;
+ struct passwd lpw, *old_pw, *pw;
+ int ch, pfd, tfd;
+ const char *password;
+ char *arg = NULL;
+ uid_t uid;
+#ifdef YP
+ struct ypclnt *ypclnt;
+ const char *yp_domain = NULL, *yp_host = NULL;
+#endif
+
+ pw = old_pw = NULL;
+ op = EDITENTRY;
+#ifdef YP
+ while ((ch = getopt(argc, argv, "a:p:s:e:d:h:loy")) != -1)
+#else
+ while ((ch = getopt(argc, argv, "a:p:s:e:")) != -1)
+#endif
+ switch (ch) {
+ case 'a':
+ op = LOADENTRY;
+ arg = optarg;
+ break;
+ case 's':
+ op = NEWSH;
+ arg = optarg;
+ break;
+ case 'p':
+ op = NEWPW;
+ arg = optarg;
+ break;
+ case 'e':
+ op = NEWEXP;
+ arg = optarg;
+ break;
+#ifdef YP
+ case 'd':
+ yp_domain = optarg;
+ break;
+ case 'h':
+ yp_host = optarg;
+ break;
+ case 'l':
+ case 'o':
+ case 'y':
+ /* compatibility */
+ break;
+#endif
+ case '?':
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1)
+ usage();
+
+ uid = getuid();
+
+ if (op == EDITENTRY || op == NEWSH || op == NEWPW || op == NEWEXP) {
+ if (argc == 0) {
+ if ((pw = getpwuid(uid)) == NULL)
+ errx(1, "unknown user: uid %lu",
+ (unsigned long)uid);
+ } else {
+ if ((pw = getpwnam(*argv)) == NULL)
+ errx(1, "unknown user: %s", *argv);
+ if (uid != 0 && uid != pw->pw_uid)
+ baduser();
+ }
+
+ /* Make a copy for later verification */
+ if ((pw = pw_dup(pw)) == NULL ||
+ (old_pw = pw_dup(pw)) == NULL)
+ err(1, "pw_dup");
+ }
+
+#ifdef YP
+ if (pw != NULL && (pw->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
+ ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_host);
+ master_mode = (ypclnt != NULL &&
+ ypclnt_connect(ypclnt) != -1 &&
+ ypclnt_havepasswdd(ypclnt) == 1);
+ ypclnt_free(ypclnt);
+ } else
+#endif
+ master_mode = (uid == 0);
+
+ if (op == NEWSH) {
+ /* protect p_shell -- it thinks NULL is /bin/sh */
+ if (!arg[0])
+ usage();
+ if (p_shell(arg, pw, (ENTRY *)NULL) == -1)
+ exit(1);
+ }
+
+ if (op == NEWEXP) {
+ if (uid) /* only root can change expire */
+ baduser();
+ if (p_expire(arg, pw, (ENTRY *)NULL) == -1)
+ exit(1);
+ }
+
+ if (op == LOADENTRY) {
+ if (uid)
+ baduser();
+ pw = &lpw;
+ old_pw = NULL;
+ if (!__pw_scan(arg, pw, _PWSCAN_WARN|_PWSCAN_MASTER))
+ exit(1);
+ }
+
+ if (op == NEWPW) {
+ if (uid)
+ baduser();
+
+ if (strchr(arg, ':'))
+ errx(1, "invalid format for password");
+ pw->pw_passwd = arg;
+ }
+
+ if (op == EDITENTRY) {
+ /*
+ * We don't really need pw_*() here, but pw_edit() (used
+ * by edit()) is just too useful...
+ */
+ if (pw_init(NULL, NULL))
+ err(1, "pw_init()");
+ if ((tfd = pw_tmp(-1)) == -1) {
+ pw_fini();
+ err(1, "pw_tmp()");
+ }
+ free(pw);
+ pw = edit(pw_tempname(), old_pw);
+ pw_fini();
+ if (pw == NULL)
+ err(1, "edit()");
+ /*
+ * pw_equal does not check for crypted passwords, so we
+ * should do it explicitly
+ */
+ if (pw_equal(old_pw, pw) &&
+ strcmp(old_pw->pw_passwd, pw->pw_passwd) == 0)
+ errx(0, "user information unchanged");
+ }
+
+ if (old_pw && !master_mode) {
+ password = getpass("Password: ");
+ if (strcmp(crypt(password, old_pw->pw_passwd),
+ old_pw->pw_passwd) != 0)
+ baduser();
+ } else {
+ password = "";
+ }
+
+ if (old_pw != NULL)
+ pw->pw_fields |= (old_pw->pw_fields & _PWF_SOURCE);
+ switch (pw->pw_fields & _PWF_SOURCE) {
+#ifdef YP
+ case _PWF_NIS:
+ ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_host);
+ if (ypclnt == NULL ||
+ ypclnt_connect(ypclnt) == -1 ||
+ ypclnt_passwd(ypclnt, pw, password) == -1) {
+ warnx("%s", ypclnt->error);
+ ypclnt_free(ypclnt);
+ exit(1);
+ }
+ ypclnt_free(ypclnt);
+ errx(0, "NIS user information updated");
+#endif /* YP */
+ case 0:
+ case _PWF_FILES:
+ if (pw_init(NULL, 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(pw->pw_name) == -1) {
+ pw_fini();
+ err(1, "pw_mkdb()");
+ }
+ pw_fini();
+ errx(0, "user information updated");
+ break;
+ default:
+ errx(1, "unsupported passwd source");
+ }
+}
+
+static void
+baduser(void)
+{
+
+ errx(1, "%s", strerror(EACCES));
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: chpass%s %s [user]\n",
+#ifdef YP
+ " [-d domain] [-h host]",
+#else
+ "",
+#endif
+ "[-a list] [-p encpass] [-s shell] [-e mmm dd yy]");
+ exit(1);
+}
diff --git a/chpass/chpass.h b/chpass/chpass.h
new file mode 100644
index 0000000..ed1a586
--- /dev/null
+++ b/chpass/chpass.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS and NAI Labs, the Security Research Division of Network
+ * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * @(#)chpass.h 8.4 (Berkeley) 4/2/94
+ * $FreeBSD$
+ */
+
+struct passwd;
+
+typedef struct _entry {
+ const char *prompt;
+ int (*func)(char *, struct passwd *, struct _entry *);
+ int restricted;
+ size_t len;
+ char *except, *save;
+} ENTRY;
+
+/* Field numbers. */
+#define E_BPHONE 8
+#define E_HPHONE 9
+#define E_LOCATE 10
+#define E_NAME 7
+#define E_OTHER 11
+#define E_SHELL 13
+
+extern ENTRY list[];
+extern int master_mode;
+
+int atot(char *, time_t *);
+struct passwd *edit(const char *, struct passwd *);
+int ok_shell(char *);
+char *dup_shell(char *);
+int p_change(char *, struct passwd *, ENTRY *);
+int p_class(char *, struct passwd *, ENTRY *);
+int p_expire(char *, struct passwd *, ENTRY *);
+int p_gecos(char *, struct passwd *, ENTRY *);
+int p_gid(char *, struct passwd *, ENTRY *);
+int p_hdir(char *, struct passwd *, ENTRY *);
+int p_login(char *, struct passwd *, ENTRY *);
+int p_passwd(char *, struct passwd *, ENTRY *);
+int p_shell(char *, struct passwd *, ENTRY *);
+int p_uid(char *, struct passwd *, ENTRY *);
+char *ttoa(time_t);
diff --git a/chpass/edit.c b/chpass/edit.c
new file mode 100644
index 0000000..ce82f8e
--- /dev/null
+++ b/chpass/edit.c
@@ -0,0 +1,296 @@
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS and NAI Labs, the Security Research Division of Network
+ * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)edit.c 8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <pw_scan.h>
+#include <libutil.h>
+
+#include "chpass.h"
+
+static int display(const char *tfn, struct passwd *pw);
+static struct passwd *verify(const char *tfn, struct passwd *pw);
+
+struct passwd *
+edit(const char *tfn, struct passwd *pw)
+{
+ struct passwd *npw;
+ char *line;
+ size_t len;
+
+ if (display(tfn, pw) == -1)
+ return (NULL);
+ for (;;) {
+ switch (pw_edit(1)) {
+ case -1:
+ return (NULL);
+ case 0:
+ return (pw_dup(pw));
+ default:
+ break;
+ }
+ if ((npw = verify(tfn, pw)) != NULL)
+ return (npw);
+ free(npw);
+ printf("re-edit the password file? ");
+ fflush(stdout);
+ if ((line = fgetln(stdin, &len)) == NULL) {
+ warn("fgetln()");
+ return (NULL);
+ }
+ if (len > 0 && (*line == 'N' || *line == 'n'))
+ return (NULL);
+ }
+}
+
+/*
+ * display --
+ * print out the file for the user to edit; strange side-effect:
+ * set conditional flag if the user gets to edit the shell.
+ */
+static int
+display(const char *tfn, struct passwd *pw)
+{
+ FILE *fp;
+ char *bp, *gecos, *p;
+
+ if ((fp = fopen(tfn, "w")) == NULL) {
+ warn("%s", tfn);
+ return (-1);
+ }
+
+ (void)fprintf(fp,
+ "#Changing user information for %s.\n", pw->pw_name);
+ if (master_mode) {
+ (void)fprintf(fp, "Login: %s\n", pw->pw_name);
+ (void)fprintf(fp, "Password: %s\n", pw->pw_passwd);
+ (void)fprintf(fp, "Uid [#]: %lu\n", (unsigned long)pw->pw_uid);
+ (void)fprintf(fp, "Gid [# or name]: %lu\n",
+ (unsigned long)pw->pw_gid);
+ (void)fprintf(fp, "Change [month day year]: %s\n",
+ ttoa(pw->pw_change));
+ (void)fprintf(fp, "Expire [month day year]: %s\n",
+ ttoa(pw->pw_expire));
+ (void)fprintf(fp, "Class: %s\n", pw->pw_class);
+ (void)fprintf(fp, "Home directory: %s\n", pw->pw_dir);
+ (void)fprintf(fp, "Shell: %s\n",
+ *pw->pw_shell ? pw->pw_shell : _PATH_BSHELL);
+ }
+ /* Only admin can change "restricted" shells. */
+#if 0
+ else if (ok_shell(pw->pw_shell))
+ /*
+ * Make shell a restricted field. Ugly with a
+ * necklace, but there's not much else to do.
+ */
+#else
+ else if ((!list[E_SHELL].restricted && ok_shell(pw->pw_shell)) ||
+ master_mode)
+ /*
+ * If change not restrict (table.c) and standard shell
+ * OR if root, then allow editing of shell.
+ */
+#endif
+ (void)fprintf(fp, "Shell: %s\n",
+ *pw->pw_shell ? pw->pw_shell : _PATH_BSHELL);
+ else
+ list[E_SHELL].restricted = 1;
+
+ if ((bp = gecos = strdup(pw->pw_gecos)) == NULL) {
+ warn(NULL);
+ fclose(fp);
+ return (-1);
+ }
+
+ p = strsep(&bp, ",");
+ p = strdup(p ? p : "");
+ list[E_NAME].save = p;
+ if (!list[E_NAME].restricted || master_mode)
+ (void)fprintf(fp, "Full Name: %s\n", p);
+
+ p = strsep(&bp, ",");
+ p = strdup(p ? p : "");
+ list[E_LOCATE].save = p;
+ if (!list[E_LOCATE].restricted || master_mode)
+ (void)fprintf(fp, "Office Location: %s\n", p);
+
+ p = strsep(&bp, ",");
+ p = strdup(p ? p : "");
+ list[E_BPHONE].save = p;
+ if (!list[E_BPHONE].restricted || master_mode)
+ (void)fprintf(fp, "Office Phone: %s\n", p);
+
+ p = strsep(&bp, ",");
+ p = strdup(p ? p : "");
+ list[E_HPHONE].save = p;
+ if (!list[E_HPHONE].restricted || master_mode)
+ (void)fprintf(fp, "Home Phone: %s\n", p);
+
+ bp = strdup(bp ? bp : "");
+ list[E_OTHER].save = bp;
+ if (!list[E_OTHER].restricted || master_mode)
+ (void)fprintf(fp, "Other information: %s\n", bp);
+
+ free(gecos);
+
+ (void)fchown(fileno(fp), getuid(), getgid());
+ (void)fclose(fp);
+ return (0);
+}
+
+static struct passwd *
+verify(const char *tfn, struct passwd *pw)
+{
+ struct passwd *npw;
+ ENTRY *ep;
+ char *buf, *p, *val;
+ struct stat sb;
+ FILE *fp;
+ int line;
+ size_t len;
+
+ if ((pw = pw_dup(pw)) == NULL)
+ return (NULL);
+ if ((fp = fopen(tfn, "r")) == NULL ||
+ fstat(fileno(fp), &sb) == -1) {
+ warn("%s", tfn);
+ free(pw);
+ return (NULL);
+ }
+ if (sb.st_size == 0) {
+ warnx("corrupted temporary file");
+ fclose(fp);
+ free(pw);
+ return (NULL);
+ }
+ val = NULL;
+ for (line = 1; (buf = fgetln(fp, &len)) != NULL; ++line) {
+ if (*buf == '\0' || *buf == '#')
+ continue;
+ while (len > 0 && isspace(buf[len - 1]))
+ --len;
+ for (ep = list;; ++ep) {
+ if (!ep->prompt) {
+ warnx("%s: unrecognized field on line %d",
+ tfn, line);
+ goto bad;
+ }
+ if (ep->len > len)
+ continue;
+ if (strncasecmp(buf, ep->prompt, ep->len) != 0)
+ continue;
+ if (ep->restricted && !master_mode) {
+ warnx("%s: you may not change the %s field",
+ tfn, ep->prompt);
+ goto bad;
+ }
+ for (p = buf; p < buf + len && *p != ':'; ++p)
+ /* nothing */ ;
+ if (*p != ':') {
+ warnx("%s: line %d corrupted", tfn, line);
+ goto bad;
+ }
+ while (++p < buf + len && isspace(*p))
+ /* nothing */ ;
+ free(val);
+ asprintf(&val, "%.*s", (int)(buf + len - p), p);
+ if (val == NULL)
+ goto bad;
+ if (ep->except && strpbrk(val, ep->except)) {
+ warnx("%s: invalid character in \"%s\" field '%s'",
+ tfn, ep->prompt, val);
+ goto bad;
+ }
+ if ((ep->func)(val, pw, ep))
+ goto bad;
+ break;
+ }
+ }
+ free(val);
+ fclose(fp);
+
+ /* Build the gecos field. */
+ len = asprintf(&p, "%s,%s,%s,%s,%s", list[E_NAME].save,
+ list[E_LOCATE].save, list[E_BPHONE].save,
+ list[E_HPHONE].save, list[E_OTHER].save);
+ if (p == NULL) {
+ warn("asprintf()");
+ free(pw);
+ return (NULL);
+ }
+ while (len > 0 && p[len - 1] == ',')
+ p[--len] = '\0';
+ pw->pw_gecos = p;
+ buf = pw_make(pw);
+ free(pw);
+ free(p);
+ if (buf == NULL) {
+ warn("pw_make()");
+ return (NULL);
+ }
+ npw = pw_scan(buf, PWSCAN_WARN|PWSCAN_MASTER);
+ free(buf);
+ return (npw);
+bad:
+ free(pw);
+ free(val);
+ fclose(fp);
+ return (NULL);
+}
diff --git a/chpass/field.c b/chpass/field.c
new file mode 100644
index 0000000..eac5561
--- /dev/null
+++ b/chpass/field.c
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS and NAI Labs, the Security Research Division of Network
+ * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)field.c 8.4 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "chpass.h"
+
+/* ARGSUSED */
+int
+p_login(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ if (!*p) {
+ warnx("empty login field");
+ return (-1);
+ }
+ if (*p == '-') {
+ warnx("login names may not begin with a hyphen");
+ return (-1);
+ }
+ if (!(pw->pw_name = strdup(p))) {
+ warnx("can't save entry");
+ return (-1);
+ }
+ if (strchr(p, '.'))
+ warnx("\'.\' is dangerous in a login name");
+ for (; *p; ++p)
+ if (isupper(*p)) {
+ warnx("upper-case letters are dangerous in a login name");
+ break;
+ }
+ return (0);
+}
+
+/* ARGSUSED */
+int
+p_passwd(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ if (!(pw->pw_passwd = strdup(p))) {
+ warnx("can't save password entry");
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* ARGSUSED */
+int
+p_uid(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ uid_t id;
+ char *np;
+
+ if (!*p) {
+ warnx("empty uid field");
+ return (-1);
+ }
+ if (!isdigit(*p)) {
+ warnx("illegal uid");
+ return (-1);
+ }
+ errno = 0;
+ id = strtoul(p, &np, 10);
+ if (*np || (id == (uid_t)ULONG_MAX && errno == ERANGE)) {
+ warnx("illegal uid");
+ return (-1);
+ }
+ pw->pw_uid = id;
+ return (0);
+}
+
+/* ARGSUSED */
+int
+p_gid(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ struct group *gr;
+ gid_t id;
+ char *np;
+
+ if (!*p) {
+ warnx("empty gid field");
+ return (-1);
+ }
+ if (!isdigit(*p)) {
+ if (!(gr = getgrnam(p))) {
+ warnx("unknown group %s", p);
+ return (-1);
+ }
+ pw->pw_gid = gr->gr_gid;
+ return (0);
+ }
+ errno = 0;
+ id = strtoul(p, &np, 10);
+ if (*np || (id == (uid_t)ULONG_MAX && errno == ERANGE)) {
+ warnx("illegal gid");
+ return (-1);
+ }
+ pw->pw_gid = id;
+ return (0);
+}
+
+/* ARGSUSED */
+int
+p_class(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ if (!(pw->pw_class = strdup(p))) {
+ warnx("can't save entry");
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* ARGSUSED */
+int
+p_change(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ if (!atot(p, &pw->pw_change))
+ return (0);
+ warnx("illegal date for change field");
+ return (-1);
+}
+
+/* ARGSUSED */
+int
+p_expire(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ if (!atot(p, &pw->pw_expire))
+ return (0);
+ warnx("illegal date for expire field");
+ return (-1);
+}
+
+/* ARGSUSED */
+int
+p_gecos(char *p, struct passwd *pw __unused, ENTRY *ep)
+{
+ if (!(ep->save = strdup(p))) {
+ warnx("can't save entry");
+ return (-1);
+ }
+ return (0);
+}
+
+/* ARGSUSED */
+int
+p_hdir(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ if (!*p) {
+ warnx("empty home directory field");
+ return (-1);
+ }
+ if (!(pw->pw_dir = strdup(p))) {
+ warnx("can't save entry");
+ return (-1);
+ }
+ return (0);
+}
+
+/* ARGSUSED */
+int
+p_shell(char *p, struct passwd *pw, ENTRY *ep __unused)
+{
+ struct stat sbuf;
+
+ if (!*p) {
+ pw->pw_shell = strdup(_PATH_BSHELL);
+ return (0);
+ }
+ /* only admin can change from or to "restricted" shells */
+ if (!master_mode && pw->pw_shell && !ok_shell(pw->pw_shell)) {
+ warnx("%s: current shell non-standard", pw->pw_shell);
+ return (-1);
+ }
+ if (!ok_shell(p)) {
+ if (!master_mode) {
+ warnx("%s: non-standard shell", p);
+ return (-1);
+ }
+ pw->pw_shell = strdup(p);
+ }
+ else
+ pw->pw_shell = dup_shell(p);
+ if (!pw->pw_shell) {
+ warnx("can't save entry");
+ return (-1);
+ }
+ if (stat(pw->pw_shell, &sbuf) < 0) {
+ if (errno == ENOENT)
+ warnx("WARNING: shell '%s' does not exist",
+ pw->pw_shell);
+ else
+ warn("WARNING: can't stat shell '%s'", pw->pw_shell);
+ return (0);
+ }
+ if (!S_ISREG(sbuf.st_mode)) {
+ warnx("WARNING: shell '%s' is not a regular file",
+ pw->pw_shell);
+ return (0);
+ }
+ if ((sbuf.st_mode & (S_IXOTH | S_IXGRP | S_IXUSR)) == 0) {
+ warnx("WARNING: shell '%s' is not executable", pw->pw_shell);
+ return (0);
+ }
+ return (0);
+}
diff --git a/chpass/table.c b/chpass/table.c
new file mode 100644
index 0000000..19f1a99
--- /dev/null
+++ b/chpass/table.c
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
+
+#if 0
+#ifndef lint
+static const char sccsid[] = "@(#)table.c 8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <stddef.h>
+#include "chpass.h"
+
+char e1[] = ": ";
+char e2[] = ":,";
+
+ENTRY list[] = {
+ { "login", p_login, 1, 5, e1, NULL },
+ { "password", p_passwd, 1, 8, e1, NULL },
+ { "uid", p_uid, 1, 3, e1, NULL },
+ { "gid", p_gid, 1, 3, e1, NULL },
+ { "class", p_class, 1, 5, e1, NULL },
+ { "change", p_change, 1, 6, NULL, NULL },
+ { "expire", p_expire, 1, 6, NULL, NULL },
+#ifdef RESTRICT_FULLNAME_CHANGE /* do not allow fullname changes */
+ { "full name", p_gecos, 1, 9, e2, NULL },
+#else
+ { "full name", p_gecos, 0, 9, e2, NULL },
+#endif
+ { "office phone", p_gecos, 0, 12, e2, NULL },
+ { "home phone", p_gecos, 0, 10, e2, NULL },
+ { "office location", p_gecos, 0, 15, e2, NULL },
+ { "other information", p_gecos, 0, 11, e1, NULL },
+ { "home directory", p_hdir, 1, 14, e1, NULL },
+ { "shell", p_shell, 0, 5, e1, NULL },
+ { NULL, NULL, 0, 0, NULL, NULL },
+};
diff --git a/chpass/util.c b/chpass/util.c
new file mode 100644
index 0000000..07d96e2
--- /dev/null
+++ b/chpass/util.c
@@ -0,0 +1,182 @@
+/*-
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS and NAI Labs, the Security Research Division of Network
+ * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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
+#if 0
+static char sccsid[] = "@(#)util.c 8.4 (Berkeley) 4/2/94";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "chpass.h"
+
+static const char *months[] =
+ { "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November",
+ "December", NULL };
+
+char *
+ttoa(time_t tval)
+{
+ struct tm *tp;
+ static char tbuf[50];
+
+ if (tval) {
+ tp = localtime(&tval);
+ (void)sprintf(tbuf, "%s %d, %d", months[tp->tm_mon],
+ tp->tm_mday, tp->tm_year + 1900);
+ }
+ else
+ *tbuf = '\0';
+ return (tbuf);
+}
+
+int
+atot(char *p, time_t *store)
+{
+ static struct tm *lt;
+ char *t;
+ const char **mp;
+ time_t tval;
+ int day, month, year;
+
+ if (!*p) {
+ *store = 0;
+ return (0);
+ }
+ if (!lt) {
+ unsetenv("TZ");
+ (void)time(&tval);
+ lt = localtime(&tval);
+ }
+ if (!(t = strtok(p, " \t")))
+ goto bad;
+ if (isdigit(*t)) {
+ month = atoi(t);
+ } else {
+ for (mp = months;; ++mp) {
+ if (!*mp)
+ goto bad;
+ if (!strncasecmp(*mp, t, 3)) {
+ month = mp - months + 1;
+ break;
+ }
+ }
+ }
+ if (!(t = strtok((char *)NULL, " \t,")) || !isdigit(*t))
+ goto bad;
+ day = atoi(t);
+ if (!(t = strtok((char *)NULL, " \t,")) || !isdigit(*t))
+ goto bad;
+ year = atoi(t);
+ if (day < 1 || day > 31 || month < 1 || month > 12)
+ goto bad;
+ /* Allow two digit years 1969-2068 */
+ if (year < 69)
+ year += 2000;
+ else if (year < 100)
+ year += 1900;
+ if (year < 1969)
+bad: return (1);
+ lt->tm_year = year - 1900;
+ lt->tm_mon = month - 1;
+ lt->tm_mday = day;
+ lt->tm_hour = 0;
+ lt->tm_min = 0;
+ lt->tm_sec = 0;
+ lt->tm_isdst = -1;
+ if ((tval = mktime(lt)) < 0)
+ return (1);
+ *store = tval;
+ return (0);
+}
+
+int
+ok_shell(char *name)
+{
+ char *p, *sh;
+
+ setusershell();
+ while ((sh = getusershell())) {
+ if (!strcmp(name, sh)) {
+ endusershell();
+ return (1);
+ }
+ /* allow just shell name, but use "real" path */
+ if ((p = strrchr(sh, '/')) && strcmp(name, p + 1) == 0) {
+ endusershell();
+ return (1);
+ }
+ }
+ endusershell();
+ return (0);
+}
+
+char *
+dup_shell(char *name)
+{
+ char *p, *sh, *ret;
+
+ setusershell();
+ while ((sh = getusershell())) {
+ if (!strcmp(name, sh)) {
+ endusershell();
+ return (strdup(name));
+ }
+ /* allow just shell name, but use "real" path */
+ if ((p = strrchr(sh, '/')) && strcmp(name, p + 1) == 0) {
+ ret = strdup(sh);
+ endusershell();
+ return (ret);
+ }
+ }
+ endusershell();
+ return (NULL);
+}
diff --git a/libc/gen/pw_scan.c b/libc/gen/pw_scan.c
new file mode 100644
index 0000000..9242dd0
--- /dev/null
+++ b/libc/gen/pw_scan.c
@@ -0,0 +1,201 @@
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)pw_scan.c 8.3 (Berkeley) 4/2/94";
+#endif /* LIBC_SCCS and not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * This module is used to "verify" password entries by chpass(1) and
+ * pwd_mkdb(8).
+ */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "pw_scan.h"
+
+/*
+ * Some software assumes that IDs are short. We should emit warnings
+ * for id's which cannot be stored in a short, but we are more liberal
+ * by default, warning for IDs greater than USHRT_MAX.
+ *
+ * If pw_big_ids_warning is -1 on entry to pw_scan(), it will be set based
+ * on the existence of PW_SCAN_BIG_IDS in the environment.
+ */
+static int pw_big_ids_warning = -1;
+
+int
+__pw_scan(char *bp, struct passwd *pw, int flags)
+{
+ uid_t id;
+ int root;
+ char *ep, *p, *sh;
+
+ if (pw_big_ids_warning == -1)
+ pw_big_ids_warning = getenv("PW_SCAN_BIG_IDS") == NULL ? 1 : 0;
+
+ pw->pw_fields = 0;
+ if (!(pw->pw_name = strsep(&bp, ":"))) /* login */
+ goto fmt;
+ root = !strcmp(pw->pw_name, "root");
+ if (pw->pw_name[0] && (pw->pw_name[0] != '+' || pw->pw_name[1] == '\0'))
+ pw->pw_fields |= _PWF_NAME;
+
+ if (!(pw->pw_passwd = strsep(&bp, ":"))) /* passwd */
+ goto fmt;
+ if (pw->pw_passwd[0])
+ pw->pw_fields |= _PWF_PASSWD;
+
+ if (!(p = strsep(&bp, ":"))) /* uid */
+ goto fmt;
+ if (p[0])
+ pw->pw_fields |= _PWF_UID;
+ else {
+ if (pw->pw_name[0] != '+' && pw->pw_name[0] != '-') {
+ if (flags & _PWSCAN_WARN)
+ warnx("no uid for user %s", pw->pw_name);
+ return (0);
+ }
+ }
+ id = strtoul(p, &ep, 10);
+ if (errno == ERANGE) {
+ if (flags & _PWSCAN_WARN)
+ warnx("%s > max uid value (%lu)", p, ULONG_MAX);
+ return (0);
+ }
+ if (*ep != '\0') {
+ if (flags & _PWSCAN_WARN)
+ warnx("%s uid is incorrect", p);
+ return (0);
+ }
+ if (root && id) {
+ if (flags & _PWSCAN_WARN)
+ warnx("root uid should be 0");
+ return (0);
+ }
+ if (flags & _PWSCAN_WARN && pw_big_ids_warning && id > USHRT_MAX) {
+ warnx("%s > recommended max uid value (%u)", p, USHRT_MAX);
+ /*return (0);*/ /* THIS SHOULD NOT BE FATAL! */
+ }
+ pw->pw_uid = id;
+
+ if (!(p = strsep(&bp, ":"))) /* gid */
+ goto fmt;
+ if (p[0])
+ pw->pw_fields |= _PWF_GID;
+ else {
+ if (pw->pw_name[0] != '+' && pw->pw_name[0] != '-') {
+ if (flags & _PWSCAN_WARN)
+ warnx("no gid for user %s", pw->pw_name);
+ return (0);
+ }
+ }
+ id = strtoul(p, &ep, 10);
+ if (errno == ERANGE) {
+ if (flags & _PWSCAN_WARN)
+ warnx("%s > max gid value (%lu)", p, ULONG_MAX);
+ return (0);
+ }
+ if (*ep != '\0') {
+ if (flags & _PWSCAN_WARN)
+ warnx("%s gid is incorrect", p);
+ return (0);
+ }
+ if (flags & _PWSCAN_WARN && pw_big_ids_warning && id > USHRT_MAX) {
+ warnx("%s > recommended max gid value (%u)", p, USHRT_MAX);
+ /* return (0); This should not be fatal! */
+ }
+ pw->pw_gid = id;
+
+ if (flags & _PWSCAN_MASTER ) {
+ if (!(pw->pw_class = strsep(&bp, ":"))) /* class */
+ goto fmt;
+ if (pw->pw_class[0])
+ pw->pw_fields |= _PWF_CLASS;
+
+ if (!(p = strsep(&bp, ":"))) /* change */
+ goto fmt;
+ if (p[0])
+ pw->pw_fields |= _PWF_CHANGE;
+ pw->pw_change = atol(p);
+
+ if (!(p = strsep(&bp, ":"))) /* expire */
+ goto fmt;
+ if (p[0])
+ pw->pw_fields |= _PWF_EXPIRE;
+ pw->pw_expire = atol(p);
+ }
+ if (!(pw->pw_gecos = strsep(&bp, ":"))) /* gecos */
+ goto fmt;
+ if (pw->pw_gecos[0])
+ pw->pw_fields |= _PWF_GECOS;
+
+ if (!(pw->pw_dir = strsep(&bp, ":"))) /* directory */
+ goto fmt;
+ if (pw->pw_dir[0])
+ pw->pw_fields |= _PWF_DIR;
+
+ if (!(pw->pw_shell = strsep(&bp, ":"))) /* shell */
+ goto fmt;
+
+ p = pw->pw_shell;
+ if (root && *p) { /* empty == /bin/sh */
+ for (setusershell();;) {
+ if (!(sh = getusershell())) {
+ if (flags & _PWSCAN_WARN)
+ warnx("warning, unknown root shell");
+ break;
+ }
+ if (!strcmp(p, sh))
+ break;
+ }
+ endusershell();
+ }
+ if (p[0])
+ pw->pw_fields |= _PWF_SHELL;
+
+ if ((p = strsep(&bp, ":"))) { /* too many */
+fmt:
+ if (flags & _PWSCAN_WARN)
+ warnx("corrupted entry");
+ return (0);
+ }
+ return (1);
+}
diff --git a/libc/gen/pw_scan.h b/libc/gen/pw_scan.h
new file mode 100644
index 0000000..468096c
--- /dev/null
+++ b/libc/gen/pw_scan.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 1994
+ * The Regents of the University of California. 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * @(#)pw_scan.h 8.1 (Berkeley) 4/1/94
+ * $FreeBSD$
+ */
+
+#define _PWSCAN_MASTER 0x01
+#define _PWSCAN_WARN 0x02
+
+extern int __pw_scan(char *, struct passwd *, int);
diff --git a/libc/stdlib/strtonum.c b/libc/stdlib/strtonum.c
new file mode 100644
index 0000000..6dccd97
--- /dev/null
+++ b/libc/stdlib/strtonum.c
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#define INVALID 1
+#define TOOSMALL 2
+#define TOOLARGE 3
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+ const char **errstrp)
+{
+ long long ll = 0;
+ char *ep;
+ int error = 0;
+ struct errval {
+ const char *errstr;
+ int err;
+ } ev[4] = {
+ { NULL, 0 },
+ { "invalid", EINVAL },
+ { "too small", ERANGE },
+ { "too large", ERANGE },
+ };
+
+ ev[0].err = errno;
+ errno = 0;
+ if (minval > maxval)
+ error = INVALID;
+ else {
+ ll = strtoll(numstr, &ep, 10);
+ if (errno == EINVAL || numstr == ep || *ep != '\0')
+ error = INVALID;
+ else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+ error = TOOSMALL;
+ else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+ error = TOOLARGE;
+ }
+ if (errstrp != NULL)
+ *errstrp = ev[error].errstr;
+ errno = ev[error].err;
+ if (error)
+ ll = 0;
+
+ return (ll);
+}
diff --git a/libutil/_secure_path.c b/libutil/_secure_path.c
new file mode 100644
index 0000000..363378b
--- /dev/null
+++ b/libutil/_secure_path.c
@@ -0,0 +1,74 @@
+/*-
+ * Based on code copyright (c) 1995,1997 by
+ * Berkeley Software Design, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, is permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice immediately at the beginning of the file, without modification,
+ * 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.
+ * 3. This work was done expressly for inclusion into FreeBSD. Other use
+ * is permitted provided this notation is included.
+ * 4. Absolutely no warranty of function or purpose is made by the authors.
+ * 5. Modifications may be freely made to this file providing the above
+ * conditions are met.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <libutil.h>
+#include <stddef.h>
+#include <syslog.h>
+
+/*
+ * Check for common security problems on a given path
+ * It must be:
+ * 1. A regular file, and exists
+ * 2. Owned and writable only by root (or given owner)
+ * 3. Group ownership is given group or is non-group writable
+ *
+ * Returns: -2 if file does not exist,
+ * -1 if security test failure
+ * 0 otherwise
+ */
+
+int
+_secure_path(const char *path, uid_t uid, gid_t gid)
+{
+ int r = -1;
+ struct stat sb;
+ const char *msg = NULL;
+
+ if (lstat(path, &sb) < 0) {
+ if (errno == ENOENT) /* special case */
+ r = -2; /* if it is just missing, skip the log entry */
+ else
+ msg = "%s: cannot stat %s: %m";
+ }
+ else if (!S_ISREG(sb.st_mode))
+ msg = "%s: %s is not a regular file";
+ else if (sb.st_mode & S_IWOTH)
+ msg = "%s: %s is world writable";
+ else if ((int)uid != -1 && sb.st_uid != uid && sb.st_uid != 0) {
+ if (uid == 0)
+ msg = "%s: %s is not owned by root";
+ else
+ msg = "%s: %s is not owned by uid %d";
+ } else if ((int)gid != -1 && sb.st_gid != gid && (sb.st_mode & S_IWGRP))
+ msg = "%s: %s is group writeable by non-authorised groups";
+ else
+ r = 0;
+ if (msg != NULL)
+ syslog(LOG_ERR, msg, "_secure_path", path, uid);
+ return r;
+}
diff --git a/libutil/flopen.c b/libutil/flopen.c
new file mode 100644
index 0000000..ae98daf
--- /dev/null
+++ b/libutil/flopen.c
@@ -0,0 +1,108 @@
+/*-
+ * Copyright (c) 2007 Dag-Erling Coïdan Smørgrav
+ * 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
+ * in this position and unchanged.
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+int
+flopen(const char *path, int flags, ...)
+{
+ int fd, operation, serrno, trunc;
+ struct flock lock;
+ struct stat sb, fsb;
+ mode_t mode;
+
+#ifdef O_EXLOCK
+ flags &= ~O_EXLOCK;
+#endif
+
+ mode = 0;
+ if (flags & O_CREAT) {
+ va_list ap;
+
+ va_start(ap, flags);
+ mode = (mode_t)va_arg(ap, int); /* mode_t promoted to int */
+ va_end(ap);
+ }
+
+ memset(&lock, 0, sizeof lock);
+ lock.l_type = ((flags & O_ACCMODE) == O_RDONLY) ? F_RDLCK : F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ operation = (flags & O_NONBLOCK) ? F_SETLK : F_SETLKW;
+
+ trunc = (flags & O_TRUNC);
+ flags &= ~O_TRUNC;
+
+ for (;;) {
+ if ((fd = open(path, flags, mode)) == -1)
+ /* non-existent or no access */
+ return (-1);
+ if (fcntl(fd, operation, &lock) == -1) {
+ /* unsupported or interrupted */
+ serrno = errno;
+ (void)close(fd);
+ errno = serrno;
+ return (-1);
+ }
+ if (stat(path, &sb) == -1) {
+ /* disappeared from under our feet */
+ (void)close(fd);
+ continue;
+ }
+ if (fstat(fd, &fsb) == -1) {
+ /* can't happen [tm] */
+ serrno = errno;
+ (void)close(fd);
+ errno = serrno;
+ return (-1);
+ }
+ if (sb.st_dev != fsb.st_dev ||
+ sb.st_ino != fsb.st_ino) {
+ /* changed under our feet */
+ (void)close(fd);
+ continue;
+ }
+ if (trunc && ftruncate(fd, 0) != 0) {
+ /* can't happen [tm] */
+ serrno = errno;
+ (void)close(fd);
+ errno = serrno;
+ return (-1);
+ }
+ return (fd);
+ }
+}
diff --git a/libutil/gr_util.c b/libutil/gr_util.c
new file mode 100644
index 0000000..77e0653
--- /dev/null
+++ b/libutil/gr_util.c
@@ -0,0 +1,250 @@
+/*-
+ * Copyright (c) 2008 Sean C. Farley <scf@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,
+ * without modification, immediately at the beginning of the file.
+ * 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 ``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 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <grp.h>
+#include <inttypes.h>
+#include <libutil.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct group_storage {
+ struct group gr;
+ char *members[];
+};
+
+static const char group_line_format[] = "%s:%s:%ju:";
+
+/*
+ * Compares two struct group's.
+ */
+int
+gr_equal(const struct group *gr1, const struct group *gr2)
+{
+ int gr1_ndx;
+ int gr2_ndx;
+ bool found;
+
+ /* Check that the non-member information is the same. */
+ if (gr1->gr_name == NULL || gr2->gr_name == NULL) {
+ if (gr1->gr_name != gr2->gr_name)
+ return (false);
+ } else if (strcmp(gr1->gr_name, gr2->gr_name) != 0)
+ return (false);
+ if (gr1->gr_passwd == NULL || gr2->gr_passwd == NULL) {
+ if (gr1->gr_passwd != gr2->gr_passwd)
+ return (false);
+ } else if (strcmp(gr1->gr_passwd, gr2->gr_passwd) != 0)
+ return (false);
+ if (gr1->gr_gid != gr2->gr_gid)
+ return (false);
+
+ /* Check all members in both groups. */
+ if (gr1->gr_mem == NULL || gr2->gr_mem == NULL) {
+ if (gr1->gr_mem != gr2->gr_mem)
+ return (false);
+ } else {
+ for (found = false, gr1_ndx = 0; gr1->gr_mem[gr1_ndx] != NULL;
+ gr1_ndx++) {
+ for (gr2_ndx = 0; gr2->gr_mem[gr2_ndx] != NULL;
+ gr2_ndx++)
+ if (strcmp(gr1->gr_mem[gr1_ndx],
+ gr2->gr_mem[gr2_ndx]) == 0) {
+ found = true;
+ break;
+ }
+ if (!found)
+ return (false);
+ }
+
+ /* Check that group2 does not have more members than group1. */
+ if (gr2->gr_mem[gr1_ndx] != NULL)
+ return (false);
+ }
+
+ return (true);
+}
+
+/*
+ * Make a group line out of a struct group.
+ */
+char *
+gr_make(const struct group *gr)
+{
+ char *line;
+ size_t line_size;
+ int ndx;
+
+ /* Calculate the length of the group line. */
+ line_size = snprintf(NULL, 0, group_line_format, gr->gr_name,
+ gr->gr_passwd, (uintmax_t)gr->gr_gid) + 1;
+ if (gr->gr_mem != NULL) {
+ for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++)
+ line_size += strlen(gr->gr_mem[ndx]) + 1;
+ if (ndx > 0)
+ line_size--;
+ }
+
+ /* Create the group line and fill it. */
+ if ((line = malloc(line_size)) == NULL)
+ return (NULL);
+ line_size = snprintf(line, line_size, group_line_format, gr->gr_name,
+ gr->gr_passwd, (uintmax_t)gr->gr_gid);
+ if (gr->gr_mem != NULL)
+ for (ndx = 0; gr->gr_mem[ndx] != NULL; ndx++) {
+ strcat(line, gr->gr_mem[ndx]);
+ if (gr->gr_mem[ndx + 1] != NULL)
+ strcat(line, ",");
+ }
+
+ return (line);
+}
+
+/*
+ * Duplicate a struct group.
+ */
+struct group *
+gr_dup(const struct group *gr)
+{
+ char *dst;
+ size_t len;
+ struct group_storage *gs;
+ int ndx;
+ int num_mem;
+
+ /* Calculate size of the group. */
+ len = sizeof(*gs);
+ if (gr->gr_name != NULL)
+ len += strlen(gr->gr_name) + 1;
+ if (gr->gr_passwd != NULL)
+ len += strlen(gr->gr_passwd) + 1;
+ if (gr->gr_mem != NULL) {
+ for (num_mem = 0; gr->gr_mem[num_mem] != NULL; num_mem++)
+ len += strlen(gr->gr_mem[num_mem]) + 1;
+ len += (num_mem + 1) * sizeof(*gr->gr_mem);
+ } else
+ num_mem = -1;
+
+ /* Create new group and copy old group into it. */
+ if ((gs = calloc(1, len)) == NULL)
+ return (NULL);
+ dst = (char *)&gs->members[num_mem + 1];
+ if (gr->gr_name != NULL) {
+ gs->gr.gr_name = dst;
+ dst = stpcpy(gs->gr.gr_name, gr->gr_name) + 1;
+ }
+ if (gr->gr_passwd != NULL) {
+ gs->gr.gr_passwd = dst;
+ dst = stpcpy(gs->gr.gr_passwd, gr->gr_passwd) + 1;
+ }
+ gs->gr.gr_gid = gr->gr_gid;
+ if (gr->gr_mem != NULL) {
+ gs->gr.gr_mem = gs->members;
+ for (ndx = 0; ndx < num_mem; ndx++) {
+ gs->gr.gr_mem[ndx] = dst;
+ dst = stpcpy(gs->gr.gr_mem[ndx], gr->gr_mem[ndx]) + 1;
+ }
+ gs->gr.gr_mem[ndx] = NULL;
+ }
+
+ return (&gs->gr);
+}
+
+/*
+ * Scan a line and place it into a group structure.
+ */
+static bool
+__gr_scan(char *line, struct group *gr)
+{
+ char *loc;
+ int ndx;
+
+ /* Assign non-member information to structure. */
+ gr->gr_name = line;
+ if ((loc = strchr(line, ':')) == NULL)
+ return (false);
+ *loc = '\0';
+ gr->gr_passwd = loc + 1;
+ if (*gr->gr_passwd == ':')
+ *gr->gr_passwd = '\0';
+ else {
+ if ((loc = strchr(loc + 1, ':')) == NULL)
+ return (false);
+ *loc = '\0';
+ }
+ if (sscanf(loc + 1, "%u", &gr->gr_gid) != 1)
+ return (false);
+
+ /* Assign member information to structure. */
+ if ((loc = strchr(loc + 1, ':')) == NULL)
+ return (false);
+ line = loc + 1;
+ gr->gr_mem = NULL;
+ ndx = 0;
+ do {
+ gr->gr_mem = reallocf(gr->gr_mem, sizeof(*gr->gr_mem) *
+ (ndx + 1));
+ if (gr->gr_mem == NULL)
+ return (false);
+
+ /* Skip locations without members (i.e., empty string). */
+ do {
+ gr->gr_mem[ndx] = strsep(&line, ",");
+ } while (gr->gr_mem[ndx] != NULL && *gr->gr_mem[ndx] == '\0');
+ } while (gr->gr_mem[ndx++] != NULL);
+
+ return (true);
+}
+
+/*
+ * Create a struct group from a line.
+ */
+struct group *
+gr_scan(const char *line)
+{
+ struct group gr;
+ char *line_copy;
+ struct group *new_gr;
+
+ if ((line_copy = strdup(line)) == NULL)
+ return (NULL);
+ if (!__gr_scan(line_copy, &gr)) {
+ free(line_copy);
+ return (NULL);
+ }
+ new_gr = gr_dup(&gr);
+ free(line_copy);
+ if (gr.gr_mem != NULL)
+ free(gr.gr_mem);
+
+ return (new_gr);
+}
diff --git a/libutil/libutil.h b/libutil/libutil.h
new file mode 100644
index 0000000..3187fb3
--- /dev/null
+++ b/libutil/libutil.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 1996 Peter Wemm <peter@FreeBSD.org>.
+ * All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS and NAI Labs, the Security Research Division of Network
+ * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, is 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.
+ * 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 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$
+ */
+
+#ifndef _LIBUTIL_H_
+#define _LIBUTIL_H_
+
+#define PROPERTY_MAX_NAME 64
+#define PROPERTY_MAX_VALUE 512
+
+/* for properties.c */
+typedef struct _property {
+ struct _property *next;
+ char *name;
+ char *value;
+} *properties;
+
+#ifdef _SYS_PARAM_H_
+/* for pidfile.c */
+struct pidfh {
+ int pf_fd;
+ char pf_path[MAXPATHLEN + 1];
+ __dev_t pf_dev;
+ ino_t pf_ino;
+};
+#endif
+
+/* Avoid pulling in all the include files for no need */
+struct termios;
+struct winsize;
+struct utmp;
+struct in_addr;
+struct kinfo_file;
+struct kinfo_vmentry;
+
+__BEGIN_DECLS
+void clean_environment(const char * const *_white,
+ const char * const *_more_white);
+int extattr_namespace_to_string(int _attrnamespace, char **_string);
+int extattr_string_to_namespace(const char *_string, int *_attrnamespace);
+int flopen(const char *_path, int _flags, ...);
+void hexdump(const void *ptr, int length, const char *hdr, int flags);
+void login(struct utmp *_ut);
+int login_tty(int _fd);
+int logout(const char *_line);
+void logwtmp(const char *_line, const char *_name, const char *_host);
+void trimdomain(char *_fullhost, int _hostsize);
+int openpty(int *_amaster, int *_aslave, char *_name,
+ struct termios *_termp, struct winsize *_winp);
+int forkpty(int *_amaster, char *_name,
+ struct termios *_termp, struct winsize *_winp);
+int humanize_number(char *_buf, size_t _len, int64_t _number,
+ const char *_suffix, int _scale, int _flags);
+int expand_number(const char *_buf, int64_t *_num);
+const char *uu_lockerr(int _uu_lockresult);
+int uu_lock(const char *_ttyname);
+int uu_unlock(const char *_ttyname);
+int uu_lock_txfr(const char *_ttyname, pid_t _pid);
+int _secure_path(const char *_path, uid_t _uid, gid_t _gid);
+properties properties_read(int fd);
+void properties_free(properties list);
+char *property_find(properties list, const char *name);
+char *auth_getval(const char *name);
+int realhostname(char *host, size_t hsize, const struct in_addr *ip);
+struct sockaddr;
+int realhostname_sa(char *host, size_t hsize, struct sockaddr *addr,
+ int addrlen);
+
+int kld_isloaded(const char *name);
+int kld_load(const char *name);
+struct kinfo_file *
+ kinfo_getfile(pid_t _pid, int *_cntp);
+struct kinfo_vmentry *
+ kinfo_getvmmap(pid_t _pid, int *_cntp);
+
+#ifdef _STDIO_H_ /* avoid adding new includes */
+char *fparseln(FILE *, size_t *, size_t *, const char[3], int);
+#endif
+
+#ifdef _PWD_H_
+int pw_copy(int _ffd, int _tfd, const struct passwd *_pw, struct passwd *_old_pw);
+struct passwd *pw_dup(const struct passwd *_pw);
+int pw_edit(int _notsetuid);
+int pw_equal(const struct passwd *_pw1, const struct passwd *_pw2);
+void pw_fini(void);
+int pw_init(const char *_dir, const char *_master);
+char *pw_make(const struct passwd *_pw);
+int pw_mkdb(const char *_user);
+int pw_lock(void);
+struct passwd *pw_scan(const char *_line, int _flags);
+const char *pw_tempname(void);
+int pw_tmp(int _mfd);
+#endif
+
+#ifdef _GRP_H_
+int gr_equal(const struct group *gr1, const struct group *gr2);
+char *gr_make(const struct group *gr);
+struct group *gr_dup(const struct group *gr);
+struct group *gr_scan(const char *line);
+#endif
+
+#ifdef _SYS_PARAM_H_
+struct pidfh *pidfile_open(const char *path, mode_t mode, pid_t *pidptr);
+int pidfile_write(struct pidfh *pfh);
+int pidfile_close(struct pidfh *pfh);
+int pidfile_remove(struct pidfh *pfh);
+#endif
+
+__END_DECLS
+
+#define UU_LOCK_INUSE (1)
+#define UU_LOCK_OK (0)
+#define UU_LOCK_OPEN_ERR (-1)
+#define UU_LOCK_READ_ERR (-2)
+#define UU_LOCK_CREAT_ERR (-3)
+#define UU_LOCK_WRITE_ERR (-4)
+#define UU_LOCK_LINK_ERR (-5)
+#define UU_LOCK_TRY_ERR (-6)
+#define UU_LOCK_OWNER_ERR (-7)
+
+/* return values from realhostname() */
+#define HOSTNAME_FOUND (0)
+#define HOSTNAME_INCORRECTNAME (1)
+#define HOSTNAME_INVALIDADDR (2)
+#define HOSTNAME_INVALIDNAME (3)
+
+/* fparseln(3) */
+#define FPARSELN_UNESCESC 0x01
+#define FPARSELN_UNESCCONT 0x02
+#define FPARSELN_UNESCCOMM 0x04
+#define FPARSELN_UNESCREST 0x08
+#define FPARSELN_UNESCALL 0x0f
+
+/* pw_scan() */
+#define PWSCAN_MASTER 0x01
+#define PWSCAN_WARN 0x02
+
+/* humanize_number(3) */
+#define HN_DECIMAL 0x01
+#define HN_NOSPACE 0x02
+#define HN_B 0x04
+#define HN_DIVISOR_1000 0x08
+
+#define HN_GETSCALE 0x10
+#define HN_AUTOSCALE 0x20
+
+/* hexdump(3) */
+#define HD_COLUMN_MASK 0xff
+#define HD_DELIM_MASK 0xff00
+#define HD_OMIT_COUNT (1 << 16)
+#define HD_OMIT_HEX (1 << 17)
+#define HD_OMIT_CHARS (1 << 18)
+
+#endif /* !_LIBUTIL_H_ */
diff --git a/libutil/login_cap.c b/libutil/login_cap.c
new file mode 100644
index 0000000..8fee760
--- /dev/null
+++ b/libutil/login_cap.c
@@ -0,0 +1,819 @@
+/*-
+ * Copyright (c) 1996 by
+ * Sean Eric Fagan <sef@kithrup.com>
+ * David Nugent <davidn@blaze.net.au>
+ * All rights reserved.
+ *
+ * Portions copyright (c) 1995,1997
+ * Berkeley Software Design, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, is permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice immediately at the beginning of the file, without modification,
+ * 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.
+ * 3. This work was done expressly for inclusion into FreeBSD. Other use
+ * is permitted provided this notation is included.
+ * 4. Absolutely no warranty of function or purpose is made by the authors.
+ * 5. Modifications may be freely made to this file providing the above
+ * conditions are met.
+ *
+ * Low-level routines relating to the user capabilities database
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <login_cap.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+/*
+ * allocstr()
+ * Manage a single static pointer for handling a local char* buffer,
+ * resizing as necessary to contain the string.
+ *
+ * allocarray()
+ * Manage a static array for handling a group of strings, resizing
+ * when necessary.
+ */
+
+static int lc_object_count = 0;
+
+static size_t internal_stringsz = 0;
+static char * internal_string = NULL;
+static size_t internal_arraysz = 0;
+static const char ** internal_array = NULL;
+
+static char path_login_conf[] = _PATH_LOGIN_CONF;
+
+static char *
+allocstr(const char *str)
+{
+ char *p;
+
+ size_t sz = strlen(str) + 1; /* realloc() only if necessary */
+ if (sz <= internal_stringsz)
+ p = strcpy(internal_string, str);
+ else if ((p = realloc(internal_string, sz)) != NULL) {
+ internal_stringsz = sz;
+ internal_string = strcpy(p, str);
+ }
+ return p;
+}
+
+
+static const char **
+allocarray(size_t sz)
+{
+ static const char **p;
+
+ if (sz <= internal_arraysz)
+ p = internal_array;
+ else if ((p = realloc(internal_array, sz * sizeof(char*))) != NULL) {
+ internal_arraysz = sz;
+ internal_array = p;
+ }
+ return p;
+}
+
+
+/*
+ * arrayize()
+ * Turn a simple string <str> separated by any of
+ * the set of <chars> into an array. The last element
+ * of the array will be NULL, as is proper.
+ * Free using freearraystr()
+ */
+
+static const char **
+arrayize(const char *str, const char *chars, int *size)
+{
+ int i;
+ char *ptr;
+ const char *cptr;
+ const char **res = NULL;
+
+ /* count the sub-strings */
+ for (i = 0, cptr = str; *cptr; i++) {
+ int count = strcspn(cptr, chars);
+ cptr += count;
+ if (*cptr)
+ ++cptr;
+ }
+
+ /* alloc the array */
+ if ((ptr = allocstr(str)) != NULL) {
+ if ((res = allocarray(++i)) == NULL)
+ free((void *)(uintptr_t)(const void *)str);
+ else {
+ /* now split the string */
+ i = 0;
+ while (*ptr) {
+ int count = strcspn(ptr, chars);
+ res[i++] = ptr;
+ ptr += count;
+ if (*ptr)
+ *ptr++ = '\0';
+ }
+ res[i] = NULL;
+ }
+ }
+
+ if (size)
+ *size = i;
+
+ return res;
+}
+
+
+/*
+ * login_close()
+ * Frees up all resources relating to a login class
+ *
+ */
+
+void
+login_close(login_cap_t * lc)
+{
+ if (lc) {
+ free(lc->lc_style);
+ free(lc->lc_class);
+ free(lc->lc_cap);
+ free(lc);
+ if (--lc_object_count == 0) {
+ free(internal_string);
+ free(internal_array);
+ internal_array = NULL;
+ internal_arraysz = 0;
+ internal_string = NULL;
+ internal_stringsz = 0;
+ cgetclose();
+ }
+ }
+}
+
+
+/*
+ * login_getclassbyname()
+ * Get the login class by its name.
+ * If the name given is NULL or empty, the default class
+ * LOGIN_DEFCLASS (i.e., "default") is fetched.
+ * If the name given is LOGIN_MECLASS and
+ * 'pwd' argument is non-NULL and contains an non-NULL
+ * dir entry, then the file _FILE_LOGIN_CONF is picked
+ * up from that directory and used before the system
+ * login database. In that case the system login database
+ * is looked up using LOGIN_MECLASS, too, which is a bug.
+ * Return a filled-out login_cap_t structure, including
+ * class name, and the capability record buffer.
+ */
+
+login_cap_t *
+login_getclassbyname(char const *name, const struct passwd *pwd)
+{
+ login_cap_t *lc;
+
+ if ((lc = malloc(sizeof(login_cap_t))) != NULL) {
+ int r, me, i = 0;
+ uid_t euid = 0;
+ gid_t egid = 0;
+ const char *msg = NULL;
+ const char *dir;
+ char userpath[MAXPATHLEN];
+
+ static char *login_dbarray[] = { NULL, NULL, NULL };
+
+ me = (name != NULL && strcmp(name, LOGIN_MECLASS) == 0);
+ dir = (!me || pwd == NULL) ? NULL : pwd->pw_dir;
+ /*
+ * Switch to user mode before checking/reading its ~/.login_conf
+ * - some NFSes have root read access disabled.
+ *
+ * XXX: This fails to configure additional groups.
+ */
+ if (dir) {
+ euid = geteuid();
+ egid = getegid();
+ (void)setegid(pwd->pw_gid);
+ (void)seteuid(pwd->pw_uid);
+ }
+
+ if (dir && snprintf(userpath, MAXPATHLEN, "%s/%s", dir,
+ _FILE_LOGIN_CONF) < MAXPATHLEN) {
+ if (_secure_path(userpath, pwd->pw_uid, pwd->pw_gid) != -1)
+ login_dbarray[i++] = userpath;
+ }
+ /*
+ * XXX: Why to add the system database if the class is `me'?
+ */
+ if (_secure_path(path_login_conf, 0, 0) != -1)
+ login_dbarray[i++] = path_login_conf;
+ login_dbarray[i] = NULL;
+
+ memset(lc, 0, sizeof(login_cap_t));
+ lc->lc_cap = lc->lc_class = lc->lc_style = NULL;
+
+ if (name == NULL || *name == '\0')
+ name = LOGIN_DEFCLASS;
+
+ switch (cgetent(&lc->lc_cap, login_dbarray, name)) {
+ case -1: /* Failed, entry does not exist */
+ if (me)
+ break; /* Don't retry default on 'me' */
+ if (i == 0)
+ r = -1;
+ else if ((r = open(login_dbarray[0], O_RDONLY)) >= 0)
+ close(r);
+ /*
+ * If there's at least one login class database,
+ * and we aren't searching for a default class
+ * then complain about a non-existent class.
+ */
+ if (r >= 0 || strcmp(name, LOGIN_DEFCLASS) != 0)
+ syslog(LOG_ERR, "login_getclass: unknown class '%s'", name);
+ /* fall-back to default class */
+ name = LOGIN_DEFCLASS;
+ msg = "%s: no default/fallback class '%s'";
+ if (cgetent(&lc->lc_cap, login_dbarray, name) != 0 && r >= 0)
+ break;
+ /* FALLTHROUGH - just return system defaults */
+ case 0: /* success! */
+ if ((lc->lc_class = strdup(name)) != NULL) {
+ if (dir) {
+ (void)seteuid(euid);
+ (void)setegid(egid);
+ }
+ ++lc_object_count;
+ return lc;
+ }
+ msg = "%s: strdup: %m";
+ break;
+ case -2:
+ msg = "%s: retrieving class information: %m";
+ break;
+ case -3:
+ msg = "%s: 'tc=' reference loop '%s'";
+ break;
+ case 1:
+ msg = "couldn't resolve 'tc=' reference in '%s'";
+ break;
+ default:
+ msg = "%s: unexpected cgetent() error '%s': %m";
+ break;
+ }
+ if (dir) {
+ (void)seteuid(euid);
+ (void)setegid(egid);
+ }
+ if (msg != NULL)
+ syslog(LOG_ERR, msg, "login_getclass", name);
+ free(lc);
+ }
+
+ return NULL;
+}
+
+
+
+/*
+ * login_getclass()
+ * Get the login class for the system (only) login class database.
+ * Return a filled-out login_cap_t structure, including
+ * class name, and the capability record buffer.
+ */
+
+login_cap_t *
+login_getclass(const char *cls)
+{
+ return login_getclassbyname(cls, NULL);
+}
+
+
+/*
+ * login_getpwclass()
+ * Get the login class for a given password entry from
+ * the system (only) login class database.
+ * If the password entry's class field is not set, or
+ * the class specified does not exist, then use the
+ * default of LOGIN_DEFCLASS (i.e., "default") for an unprivileged
+ * user or that of LOGIN_DEFROOTCLASS (i.e., "root") for a super-user.
+ * Return a filled-out login_cap_t structure, including
+ * class name, and the capability record buffer.
+ */
+
+login_cap_t *
+login_getpwclass(const struct passwd *pwd)
+{
+ const char *cls = NULL;
+
+ if (pwd != NULL) {
+ cls = pwd->pw_class;
+ if (cls == NULL || *cls == '\0')
+ cls = (pwd->pw_uid == 0) ? LOGIN_DEFROOTCLASS : LOGIN_DEFCLASS;
+ }
+ /*
+ * XXX: pwd should be unused by login_getclassbyname() unless cls is `me',
+ * so NULL can be passed instead of pwd for more safety.
+ */
+ return login_getclassbyname(cls, pwd);
+}
+
+
+/*
+ * login_getuserclass()
+ * Get the `me' login class, allowing user overrides via ~/.login_conf.
+ * Note that user overrides are allowed only in the `me' class.
+ */
+
+login_cap_t *
+login_getuserclass(const struct passwd *pwd)
+{
+ return login_getclassbyname(LOGIN_MECLASS, pwd);
+}
+
+
+/*
+ * login_getcapstr()
+ * Given a login_cap entry, and a capability name, return the
+ * value defined for that capability, a default if not found, or
+ * an error string on error.
+ */
+
+const char *
+login_getcapstr(login_cap_t *lc, const char *cap, const char *def, const char *error)
+{
+ char *res;
+ int ret;
+
+ if (lc == NULL || cap == NULL || lc->lc_cap == NULL || *cap == '\0')
+ return def;
+
+ if ((ret = cgetstr(lc->lc_cap, cap, &res)) == -1)
+ return def;
+ return (ret >= 0) ? res : error;
+}
+
+
+/*
+ * login_getcaplist()
+ * Given a login_cap entry, and a capability name, return the
+ * value defined for that capability split into an array of
+ * strings.
+ */
+
+const char **
+login_getcaplist(login_cap_t *lc, const char *cap, const char *chars)
+{
+ const char *lstring;
+
+ if (chars == NULL)
+ chars = ", \t";
+ if ((lstring = login_getcapstr(lc, cap, NULL, NULL)) != NULL)
+ return arrayize(lstring, chars, NULL);
+ return NULL;
+}
+
+
+/*
+ * login_getpath()
+ * From the login_cap_t <lc>, get the capability <cap> which is
+ * formatted as either a space or comma delimited list of paths
+ * and append them all into a string and separate by semicolons.
+ * If there is an error of any kind, return <error>.
+ */
+
+const char *
+login_getpath(login_cap_t *lc, const char *cap, const char *error)
+{
+ const char *str;
+ char *ptr;
+ int count;
+
+ str = login_getcapstr(lc, cap, NULL, NULL);
+ if (str == NULL)
+ return error;
+ ptr = __DECONST(char *, str); /* XXXX Yes, very dodgy */
+ while (*ptr) {
+ count = strcspn(ptr, ", \t");
+ ptr += count;
+ if (*ptr)
+ *ptr++ = ':';
+ }
+ return str;
+}
+
+
+static int
+isinfinite(const char *s)
+{
+ static const char *infs[] = {
+ "infinity",
+ "inf",
+ "unlimited",
+ "unlimit",
+ "-1",
+ NULL
+ };
+ const char **i = &infs[0];
+
+ while (*i != NULL) {
+ if (strcasecmp(s, *i) == 0)
+ return 1;
+ ++i;
+ }
+ return 0;
+}
+
+
+static u_quad_t
+rmultiply(u_quad_t n1, u_quad_t n2)
+{
+ u_quad_t m, r;
+ int b1, b2;
+
+ static int bpw = 0;
+
+ /* Handle simple cases */
+ if (n1 == 0 || n2 == 0)
+ return 0;
+ if (n1 == 1)
+ return n2;
+ if (n2 == 1)
+ return n1;
+
+ /*
+ * sizeof() returns number of bytes needed for storage.
+ * This may be different from the actual number of useful bits.
+ */
+ if (!bpw) {
+ bpw = sizeof(u_quad_t) * 8;
+ while (((u_quad_t)1 << (bpw-1)) == 0)
+ --bpw;
+ }
+
+ /*
+ * First check the magnitude of each number. If the sum of the
+ * magnatude is way to high, reject the number. (If this test
+ * is not done then the first multiply below may overflow.)
+ */
+ for (b1 = bpw; (((u_quad_t)1 << (b1-1)) & n1) == 0; --b1)
+ ;
+ for (b2 = bpw; (((u_quad_t)1 << (b2-1)) & n2) == 0; --b2)
+ ;
+ if (b1 + b2 - 2 > bpw) {
+ errno = ERANGE;
+ return (UQUAD_MAX);
+ }
+
+ /*
+ * Decompose the multiplication to be:
+ * h1 = n1 & ~1
+ * h2 = n2 & ~1
+ * l1 = n1 & 1
+ * l2 = n2 & 1
+ * (h1 + l1) * (h2 + l2)
+ * (h1 * h2) + (h1 * l2) + (l1 * h2) + (l1 * l2)
+ *
+ * Since h1 && h2 do not have the low bit set, we can then say:
+ *
+ * (h1>>1 * h2>>1 * 4) + ...
+ *
+ * So if (h1>>1 * h2>>1) > (1<<(bpw - 2)) then the result will
+ * overflow.
+ *
+ * Finally, if MAX - ((h1 * l2) + (l1 * h2) + (l1 * l2)) < (h1*h2)
+ * then adding in residual amout will cause an overflow.
+ */
+
+ m = (n1 >> 1) * (n2 >> 1);
+ if (m >= ((u_quad_t)1 << (bpw-2))) {
+ errno = ERANGE;
+ return (UQUAD_MAX);
+ }
+ m *= 4;
+
+ r = (n1 & n2 & 1)
+ + (n2 & 1) * (n1 & ~(u_quad_t)1)
+ + (n1 & 1) * (n2 & ~(u_quad_t)1);
+
+ if ((u_quad_t)(m + r) < m) {
+ errno = ERANGE;
+ return (UQUAD_MAX);
+ }
+ m += r;
+
+ return (m);
+}
+
+
+/*
+ * login_getcaptime()
+ * From the login_cap_t <lc>, get the capability <cap>, which is
+ * formatted as a time (e.g., "<cap>=10h3m2s"). If <cap> is not
+ * present in <lc>, return <def>; if there is an error of some kind,
+ * return <error>.
+ */
+
+rlim_t
+login_getcaptime(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error)
+{
+ char *res, *ep, *oval;
+ int r;
+ rlim_t tot;
+
+ errno = 0;
+ if (lc == NULL || lc->lc_cap == NULL)
+ return def;
+
+ /*
+ * Look for <cap> in lc_cap.
+ * If it's not there (-1), return <def>.
+ * If there's an error, return <error>.
+ */
+
+ if ((r = cgetstr(lc->lc_cap, cap, &res)) == -1)
+ return def;
+ else if (r < 0) {
+ errno = ERANGE;
+ return error;
+ }
+
+ /* "inf" and "infinity" are special cases */
+ if (isinfinite(res))
+ return RLIM_INFINITY;
+
+ /*
+ * Now go through the string, turning something like 1h2m3s into
+ * an integral value. Whee.
+ */
+
+ errno = 0;
+ tot = 0;
+ oval = res;
+ while (*res) {
+ rlim_t tim = strtoq(res, &ep, 0);
+ rlim_t mult = 1;
+
+ if (ep == NULL || ep == res || errno != 0) {
+ invalid:
+ syslog(LOG_WARNING, "login_getcaptime: class '%s' bad value %s=%s",
+ lc->lc_class, cap, oval);
+ errno = ERANGE;
+ return error;
+ }
+ /* Look for suffixes */
+ switch (*ep++) {
+ case 0:
+ ep--;
+ break; /* end of string */
+ case 's': case 'S': /* seconds */
+ break;
+ case 'm': case 'M': /* minutes */
+ mult = 60;
+ break;
+ case 'h': case 'H': /* hours */
+ mult = 60L * 60L;
+ break;
+ case 'd': case 'D': /* days */
+ mult = 60L * 60L * 24L;
+ break;
+ case 'w': case 'W': /* weeks */
+ mult = 60L * 60L * 24L * 7L;
+ break;
+ case 'y': case 'Y': /* 365-day years */
+ mult = 60L * 60L * 24L * 365L;
+ break;
+ default:
+ goto invalid;
+ }
+ res = ep;
+ tot += rmultiply(tim, mult);
+ if (errno)
+ goto invalid;
+ }
+
+ return tot;
+}
+
+
+/*
+ * login_getcapnum()
+ * From the login_cap_t <lc>, extract the numerical value <cap>.
+ * If it is not present, return <def> for a default, and return
+ * <error> if there is an error.
+ * Like login_getcaptime(), only it only converts to a number, not
+ * to a time; "infinity" and "inf" are 'special.'
+ */
+
+rlim_t
+login_getcapnum(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error)
+{
+ char *ep, *res;
+ int r;
+ rlim_t val;
+
+ if (lc == NULL || lc->lc_cap == NULL)
+ return def;
+
+ /*
+ * For BSDI compatibility, try for the tag=<val> first
+ */
+ if ((r = cgetstr(lc->lc_cap, cap, &res)) == -1) {
+ long lval;
+ /* string capability not present, so try for tag#<val> as numeric */
+ if ((r = cgetnum(lc->lc_cap, cap, &lval)) == -1)
+ return def; /* Not there, so return default */
+ else if (r >= 0)
+ return (rlim_t)lval;
+ }
+
+ if (r < 0) {
+ errno = ERANGE;
+ return error;
+ }
+
+ if (isinfinite(res))
+ return RLIM_INFINITY;
+
+ errno = 0;
+ val = strtoq(res, &ep, 0);
+ if (ep == NULL || ep == res || errno != 0) {
+ syslog(LOG_WARNING, "login_getcapnum: class '%s' bad value %s=%s",
+ lc->lc_class, cap, res);
+ errno = ERANGE;
+ return error;
+ }
+
+ return val;
+}
+
+
+
+/*
+ * login_getcapsize()
+ * From the login_cap_t <lc>, extract the capability <cap>, which is
+ * formatted as a size (e.g., "<cap>=10M"); it can also be "infinity".
+ * If not present, return <def>, or <error> if there is an error of
+ * some sort.
+ */
+
+rlim_t
+login_getcapsize(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error)
+{
+ char *ep, *res, *oval;
+ int r;
+ rlim_t tot;
+
+ if (lc == NULL || lc->lc_cap == NULL)
+ return def;
+
+ if ((r = cgetstr(lc->lc_cap, cap, &res)) == -1)
+ return def;
+ else if (r < 0) {
+ errno = ERANGE;
+ return error;
+ }
+
+ if (isinfinite(res))
+ return RLIM_INFINITY;
+
+ errno = 0;
+ tot = 0;
+ oval = res;
+ while (*res) {
+ rlim_t siz = strtoq(res, &ep, 0);
+ rlim_t mult = 1;
+
+ if (ep == NULL || ep == res || errno != 0) {
+ invalid:
+ syslog(LOG_WARNING, "login_getcapsize: class '%s' bad value %s=%s",
+ lc->lc_class, cap, oval);
+ errno = ERANGE;
+ return error;
+ }
+ switch (*ep++) {
+ case 0: /* end of string */
+ ep--;
+ break;
+ case 'b': case 'B': /* 512-byte blocks */
+ mult = 512;
+ break;
+ case 'k': case 'K': /* 1024-byte Kilobytes */
+ mult = 1024;
+ break;
+ case 'm': case 'M': /* 1024-k kbytes */
+ mult = 1024 * 1024;
+ break;
+ case 'g': case 'G': /* 1Gbyte */
+ mult = 1024 * 1024 * 1024;
+ break;
+ case 't': case 'T': /* 1TBte */
+ mult = 1024LL * 1024LL * 1024LL * 1024LL;
+ break;
+ default:
+ goto invalid;
+ }
+ res = ep;
+ tot += rmultiply(siz, mult);
+ if (errno)
+ goto invalid;
+ }
+
+ return tot;
+}
+
+
+/*
+ * login_getcapbool()
+ * From the login_cap_t <lc>, check for the existance of the capability
+ * of <cap>. Return <def> if <lc>->lc_cap is NULL, otherwise return
+ * the whether or not <cap> exists there.
+ */
+
+int
+login_getcapbool(login_cap_t *lc, const char *cap, int def)
+{
+ if (lc == NULL || lc->lc_cap == NULL)
+ return def;
+ return (cgetcap(lc->lc_cap, cap, ':') != NULL);
+}
+
+
+/*
+ * login_getstyle()
+ * Given a login_cap entry <lc>, and optionally a type of auth <auth>,
+ * and optionally a style <style>, find the style that best suits these
+ * rules:
+ * 1. If <auth> is non-null, look for an "auth-<auth>=" string
+ * in the capability; if not present, default to "auth=".
+ * 2. If there is no auth list found from (1), default to
+ * "passwd" as an authorization list.
+ * 3. If <style> is non-null, look for <style> in the list of
+ * authorization methods found from (2); if <style> is NULL, default
+ * to LOGIN_DEFSTYLE ("passwd").
+ * 4. If the chosen style is found in the chosen list of authorization
+ * methods, return that; otherwise, return NULL.
+ * E.g.:
+ * login_getstyle(lc, NULL, "ftp");
+ * login_getstyle(lc, "login", NULL);
+ * login_getstyle(lc, "skey", "network");
+ */
+
+const char *
+login_getstyle(login_cap_t *lc, const char *style, const char *auth)
+{
+ int i;
+ const char **authtypes = NULL;
+ char *auths= NULL;
+ char realauth[64];
+
+ static const char *defauthtypes[] = { LOGIN_DEFSTYLE, NULL };
+
+ if (auth != NULL && *auth != '\0') {
+ if (snprintf(realauth, sizeof realauth, "auth-%s", auth) < (int)sizeof(realauth))
+ authtypes = login_getcaplist(lc, realauth, NULL);
+ }
+
+ if (authtypes == NULL)
+ authtypes = login_getcaplist(lc, "auth", NULL);
+
+ if (authtypes == NULL)
+ authtypes = defauthtypes;
+
+ /*
+ * We have at least one authtype now; auths is a comma-separated
+ * (or space-separated) list of authentication types. We have to
+ * convert from this to an array of char*'s; authtypes then gets this.
+ */
+ i = 0;
+ if (style != NULL && *style != '\0') {
+ while (authtypes[i] != NULL && strcmp(style, authtypes[i]) != 0)
+ i++;
+ }
+
+ lc->lc_style = NULL;
+ if (authtypes[i] != NULL && (auths = strdup(authtypes[i])) != NULL)
+ lc->lc_style = auths;
+
+ if (lc->lc_style != NULL)
+ lc->lc_style = strdup(lc->lc_style);
+
+ return lc->lc_style;
+}
diff --git a/libutil/login_cap.h b/libutil/login_cap.h
new file mode 100644
index 0000000..082e34b
--- /dev/null
+++ b/libutil/login_cap.h
@@ -0,0 +1,166 @@
+/*-
+ * Copyright (c) 1996 by
+ * Sean Eric Fagan <sef@kithrup.com>
+ * David Nugent <davidn@blaze.net.au>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, is permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice immediately at the beginning of the file, without modification,
+ * 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.
+ * 3. This work was done expressly for inclusion into FreeBSD. Other use
+ * is permitted provided this notation is included.
+ * 4. Absolutely no warranty of function or purpose is made by the authors.
+ * 5. Modifications may be freely made to this file providing the above
+ * conditions are met.
+ *
+ * Low-level routines relating to the user capabilities database
+ *
+ * Was login_cap.h,v 1.9 1997/05/07 20:00:01 eivind Exp
+ * $FreeBSD$
+ */
+
+#ifndef _LOGIN_CAP_H_
+#define _LOGIN_CAP_H_
+
+#define LOGIN_DEFCLASS "default"
+#define LOGIN_DEFROOTCLASS "root"
+#define LOGIN_MECLASS "me"
+#define LOGIN_DEFSTYLE "passwd"
+#define LOGIN_DEFSERVICE "login"
+#define LOGIN_DEFUMASK 022
+#define LOGIN_DEFPRI 0
+#define _PATH_LOGIN_CONF "/etc/login.conf"
+#define _FILE_LOGIN_CONF ".login_conf"
+#define _PATH_AUTHPROG "/usr/libexec/login_"
+
+#define LOGIN_SETGROUP 0x0001 /* set group */
+#define LOGIN_SETLOGIN 0x0002 /* set login (via setlogin) */
+#define LOGIN_SETPATH 0x0004 /* set path */
+#define LOGIN_SETPRIORITY 0x0008 /* set priority */
+#define LOGIN_SETRESOURCES 0x0010 /* set resources (cputime, etc.) */
+#define LOGIN_SETUMASK 0x0020 /* set umask, obviously */
+#define LOGIN_SETUSER 0x0040 /* set user (via setuid) */
+#define LOGIN_SETENV 0x0080 /* set user environment */
+#define LOGIN_SETMAC 0x0100 /* set user default MAC label */
+#define LOGIN_SETCPUMASK 0x0200 /* set user cpumask */
+#define LOGIN_SETALL 0x03ff /* set everything */
+
+#define BI_AUTH "authorize" /* accepted authentication */
+#define BI_REJECT "reject" /* rejected authentication */
+#define BI_CHALLENG "reject challenge" /* reject with a challenge */
+#define BI_SILENT "reject silent" /* reject silently */
+#define BI_REMOVE "remove" /* remove file on error */
+#define BI_ROOTOKAY "authorize root" /* root authenticated */
+#define BI_SECURE "authorize secure" /* okay on non-secure line */
+#define BI_SETENV "setenv" /* set environment variable */
+#define BI_VALUE "value" /* set local variable */
+
+#define AUTH_OKAY 0x01 /* user authenticated */
+#define AUTH_ROOTOKAY 0x02 /* root login okay */
+#define AUTH_SECURE 0x04 /* secure login */
+#define AUTH_SILENT 0x08 /* silent rejection */
+#define AUTH_CHALLENGE 0x10 /* a chellenge was given */
+
+#define AUTH_ALLOW (AUTH_OKAY | AUTH_ROOTOKAY | AUTH_SECURE)
+
+typedef struct login_cap {
+ char *lc_class;
+ char *lc_cap;
+ char *lc_style;
+} login_cap_t;
+
+typedef struct login_time {
+ u_short lt_start; /* Start time */
+ u_short lt_end; /* End time */
+#define LTM_NONE 0x00
+#define LTM_SUN 0x01
+#define LTM_MON 0x02
+#define LTM_TUE 0x04
+#define LTM_WED 0x08
+#define LTM_THU 0x10
+#define LTM_FRI 0x20
+#define LTM_SAT 0x40
+#define LTM_ANY 0x7F
+#define LTM_WK 0x3E
+#define LTM_WD 0x41
+ u_char lt_dow; /* Days of week */
+} login_time_t;
+
+#define LC_MAXTIMES 64
+
+#include <sys/cdefs.h>
+__BEGIN_DECLS
+struct passwd;
+
+void login_close(login_cap_t *);
+login_cap_t *login_getclassbyname(const char *, const struct passwd *);
+login_cap_t *login_getclass(const char *);
+login_cap_t *login_getpwclass(const struct passwd *);
+login_cap_t *login_getuserclass(const struct passwd *);
+
+const char *login_getcapstr(login_cap_t *, const char *, const char *,
+ const char *);
+const char **login_getcaplist(login_cap_t *, const char *, const char *);
+const char *login_getstyle(login_cap_t *, const char *, const char *);
+rlim_t login_getcaptime(login_cap_t *, const char *, rlim_t, rlim_t);
+rlim_t login_getcapnum(login_cap_t *, const char *, rlim_t, rlim_t);
+rlim_t login_getcapsize(login_cap_t *, const char *, rlim_t, rlim_t);
+const char *login_getpath(login_cap_t *, const char *, const char *);
+int login_getcapbool(login_cap_t *, const char *, int);
+const char *login_setcryptfmt(login_cap_t *, const char *, const char *);
+
+int setclasscontext(const char *, unsigned int);
+void setclasscpumask(login_cap_t *);
+int setusercontext(login_cap_t *, const struct passwd *, uid_t, unsigned int);
+void setclassresources(login_cap_t *);
+void setclassenvironment(login_cap_t *, const struct passwd *, int);
+
+/* Most of these functions are deprecated */
+int auth_approve(login_cap_t *, const char *, const char *);
+int auth_check(const char *, const char *, const char *, const char *, int *);
+void auth_env(void);
+char *auth_mkvalue(const char *);
+int auth_response(const char *, const char *, const char *, const char *, int *,
+ const char *, const char *);
+void auth_rmfiles(void);
+int auth_scan(int);
+int auth_script(const char *, ...);
+int auth_script_data(const char *, int, const char *, ...);
+char *auth_valud(const char *);
+int auth_setopt(const char *, const char *);
+void auth_clropts(void);
+
+void auth_checknologin(login_cap_t *);
+int auth_cat(const char *);
+
+int auth_ttyok(login_cap_t *, const char *);
+int auth_hostok(login_cap_t *, const char *, char const *);
+int auth_timeok(login_cap_t *, time_t);
+
+struct tm;
+
+login_time_t parse_lt(const char *);
+int in_lt(const login_time_t *, time_t *);
+int in_ltm(const login_time_t *, struct tm *, time_t *);
+int in_ltms(const login_time_t *, struct tm *, time_t *);
+int in_lts(const login_time_t *, time_t *);
+
+/* helper functions */
+
+int login_strinlist(const char **, char const *, int);
+int login_str2inlist(const char **, const char *, const char *, int);
+login_time_t * login_timelist(login_cap_t *, char const *, int *,
+ login_time_t **);
+int login_ttyok(login_cap_t *, const char *, const char *, const char *);
+int login_hostok(login_cap_t *, const char *, const char *, const char *,
+ const char *);
+
+__END_DECLS
+
+#endif /* _LOGIN_CAP_H_ */
diff --git a/libutil/login_crypt.c b/libutil/login_crypt.c
new file mode 100644
index 0000000..c65fc9b
--- /dev/null
+++ b/libutil/login_crypt.c
@@ -0,0 +1,50 @@
+/*-
+ * Copyright (c) 2000 Brian Fundakowski Feldman
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <login_cap.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+const char *
+login_setcryptfmt(login_cap_t *lc, const char *def, const char *error) {
+ const char *cipher;
+
+ cipher = login_getcapstr(lc, "passwd_format", def, NULL);
+ if (getenv("CRYPT_DEBUG") != NULL)
+ fprintf(stderr, "login_setcryptfmt: "
+ "passwd_format = %s\n", cipher);
+ if (cipher == NULL)
+ return (error);
+ if (!crypt_set_format(cipher))
+ return (error);
+ return (cipher);
+}
diff --git a/libutil/pw_util.c b/libutil/pw_util.c
new file mode 100644
index 0000000..69232fb
--- /dev/null
+++ b/libutil/pw_util.c
@@ -0,0 +1,621 @@
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS and NAI Labs, the Security Research Division of Network
+ * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * 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.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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
+#if 0
+static const char sccsid[] = "@(#)pw_util.c 8.3 (Berkeley) 4/2/94";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * This file is used by all the "password" programs; vipw(8), chpass(1),
+ * and passwd(1).
+ */
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+static pid_t editpid = -1;
+static int lockfd = -1;
+static char masterpasswd[PATH_MAX];
+static char passwd_dir[PATH_MAX];
+static char tempname[PATH_MAX];
+static int initialized;
+
+#if 0
+void
+pw_cont(int sig)
+{
+
+ if (editpid != -1)
+ kill(editpid, sig);
+}
+#endif
+
+/*
+ * Initialize statics and set limits, signals & umask to try to avoid
+ * interruptions, crashes etc. that might expose passord data.
+ */
+int
+pw_init(const char *dir, const char *master)
+{
+#if 0
+ struct rlimit rlim;
+#endif
+
+ if (dir == NULL) {
+ strcpy(passwd_dir, _PATH_ETC);
+ } else {
+ if (strlen(dir) >= sizeof(passwd_dir)) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ strcpy(passwd_dir, dir);
+ }
+
+ if (master == NULL) {
+ if (dir == NULL) {
+ strcpy(masterpasswd, _PATH_MASTERPASSWD);
+ } else if (snprintf(masterpasswd, sizeof(masterpasswd), "%s/%s",
+ passwd_dir, _MASTERPASSWD) > (int)sizeof(masterpasswd)) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ } else {
+ if (strlen(master) >= sizeof(masterpasswd)) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ strcpy(masterpasswd, master);
+ }
+
+ /*
+ * The code that follows is extremely disruptive to the calling
+ * process, and is therefore disabled until someone can conceive
+ * of a realistic scenario where it would fend off a compromise.
+ * Race conditions concerning the temporary files can be guarded
+ * against in other ways than masking signals (by checking stat(2)
+ * results after creation).
+ */
+#if 0
+ /* Unlimited resource limits. */
+ rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
+ (void)setrlimit(RLIMIT_CPU, &rlim);
+ (void)setrlimit(RLIMIT_FSIZE, &rlim);
+ (void)setrlimit(RLIMIT_STACK, &rlim);
+ (void)setrlimit(RLIMIT_DATA, &rlim);
+ (void)setrlimit(RLIMIT_RSS, &rlim);
+
+ /* Don't drop core (not really necessary, but GP's). */
+ rlim.rlim_cur = rlim.rlim_max = 0;
+ (void)setrlimit(RLIMIT_CORE, &rlim);
+
+ /* Turn off signals. */
+ (void)signal(SIGALRM, SIG_IGN);
+ (void)signal(SIGHUP, SIG_IGN);
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGPIPE, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ (void)signal(SIGTERM, SIG_IGN);
+ (void)signal(SIGCONT, pw_cont);
+
+ /* Create with exact permissions. */
+ (void)umask(0);
+#endif
+ initialized = 1;
+ return (0);
+}
+
+/*
+ * Lock the master password file.
+ */
+int
+pw_lock(void)
+{
+
+ if (*masterpasswd == '\0')
+ return (-1);
+
+ /*
+ * If the master password file doesn't exist, the system is hosed.
+ * Might as well try to build one. Set the close-on-exec bit so
+ * that users can't get at the encrypted passwords while editing.
+ * Open should allow flock'ing the file; see 4.4BSD. XXX
+ */
+ for (;;) {
+ struct stat st;
+
+ lockfd = open(masterpasswd, O_RDONLY, 0);
+ if (lockfd < 0 || fcntl(lockfd, F_SETFD, 1) == -1)
+ err(1, "%s", masterpasswd);
+ /* XXX vulnerable to race conditions */
+ if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) {
+ if (errno == EWOULDBLOCK) {
+ errx(1, "the password db file is busy");
+ } else {
+ err(1, "could not lock the passwd file: ");
+ }
+ }
+
+ /*
+ * If the password file was replaced while we were trying to
+ * get the lock, our hardlink count will be 0 and we have to
+ * close and retry.
+ */
+ if (fstat(lockfd, &st) == -1)
+ err(1, "fstat() failed: ");
+ if (st.st_nlink != 0)
+ break;
+ close(lockfd);
+ lockfd = -1;
+ }
+ return (lockfd);
+}
+
+/*
+ * Create and open a presumably safe temp file for editing the password
+ * data, and copy the master password file into it.
+ */
+int
+pw_tmp(int mfd)
+{
+ char buf[8192];
+ ssize_t nr;
+ const char *p;
+ int tfd;
+
+ if (*masterpasswd == '\0')
+ return (-1);
+ if ((p = strrchr(masterpasswd, '/')))
+ ++p;
+ else
+ p = masterpasswd;
+ if (snprintf(tempname, sizeof(tempname), "%.*spw.XXXXXX",
+ (int)(p - masterpasswd), masterpasswd) >= (int)sizeof(tempname)) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ if ((tfd = mkstemp(tempname)) == -1)
+ return (-1);
+ if (mfd != -1) {
+ while ((nr = read(mfd, buf, sizeof(buf))) > 0)
+ if (write(tfd, buf, (size_t)nr) != nr)
+ break;
+ if (nr != 0) {
+ unlink(tempname);
+ *tempname = '\0';
+ close(tfd);
+ return (-1);
+ }
+ }
+ return (tfd);
+}
+
+/*
+ * Regenerate the password database.
+ */
+int
+pw_mkdb(const char *user)
+{
+ int pstat;
+ pid_t pid;
+
+ (void)fflush(stderr);
+ switch ((pid = fork())) {
+ case -1:
+ return (-1);
+ case 0:
+ /* child */
+ if (user == NULL)
+ execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p",
+ "-d", passwd_dir, tempname, (char *)NULL);
+ else
+ execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p",
+ "-d", passwd_dir, "-u", user, tempname,
+ (char *)NULL);
+ _exit(1);
+ /* NOTREACHED */
+ default:
+ /* parent */
+ break;
+ }
+ if (waitpid(pid, &pstat, 0) == -1)
+ return (-1);
+ if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0)
+ return (0);
+ errno = 0;
+ return (-1);
+}
+
+/*
+ * Edit the temp file. Return -1 on error, >0 if the file was modified, 0
+ * if it was not.
+ */
+int
+pw_edit(int notsetuid)
+{
+ struct sigaction sa, sa_int, sa_quit;
+ sigset_t oldsigset, sigset;
+ struct stat st1, st2;
+ const char *editor;
+ int pstat;
+
+ if ((editor = getenv("EDITOR")) == NULL)
+ editor = _PATH_VI;
+ if (stat(tempname, &st1) == -1)
+ return (-1);
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGINT, &sa, &sa_int);
+ sigaction(SIGQUIT, &sa, &sa_quit);
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sigset, &oldsigset);
+ switch ((editpid = fork())) {
+ case -1:
+ return (-1);
+ case 0:
+ sigaction(SIGINT, &sa_int, NULL);
+ sigaction(SIGQUIT, &sa_quit, NULL);
+ sigprocmask(SIG_SETMASK, &oldsigset, NULL);
+ if (notsetuid) {
+ (void)setgid(getgid());
+ (void)setuid(getuid());
+ }
+ errno = 0;
+ execlp(editor, basename(editor), tempname, (char *)NULL);
+ _exit(errno);
+ default:
+ /* parent */
+ break;
+ }
+ for (;;) {
+ if (waitpid(editpid, &pstat, WUNTRACED) == -1) {
+ if (errno == EINTR)
+ continue;
+ unlink(tempname);
+ editpid = -1;
+ break;
+ } else if (WIFSTOPPED(pstat)) {
+ raise(WSTOPSIG(pstat));
+ } else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0) {
+ editpid = -1;
+ break;
+ } else {
+ unlink(tempname);
+ editpid = -1;
+ break;
+ }
+ }
+ sigaction(SIGINT, &sa_int, NULL);
+ sigaction(SIGQUIT, &sa_quit, NULL);
+ sigprocmask(SIG_SETMASK, &oldsigset, NULL);
+ if (stat(tempname, &st2) == -1)
+ return (-1);
+ return (st1.st_mtime != st2.st_mtime);
+}
+
+/*
+ * Clean up. Preserve errno for the caller's convenience.
+ */
+void
+pw_fini(void)
+{
+ int serrno, status;
+
+ if (!initialized)
+ return;
+ initialized = 0;
+ serrno = errno;
+ if (editpid != -1) {
+ kill(editpid, SIGTERM);
+ kill(editpid, SIGCONT);
+ waitpid(editpid, &status, 0);
+ editpid = -1;
+ }
+ if (*tempname != '\0') {
+ unlink(tempname);
+ *tempname = '\0';
+ }
+ if (lockfd != -1)
+ close(lockfd);
+ errno = serrno;
+}
+
+/*
+ * Compares two struct pwds.
+ */
+int
+pw_equal(const struct passwd *pw1, const struct passwd *pw2)
+{
+ return (strcmp(pw1->pw_name, pw2->pw_name) == 0 &&
+ pw1->pw_uid == pw2->pw_uid &&
+ pw1->pw_gid == pw2->pw_gid &&
+ strcmp(pw1->pw_class, pw2->pw_class) == 0 &&
+ pw1->pw_change == pw2->pw_change &&
+ pw1->pw_expire == pw2->pw_expire &&
+ strcmp(pw1->pw_gecos, pw2->pw_gecos) == 0 &&
+ strcmp(pw1->pw_dir, pw2->pw_dir) == 0 &&
+ strcmp(pw1->pw_shell, pw2->pw_shell) == 0);
+}
+
+/*
+ * Make a passwd line out of a struct passwd.
+ */
+char *
+pw_make(const struct passwd *pw)
+{
+ char *line;
+
+ asprintf(&line, "%s:%s:%ju:%ju:%s:%ju:%ju:%s:%s:%s", pw->pw_name,
+ pw->pw_passwd, (uintmax_t)pw->pw_uid, (uintmax_t)pw->pw_gid,
+ pw->pw_class, (uintmax_t)pw->pw_change, (uintmax_t)pw->pw_expire,
+ pw->pw_gecos, pw->pw_dir, pw->pw_shell);
+ return line;
+}
+
+/*
+ * Copy password file from one descriptor to another, replacing or adding
+ * a single record on the way.
+ */
+int
+pw_copy(int ffd, int tfd, const struct passwd *pw, struct passwd *old_pw)
+{
+ char buf[8192], *end, *line, *p, *q, *r, t;
+ struct passwd *fpw;
+ size_t len;
+ int eof, readlen;
+
+ if ((line = pw_make(pw)) == NULL)
+ return (-1);
+
+ eof = 0;
+ len = 0;
+ p = q = end = buf;
+ for (;;) {
+ /* find the end of the current line */
+ for (p = q; q < end && *q != '\0'; ++q)
+ if (*q == '\n')
+ break;
+
+ /* if we don't have a complete line, fill up the buffer */
+ if (q >= end) {
+ if (eof)
+ break;
+ if ((size_t)(q - p) >= sizeof(buf)) {
+ warnx("passwd line too long");
+ errno = EINVAL; /* hack */
+ goto err;
+ }
+ if (p < end) {
+ q = memmove(buf, p, end - p);
+ end -= p - buf;
+ } else {
+ p = q = end = buf;
+ }
+ readlen = read(ffd, end, sizeof(buf) - (end - buf));
+ if (readlen == -1)
+ goto err;
+ else
+ len = (size_t)readlen;
+ if (len == 0 && p == buf)
+ break;
+ end += len;
+ len = end - buf;
+ if (len < (ssize_t)sizeof(buf)) {
+ eof = 1;
+ if (len > 0 && buf[len - 1] != '\n')
+ ++len, *end++ = '\n';
+ }
+ continue;
+ }
+
+ /* is it a blank line or a comment? */
+ for (r = p; r < q && isspace(*r); ++r)
+ /* nothing */ ;
+ if (r == q || *r == '#') {
+ /* yep */
+ if (write(tfd, p, q - p + 1) != q - p + 1)
+ goto err;
+ ++q;
+ continue;
+ }
+
+ /* is it the one we're looking for? */
+
+ t = *q;
+ *q = '\0';
+
+ fpw = pw_scan(r, PWSCAN_MASTER);
+
+ /*
+ * fpw is either the struct passwd for the current line,
+ * or NULL if the line is malformed.
+ */
+
+ *q = t;
+ if (fpw == NULL || strcmp(fpw->pw_name, pw->pw_name) != 0) {
+ /* nope */
+ if (fpw != NULL)
+ free(fpw);
+ if (write(tfd, p, q - p + 1) != q - p + 1)
+ goto err;
+ ++q;
+ continue;
+ }
+ if (old_pw && !pw_equal(fpw, old_pw)) {
+ warnx("entry inconsistent");
+ free(fpw);
+ errno = EINVAL; /* hack */
+ goto err;
+ }
+ free(fpw);
+
+ /* it is, replace it */
+ len = strlen(line);
+ if (write(tfd, line, len) != (int)len)
+ goto err;
+
+ /* we're done, just copy the rest over */
+ for (;;) {
+ if (write(tfd, q, end - q) != end - q)
+ goto err;
+ q = buf;
+ readlen = read(ffd, buf, sizeof(buf));
+ if (readlen == 0)
+ break;
+ else
+ len = (size_t)readlen;
+ if (readlen == -1)
+ goto err;
+ end = buf + len;
+ }
+ goto done;
+ }
+
+ /* if we got here, we have a new entry */
+ len = strlen(line);
+ if ((size_t)write(tfd, line, len) != len ||
+ write(tfd, "\n", 1) != 1)
+ goto err;
+ done:
+ free(line);
+ return (0);
+ err:
+ free(line);
+ return (-1);
+}
+
+/*
+ * Return the current value of tempname.
+ */
+const char *
+pw_tempname(void)
+{
+
+ return (tempname);
+}
+
+/*
+ * Duplicate a struct passwd.
+ */
+struct passwd *
+pw_dup(const struct passwd *pw)
+{
+ struct passwd *npw;
+ ssize_t len;
+
+ len = sizeof(*npw) +
+ (pw->pw_name ? strlen(pw->pw_name) + 1 : 0) +
+ (pw->pw_passwd ? strlen(pw->pw_passwd) + 1 : 0) +
+ (pw->pw_class ? strlen(pw->pw_class) + 1 : 0) +
+ (pw->pw_gecos ? strlen(pw->pw_gecos) + 1 : 0) +
+ (pw->pw_dir ? strlen(pw->pw_dir) + 1 : 0) +
+ (pw->pw_shell ? strlen(pw->pw_shell) + 1 : 0);
+ if ((npw = malloc((size_t)len)) == NULL)
+ return (NULL);
+ memcpy(npw, pw, sizeof(*npw));
+ len = sizeof(*npw);
+ if (pw->pw_name) {
+ npw->pw_name = ((char *)npw) + len;
+ len += sprintf(npw->pw_name, "%s", pw->pw_name) + 1;
+ }
+ if (pw->pw_passwd) {
+ npw->pw_passwd = ((char *)npw) + len;
+ len += sprintf(npw->pw_passwd, "%s", pw->pw_passwd) + 1;
+ }
+ if (pw->pw_class) {
+ npw->pw_class = ((char *)npw) + len;
+ len += sprintf(npw->pw_class, "%s", pw->pw_class) + 1;
+ }
+ if (pw->pw_gecos) {
+ npw->pw_gecos = ((char *)npw) + len;
+ len += sprintf(npw->pw_gecos, "%s", pw->pw_gecos) + 1;
+ }
+ if (pw->pw_dir) {
+ npw->pw_dir = ((char *)npw) + len;
+ len += sprintf(npw->pw_dir, "%s", pw->pw_dir) + 1;
+ }
+ if (pw->pw_shell) {
+ npw->pw_shell = ((char *)npw) + len;
+ len += sprintf(npw->pw_shell, "%s", pw->pw_shell) + 1;
+ }
+ return (npw);
+}
+
+#include "pw_scan.h"
+
+/*
+ * Wrapper around an internal libc function
+ */
+struct passwd *
+pw_scan(const char *line, int flags)
+{
+ struct passwd pw, *ret;
+ char *bp;
+
+ if ((bp = strdup(line)) == NULL)
+ return (NULL);
+ if (!__pw_scan(bp, &pw, flags)) {
+ free(bp);
+ return (NULL);
+ }
+ ret = pw_dup(&pw);
+ free(bp);
+ return (ret);
+}
diff --git a/pw/Makefile b/pw/Makefile
new file mode 100644
index 0000000..8937124
--- /dev/null
+++ b/pw/Makefile
@@ -0,0 +1,12 @@
+# $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 edgroup.c psdate.c \
+ bitmap.c cpdir.c rm_r.c
+
+DPADD= ${LIBCRYPT} ${LIBUTIL}
+LDADD= -lcrypt -lutil
+
+.include <bsd.prog.mk>
diff --git a/pw/README b/pw/README
new file mode 100644
index 0000000..bbb1539
--- /dev/null
+++ b/pw/README
@@ -0,0 +1,22 @@
+
+pw is a command-line driven passwd/group editor utility that provides
+an easy and safe means of modifying of any/all fields in the system
+password files, and has an add, modify and delete mode for user and
+group records. Command line options have been fashioned to be similar
+to those used by the Sun/shadow commands: useradd, usermod, userdel,
+groupadd, groupmod, groupdel, but combines all operations within the
+single command `pw'.
+
+User add mode also provides a means of easily setting system useradd
+defaults (see pw.conf.5), so that adding a user is as easy as issuing
+the command "pw useradd <loginid>". Creation of a unique primary
+group for each user and automatic membership in secondary groups
+is fully supported.
+
+This program may be FreeBSD specific, but should be trivial to port to
+other bsd4.4 variants.
+
+Author and maintainer: David L. Nugent, <davidn@blaze.net.au>
+
+$FreeBSD$
+
diff --git a/pw/bitmap.c b/pw/bitmap.c
new file mode 100644
index 0000000..bcfea7e
--- /dev/null
+++ b/pw/bitmap.c
@@ -0,0 +1,132 @@
+/*-
+ * 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 <stdlib.h>
+#include <string.h>
+
+#include "bitmap.h"
+
+struct bitmap
+bm_alloc(int size)
+{
+ struct bitmap bm;
+ int szmap = (size / 8) + !!(size % 8);
+
+ bm.size = size;
+ bm.map = malloc(szmap);
+ if (bm.map)
+ memset(bm.map, 0, szmap);
+ return bm;
+}
+
+void
+bm_dealloc(struct bitmap * bm)
+{
+ if (bm->map)
+ free(bm->map);
+}
+
+static void
+bm_getmask(int *pos, unsigned char *bmask)
+{
+ *bmask = (unsigned char) (1 << (*pos % 8));
+ *pos /= 8;
+}
+
+void
+bm_setbit(struct bitmap * bm, int pos)
+{
+ unsigned char bmask;
+
+ bm_getmask(&pos, &bmask);
+ bm->map[pos] |= bmask;
+}
+
+void
+bm_clrbit(struct bitmap * bm, int pos)
+{
+ unsigned char bmask;
+
+ bm_getmask(&pos, &bmask);
+ bm->map[pos] &= ~bmask;
+}
+
+int
+bm_isset(struct bitmap * bm, int pos)
+{
+ unsigned char bmask;
+
+ bm_getmask(&pos, &bmask);
+ return !!(bm->map[pos] & bmask);
+}
+
+int
+bm_firstunset(struct bitmap * bm)
+{
+ int szmap = (bm->size / 8) + !!(bm->size % 8);
+ int at = 0;
+ int pos = 0;
+
+ while (pos < szmap) {
+ unsigned char bmv = bm->map[pos++];
+ unsigned char bmask = 1;
+
+ while (bmask & 0xff) {
+ if ((bmv & bmask) == 0)
+ return at;
+ bmask <<= 1;
+ ++at;
+ }
+ }
+ return at;
+}
+
+int
+bm_lastset(struct bitmap * bm)
+{
+ int szmap = (bm->size / 8) + !!(bm->size % 8);
+ int at = 0;
+ int pos = 0;
+ int ofs = 0;
+
+ while (pos < szmap) {
+ unsigned char bmv = bm->map[pos++];
+ unsigned char bmask = 1;
+
+ while (bmask & 0xff) {
+ if ((bmv & bmask) != 0)
+ ofs = at;
+ bmask <<= 1;
+ ++at;
+ }
+ }
+ return ofs;
+}
diff --git a/pw/bitmap.h b/pw/bitmap.h
new file mode 100644
index 0000000..4d6cfe4
--- /dev/null
+++ b/pw/bitmap.h
@@ -0,0 +1,50 @@
+/*-
+ * 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 _BITMAP_H_
+#define _BITMAP_H_
+
+#include <sys/cdefs.h>
+
+struct bitmap
+{
+ int size;
+ unsigned char *map;
+};
+
+__BEGIN_DECLS
+struct bitmap bm_alloc(int size);
+void bm_dealloc(struct bitmap * bm);
+void bm_setbit(struct bitmap * bm, int pos);
+void bm_clrbit(struct bitmap * bm, int pos);
+int bm_isset(struct bitmap * bm, int pos);
+int bm_firstunset(struct bitmap * bm);
+int bm_lastset(struct bitmap * bm);
+__END_DECLS
+
+#endif /* !_BITMAP_H */
diff --git a/pw/cpdir.c b/pw/cpdir.c
new file mode 100644
index 0000000..f370421
--- /dev/null
+++ b/pw/cpdir.c
@@ -0,0 +1,130 @@
+/*-
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <dirent.h>
+
+#include "pwupd.h"
+
+void
+copymkdir(char const * dir, char const * skel, mode_t mode, uid_t uid, gid_t gid)
+{
+ char src[MAXPATHLEN];
+ char dst[MAXPATHLEN];
+ char lnk[MAXPATHLEN];
+ int len;
+
+ if (mkdir(dir, mode) != 0 && errno != EEXIST) {
+ warn("mkdir(%s)", dir);
+ } else {
+ int infd, outfd;
+ struct stat st;
+
+ static char counter = 0;
+ static char *copybuf = NULL;
+
+ ++counter;
+ chown(dir, uid, gid);
+ if (skel != NULL && *skel != '\0') {
+ DIR *d = opendir(skel);
+
+ if (d != NULL) {
+ struct dirent *e;
+
+ while ((e = readdir(d)) != NULL) {
+ char *p = e->d_name;
+
+ if (snprintf(src, sizeof(src), "%s/%s", skel, p) >= (int)sizeof(src))
+ warn("warning: pathname too long '%s/%s' (skel not copied)", skel, p);
+ else if (lstat(src, &st) == 0) {
+ if (strncmp(p, "dot.", 4) == 0) /* Conversion */
+ p += 3;
+ if (snprintf(dst, sizeof(dst), "%s/%s", dir, p) >= (int)sizeof(dst))
+ warn("warning: path too long '%s/%s' (skel file skipped)", dir, p);
+ else {
+ if (S_ISDIR(st.st_mode)) { /* Recurse for this */
+ if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0)
+ copymkdir(dst, src, (st.st_mode & 0777), uid, gid);
+ chflags(dst, st.st_flags); /* propogate flags */
+ } else if (S_ISLNK(st.st_mode) && (len = readlink(src, lnk, sizeof(lnk))) != -1) {
+ lnk[len] = '\0';
+ symlink(lnk, dst);
+ lchown(dst, uid, gid);
+ /*
+ * Note: don't propogate special attributes
+ * but do propogate file flags
+ */
+ } else if (S_ISREG(st.st_mode) && (outfd = open(dst, O_RDWR | O_CREAT | O_EXCL, st.st_mode)) != -1) {
+ if ((infd = open(src, O_RDONLY)) == -1) {
+ close(outfd);
+ remove(dst);
+ } else {
+ int b;
+
+ /*
+ * Allocate our copy buffer if we need to
+ */
+ if (copybuf == NULL)
+ copybuf = malloc(4096);
+ while ((b = read(infd, copybuf, 4096)) > 0)
+ write(outfd, copybuf, b);
+ close(infd);
+ /*
+ * Propogate special filesystem flags
+ */
+ fchown(outfd, uid, gid);
+ fchflags(outfd, st.st_flags);
+ close(outfd);
+ chown(dst, uid, gid);
+ }
+ }
+ }
+ }
+ }
+ closedir(d);
+ }
+ }
+ if (--counter == 0 && copybuf != NULL) {
+ free(copybuf);
+ copybuf = NULL;
+ }
+ }
+}
+
diff --git a/pw/edgroup.c b/pw/edgroup.c
new file mode 100644
index 0000000..1cc46b4
--- /dev/null
+++ b/pw/edgroup.c
@@ -0,0 +1,229 @@
+/*-
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <sys/param.h>
+#include <ctype.h>
+
+#include "pwupd.h"
+
+static int
+isingroup(char const * name, char **mem)
+{
+ int i;
+
+ for (i = 0; mem[i] != NULL; i++)
+ if (strcmp(name, mem[i]) == 0)
+ return i;
+ return -1;
+}
+
+int
+editgroups(char *name, char **groups)
+{
+ int rc = 0;
+ int infd;
+ char groupfile[MAXPATHLEN];
+ char grouptmp[MAXPATHLEN];
+
+ strncpy(groupfile, getgrpath(_GROUP), MAXPATHLEN - 5);
+ groupfile[MAXPATHLEN - 5] = '\0';
+ strcpy(grouptmp, groupfile);
+ strcat(grouptmp, ".new");
+
+ if ((infd = open(groupfile, O_RDWR | O_CREAT | O_EXLOCK, 0644)) != -1) {
+ FILE *infp;
+
+ if ((infp = fdopen(infd, "r+")) == NULL)
+ close(infd);
+ else {
+ int outfd;
+
+ if ((outfd = open(grouptmp, O_RDWR | O_CREAT | O_TRUNC, 0644)) != -1) {
+ FILE *outfp;
+
+ if ((outfp = fdopen(outfd, "w+")) == NULL)
+ close(outfd);
+ else {
+ int linelen = PWBUFSZ;
+ int outlen = PWBUFSZ;
+ int memlen = 200; /* Arbitrary */
+ char *line = malloc(linelen);
+ char *outl = malloc(outlen);
+ char **mems = malloc(memlen * sizeof(char *));
+ int namlen = strlen(name);
+
+ if (line == NULL || outl == NULL || mems == NULL) {
+ mem_abort:
+ rc = 0;
+ } else {
+ while (fgets(line, linelen, infp) != NULL) {
+ char *p;
+ int l;
+
+ while ((p = strchr(line, '\n')) == NULL)
+ {
+ if (extendline(&line, &linelen, linelen + PWBUFSZ) == -1) {
+ goto mem_abort;
+ }
+ l = strlen(line);
+ if (fgets(line + l, linelen - l, infp) == NULL)
+ break; /* No newline terminator on last line */
+ }
+ l = strlen(line) + namlen + 1;
+ if (extendline(&outl, &outlen, l) == -1) {
+ goto mem_abort;
+ }
+ if (*line == '#')
+ strcpy(outl, line);
+ else if (*line == '\n')
+ *outl = '\0';
+ else {
+ int i,
+ mno = 0;
+ char *cp = line;
+ char const *sep = ":\n";
+ struct group grp;
+
+ memset(&grp, 0, sizeof grp);
+ for (i = 0; (p = strsep(&cp, sep)) != NULL; i++) {
+ switch (i) {
+ case 0: /* Group name */
+ grp.gr_name = p;
+ break;
+ case 1: /* Group password */
+ grp.gr_passwd = p;
+ break;
+ case 2: /* Group id */
+ grp.gr_gid = atoi(p);
+ break;
+ case 3: /* Member list */
+ cp = p;
+ sep = ",\n";
+ break;
+ default: /* Individual members */
+ if (*p) {
+ if (extendarray(&mems, &memlen, mno + 2) == -1) {
+ goto mem_abort;
+ }
+ mems[mno++] = p;
+ }
+ break;
+ }
+ }
+ if (i < 2) /* Bail out - insufficient fields */
+ continue;
+
+ grp.gr_mem = mems;
+ for (i = mno; i < memlen; i++)
+ mems[i] = NULL;
+
+ /*
+ * Delete from group, or add to group?
+ */
+ if (groups == NULL || isingroup(grp.gr_name, groups) == -1) { /* Delete */
+ int idx;
+
+ while ((idx = isingroup(name, mems)) != -1) {
+ for (i = idx; i < (memlen - 1); i++)
+ mems[i] = mems[i + 1];
+ mems[i] = NULL;
+ --mno;
+ }
+ /*
+ * Special case - deleting user and group may be user's own
+ */
+ if (groups == NULL && mems[0] == NULL && strcmp(name, grp.gr_name) == 0) {
+ /*
+ * First, make _sure_ we don't have other members
+ */
+ struct passwd *pwd;
+
+ SETPWENT();
+ while ((pwd = GETPWENT()) != NULL && (gid_t)pwd->pw_gid != (gid_t)grp.gr_gid);
+ ENDPWENT();
+ if (pwd == NULL) /* No members at all */
+ continue; /* Drop the group */
+ }
+ } else if (isingroup(name, mems) == -1) {
+ if (extendarray(&mems, &memlen, mno + 2) == -1) {
+ goto mem_abort;
+ }
+ grp.gr_mem = mems; /* May have realloced() */
+ mems[mno++] = name;
+ mems[mno ] = NULL;
+ }
+ fmtgrentry(&outl, &outlen, &grp, PWF_GROUP);
+ }
+ fputs(outl, outfp);
+ }
+ if (fflush(outfp) != EOF) {
+ rc = 1;
+
+ /*
+ * Copy data back into the original file and truncate
+ */
+ rewind(infp);
+ rewind(outfp);
+ while (fgets(outl, outlen, outfp) != NULL)
+ fputs(outl, infp);
+
+ /*
+ * This is a gross hack, but we may have corrupted the
+ * original file.
+ */
+ if (fflush(infp) == EOF || ferror(infp))
+ rc = rename(grouptmp, groupfile) == 0;
+ else
+ ftruncate(infd, ftell(infp));
+ }
+ }
+ free(mems);
+ free(outl);
+ free(line);
+ fclose(outfp);
+ }
+ remove(grouptmp);
+ }
+ fclose(infp);
+ }
+ }
+ return rc;
+}
diff --git a/pw/fileupd.c b/pw/fileupd.c
new file mode 100644
index 0000000..b88f4fa
--- /dev/null
+++ b/pw/fileupd.c
@@ -0,0 +1,203 @@
+/*-
+ * 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;
+}
+
+
+int
+fileupdate(char const * filename, mode_t fmode, char const * newline, char const * prefix, int pfxlen, int updmode)
+{
+ int rc = 0;
+
+ if (pfxlen <= 1)
+ rc = EINVAL;
+ else {
+ int infd = open(filename, O_RDWR | O_CREAT | O_EXLOCK, fmode);
+
+ if (infd == -1)
+ rc = errno;
+ else {
+ FILE *infp = fdopen(infd, "r+");
+
+ if (infp == NULL) {
+ rc = errno; /* Assumes fopen(3) sets errno from open(2) */
+ close(infd);
+ } else {
+ int outfd;
+ char file[MAXPATHLEN];
+
+ strcpy(file, filename);
+ strcat(file, ".new");
+ outfd = open(file, O_RDWR | O_CREAT | O_TRUNC, fmode);
+ if (outfd == -1)
+ rc = errno;
+ else {
+ FILE *outfp = fdopen(outfd, "w+");
+
+ if (outfp == NULL) {
+ rc = errno;
+ close(outfd);
+ } else {
+ int updated = UPD_CREATE;
+ int linesize = PWBUFSZ;
+ char *line = malloc(linesize);
+
+ nextline:
+ while (fgets(line, linesize, infp) != NULL) {
+ char *p = strchr(line, '\n');
+
+ while ((p = strchr(line, '\n')) == NULL) {
+ int l;
+ if (extendline(&line, &linesize, linesize + PWBUFSZ) == -1) {
+ int ch;
+ fputs(line, outfp);
+ while ((ch = fgetc(infp)) != EOF) {
+ fputc(ch, outfp);
+ if (ch == '\n')
+ break;
+ }
+ goto nextline;
+ }
+ l = strlen(line);
+ if (fgets(line + l, linesize - l, infp) == NULL)
+ break;
+ }
+ if (*line != '#' && *line != '\n') {
+ if (!updated && strncmp(line, prefix, pfxlen) == 0) {
+ updated = updmode == UPD_REPLACE ? UPD_REPLACE : UPD_DELETE;
+
+ /*
+ * Only actually write changes if updating
+ */
+ if (updmode == UPD_REPLACE)
+ strcpy(line, newline);
+ else if (updmode == UPD_DELETE)
+ continue;
+ }
+ }
+ fputs(line, outfp);
+ }
+
+ /*
+ * Now, we need to decide what to do: If we are in
+ * update mode, and no record was updated, then error If
+ * we are in insert mode, and record already exists,
+ * then error
+ */
+ if (updmode != updated)
+ /* -1 return means:
+ * update,delete=no user entry
+ * create=entry exists
+ */
+ rc = -1;
+ else {
+
+ /*
+ * If adding a new record, append it to the end
+ */
+ if (updmode == UPD_CREATE)
+ fputs(newline, outfp);
+
+ /*
+ * Flush the file and check for the result
+ */
+ if (fflush(outfp) == EOF)
+ rc = errno; /* Failed to update */
+ else {
+ /*
+ * Copy data back into the
+ * original file and truncate
+ */
+ rewind(infp);
+ rewind(outfp);
+ while (fgets(line, linesize, outfp) != NULL)
+ fputs(line, infp);
+
+ /*
+ * If there was a problem with copying
+ * we will just rename 'file.new'
+ * to 'file'.
+ * This is a gross hack, but we may have
+ * corrupted the original file
+ */
+ if (fflush(infp) == EOF || ferror(infp))
+ rename(file, filename);
+ else
+ ftruncate(infd, ftell(infp));
+ }
+ }
+ free(line);
+ fclose(outfp);
+ }
+ remove(file);
+ }
+ fclose(infp);
+ }
+ }
+ }
+ return rc;
+}
diff --git a/pw/grupd.c b/pw/grupd.c
new file mode 100644
index 0000000..edff76d
--- /dev/null
+++ b/pw/grupd.c
@@ -0,0 +1,171 @@
+/*-
+ * 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 <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;
+}
+
+int
+grdb(char *arg,...)
+{
+ /*
+ * This is a stub for now, but maybe eventually be functional
+ * if ever an indexed version of /etc/groups is implemented.
+ */
+ arg=arg;
+ return 0;
+}
+
+int
+fmtgrentry(char **buf, int * buflen, struct group * grp, int type)
+{
+ int i, l;
+
+ /*
+ * Since a group line is of arbitrary length,
+ * we need to calculate up-front just how long
+ * it will need to be...
+ */
+ /* groupname : password : gid : */
+ l = strlen(grp->gr_name) + 1 + strlen(grp->gr_passwd) + 1 + 5 + 1;
+ /* group members + comma separator */
+ for (i = 0; grp->gr_mem[i] != NULL; i++) {
+ l += strlen(grp->gr_mem[i]) + 1;
+ }
+ l += 2; /* For newline & NUL */
+ if (extendline(buf, buflen, l) == -1)
+ l = -1;
+ else{
+ /*
+ * Now we can safely format
+ */
+ if (type == PWF_STANDARD)
+ l = sprintf(*buf, "%s:*:%ld:", grp->gr_name, (long) grp->gr_gid);
+ else
+ l = sprintf(*buf, "%s:%s:%ld:", grp->gr_name, grp->gr_passwd, (long) grp->gr_gid);
+
+ /*
+ * List members
+ */
+ for (i = 0; grp->gr_mem[i] != NULL; i++) {
+ l += sprintf(*buf + l, "%s%s", i ? "," : "", grp->gr_mem[i]);
+ }
+
+ (*buf)[l++] = '\n';
+ (*buf)[l] = '\0';
+ }
+ return l;
+}
+
+
+int
+fmtgrent(char **buf, int * buflen, struct group * grp)
+{
+ return fmtgrentry(buf, buflen, grp, PWF_STANDARD);
+}
+
+
+static int
+gr_update(struct group * grp, char const * group, int mode)
+{
+ int l;
+ char pfx[64];
+ int grbuflen = 0;
+ char *grbuf = NULL;
+
+ ENDGRENT();
+ l = snprintf(pfx, sizeof pfx, "%s:", group);
+
+ /*
+ * Update the group file
+ */
+ if (grp != NULL && fmtgrentry(&grbuf, &grbuflen, grp, PWF_PASSWD) == -1)
+ l = -1;
+ else {
+ l = fileupdate(getgrpath(_GROUP), 0644, grbuf, pfx, l, mode);
+ if (l == 0)
+ l = grdb(NULL);
+ }
+ if (grbuf != NULL)
+ free(grbuf);
+ return l;
+}
+
+
+int
+addgrent(struct group * grp)
+{
+ return gr_update(grp, grp->gr_name, UPD_CREATE);
+}
+
+int
+chggrent(char const * login, struct group * grp)
+{
+ return gr_update(grp, login, UPD_REPLACE);
+}
+
+int
+delgrent(struct group * grp)
+{
+ return gr_update(NULL, grp->gr_name, UPD_DELETE);
+}
diff --git a/pw/psdate.c b/pw/psdate.c
new file mode 100644
index 0000000..3f4c010
--- /dev/null
+++ b/pw/psdate.c
@@ -0,0 +1,295 @@
+/*-
+ * 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 <ctype.h>
+
+#include "psdate.h"
+
+
+static int
+a2i(char const ** str)
+{
+ int i = 0;
+ char const *s = *str;
+
+ if (isdigit((unsigned char)*s)) {
+ i = atoi(s);
+ while (isdigit((unsigned char)*s))
+ ++s;
+ *str = s;
+ }
+ return i;
+}
+
+static int
+numerics(char const * str)
+{
+ int rc = isdigit((unsigned char)*str);
+
+ if (rc)
+ while (isdigit((unsigned char)*str) || *str == 'x')
+ ++str;
+ return rc && !*str;
+}
+
+static int
+aindex(char const * arr[], char const ** str, int len)
+{
+ int l, i;
+ char mystr[32];
+
+ mystr[len] = '\0';
+ l = strlen(strncpy(mystr, *str, len));
+ for (i = 0; i < l; i++)
+ mystr[i] = (char) tolower((unsigned char)mystr[i]);
+ for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
+ if (arr[i] == NULL)
+ i = -1;
+ else { /* Skip past it */
+ while (**str && isalpha((unsigned char)**str))
+ ++(*str);
+ /* And any following whitespace */
+ while (**str && (**str == ',' || isspace((unsigned char)**str)))
+ ++(*str);
+ } /* Return index */
+ return i;
+}
+
+static int
+weekday(char const ** str)
+{
+ static char const *days[] =
+ {"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
+
+ return aindex(days, str, 3);
+}
+
+static int
+month(char const ** str)
+{
+ static char const *months[] =
+ {"jan", "feb", "mar", "apr", "may", "jun", "jul",
+ "aug", "sep", "oct", "nov", "dec", NULL};
+
+ return aindex(months, str, 3);
+}
+
+static void
+parse_time(char const * str, int *hour, int *min, int *sec)
+{
+ *hour = a2i(&str);
+ if ((str = strchr(str, ':')) == NULL)
+ *min = *sec = 0;
+ else {
+ ++str;
+ *min = a2i(&str);
+ *sec = ((str = strchr(str, ':')) == NULL) ? 0 : atoi(++str);
+ }
+}
+
+
+static void
+parse_datesub(char const * str, int *day, int *mon, int *year)
+{
+ int i;
+
+ static char const nchrs[] = "0123456789 \t,/-.";
+
+ if ((i = month(&str)) != -1) {
+ *mon = i;
+ if ((i = a2i(&str)) != 0)
+ *day = i;
+ } else if ((i = a2i(&str)) != 0) {
+ *day = i;
+ while (*str && strchr(nchrs + 10, *str) != NULL)
+ ++str;
+ if ((i = month(&str)) != -1)
+ *mon = i;
+ else if ((i = a2i(&str)) != 0)
+ *mon = i - 1;
+ } else
+ return;
+
+ while (*str && strchr(nchrs + 10, *str) != NULL)
+ ++str;
+ if (isdigit((unsigned char)*str)) {
+ *year = atoi(str);
+ if (*year > 1900)
+ *year -= 1900;
+ else if (*year < 32)
+ *year += 100;
+ }
+}
+
+
+/*-
+ * Parse time must be flexible, it handles the following formats:
+ * nnnnnnnnnnn UNIX timestamp (all numeric), 0 = now
+ * 0xnnnnnnnn UNIX timestamp in hexadecimal
+ * 0nnnnnnnnn UNIX timestamp in octal
+ * 0 Given time
+ * +nnnn[smhdwoy] Given time + nnnn hours, mins, days, weeks, months or years
+ * -nnnn[smhdwoy] Given time - nnnn hours, mins, days, weeks, months or years
+ * dd[ ./-]mmm[ ./-]yy Date }
+ * hh:mm:ss Time } May be combined
+ */
+
+time_t
+parse_date(time_t dt, char const * str)
+{
+ char *p;
+ int i;
+ long val;
+ struct tm *T;
+
+ if (dt == 0)
+ dt = time(NULL);
+
+ while (*str && isspace((unsigned char)*str))
+ ++str;
+
+ if (numerics(str)) {
+ dt = strtol(str, &p, 0);
+ } else if (*str == '+' || *str == '-') {
+ val = strtol(str, &p, 0);
+ switch (*p) {
+ case 'h':
+ case 'H': /* hours */
+ dt += (val * 3600L);
+ break;
+ case '\0':
+ case 'm':
+ case 'M': /* minutes */
+ dt += (val * 60L);
+ break;
+ case 's':
+ case 'S': /* seconds */
+ dt += val;
+ break;
+ case 'd':
+ case 'D': /* days */
+ dt += (val * 86400L);
+ break;
+ case 'w':
+ case 'W': /* weeks */
+ dt += (val * 604800L);
+ break;
+ case 'o':
+ case 'O': /* months */
+ T = localtime(&dt);
+ T->tm_mon += (int) val;
+ i = T->tm_mday;
+ goto fixday;
+ case 'y':
+ case 'Y': /* years */
+ T = localtime(&dt);
+ T->tm_year += (int) val;
+ i = T->tm_mday;
+ fixday:
+ dt = mktime(T);
+ T = localtime(&dt);
+ if (T->tm_mday != i) {
+ T->tm_mday = 1;
+ dt = mktime(T);
+ dt -= (time_t) 86400L;
+ }
+ default: /* unknown */
+ break; /* leave untouched */
+ }
+ } else {
+ char *q, tmp[64];
+
+ /*
+ * Skip past any weekday prefix
+ */
+ weekday(&str);
+ strlcpy(tmp, str, sizeof(tmp));
+ str = tmp;
+ T = localtime(&dt);
+
+ /*
+ * See if we can break off any timezone
+ */
+ while ((q = strrchr(tmp, ' ')) != NULL) {
+ if (strchr("(+-", q[1]) != NULL)
+ *q = '\0';
+ else {
+ int j = 1;
+
+ while (q[j] && isupper((unsigned char)q[j]))
+ ++j;
+ if (q[j] == '\0')
+ *q = '\0';
+ else
+ break;
+ }
+ }
+
+ /*
+ * See if there is a time hh:mm[:ss]
+ */
+ if ((p = strchr(tmp, ':')) == NULL) {
+
+ /*
+ * No time string involved
+ */
+ T->tm_hour = T->tm_min = T->tm_sec = 0;
+ parse_datesub(tmp, &T->tm_mday, &T->tm_mon, &T->tm_year);
+ } else {
+ char datestr[64], timestr[64];
+
+ /*
+ * Let's chip off the time string
+ */
+ if ((q = strpbrk(p, " \t")) != NULL) { /* Time first? */
+ int l = q - str;
+
+ strlcpy(timestr, str, l + 1);
+ strlcpy(datestr, q + 1, sizeof(datestr));
+ parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
+ parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
+ } else if ((q = strrchr(tmp, ' ')) != NULL) { /* Time last */
+ int l = q - tmp;
+
+ strlcpy(timestr, q + 1, sizeof(timestr));
+ strlcpy(datestr, tmp, l + 1);
+ } else /* Bail out */
+ return dt;
+ parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
+ parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
+ }
+ dt = mktime(T);
+ }
+ return dt;
+}
diff --git a/pw/psdate.h b/pw/psdate.h
new file mode 100644
index 0000000..a1e99d4
--- /dev/null
+++ b/pw/psdate.h
@@ -0,0 +1,40 @@
+/*-
+ * 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 _PSDATE_H_
+#define _PSDATE_H_
+
+#include <time.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+time_t parse_date(time_t dt, char const * str);
+void print_date(char *buf, time_t t, int dotime);
+__END_DECLS
+
+#endif /* !_PSDATE_H_ */
diff --git a/pw/pw.8 b/pw/pw.8
new file mode 100644
index 0000000..4287055
--- /dev/null
+++ b/pw/pw.8
@@ -0,0 +1,1001 @@
+.\" 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$
+.\"
+.Dd March 30, 2007
+.Dt PW 8
+.Os
+.Sh NAME
+.Nm pw
+.Nd create, remove, modify & display system users and groups
+.Sh SYNOPSIS
+.Nm
+.Op Fl V Ar etcdir
+.Ar useradd
+.Op name|uid
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl n Ar name
+.Op Fl u Ar uid
+.Op Fl c Ar comment
+.Op Fl d Ar dir
+.Op Fl e Ar date
+.Op Fl p Ar date
+.Op Fl g Ar group
+.Op Fl G Ar grouplist
+.Op Fl m
+.Op Fl M Ar mode
+.Op Fl k Ar dir
+.Op Fl w Ar method
+.Op Fl s Ar shell
+.Op Fl o
+.Op Fl L Ar class
+.Op Fl h Ar fd | Fl H Ar fd
+.Op Fl N
+.Op Fl P
+.Op Fl Y
+.Nm
+.Op Fl V Ar etcdir
+.Ar useradd
+.Op name|uid
+.Fl D
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl b Ar dir
+.Op Fl e Ar days
+.Op Fl p Ar days
+.Op Fl g Ar group
+.Op Fl G Ar grouplist
+.Op Fl k Ar dir
+.Op Fl M Ar mode
+.Op Fl u Ar min , Ns Ar max
+.Op Fl i Ar min , Ns Ar max
+.Op Fl w Ar method
+.Op Fl s Ar shell
+.Op Fl y Ar path
+.Nm
+.Op Fl V Ar etcdir
+.Ar userdel
+.Op name|uid
+.Op Fl n Ar name
+.Op Fl u Ar uid
+.Op Fl r
+.Op Fl Y
+.Nm
+.Op Fl V Ar etcdir
+.Ar usermod
+.Op name|uid
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl n Ar name
+.Op Fl u Ar uid
+.Op Fl c Ar comment
+.Op Fl d Ar dir
+.Op Fl e Ar date
+.Op Fl p Ar date
+.Op Fl g Ar group
+.Op Fl G Ar grouplist
+.Op Fl l Ar name
+.Op Fl m
+.Op Fl M Ar mode
+.Op Fl k Ar dir
+.Op Fl w Ar method
+.Op Fl s Ar shell
+.Op Fl L Ar class
+.Op Fl h Ar fd | Fl H Ar fd
+.Op Fl N
+.Op Fl P
+.Op Fl Y
+.Nm
+.Op Fl V Ar etcdir
+.Ar usershow
+.Op name|uid
+.Op Fl n Ar name
+.Op Fl u Ar uid
+.Op Fl F
+.Op Fl P
+.Op Fl 7
+.Op Fl a
+.Nm
+.Op Fl V Ar etcdir
+.Ar usernext
+.Op Fl C Ar config
+.Op Fl q
+.Nm
+.Op Fl V Ar etcdir
+.Ar groupadd
+.Op group|gid
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl n Ar group
+.Op Fl g Ar gid
+.Op Fl M Ar members
+.Op Fl o
+.Op Fl h Ar fd | Fl H Ar fd
+.Op Fl N
+.Op Fl P
+.Op Fl Y
+.Nm
+.Op Fl V Ar etcdir
+.Ar groupdel
+.Op group|gid
+.Op Fl n Ar name
+.Op Fl g Ar gid
+.Op Fl Y
+.Nm
+.Op Fl V Ar etcdir
+.Ar groupmod
+.Op group|gid
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl n Ar name
+.Op Fl g Ar gid
+.Op Fl l Ar name
+.Op Fl M Ar members
+.Op Fl m Ar newmembers
+.Op Fl d Ar oldmembers
+.Op Fl h Ar fd | Fl H Ar fd
+.Op Fl N
+.Op Fl P
+.Op Fl Y
+.Nm
+.Op Fl V Ar etcdir
+.Ar groupshow
+.Op group|gid
+.Op Fl n Ar name
+.Op Fl g Ar gid
+.Op Fl F
+.Op Fl P
+.Op Fl a
+.Nm
+.Op Fl V Ar etcdir
+.Ar groupnext
+.Op Fl C Ar config
+.Op Fl q
+.Nm
+.Op Fl V Ar etcdir
+.Ar lock
+.Op name|uid
+.Op Fl C Ar config
+.Op Fl q
+.Nm
+.Op Fl V Ar etcdir
+.Ar unlock
+.Op name|uid
+.Op Fl C Ar config
+.Op Fl q
+.Sh DESCRIPTION
+The
+.Nm
+utility is a command-line based editor for the system
+.Ar user
+and
+.Ar group
+files, allowing the superuser an easy to use and standardized way of adding,
+modifying and removing users and groups.
+Note that
+.Nm
+only operates on the local user and group files.
+.Tn NIS
+users and groups must be
+maintained on the
+.Tn NIS
+server.
+The
+.Nm
+utility handles updating the
+.Pa passwd ,
+.Pa master.passwd ,
+.Pa group
+and the secure and insecure
+password database files, and must be run as root.
+.Pp
+The first one or two keywords provided to
+.Nm
+on the command line provide the context for the remainder of the arguments.
+The keywords
+.Ar user
+and
+.Ar group
+may be combined with
+.Ar add ,
+.Ar del ,
+.Ar mod ,
+.Ar show ,
+or
+.Ar next
+in any order.
+(For example,
+.Ar showuser ,
+.Ar usershow ,
+.Ar show user ,
+and
+.Ar user show
+all mean the same thing.)
+This flexibility is useful for interactive scripts calling
+.Nm
+for user and group database manipulation.
+Following these keywords, you may optionally specify the user or group name or numeric
+id as an alternative to using the
+.Fl n Ar name ,
+.Fl u Ar uid ,
+.Fl g Ar gid
+options.
+.Pp
+The following flags are common to most or all modes of operation:
+.Pp
+.Bl -tag -width "-G grouplist"
+.It Fl V Ar etcdir
+This flag sets an alternate location for the password, group and configuration files,
+and may be used to maintain a user/group database in an alternate location.
+If this switch is specified, the system
+.Pa /etc/pw.conf
+will not be sourced for default configuration data, but the file pw.conf in the
+specified directory will be used instead (or none, if it does not exist).
+The
+.Fl C
+flag may be used to override this behaviour.
+As an exception to the general rule where options must follow the operation
+type, the
+.Fl V
+flag may be used on the command line before the operation keyword.
+.It Fl C Ar config
+By default,
+.Nm
+reads the file
+.Pa /etc/pw.conf
+to obtain policy information on how new user accounts and groups are to be created.
+The
+.Fl C
+option specifies a different configuration file.
+While most of the contents of the configuration file may be overridden via
+command-line options, it may be more convenient to keep standard information in a
+configuration file.
+.It Fl q
+Use of this option causes
+.Nm
+to suppress error messages, which may be useful in interactive environments where it
+is preferable to interpret status codes returned by
+.Nm
+rather than messing up a carefully formatted display.
+.It Fl N
+This option is available in
+.Ar add
+and
+.Ar modify
+operations, and tells
+.Nm
+to output the result of the operation without updating the user or group
+databases.
+You may use the
+.Fl P
+option to switch between standard passwd and readable formats.
+.It Fl Y
+Using this option with any of the update modes causes
+.Nm
+to run
+.Xr make 1
+after changing to the directory
+.Pa /var/yp .
+This is intended to allow automatic updating of
+.Tn NIS
+database files.
+If separate passwd and group files are being used by
+.Tn NIS ,
+then use the
+.Fl y Ar path
+option to specify the location of the
+.Tn NIS
+passwd database so that
+.Nm
+will concurrently update it with the system password
+databases.
+.El
+.Sh USER OPTIONS
+The following options apply to the
+.Ar useradd
+and
+.Ar usermod
+commands:
+.Pp
+.Bl -tag -width "-G grouplist"
+.It Fl n Ar name
+Specify the user/account name.
+.It Fl u Ar uid
+Specify the user/account numeric id.
+.Pp
+Usually, you only need to provide one or the other of these options, as the account
+name will imply the uid, or vice versa.
+However, there are times when you need to provide both.
+For example, when changing the uid of an existing user with
+.Ar usermod ,
+or overriding the default uid when creating a new account.
+If you wish
+.Nm
+to automatically allocate the uid to a new user with
+.Ar useradd ,
+then you should
+.Em not
+use the
+.Fl u
+option.
+You may also provide either the account or userid immediately after the
+.Ar useradd ,
+.Ar userdel ,
+.Ar usermod
+or
+.Ar usershow
+keywords on the command line without using the
+.Fl n
+or
+.Fl u
+options.
+.El
+.Pp
+.Bl -tag -width "-G grouplist"
+.It Fl c Ar comment
+This field sets the contents of the passwd GECOS field, which normally contains up
+to four comma-separated fields containing the user's full name, office or location,
+and work and home phone numbers.
+These sub-fields are used by convention only, however, and are optional.
+If this field is to contain spaces, you need to quote the comment itself with double
+quotes
+.Ql \&" .
+Avoid using commas in this field as these are used as sub-field separators, and the
+colon
+.Ql \&:
+character also cannot be used as this is the field separator for the passwd
+file itself.
+.It Fl d Ar dir
+This option sets the account's home directory.
+Normally, you will only use this if the home directory is to be different from the
+default determined from
+.Pa /etc/pw.conf
+- normally
+.Pa /home
+with the account name as a subdirectory.
+.It Fl e Ar date
+Set the account's expiration date.
+Format of the date is either a UNIX time in decimal, or a date in
+.Ql dd-mmm-yy[yy]
+format, where dd is the day, mmm is the month, either in numeric or alphabetic format
+('Jan', 'Feb', etc) and year is either a two or four digit year.
+This option also accepts a relative date in the form
+.Ql \&+n[mhdwoy]
+where
+.Ql \&n
+is a decimal, octal (leading 0) or hexadecimal (leading 0x) digit followed by the
+number of Minutes, Hours, Days, Weeks, Months or Years from the current date at
+which the expiration date is to be set.
+.It Fl p Ar date
+Set the account's password expiration date.
+This field is similar to the account expiration date option, except that it
+applies to forced password changes.
+This is set in the same manner as the
+.Fl e
+option.
+.It Fl g Ar group
+Set the account's primary group to the given group.
+.Ar group
+may be defined by either its name or group number.
+.It Fl G Ar grouplist
+Set additional group memberships for an account.
+.Ar grouplist
+is a comma, space or tab-separated list of group names or group numbers.
+The user's name is added to the group lists in
+.Pa /etc/group ,
+and
+removed from any groups not specified in
+.Ar grouplist .
+Note: a user should not be added to their primary group with
+.Ar grouplist .
+Also, group membership changes do not take effect for current user login
+sessions, requiring the user to reconnect to be affected by the changes.
+.It Fl L Ar class
+This option sets the login class for the user being created.
+See
+.Xr login.conf 5
+and
+.Xr passwd 5
+for more information on user login classes.
+.It Fl m
+This option instructs
+.Nm
+to attempt to create the user's home directory.
+While primarily useful when adding a new account with
+.Ar useradd ,
+this may also be of use when moving an existing user's home directory elsewhere on
+the file system.
+The new home directory is populated with the contents of the
+.Ar skeleton
+directory, which typically contains a set of shell configuration files that the
+user may personalize to taste.
+Files in this directory are usually named
+.Pa dot . Ns Aq Ar config
+where the
+.Pa dot
+prefix will be stripped.
+When
+.Fl m
+is used on an account with
+.Ar usermod ,
+existing configuration files in the user's home directory are
+.Em not
+overwritten from the skeleton files.
+.Pp
+When a user's home directory is created, it will by default be a subdirectory of the
+.Ar basehome
+directory as specified by the
+.Fl b
+option (see below), bearing the name of the new account.
+This can be overridden by the
+.Fl d
+option on the command line, if desired.
+.It Fl M Ar mode
+Create the user's home directory with the specified
+.Ar mode ,
+modified by the current
+.Xr umask 2 .
+If omitted, it is derived from the parent process'
+.Xr umask 2 .
+This option is only useful in combination with the
+.Fl m
+flag.
+.It Fl k Ar dir
+Set the
+.Ar skeleton
+directory, from which basic startup and configuration files are copied when
+the user's home directory is created.
+This option only has meaning when used with the
+.Fl d
+or
+.Fl m
+flags.
+.It Fl s Ar shell
+Set or changes the user's login shell to
+.Ar shell .
+If the path to the shell program is omitted,
+.Nm
+searches the
+.Ar shellpath
+specified in
+.Pa /etc/pw.conf
+and fills it in as appropriate.
+Note that unless you have a specific reason to do so, you should avoid
+specifying the path - this will allow
+.Nm
+to validate that the program exists and is executable.
+Specifying a full path (or supplying a blank "" shell) avoids this check
+and allows for such entries as
+.Pa /nonexistent
+that should be set for accounts not intended for interactive login.
+.It Fl h Ar fd
+This option provides a special interface by which interactive scripts can
+set an account password using
+.Nm .
+Because the command line and environment are fundamentally insecure mechanisms
+by which programs can accept information,
+.Nm
+will only allow setting of account and group passwords via a file descriptor
+(usually a pipe between an interactive script and the program).
+.Ar sh ,
+.Ar bash ,
+.Ar ksh
+and
+.Ar perl
+all possess mechanisms by which this can be done.
+Alternatively,
+.Nm
+will prompt for the user's password if
+.Fl h Ar 0
+is given, nominating
+.Em stdin
+as the file descriptor on which to read the password.
+Note that this password will be read only once and is intended
+for use by a script rather than for interactive use.
+If you wish to have new password confirmation along the lines of
+.Xr passwd 1 ,
+this must be implemented as part of an interactive script that calls
+.Nm .
+.Pp
+If a value of
+.Ql \&-
+is given as the argument
+.Ar fd ,
+then the password will be set to
+.Ql \&* ,
+rendering the account inaccessible via password-based login.
+.It Fl H Ar fd
+Read an encrypted password string from the specified file descriptor.
+This is like
+.Fl h ,
+but the password should be supplied already encrypted in a form
+suitable for writing directly to the password database.
+.El
+.Pp
+It is possible to use
+.Ar useradd
+to create a new account that duplicates an existing user id.
+While this is normally considered an error and will be rejected, the
+.Fl o
+option overrides the check for duplicates and allows the duplication of
+the user id.
+This may be useful if you allow the same user to login under
+different contexts (different group allocations, different home
+directory, different shell) while providing basically the same
+permissions for access to the user's files in each account.
+.Pp
+The
+.Ar useradd
+command also has the ability to set new user and group defaults by using the
+.Fl D
+option.
+Instead of adding a new user,
+.Nm
+writes a new set of defaults to its configuration file,
+.Pa /etc/pw.conf .
+When using the
+.Fl D
+option, you must not use either
+.Fl n Ar name
+or
+.Fl u Ar uid
+or an error will result.
+Use of
+.Fl D
+changes the meaning of several command line switches in the
+.Ar useradd
+command.
+These are:
+.Bl -tag -width "-G grouplist"
+.It Fl D
+Set default values in
+.Pa /etc/pw.conf
+configuration file, or a different named configuration file if the
+.Fl C Ar config
+option is used.
+.It Fl b Ar dir
+Set the root directory in which user home directories are created.
+The default value for this is
+.Pa /home ,
+but it may be set elsewhere as desired.
+.It Fl e Ar days
+Set the default account expiration period in days.
+Unlike use without
+.Fl D ,
+the argument must be numeric, which specifies the number of days after creation when
+the account is to expire.
+A value of 0 suppresses automatic calculation of the expiry date.
+.It Fl p Ar days
+Set the default password expiration period in days.
+.It Fl g Ar group
+Set the default group for new users.
+If a blank group is specified using
+.Fl g Ar \&"" ,
+then new users will be allocated their own private primary group
+with the same name as their login name.
+If a group is supplied, either its name or uid may be given as an argument.
+.It Fl G Ar grouplist
+Set the default groups in which new users are granted membership.
+This is a separate set of groups from the primary group, and you should avoid
+nominating the same group as both primary and extra groups.
+In other words, these extra groups determine membership in groups
+.Em other than
+the primary group.
+.Ar grouplist
+is a comma-separated list of group names or ids, and are always
+stored in
+.Pa /etc/pw.conf
+by their symbolic names.
+.It Fl L Ar class
+This option sets the default login class for new users.
+.It Fl k Ar dir
+Set the default
+.Em skeleton
+directory, from which prototype shell and other initialization files are copied when
+.Nm
+creates a user's home directory.
+See description of
+.Fl k
+for naming conventions of these files.
+.It Xo
+.Fl u Ar min , Ns Ar max ,
+.Fl i Ar min , Ns Ar max
+.Xc
+These options set the minimum and maximum user and group ids allocated for new accounts
+and groups created by
+.Nm .
+The default values for each is 1000 minimum and 32000 maximum.
+.Ar min
+and
+.Ar max
+are both numbers, where max must be greater than min, and both must be between 0
+and 32767.
+In general, user and group ids less than 100 are reserved for use by the system,
+and numbers greater than 32000 may also be reserved for special purposes (used by
+some system daemons).
+.It Fl w Ar method
+The
+.Fl w
+option sets the default method used to set passwords for newly created user accounts.
+.Ar method
+is one of:
+.Pp
+.Bl -tag -width random -offset indent -compact
+.It no
+disable login on newly created accounts
+.It yes
+force the password to be the account name
+.It none
+force a blank password
+.It random
+generate a random password
+.El
+.Pp
+The
+.Ql \&random
+or
+.Ql \&no
+methods are the most secure; in the former case,
+.Nm
+generates a password and prints it to stdout, which is suitable where you issue
+users with passwords to access their accounts rather than having the user nominate
+their own (possibly poorly chosen) password.
+The
+.Ql \&no
+method requires that the superuser use
+.Xr passwd 1
+to render the account accessible with a password.
+.It Fl y Ar path
+This sets the pathname of the database used by
+.Tn NIS
+if you are not sharing
+the information from
+.Pa /etc/master.passwd
+directly with
+.Tn NIS .
+You should only set this option for
+.Tn NIS
+servers.
+.El
+.Pp
+The
+.Ar userdel
+command has only three valid options.
+The
+.Fl n Ar name
+and
+.Fl u Ar uid
+options have already been covered above.
+The additional option is:
+.Bl -tag -width "-G grouplist"
+.It Fl r
+This tells
+.Nm
+to remove the user's home directory and all of its contents.
+The
+.Nm
+utility errs on the side of caution when removing files from the system.
+Firstly, it will not do so if the uid of the account being removed is also used by
+another account on the system, and the 'home' directory in the password file is
+a valid path that commences with the character
+.Ql \&/ .
+Secondly, it will only remove files and directories that are actually owned by
+the user, or symbolic links owned by anyone under the user's home directory.
+Finally, after deleting all contents owned by the user only empty directories
+will be removed.
+If any additional cleanup work is required, this is left to the administrator.
+.El
+.Pp
+Mail spool files and crontabs are always removed when an account is deleted as these
+are unconditionally attached to the user name.
+Jobs queued for processing by
+.Ar at
+are also removed if the user's uid is unique and not also used by another account on the
+system.
+.Pp
+The
+.Ar usershow
+command allows viewing of an account in one of two formats.
+By default, the format is identical to the format used in
+.Pa /etc/master.passwd
+with the password field replaced with a
+.Ql \&* .
+If the
+.Fl P
+option is used, then
+.Nm
+outputs the account details in a more human readable form.
+If the
+.Fl 7
+option is used, the account details are shown in v7 format.
+The
+.Fl a
+option lists all users currently on file.
+Using
+.Fl F
+forces
+.Nm
+to print the details of an account even if it does not exist.
+.Pp
+The command
+.Ar usernext
+returns the next available user and group ids separated by a colon.
+This is normally of interest only to interactive scripts or front-ends
+that use
+.Nm .
+.Sh GROUP OPTIONS
+The
+.Fl C
+and
+.Fl q
+options (explained at the start of the previous section) are available
+with the group manipulation commands.
+Other common options to all group-related commands are:
+.Bl -tag -width "-m newmembers"
+.It Fl n Ar name
+Specify the group name.
+.It Fl g Ar gid
+Specify the group numeric id.
+.Pp
+As with the account name and id fields, you will usually only need
+to supply one of these, as the group name implies the uid and vice
+versa.
+You will only need to use both when setting a specific group id
+against a new group or when changing the uid of an existing group.
+.It Fl M Ar memberlist
+This option provides an alternative way to add existing users to a
+new group (in groupadd) or replace an existing membership list (in
+groupmod).
+.Ar memberlist
+is a comma separated list of valid and existing user names or uids.
+.It Fl m Ar newmembers
+Similar to
+.Fl M ,
+this option allows the
+.Em addition
+of existing users to a group without replacing the existing list of
+members.
+Login names or user ids may be used, and duplicate users are
+silently eliminated.
+.It Fl d Ar oldmembers
+Similar to
+.Fl M ,
+this option allows the
+.Em deletion
+of existing users from a group without replacing the existing list of
+members.
+Login names or user ids may be used, and duplicate users are
+silently eliminated.
+.El
+.Pp
+.Ar groupadd
+also has a
+.Fl o
+option that allows allocation of an existing group id to a new group.
+The default action is to reject an attempt to add a group, and this option overrides
+the check for duplicate group ids.
+There is rarely any need to duplicate a group id.
+.Pp
+The
+.Ar groupmod
+command adds one additional option:
+.Pp
+.Bl -tag -width "-m newmembers"
+.It Fl l Ar name
+This option allows changing of an existing group name to
+.Ql \&name .
+The new name must not already exist, and any attempt to duplicate an existing group
+name will be rejected.
+.El
+.Pp
+Options for
+.Ar groupshow
+are the same as for
+.Ar usershow ,
+with the
+.Fl g Ar gid
+replacing
+.Fl u Ar uid
+to specify the group id.
+The
+.Fl 7
+option does not apply to the
+.Ar groupshow
+command.
+.Pp
+The command
+.Ar groupnext
+returns the next available group id on standard output.
+.Sh USER LOCKING
+The
+.Nm
+utility
+supports a simple password locking mechanism for users; it works by
+prepending the string
+.Ql *LOCKED*
+to the beginning of the password field in
+.Pa master.passwd
+to prevent successful authentication.
+.Pp
+The
+.Ar lock
+and
+.Ar unlock
+commands take a user name or uid of the account to lock or unlock,
+respectively.
+The
+.Fl V ,
+.Fl C ,
+and
+.Fl q
+options as described above are accepted by these commands.
+.Sh NOTES
+For a summary of options available with each command, you can use
+.Dl pw [command] help
+For example,
+.Dl pw useradd help
+lists all available options for the useradd operation.
+.Pp
+The
+.Nm
+utility allows 8-bit characters in the passwd GECOS field (user's full name,
+office, work and home phone number subfields), but disallows them in
+user login and group names.
+Use 8-bit characters with caution, as connection to the Internet will
+require that your mail transport program supports 8BITMIME, and will
+convert headers containing 8-bit characters to 7-bit quoted-printable
+format.
+.Xr sendmail 8
+does support this.
+Use of 8-bit characters in the GECOS field should be used in
+conjunction with the user's default locale and character set
+and should not be implemented without their use.
+Using 8-bit characters may also affect other
+programs that transmit the contents of the GECOS field over the
+Internet, such as
+.Xr fingerd 8 ,
+and a small number of TCP/IP clients, such as IRC, where full names
+specified in the passwd file may be used by default.
+.Pp
+The
+.Nm
+utility writes a log to the
+.Pa /var/log/userlog
+file when actions such as user or group additions or deletions occur.
+The location of this logfile can be changed in
+.Xr pw.conf 5 .
+.Sh FILES
+.Bl -tag -width /etc/master.passwd.new -compact
+.It Pa /etc/master.passwd
+The user database
+.It Pa /etc/passwd
+A Version 7 format password file
+.It Pa /etc/login.conf
+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
+User/group modification logfile
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility returns EXIT_SUCCESS on successful operation, otherwise
+.Nm
+returns one of the
+following exit codes defined by
+.Xr sysexits 3
+as follows:
+.Bl -tag -width xxxx
+.It EX_USAGE
+.Bl -bullet -compact
+.It
+Command line syntax errors (invalid keyword, unknown option).
+.El
+.It EX_NOPERM
+.Bl -bullet -compact
+.It
+Attempting to run one of the update modes as non-root.
+.El
+.It EX_OSERR
+.Bl -bullet -compact
+.It
+Memory allocation error.
+.It
+Read error from password file descriptor.
+.El
+.It EX_DATAERR
+.Bl -bullet -compact
+.It
+Bad or invalid data provided or missing on the command line or
+via the password file descriptor.
+.It
+Attempted to remove, rename root account or change its uid.
+.El
+.It EX_OSFILE
+.Bl -bullet -compact
+.It
+Skeleton directory is invalid or does not exist.
+.It
+Base home directory is invalid or does not exist.
+.It
+Invalid or non-existent shell specified.
+.El
+.It EX_NOUSER
+.Bl -bullet -compact
+.It
+User, user id, group or group id specified does not exist.
+.It
+User or group recorded, added, or modified unexpectedly disappeared.
+.El
+.It EX_SOFTWARE
+.Bl -bullet -compact
+.It
+No more group or user ids available within specified range.
+.El
+.It EX_IOERR
+.Bl -bullet -compact
+.It
+Unable to rewrite configuration file.
+.It
+Error updating group or user database files.
+.It
+Update error for passwd or group database files.
+.El
+.It EX_CONFIG
+.Bl -bullet -compact
+.It
+No base home directory configured.
+.El
+.El
+.Sh SEE ALSO
+.Xr chpass 1 ,
+.Xr passwd 1 ,
+.Xr umask 2 ,
+.Xr group 5 ,
+.Xr login.conf 5 ,
+.Xr passwd 5 ,
+.Xr pw.conf 5 ,
+.Xr pwd_mkdb 8 ,
+.Xr vipw 8
+.Sh HISTORY
+The
+.Nm
+utility was written to mimic many of the options used in the SYSV
+.Em shadow
+support suite, but is modified for passwd and group fields specific to
+the
+.Bx 4.4
+operating system, and combines all of the major elements
+into a single command.
diff --git a/pw/pw.c b/pw/pw.c
new file mode 100644
index 0000000..e9d9363
--- /dev/null
+++ b/pw/pw.c
@@ -0,0 +1,456 @@
+/*-
+ * 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,
+ pwdb,
+ setgrent,
+ endgrent,
+ getgrent,
+ getgrgid,
+ getgrnam,
+ grdb
+
+};
+struct pwf VPWF =
+{
+ 1,
+ vsetpwent,
+ vendpwent,
+ vgetpwent,
+ vgetpwuid,
+ vgetpwnam,
+ vpwdb,
+ vsetgrent,
+ vendgrent,
+ vgetgrent,
+ vgetgrgid,
+ vgetgrnam,
+ vgrdb
+};
+
+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.conf.5 b/pw/pw.conf.5
new file mode 100644
index 0000000..3f023aa
--- /dev/null
+++ b/pw/pw.conf.5
@@ -0,0 +1,318 @@
+.\" 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$
+.\"
+.Dd March 30, 2007
+.Dt PW.CONF 5
+.Os
+.Sh NAME
+.Nm pw.conf
+.Nd format of the pw.conf configuration file
+.Sh DESCRIPTION
+The file
+.In /etc/pw.conf
+contains configuration data for the
+.Xr pw 8
+utility.
+The
+.Xr pw 8
+utility is used for maintenance of the system password and group
+files, allowing users and groups to be added, deleted and changed.
+This file may be modified via the
+.Xr pw 8
+command using the
+.Ar useradd
+command and the
+.Fl D
+option, or by editing it directly with a text editor.
+.Pp
+Each line in
+.Pa /etc/pw.conf
+is treated either a comment or as configuration data;
+blank lines and lines commencing with a
+.Ql \&#
+character are considered comments, and any remaining lines are
+examined for a leading keyword, followed by corresponding data.
+.Pp
+Keywords recognized by
+.Xr pw 8
+are:
+.Bl -tag -width password_days -offset indent -compact
+.It defaultpasswd
+affect passwords generated for new users
+.It reuseuids
+reuse gaps in uid sequences
+.It reusegids
+reuse gaps in gid sequences
+.It nispasswd
+path to the
+.Tn NIS
+passwd database
+.It skeleton
+where to obtain default home contents
+.It newmail
+mail to send to new users
+.It logfile
+log user/group modifications to this file
+.It home
+root directory for home directories
+.It homemode
+permissions for home directory
+.It shellpath
+paths in which to locate shell programs
+.It shells
+list of valid shells (without path)
+.It defaultshell
+default shell (without path)
+.It defaultgroup
+default group
+.It extragroups
+add new users to this groups
+.It defaultclass
+place new users in this login class
+.It minuid
+.It maxuid
+range of valid default user ids
+.It mingid
+.It maxgid
+range of valid default group ids
+.It expire_days
+days after which account expires
+.It password_days
+days after which password expires
+.El
+.Pp
+Valid values for
+.Ar defaultpasswd
+are:
+.Bl -tag -width password_days -offset indent -compact
+.It no
+disable login on newly created accounts
+.It yes
+force the password to be the account name
+.It none
+force a blank password
+.It random
+generate a random password
+.El
+.Pp
+The second and third options are insecure and should be avoided if
+possible on a publicly accessible system.
+The first option requires that the superuser run
+.Xr passwd 1
+to set a password before the account may be used.
+This may also be useful for creating administrative accounts.
+The final option causes
+.Xr pw 8
+to respond by printing a randomly generated password on stdout.
+This is the preferred and most secure option.
+The
+.Xr pw 8
+utility also provides a method of setting a specific password for the new
+user via a filehandle (command lines are not secure).
+.Pp
+Both
+.Ar reuseuids
+and
+.Ar reusegids
+determine the method by which new user and group id numbers are
+generated.
+A
+.Ql \&yes
+in this field will cause
+.Xr pw 8
+to search for the first unused user or group id within the allowed
+range, whereas a
+.Ql \&no
+will ensure that no other existing user or group id within the range
+is numerically lower than the new one generated, and therefore avoids
+reusing gaps in the user or group id sequence that are caused by
+previous user or group deletions.
+Note that if the default group is not specified using the
+.Ar defaultgroup
+keyword,
+.Xr pw 8
+will create a new group for the user and attempt to keep the new
+user's uid and gid the same.
+If the new user's uid is currently in use as a group id, then the next
+available group id is chosen instead.
+.Pp
+On
+.Tn NIS
+servers which maintain a separate passwd database to
+.Pa /etc/master.passwd ,
+this option allows the additional file to be concurrently updated
+as user records are added, modified or removed.
+If blank or set to 'no', no additional database is updated.
+An absolute pathname must be used.
+.Pp
+The
+.Ar skeleton
+keyword nominates a directory from which the contents of a user's
+new home directory is constructed.
+This is
+.Pa /usr/share/skel
+by default.
+The
+.Xr pw 8 Ns 's
+.Fl m
+option causes the user's home directory to be created and populated
+using the files contained in the
+.Ar skeleton
+directory.
+.Pp
+To send an initial email to new users, the
+.Ar newmail
+keyword may be used to specify a path name to a file containing
+the message body of the message to be sent.
+To avoid sending mail when accounts are created, leave this entry
+blank or specify
+.Ql \&no .
+.Pp
+The
+.Ar logfile
+option allows logging of password file modifications into the
+nominated log file.
+To avoid creating or adding to such a logfile, then leave this
+field blank or specify
+.Ql \&no .
+.Pp
+The
+.Ar home
+keyword is mandatory.
+This specifies the location of the directory in which all new user
+home directories are created.
+.Pp
+The
+.Ar homemode
+keyword is optional.
+It specifies the creation mask of the user's home directory and is modified by
+.Xr umask 2 .
+.Pp
+The
+.Ar shellpath
+keyword specifies a list of directories - separated by colons
+.Ql \&:
+- which contain the programs used by the login shells.
+.Pp
+The
+.Ar shells
+keyword specifies a list of programs available for use as login
+shells.
+This list is a comma-separated list of shell names which should
+not contain a path.
+These shells must exist in one of the directories nominated by
+.Ar shellpath .
+.Pp
+The
+.Ar defaultshell
+keyword nominates which shell program to use for new users when
+none is specified on the
+.Xr pw 8
+command line.
+.Pp
+The
+.Ar defaultgroup
+keyword defines the primary group (the group id number in the
+password file) used for new accounts.
+If left blank, or the word
+.Ql \&no
+is used, then each new user will have a corresponding group of
+their own created automatically.
+This is the recommended procedure for new users as it best secures each
+user's files against interference by other users of the system
+irrespective of the
+.Em umask
+normally used by the user.
+.Pp
+The
+.Ar extragroups
+keyword provides an automatic means of placing new users into groups within
+the
+.Pa /etc/groups
+file.
+This is useful where all users share some resources, and is preferable
+to placing users into the same primary group.
+The effect of this keyword can be overridden using the
+.Fl G
+option on the
+.Xr pw 8
+command line.
+.Pp
+The
+.Ar defaultclass
+field determines the login class (See
+.Xr login.conf 5 )
+that new users will be allocated unless overwritten by
+.Xr pw 8 .
+.Pp
+The
+.Ar minuid ,
+.Ar maxuid ,
+.Ar mingid ,
+.Ar maxgid
+keywords determine the allowed ranges of automatically allocated user
+and group id numbers.
+The default values for both user and group ids are 1000 and 32000 as
+minimum and maximum respectively.
+The user and group id's actually used when creating an account with
+.Xr pw 8
+may be overridden using the
+.Fl u
+and
+.Fl g
+command line options.
+.Pp
+The
+.Ar expire_days
+and
+.Ar password_days
+are used to automatically calculate the number of days from the date
+on which an account is created when the account will expire or the
+user will be forced to change the account's password.
+A value of
+.Ql \&0
+in either field will disable the corresponding (account or password)
+expiration date.
+.Sh LIMITS
+The maximum line length of
+.Pa /etc/pw.conf
+is 1024 characters.
+Longer lines will be skipped and treated
+as comments.
+.Sh FILES
+.Bl -tag -width /etc/master.passwd -compact
+.It Pa /etc/pw.conf
+.It Pa /etc/passwd
+.It Pa /etc/master.passwd
+.It Pa /etc/group
+.El
+.Sh SEE ALSO
+.Xr passwd 1 ,
+.Xr umask 2 ,
+.Xr group 5 ,
+.Xr login.conf 5 ,
+.Xr passwd 5 ,
+.Xr pw 8
diff --git a/pw/pw.h b/pw/pw.h
new file mode 100644
index 0000000..7568c22
--- /dev/null
+++ b/pw/pw.h
@@ -0,0 +1,132 @@
+/*-
+ * 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 _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 addpwent(struct passwd * pwd);
+int delpwent(struct passwd * pwd);
+int chgpwent(char const * login, struct passwd * pwd);
+int fmtpwent(char *buf, struct passwd * pwd);
+
+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 addgrent(struct group * grp);
+int delgrent(struct group * grp);
+int chggrent(char const * login, struct group * grp);
+
+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_conf.c b/pw/pw_conf.c
new file mode 100644
index 0000000..51672b9
--- /dev/null
+++ b/pw/pw_conf.c
@@ -0,0 +1,516 @@
+/*-
+ * 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 <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#include "pw.h"
+
+#define debugging 0
+
+enum {
+ _UC_NONE,
+ _UC_DEFAULTPWD,
+ _UC_REUSEUID,
+ _UC_REUSEGID,
+ _UC_NISPASSWD,
+ _UC_DOTDIR,
+ _UC_NEWMAIL,
+ _UC_LOGFILE,
+ _UC_HOMEROOT,
+ _UC_HOMEMODE,
+ _UC_SHELLPATH,
+ _UC_SHELLS,
+ _UC_DEFAULTSHELL,
+ _UC_DEFAULTGROUP,
+ _UC_EXTRAGROUPS,
+ _UC_DEFAULTCLASS,
+ _UC_MINUID,
+ _UC_MAXUID,
+ _UC_MINGID,
+ _UC_MAXGID,
+ _UC_EXPIRE,
+ _UC_PASSWORD,
+ _UC_FIELDS
+};
+
+static char bourne_shell[] = "sh";
+
+static char *system_shells[_UC_MAXSHELLS] =
+{
+ bourne_shell,
+ "csh",
+ "tcsh"
+};
+
+static char const *booltrue[] =
+{
+ "yes", "true", "1", "on", NULL
+};
+static char const *boolfalse[] =
+{
+ "no", "false", "0", "off", NULL
+};
+
+static struct userconf config =
+{
+ 0, /* Default password for new users? (nologin) */
+ 0, /* Reuse uids? */
+ 0, /* Reuse gids? */
+ NULL, /* NIS version of the passwd file */
+ "/usr/share/skel", /* Where to obtain skeleton files */
+ NULL, /* Mail to send to new accounts */
+ "/var/log/userlog", /* Where to log changes */
+ "/home", /* Where to create home directory */
+ 0777, /* Home directory perms, modified by umask */
+ "/bin", /* Where shells are located */
+ system_shells, /* List of shells (first is default) */
+ bourne_shell, /* Default shell */
+ NULL, /* Default group name */
+ NULL, /* Default (additional) groups */
+ NULL, /* Default login class */
+ 1000, 32000, /* Allowed range of uids */
+ 1000, 32000, /* Allowed range of gids */
+ 0, /* Days until account expires */
+ 0, /* Days until password expires */
+ 0 /* size of default_group array */
+};
+
+static char const *comments[_UC_FIELDS] =
+{
+ "#\n# pw.conf - user/group configuration defaults\n#\n",
+ "\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
+ "\n# Reuse gaps in uid sequence? (yes or no)\n",
+ "\n# Reuse gaps in gid sequence? (yes or no)\n",
+ "\n# Path to the NIS passwd file (blank or 'no' for none)\n",
+ "\n# Obtain default dotfiles from this directory\n",
+ "\n# Mail this file to new user (/etc/newuser.msg or no)\n",
+ "\n# Log add/change/remove information in this file\n",
+ "\n# Root directory in which $HOME directory is created\n",
+ "\n# Mode for the new $HOME directory, will be modified by umask\n",
+ "\n# Colon separated list of directories containing valid shells\n",
+ "\n# Comma separated list of available shells (without paths)\n",
+ "\n# Default shell (without path)\n",
+ "\n# Default group (leave blank for new group per user)\n",
+ "\n# Extra groups for new users\n",
+ "\n# Default login class for new users\n",
+ "\n# Range of valid default user ids\n",
+ NULL,
+ "\n# Range of valid default group ids\n",
+ NULL,
+ "\n# Days after which account expires (0=disabled)\n",
+ "\n# Days after which password expires (0=disabled)\n"
+};
+
+static char const *kwds[] =
+{
+ "",
+ "defaultpasswd",
+ "reuseuids",
+ "reusegids",
+ "nispasswd",
+ "skeleton",
+ "newmail",
+ "logfile",
+ "home",
+ "homemode",
+ "shellpath",
+ "shells",
+ "defaultshell",
+ "defaultgroup",
+ "extragroups",
+ "defaultclass",
+ "minuid",
+ "maxuid",
+ "mingid",
+ "maxgid",
+ "expire_days",
+ "password_days",
+ NULL
+};
+
+static char *
+unquote(char const * str)
+{
+ if (str && (*str == '"' || *str == '\'')) {
+ char *p = strchr(str + 1, *str);
+
+ if (p != NULL)
+ *p = '\0';
+ return (char *) (*++str ? str : NULL);
+ }
+ return (char *) str;
+}
+
+int
+boolean_val(char const * str, int dflt)
+{
+ if ((str = unquote(str)) != NULL) {
+ int i;
+
+ for (i = 0; booltrue[i]; i++)
+ if (strcmp(str, booltrue[i]) == 0)
+ return 1;
+ for (i = 0; boolfalse[i]; i++)
+ if (strcmp(str, boolfalse[i]) == 0)
+ return 0;
+
+ /*
+ * Special cases for defaultpassword
+ */
+ if (strcmp(str, "random") == 0)
+ return -1;
+ if (strcmp(str, "none") == 0)
+ return -2;
+ }
+ return dflt;
+}
+
+char const *
+boolean_str(int val)
+{
+ if (val == -1)
+ return "random";
+ else if (val == -2)
+ return "none";
+ else
+ return val ? booltrue[0] : boolfalse[0];
+}
+
+char *
+newstr(char const * p)
+{
+ char *q = NULL;
+
+ if ((p = unquote(p)) != NULL) {
+ int l = strlen(p) + 1;
+
+ if ((q = malloc(l)) != NULL)
+ memcpy(q, p, l);
+ }
+ return q;
+}
+
+#define LNBUFSZ 1024
+
+
+struct userconf *
+read_userconfig(char const * file)
+{
+ FILE *fp;
+
+ extendarray(&config.groups, &config.numgroups, 200);
+ memset(config.groups, 0, config.numgroups * sizeof(char *));
+ if (file == NULL)
+ file = _PATH_PW_CONF;
+ if ((fp = fopen(file, "r")) != NULL) {
+ int buflen = LNBUFSZ;
+ char *buf = malloc(buflen);
+
+ nextline:
+ while (fgets(buf, buflen, fp) != NULL) {
+ char *p;
+
+ while ((p = strchr(buf, '\n')) == NULL) {
+ int l;
+ if (extendline(&buf, &buflen, buflen + LNBUFSZ) == -1) {
+ int ch;
+ while ((ch = fgetc(fp)) != '\n' && ch != EOF);
+ goto nextline; /* Ignore it */
+ }
+ l = strlen(buf);
+ if (fgets(buf + l, buflen - l, fp) == NULL)
+ break; /* Unterminated last line */
+ }
+
+ if (p != NULL)
+ *p = '\0';
+
+ if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
+ static char const toks[] = " \t\r\n,=";
+ char *q = strtok(NULL, toks);
+ int i = 0;
+ mode_t *modeset;
+
+ while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
+ ++i;
+#if debugging
+ if (i == _UC_FIELDS)
+ printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
+ else
+ printf("Got kwd[%s]=%s\n", p, q);
+#endif
+ switch (i) {
+ case _UC_DEFAULTPWD:
+ config.default_password = boolean_val(q, 1);
+ break;
+ case _UC_REUSEUID:
+ config.reuse_uids = boolean_val(q, 0);
+ break;
+ case _UC_REUSEGID:
+ config.reuse_gids = boolean_val(q, 0);
+ break;
+ case _UC_NISPASSWD:
+ config.nispasswd = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_DOTDIR:
+ config.dotdir = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_NEWMAIL:
+ config.newmail = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_LOGFILE:
+ config.logfile = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_HOMEROOT:
+ config.home = (q == NULL || !boolean_val(q, 1))
+ ? "/home" : newstr(q);
+ break;
+ case _UC_HOMEMODE:
+ modeset = setmode(q);
+ config.homemode = (q == NULL || !boolean_val(q, 1))
+ ? 0777 : getmode(modeset, 0777);
+ free(modeset);
+ break;
+ case _UC_SHELLPATH:
+ config.shelldir = (q == NULL || !boolean_val(q, 1))
+ ? "/bin" : newstr(q);
+ break;
+ case _UC_SHELLS:
+ for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
+ system_shells[i] = newstr(q);
+ if (i > 0)
+ while (i < _UC_MAXSHELLS)
+ system_shells[i++] = NULL;
+ break;
+ case _UC_DEFAULTSHELL:
+ config.shell_default = (q == NULL || !boolean_val(q, 1))
+ ? (char *) bourne_shell : newstr(q);
+ break;
+ case _UC_DEFAULTGROUP:
+ q = unquote(q);
+ config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
+ ? NULL : newstr(q);
+ break;
+ case _UC_EXTRAGROUPS:
+ for (i = 0; q != NULL; q = strtok(NULL, toks)) {
+ if (extendarray(&config.groups, &config.numgroups, i + 2) != -1)
+ config.groups[i++] = newstr(q);
+ }
+ if (i > 0)
+ while (i < config.numgroups)
+ config.groups[i++] = NULL;
+ break;
+ case _UC_DEFAULTCLASS:
+ config.default_class = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_MINUID:
+ if ((q = unquote(q)) != NULL && isdigit(*q))
+ config.min_uid = (uid_t) atol(q);
+ break;
+ case _UC_MAXUID:
+ if ((q = unquote(q)) != NULL && isdigit(*q))
+ config.max_uid = (uid_t) atol(q);
+ break;
+ case _UC_MINGID:
+ if ((q = unquote(q)) != NULL && isdigit(*q))
+ config.min_gid = (gid_t) atol(q);
+ break;
+ case _UC_MAXGID:
+ if ((q = unquote(q)) != NULL && isdigit(*q))
+ config.max_gid = (gid_t) atol(q);
+ break;
+ case _UC_EXPIRE:
+ if ((q = unquote(q)) != NULL && isdigit(*q))
+ config.expire_days = atoi(q);
+ break;
+ case _UC_PASSWORD:
+ if ((q = unquote(q)) != NULL && isdigit(*q))
+ config.password_days = atoi(q);
+ break;
+ case _UC_FIELDS:
+ case _UC_NONE:
+ break;
+ }
+ }
+ }
+ free(buf);
+ fclose(fp);
+ }
+ return &config;
+}
+
+
+int
+write_userconfig(char const * file)
+{
+ int fd;
+
+ if (file == NULL)
+ file = _PATH_PW_CONF;
+
+ if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) {
+ FILE *fp;
+
+ if ((fp = fdopen(fd, "w")) == NULL)
+ close(fd);
+ else {
+ int i, j, k;
+ int len = LNBUFSZ;
+ char *buf = malloc(len);
+
+ for (i = _UC_NONE; i < _UC_FIELDS; i++) {
+ int quote = 1;
+ char const *val = buf;
+
+ *buf = '\0';
+ switch (i) {
+ case _UC_DEFAULTPWD:
+ val = boolean_str(config.default_password);
+ break;
+ case _UC_REUSEUID:
+ val = boolean_str(config.reuse_uids);
+ break;
+ case _UC_REUSEGID:
+ val = boolean_str(config.reuse_gids);
+ break;
+ case _UC_NISPASSWD:
+ val = config.nispasswd ? config.nispasswd : "";
+ quote = 0;
+ break;
+ case _UC_DOTDIR:
+ val = config.dotdir ? config.dotdir : boolean_str(0);
+ break;
+ case _UC_NEWMAIL:
+ val = config.newmail ? config.newmail : boolean_str(0);
+ break;
+ case _UC_LOGFILE:
+ val = config.logfile ? config.logfile : boolean_str(0);
+ break;
+ case _UC_HOMEROOT:
+ val = config.home;
+ break;
+ case _UC_HOMEMODE:
+ sprintf(buf, "%04o", config.homemode);
+ quote = 0;
+ break;
+ case _UC_SHELLPATH:
+ val = config.shelldir;
+ break;
+ case _UC_SHELLS:
+ for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++) {
+ char lbuf[64];
+ int l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", system_shells[j]);
+ if (l < 0)
+ l = 0;
+ if (l + k + 1 < len || extendline(&buf, &len, len + LNBUFSZ) != -1) {
+ strcpy(buf + k, lbuf);
+ k += l;
+ }
+ }
+ quote = 0;
+ break;
+ case _UC_DEFAULTSHELL:
+ val = config.shell_default ? config.shell_default : bourne_shell;
+ break;
+ case _UC_DEFAULTGROUP:
+ val = config.default_group ? config.default_group : "";
+ break;
+ case _UC_EXTRAGROUPS:
+ extendarray(&config.groups, &config.numgroups, 200);
+ for (j = k = 0; j < config.numgroups && config.groups[j] != NULL; j++) {
+ char lbuf[64];
+ int l = snprintf(lbuf, sizeof lbuf, "%s\"%s\"", k ? "," : "", config.groups[j]);
+ if (l < 0)
+ l = 0;
+ if (l + k + 1 < len || extendline(&buf, &len, len + 1024) != -1) {
+ strcpy(buf + k, lbuf);
+ k += l;
+ }
+ }
+ quote = 0;
+ break;
+ case _UC_DEFAULTCLASS:
+ val = config.default_class ? config.default_class : "";
+ break;
+ case _UC_MINUID:
+ sprintf(buf, "%lu", (unsigned long) config.min_uid);
+ quote = 0;
+ break;
+ case _UC_MAXUID:
+ sprintf(buf, "%lu", (unsigned long) config.max_uid);
+ quote = 0;
+ break;
+ case _UC_MINGID:
+ sprintf(buf, "%lu", (unsigned long) config.min_gid);
+ quote = 0;
+ break;
+ case _UC_MAXGID:
+ sprintf(buf, "%lu", (unsigned long) config.max_gid);
+ quote = 0;
+ break;
+ case _UC_EXPIRE:
+ sprintf(buf, "%d", config.expire_days);
+ quote = 0;
+ break;
+ case _UC_PASSWORD:
+ sprintf(buf, "%d", config.password_days);
+ quote = 0;
+ break;
+ case _UC_NONE:
+ break;
+ }
+
+ if (comments[i])
+ fputs(comments[i], fp);
+
+ if (*kwds[i]) {
+ if (quote)
+ fprintf(fp, "%s = \"%s\"\n", kwds[i], val);
+ else
+ fprintf(fp, "%s = %s\n", kwds[i], val);
+#if debugging
+ printf("WROTE: %s = %s\n", kwds[i], val);
+#endif
+ }
+ }
+ free(buf);
+ return fclose(fp) != EOF;
+ }
+ }
+ return 0;
+}
diff --git a/pw/pw_group.c b/pw/pw_group.c
new file mode 100644
index 0000000..a8f182c
--- /dev/null
+++ b/pw/pw_group.c
@@ -0,0 +1,423 @@
+/*-
+ * 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 "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) {
+ int buflen = 0;
+ char *buf = NULL;
+
+ fmtgrent(&buf, &buflen, 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..fc85828
--- /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];
+ 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..74a3ed0
--- /dev/null
+++ b/pw/pw_nis.c
@@ -0,0 +1,72 @@
+/*-
+ * 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 "pw.h"
+
+static int
+pw_nisupdate(const char * path, struct passwd * pwd, char const * user, int mode)
+{
+ char pfx[32];
+ char pwbuf[PWBUFSZ];
+ int l = sprintf(pfx, "%s:", user);
+
+ /*
+ * Update the passwd file first
+ */
+ if (pwd == NULL)
+ *pwbuf = '\0';
+ else
+ fmtpwentry(pwbuf, pwd, PWF_MASTER);
+ return fileupdate(path, 0600, pwbuf, pfx, l, mode) != 0;
+}
+
+int
+addnispwent(const char *path, struct passwd * pwd)
+{
+ return pw_nisupdate(path, pwd, pwd->pw_name, UPD_CREATE);
+}
+
+int
+chgnispwent(const char *path, char const * login, struct passwd * pwd)
+{
+ return pw_nisupdate(path, pwd, login, UPD_REPLACE);
+}
+
+int
+delnispwent(const char *path, const char *login)
+{
+ return pw_nisupdate(path, NULL, login, UPD_DELETE);
+}
diff --git a/pw/pw_user.c b/pw/pw_user.c
new file mode 100644
index 0000000..4c62fe8
--- /dev/null
+++ b/pw/pw_user.c
@@ -0,0 +1,1279 @@
+/*-
+ * 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 <fcntl.h>
+#include <sys/param.h>
+#include <dirent.h>
+#include <paths.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <utmp.h>
+#include <login_cap.h>
+#include "pw.h"
+#include "bitmap.h"
+
+#if (MAXLOGNAME-1) > UT_NAMESIZE
+#define LOGNAMESIZE UT_NAMESIZE
+#else
+#define LOGNAMESIZE (MAXLOGNAME-1)
+#endif
+
+static char locked_str[] = "*LOCKED*";
+
+static int print_user(struct passwd * pwd, int pretty, int v7);
+static uid_t pw_uidpolicy(struct userconf * cnf, struct cargs * args);
+static uid_t pw_gidpolicy(struct userconf * cnf, struct cargs * args, char *nam, gid_t prefer);
+static time_t pw_pwdpolicy(struct userconf * cnf, struct cargs * args);
+static time_t pw_exppolicy(struct userconf * cnf, struct cargs * args);
+static char *pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user);
+static char *pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell);
+static char *pw_password(struct userconf * cnf, struct cargs * args, char const * user);
+static char *shell_path(char const * path, char *shells[], char *sh);
+static void rmat(uid_t uid);
+static void rmopie(char const * name);
+
+/*-
+ * -C config configuration file
+ * -q quiet operation
+ * -n name login name
+ * -u uid user id
+ * -c comment user name/comment
+ * -d directory home directory
+ * -e date account expiry date
+ * -p date password expiry date
+ * -g grp primary group
+ * -G grp1,grp2 additional groups
+ * -m [ -k dir ] create and set up home
+ * -s shell name of login shell
+ * -o duplicate uid ok
+ * -L class user class
+ * -l name new login name
+ * -h fd password filehandle
+ * -H fd encrypted password filehandle
+ * -F force print or add
+ * Setting defaults:
+ * -D set user defaults
+ * -b dir default home root dir
+ * -e period default expiry period
+ * -p period default password change period
+ * -g group default group
+ * -G grp1,grp2.. default additional groups
+ * -L class default login class
+ * -k dir default home skeleton
+ * -s shell default shell
+ * -w method default password method
+ */
+
+int
+pw_user(struct userconf * cnf, int mode, struct cargs * args)
+{
+ int rc, edited = 0;
+ char *p = NULL;
+ char *passtmp;
+ struct carg *a_name;
+ struct carg *a_uid;
+ struct carg *arg;
+ struct passwd *pwd = NULL;
+ struct group *grp;
+ struct stat st;
+ char line[_PASSWORD_LEN+1];
+ FILE *fp;
+ mode_t dmode;
+ char *dmode_c;
+ void *set = NULL;
+
+ static struct passwd fakeuser =
+ {
+ NULL,
+ "*",
+ -1,
+ -1,
+ 0,
+ "",
+ "User &",
+ "/nonexistent",
+ "/bin/sh",
+ 0
+#if defined(__FreeBSD__)
+ ,0
+#endif
+ };
+
+
+ /*
+ * With M_NEXT, we only need to return the
+ * next uid to stdout
+ */
+ if (mode == M_NEXT)
+ {
+ uid_t next = pw_uidpolicy(cnf, args);
+ if (getarg(args, 'q'))
+ return next;
+ printf("%ld:", (long)next);
+ pw_group(cnf, mode, args);
+ return EXIT_SUCCESS;
+ }
+
+ /*
+ * We can do all of the common legwork here
+ */
+
+ if ((arg = getarg(args, 'b')) != NULL) {
+ cnf->home = arg->val;
+ }
+
+ if ((arg = getarg(args, 'M')) != NULL) {
+ dmode_c = arg->val;
+ if ((set = setmode(dmode_c)) == NULL)
+ errx(EX_DATAERR, "invalid directory creation mode '%s'",
+ dmode_c);
+ dmode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO);
+ free(set);
+ cnf->homemode = dmode;
+ }
+
+ /*
+ * If we'll need to use it or we're updating it,
+ * then create the base home directory if necessary
+ */
+ if (arg != NULL || getarg(args, 'm') != NULL) {
+ int l = strlen(cnf->home);
+
+ if (l > 1 && cnf->home[l-1] == '/') /* Shave off any trailing path delimiter */
+ cnf->home[--l] = '\0';
+
+ if (l < 2 || *cnf->home != '/') /* Check for absolute path name */
+ errx(EX_DATAERR, "invalid base directory for home '%s'", cnf->home);
+
+ if (stat(cnf->home, &st) == -1) {
+ char dbuf[MAXPATHLEN];
+
+ /*
+ * This is a kludge especially for Joerg :)
+ * If the home directory would be created in the root partition, then
+ * we really create it under /usr which is likely to have more space.
+ * But we create a symlink from cnf->home -> "/usr" -> cnf->home
+ */
+ if (strchr(cnf->home+1, '/') == NULL) {
+ strcpy(dbuf, "/usr");
+ strncat(dbuf, cnf->home, MAXPATHLEN-5);
+ if (mkdir(dbuf, cnf->homemode) != -1 || errno == EEXIST) {
+ chown(dbuf, 0, 0);
+ /*
+ * Skip first "/" and create symlink:
+ * /home -> usr/home
+ */
+ symlink(dbuf+1, cnf->home);
+ }
+ /* If this falls, fall back to old method */
+ }
+ strlcpy(dbuf, cnf->home, sizeof(dbuf));
+ p = dbuf;
+ if (stat(dbuf, &st) == -1) {
+ while ((p = strchr(++p, '/')) != NULL) {
+ *p = '\0';
+ if (stat(dbuf, &st) == -1) {
+ if (mkdir(dbuf, cnf->homemode) == -1)
+ goto direrr;
+ chown(dbuf, 0, 0);
+ } else if (!S_ISDIR(st.st_mode))
+ errx(EX_OSFILE, "'%s' (root home parent) is not a directory", dbuf);
+ *p = '/';
+ }
+ }
+ if (stat(dbuf, &st) == -1) {
+ if (mkdir(dbuf, cnf->homemode) == -1) {
+ direrr: err(EX_OSFILE, "mkdir '%s'", dbuf);
+ }
+ chown(dbuf, 0, 0);
+ }
+ } else if (!S_ISDIR(st.st_mode))
+ errx(EX_OSFILE, "root home `%s' is not a directory", cnf->home);
+ }
+
+ if ((arg = getarg(args, 'e')) != NULL)
+ cnf->expire_days = atoi(arg->val);
+
+ if ((arg = getarg(args, 'y')) != NULL)
+ cnf->nispasswd = arg->val;
+
+ if ((arg = getarg(args, 'p')) != NULL && arg->val)
+ cnf->password_days = atoi(arg->val);
+
+ if ((arg = getarg(args, 'g')) != NULL) {
+ if (!*(p = arg->val)) /* Handle empty group list specially */
+ cnf->default_group = "";
+ else {
+ if ((grp = GETGRNAM(p)) == NULL) {
+ if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL)
+ errx(EX_NOUSER, "group `%s' does not exist", p);
+ }
+ cnf->default_group = newstr(grp->gr_name);
+ }
+ }
+ if ((arg = getarg(args, 'L')) != NULL)
+ cnf->default_class = pw_checkname((u_char *)arg->val, 0);
+
+ if ((arg = getarg(args, 'G')) != NULL && arg->val) {
+ int i = 0;
+
+ for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
+ if ((grp = GETGRNAM(p)) == NULL) {
+ if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL)
+ errx(EX_NOUSER, "group `%s' does not exist", p);
+ }
+ if (extendarray(&cnf->groups, &cnf->numgroups, i + 2) != -1)
+ cnf->groups[i++] = newstr(grp->gr_name);
+ }
+ while (i < cnf->numgroups)
+ cnf->groups[i++] = NULL;
+ }
+
+ if ((arg = getarg(args, 'k')) != NULL) {
+ if (stat(cnf->dotdir = arg->val, &st) == -1 || !S_ISDIR(st.st_mode))
+ errx(EX_OSFILE, "skeleton `%s' is not a directory or does not exist", cnf->dotdir);
+ }
+
+ if ((arg = getarg(args, 's')) != NULL)
+ cnf->shell_default = arg->val;
+
+ if ((arg = getarg(args, 'w')) != NULL)
+ cnf->default_password = boolean_val(arg->val, cnf->default_password);
+ if (mode == M_ADD && getarg(args, 'D')) {
+ if (getarg(args, 'n') != NULL)
+ errx(EX_DATAERR, "can't combine `-D' with `-n name'");
+ if ((arg = getarg(args, 'u')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) {
+ if ((cnf->min_uid = (uid_t) atoi(p)) == 0)
+ cnf->min_uid = 1000;
+ if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_uid = (uid_t) atoi(p)) < cnf->min_uid)
+ cnf->max_uid = 32000;
+ }
+ if ((arg = getarg(args, 'i')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) {
+ if ((cnf->min_gid = (gid_t) atoi(p)) == 0)
+ cnf->min_gid = 1000;
+ if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_gid = (gid_t) atoi(p)) < cnf->min_gid)
+ cnf->max_gid = 32000;
+ }
+
+ arg = getarg(args, 'C');
+ if (write_userconfig(arg ? arg->val : NULL))
+ return EXIT_SUCCESS;
+ warn("config update");
+ return EX_IOERR;
+ }
+
+ 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);
+ ENDPWENT();
+ return EXIT_SUCCESS;
+ }
+
+ if ((a_name = getarg(args, 'n')) != NULL)
+ pwd = GETPWNAM(pw_checkname((u_char *)a_name->val, 0));
+ a_uid = getarg(args, 'u');
+
+ if (a_uid == NULL) {
+ if (a_name == NULL)
+ errx(EX_DATAERR, "user name or id required");
+
+ /*
+ * Determine whether 'n' switch is name or uid - we don't
+ * really don't really care which we have, but we need to
+ * know.
+ */
+ if (mode != M_ADD && pwd == NULL
+ && strspn(a_name->val, "0123456789") == strlen(a_name->val)
+ && atoi(a_name->val) > 0) { /* Assume uid */
+ (a_uid = a_name)->ch = 'u';
+ a_name = NULL;
+ }
+ }
+
+ /*
+ * Update, delete & print require that the user exists
+ */
+ if (mode == M_UPDATE || mode == M_DELETE ||
+ mode == M_PRINT || mode == M_LOCK || mode == M_UNLOCK) {
+
+ if (a_name == NULL && pwd == NULL) /* Try harder */
+ pwd = GETPWUID(atoi(a_uid->val));
+
+ if (pwd == NULL) {
+ if (mode == M_PRINT && getarg(args, 'F')) {
+ fakeuser.pw_name = a_name ? a_name->val : "nouser";
+ fakeuser.pw_uid = a_uid ? (uid_t) atol(a_uid->val) : -1;
+ return print_user(&fakeuser,
+ getarg(args, 'P') != NULL,
+ getarg(args, '7') != NULL);
+ }
+ if (a_name == NULL)
+ errx(EX_NOUSER, "no such uid `%s'", a_uid->val);
+ errx(EX_NOUSER, "no such user `%s'", a_name->val);
+ }
+
+ if (a_name == NULL) /* May be needed later */
+ a_name = addarg(args, 'n', newstr(pwd->pw_name));
+
+ /*
+ * The M_LOCK and M_UNLOCK functions simply add or remove
+ * a "*LOCKED*" prefix from in front of the password to
+ * prevent it decoding correctly, and therefore prevents
+ * access. Of course, this only prevents access via
+ * password authentication (not ssh, kerberos or any
+ * other method that does not use the UNIX password) but
+ * that is a known limitation.
+ */
+
+ if (mode == M_LOCK) {
+ if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) == 0)
+ errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name);
+ passtmp = malloc(strlen(pwd->pw_passwd) + sizeof(locked_str));
+ if (passtmp == NULL) /* disaster */
+ errx(EX_UNAVAILABLE, "out of memory");
+ strcpy(passtmp, locked_str);
+ strcat(passtmp, pwd->pw_passwd);
+ pwd->pw_passwd = passtmp;
+ edited = 1;
+ } else if (mode == M_UNLOCK) {
+ if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) != 0)
+ errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name);
+ pwd->pw_passwd += sizeof(locked_str)-1;
+ edited = 1;
+ } else if (mode == M_DELETE) {
+ /*
+ * Handle deletions now
+ */
+ char file[MAXPATHLEN];
+ char home[MAXPATHLEN];
+ uid_t uid = pwd->pw_uid;
+
+ if (strcmp(pwd->pw_name, "root") == 0)
+ errx(EX_DATAERR, "cannot remove user 'root'");
+
+ if (!PWALTDIR()) {
+ /*
+ * Remove opie record from /etc/opiekeys
+ */
+
+ rmopie(pwd->pw_name);
+
+ /*
+ * Remove crontabs
+ */
+ sprintf(file, "/var/cron/tabs/%s", pwd->pw_name);
+ if (access(file, F_OK) == 0) {
+ sprintf(file, "crontab -u %s -r", pwd->pw_name);
+ system(file);
+ }
+ }
+ /*
+ * Save these for later, since contents of pwd may be
+ * invalidated by deletion
+ */
+ sprintf(file, "%s/%s", _PATH_MAILDIR, pwd->pw_name);
+ strlcpy(home, pwd->pw_dir, sizeof(home));
+
+ rc = delpwent(pwd);
+ if (rc == -1)
+ err(EX_IOERR, "user '%s' does not exist", pwd->pw_name);
+ else if (rc != 0) {
+ warn("passwd update");
+ return EX_IOERR;
+ }
+
+ if (cnf->nispasswd && *cnf->nispasswd=='/') {
+ rc = delnispwent(cnf->nispasswd, a_name->val);
+ if (rc == -1)
+ warnx("WARNING: user '%s' does not exist in NIS passwd", pwd->pw_name);
+ else if (rc != 0)
+ warn("WARNING: NIS passwd update");
+ /* non-fatal */
+ }
+
+ editgroups(a_name->val, NULL);
+
+ pw_log(cnf, mode, W_USER, "%s(%ld) account removed", a_name->val, (long) uid);
+
+ if (!PWALTDIR()) {
+ /*
+ * Remove mail file
+ */
+ remove(file);
+
+ /*
+ * Remove at jobs
+ */
+ if (getpwuid(uid) == NULL)
+ rmat(uid);
+
+ /*
+ * Remove home directory and contents
+ */
+ if (getarg(args, 'r') != NULL && *home == '/' && getpwuid(uid) == NULL) {
+ if (stat(home, &st) != -1) {
+ rm_r(home, uid);
+ pw_log(cnf, mode, W_USER, "%s(%ld) home '%s' %sremoved",
+ a_name->val, (long) uid, home,
+ stat(home, &st) == -1 ? "" : "not completely ");
+ }
+ }
+ }
+ return EXIT_SUCCESS;
+ } else if (mode == M_PRINT)
+ return print_user(pwd,
+ getarg(args, 'P') != NULL,
+ getarg(args, '7') != NULL);
+
+ /*
+ * The rest is edit code
+ */
+ if ((arg = getarg(args, 'l')) != NULL) {
+ if (strcmp(pwd->pw_name, "root") == 0)
+ errx(EX_DATAERR, "can't rename `root' account");
+ pwd->pw_name = pw_checkname((u_char *)arg->val, 0);
+ edited = 1;
+ }
+
+ if ((arg = getarg(args, 'u')) != NULL && isdigit((unsigned char)*arg->val)) {
+ pwd->pw_uid = (uid_t) atol(arg->val);
+ edited = 1;
+ if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0)
+ errx(EX_DATAERR, "can't change uid of `root' account");
+ if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
+ warnx("WARNING: account `%s' will have a uid of 0 (superuser access!)", pwd->pw_name);
+ }
+
+ if ((arg = getarg(args, 'g')) != NULL && pwd->pw_uid != 0) { /* Already checked this */
+ gid_t newgid = (gid_t) GETGRNAM(cnf->default_group)->gr_gid;
+ if (newgid != pwd->pw_gid) {
+ edited = 1;
+ pwd->pw_gid = newgid;
+ }
+ }
+
+ if ((arg = getarg(args, 'p')) != NULL) {
+ if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) {
+ if (pwd->pw_change != 0) {
+ pwd->pw_change = 0;
+ edited = 1;
+ }
+ }
+ else {
+ time_t now = time(NULL);
+ time_t expire = parse_date(now, arg->val);
+
+ if (now == expire)
+ errx(EX_DATAERR, "invalid password change date `%s'", arg->val);
+ if (pwd->pw_change != expire) {
+ pwd->pw_change = expire;
+ edited = 1;
+ }
+ }
+ }
+
+ if ((arg = getarg(args, 'e')) != NULL) {
+ if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) {
+ if (pwd->pw_expire != 0) {
+ pwd->pw_expire = 0;
+ edited = 1;
+ }
+ }
+ else {
+ time_t now = time(NULL);
+ time_t expire = parse_date(now, arg->val);
+
+ if (now == expire)
+ errx(EX_DATAERR, "invalid account expiry date `%s'", arg->val);
+ if (pwd->pw_expire != expire) {
+ pwd->pw_expire = expire;
+ edited = 1;
+ }
+ }
+ }
+
+ if ((arg = getarg(args, 's')) != NULL) {
+ char *shell = shell_path(cnf->shelldir, cnf->shells, arg->val);
+ if (shell == NULL)
+ shell = "";
+ if (strcmp(shell, pwd->pw_shell) != 0) {
+ pwd->pw_shell = shell;
+ edited = 1;
+ }
+ }
+
+ if (getarg(args, 'L')) {
+ if (cnf->default_class == NULL)
+ cnf->default_class = "";
+ if (strcmp(pwd->pw_class, cnf->default_class) != 0) {
+ pwd->pw_class = cnf->default_class;
+ edited = 1;
+ }
+ }
+
+ if ((arg = getarg(args, 'd')) != NULL) {
+ if (strcmp(pwd->pw_dir, arg->val))
+ edited = 1;
+ if (stat(pwd->pw_dir = arg->val, &st) == -1) {
+ if (getarg(args, 'm') == NULL && strcmp(pwd->pw_dir, "/nonexistent") != 0)
+ warnx("WARNING: home `%s' does not exist", pwd->pw_dir);
+ } else if (!S_ISDIR(st.st_mode))
+ warnx("WARNING: home `%s' is not a directory", pwd->pw_dir);
+ }
+
+ if ((arg = getarg(args, 'w')) != NULL &&
+ getarg(args, 'h') == NULL && getarg(args, 'H') == NULL) {
+ login_cap_t *lc;
+
+ lc = login_getpwclass(pwd);
+ if (lc == NULL ||
+ login_setcryptfmt(lc, "md5", NULL) == NULL)
+ warn("setting crypt(3) format");
+ login_close(lc);
+ pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name);
+ edited = 1;
+ }
+
+ } else {
+ login_cap_t *lc;
+
+ /*
+ * Add code
+ */
+
+ if (a_name == NULL) /* Required */
+ errx(EX_DATAERR, "login name required");
+ else if ((pwd = GETPWNAM(a_name->val)) != NULL) /* Exists */
+ errx(EX_DATAERR, "login name `%s' already exists", a_name->val);
+
+ /*
+ * Now, set up defaults for a new user
+ */
+ pwd = &fakeuser;
+ pwd->pw_name = a_name->val;
+ pwd->pw_class = cnf->default_class ? cnf->default_class : "";
+ pwd->pw_uid = pw_uidpolicy(cnf, args);
+ pwd->pw_gid = pw_gidpolicy(cnf, args, pwd->pw_name, (gid_t) pwd->pw_uid);
+ pwd->pw_change = pw_pwdpolicy(cnf, args);
+ pwd->pw_expire = pw_exppolicy(cnf, args);
+ pwd->pw_dir = pw_homepolicy(cnf, args, pwd->pw_name);
+ pwd->pw_shell = pw_shellpolicy(cnf, args, NULL);
+ lc = login_getpwclass(pwd);
+ if (lc == NULL || login_setcryptfmt(lc, "md5", NULL) == NULL)
+ warn("setting crypt(3) format");
+ login_close(lc);
+ pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name);
+ edited = 1;
+
+ if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
+ warnx("WARNING: new account `%s' has a uid of 0 (superuser access!)", pwd->pw_name);
+ }
+
+ /*
+ * Shared add/edit code
+ */
+ if ((arg = getarg(args, 'c')) != NULL) {
+ char *gecos = pw_checkname((u_char *)arg->val, 1);
+ if (strcmp(pwd->pw_gecos, gecos) != 0) {
+ pwd->pw_gecos = gecos;
+ edited = 1;
+ }
+ }
+
+ if ((arg = getarg(args, 'h')) != NULL ||
+ (arg = getarg(args, 'H')) != NULL) {
+ if (strcmp(arg->val, "-") == 0) {
+ if (!pwd->pw_passwd || *pwd->pw_passwd != '*') {
+ pwd->pw_passwd = "*"; /* No access */
+ edited = 1;
+ }
+ } else {
+ int fd = atoi(arg->val);
+ int precrypt = (arg->ch == 'H');
+ int b;
+ int istty = isatty(fd);
+ struct termios t;
+ login_cap_t *lc;
+
+ 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("%s%spassword for user %s:",
+ (mode == M_UPDATE) ? "new " : "",
+ precrypt ? "encrypted " : "",
+ pwd->pw_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("-%c file descriptor", precrypt ? 'H' :
+ 'h');
+ return EX_IOERR;
+ }
+ line[b] = '\0';
+ if ((p = strpbrk(line, "\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;
+ pwd->pw_passwd = line;
+ } else {
+ lc = login_getpwclass(pwd);
+ if (lc == NULL ||
+ login_setcryptfmt(lc, "md5", NULL) == NULL)
+ warn("setting crypt(3) format");
+ login_close(lc);
+ pwd->pw_passwd = pw_pwcrypt(line);
+ }
+ edited = 1;
+ }
+ }
+
+ /*
+ * Special case: -N only displays & exits
+ */
+ if (getarg(args, 'N') != NULL)
+ return print_user(pwd,
+ getarg(args, 'P') != NULL,
+ getarg(args, '7') != NULL);
+
+ if (mode == M_ADD) {
+ edited = 1; /* Always */
+ rc = addpwent(pwd);
+ if (rc == -1) {
+ warnx("user '%s' already exists", pwd->pw_name);
+ return EX_IOERR;
+ } else if (rc != 0) {
+ warn("passwd file update");
+ return EX_IOERR;
+ }
+ if (cnf->nispasswd && *cnf->nispasswd=='/') {
+ rc = addnispwent(cnf->nispasswd, pwd);
+ if (rc == -1)
+ warnx("User '%s' already exists in NIS passwd", pwd->pw_name);
+ else
+ warn("NIS passwd update");
+ /* NOTE: we treat NIS-only update errors as non-fatal */
+ }
+ } else if (mode == M_UPDATE || mode == M_LOCK || mode == M_UNLOCK) {
+ if (edited) { /* Only updated this if required */
+ rc = chgpwent(a_name->val, pwd);
+ if (rc == -1) {
+ warnx("user '%s' does not exist (NIS?)", pwd->pw_name);
+ return EX_IOERR;
+ } else if (rc != 0) {
+ warn("passwd file update");
+ return EX_IOERR;
+ }
+ if ( cnf->nispasswd && *cnf->nispasswd=='/') {
+ rc = chgnispwent(cnf->nispasswd, a_name->val, pwd);
+ if (rc == -1)
+ warn("User '%s' not found in NIS passwd", pwd->pw_name);
+ else
+ warn("NIS passwd update");
+ /* NOTE: NIS-only update errors are not fatal */
+ }
+ }
+ }
+
+ /*
+ * Ok, user is created or changed - now edit group file
+ */
+
+ if (mode == M_ADD || getarg(args, 'G') != NULL)
+ editgroups(pwd->pw_name, cnf->groups);
+
+ /* go get a current version of pwd */
+ pwd = GETPWNAM(a_name->val);
+ if (pwd == NULL) {
+ /* This will fail when we rename, so special case that */
+ if (mode == M_UPDATE && (arg = getarg(args, 'l')) != NULL) {
+ a_name->val = arg->val; /* update new name */
+ pwd = GETPWNAM(a_name->val); /* refetch renamed rec */
+ }
+ }
+ if (pwd == NULL) /* can't go on without this */
+ errx(EX_NOUSER, "user '%s' disappeared during update", a_name->val);
+
+ grp = GETGRGID(pwd->pw_gid);
+ pw_log(cnf, mode, W_USER, "%s(%ld):%s(%ld):%s:%s:%s",
+ pwd->pw_name, (long) pwd->pw_uid,
+ grp ? grp->gr_name : "unknown", (long) (grp ? grp->gr_gid : -1),
+ pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
+
+ /*
+ * If adding, let's touch and chown the user's mail file. This is not
+ * strictly necessary under BSD with a 0755 maildir but it also
+ * doesn't hurt anything to create the empty mailfile
+ */
+ if (mode == M_ADD) {
+ if (!PWALTDIR()) {
+ sprintf(line, "%s/%s", _PATH_MAILDIR, pwd->pw_name);
+ close(open(line, O_RDWR | O_CREAT, 0600)); /* Preserve contents &
+ * mtime */
+ chown(line, pwd->pw_uid, pwd->pw_gid);
+ }
+ }
+
+ /*
+ * Let's create and populate the user's home directory. Note
+ * that this also `works' for editing users if -m is used, but
+ * existing files will *not* be overwritten.
+ */
+ if (!PWALTDIR() && getarg(args, 'm') != NULL && pwd->pw_dir && *pwd->pw_dir == '/' && pwd->pw_dir[1]) {
+ copymkdir(pwd->pw_dir, cnf->dotdir, cnf->homemode, pwd->pw_uid, pwd->pw_gid);
+ pw_log(cnf, mode, W_USER, "%s(%ld) home %s made",
+ pwd->pw_name, (long) pwd->pw_uid, pwd->pw_dir);
+ }
+
+
+ /*
+ * Finally, send mail to the new user as well, if we are asked to
+ */
+ if (mode == M_ADD && !PWALTDIR() && cnf->newmail && *cnf->newmail && (fp = fopen(cnf->newmail, "r")) != NULL) {
+ FILE *pfp = popen(_PATH_SENDMAIL " -t", "w");
+
+ if (pfp == NULL)
+ warn("sendmail");
+ else {
+ fprintf(pfp, "From: root\n" "To: %s\n" "Subject: Welcome!\n\n", pwd->pw_name);
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ /* Do substitutions? */
+ fputs(line, pfp);
+ }
+ pclose(pfp);
+ pw_log(cnf, mode, W_USER, "%s(%ld) new user mail sent",
+ pwd->pw_name, (long) pwd->pw_uid);
+ }
+ fclose(fp);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+
+static uid_t
+pw_uidpolicy(struct userconf * cnf, struct cargs * args)
+{
+ struct passwd *pwd;
+ uid_t uid = (uid_t) - 1;
+ struct carg *a_uid = getarg(args, 'u');
+
+ /*
+ * Check the given uid, if any
+ */
+ if (a_uid != NULL) {
+ uid = (uid_t) atol(a_uid->val);
+
+ if ((pwd = GETPWUID(uid)) != NULL && getarg(args, 'o') == NULL)
+ errx(EX_DATAERR, "uid `%ld' has already been allocated", (long) pwd->pw_uid);
+ } else {
+ struct bitmap bm;
+
+ /*
+ * We need to allocate the next available uid under one of
+ * two policies a) Grab the first unused uid b) Grab the
+ * highest possible unused uid
+ */
+ if (cnf->min_uid >= cnf->max_uid) { /* Sanity
+ * claus^H^H^H^Hheck */
+ cnf->min_uid = 1000;
+ cnf->max_uid = 32000;
+ }
+ bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1);
+
+ /*
+ * Now, let's fill the bitmap from the password file
+ */
+ SETPWENT();
+ while ((pwd = GETPWENT()) != NULL)
+ if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid)
+ bm_setbit(&bm, pwd->pw_uid - cnf->min_uid);
+ ENDPWENT();
+
+ /*
+ * Then apply the policy, with fallback to reuse if necessary
+ */
+ if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid)
+ uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid);
+
+ /*
+ * Another sanity check
+ */
+ if (uid < cnf->min_uid || uid > cnf->max_uid)
+ errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used");
+ bm_dealloc(&bm);
+ }
+ return uid;
+}
+
+
+static uid_t
+pw_gidpolicy(struct userconf * cnf, struct cargs * args, char *nam, gid_t prefer)
+{
+ struct group *grp;
+ gid_t gid = (uid_t) - 1;
+ struct carg *a_gid = getarg(args, 'g');
+
+ /*
+ * If no arg given, see if default can help out
+ */
+ if (a_gid == NULL && cnf->default_group && *cnf->default_group)
+ a_gid = addarg(args, 'g', cnf->default_group);
+
+ /*
+ * Check the given gid, if any
+ */
+ SETGRENT();
+ if (a_gid != NULL) {
+ if ((grp = GETGRNAM(a_gid->val)) == NULL) {
+ gid = (gid_t) atol(a_gid->val);
+ if ((gid == 0 && !isdigit((unsigned char)*a_gid->val)) || (grp = GETGRGID(gid)) == NULL)
+ errx(EX_NOUSER, "group `%s' is not defined", a_gid->val);
+ }
+ gid = grp->gr_gid;
+ } else if ((grp = GETGRNAM(nam)) != NULL && grp->gr_mem[0] == NULL) {
+ gid = grp->gr_gid; /* Already created? Use it anyway... */
+ } else {
+ struct cargs grpargs;
+ char tmp[32];
+
+ LIST_INIT(&grpargs);
+ addarg(&grpargs, 'n', nam);
+
+ /*
+ * We need to auto-create a group with the user's name. We
+ * can send all the appropriate output to our sister routine
+ * bit first see if we can create a group with gid==uid so we
+ * can keep the user and group ids in sync. We purposely do
+ * NOT check the gid range if we can force the sync. If the
+ * user's name dups an existing group, then the group add
+ * function will happily handle that case for us and exit.
+ */
+ if (GETGRGID(prefer) == NULL) {
+ sprintf(tmp, "%lu", (unsigned long) prefer);
+ addarg(&grpargs, 'g', tmp);
+ }
+ if (getarg(args, 'N'))
+ {
+ addarg(&grpargs, 'N', NULL);
+ addarg(&grpargs, 'q', NULL);
+ gid = pw_group(cnf, M_NEXT, &grpargs);
+ }
+ else
+ {
+ pw_group(cnf, M_ADD, &grpargs);
+ if ((grp = GETGRNAM(nam)) != NULL)
+ gid = grp->gr_gid;
+ }
+ a_gid = LIST_FIRST(&grpargs);
+ while (a_gid != NULL) {
+ struct carg *t = LIST_NEXT(a_gid, list);
+ LIST_REMOVE(a_gid, list);
+ a_gid = t;
+ }
+ }
+ ENDGRENT();
+ return gid;
+}
+
+
+static time_t
+pw_pwdpolicy(struct userconf * cnf, struct cargs * args)
+{
+ time_t result = 0;
+ time_t now = time(NULL);
+ struct carg *arg = getarg(args, 'p');
+
+ if (arg != NULL) {
+ if ((result = parse_date(now, arg->val)) == now)
+ errx(EX_DATAERR, "invalid date/time `%s'", arg->val);
+ } else if (cnf->password_days > 0)
+ result = now + ((long) cnf->password_days * 86400L);
+ return result;
+}
+
+
+static time_t
+pw_exppolicy(struct userconf * cnf, struct cargs * args)
+{
+ time_t result = 0;
+ time_t now = time(NULL);
+ struct carg *arg = getarg(args, 'e');
+
+ if (arg != NULL) {
+ if ((result = parse_date(now, arg->val)) == now)
+ errx(EX_DATAERR, "invalid date/time `%s'", arg->val);
+ } else if (cnf->expire_days > 0)
+ result = now + ((long) cnf->expire_days * 86400L);
+ return result;
+}
+
+
+static char *
+pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user)
+{
+ struct carg *arg = getarg(args, 'd');
+
+ if (arg)
+ return arg->val;
+ else {
+ static char home[128];
+
+ if (cnf->home == NULL || *cnf->home == '\0')
+ errx(EX_CONFIG, "no base home directory set");
+ sprintf(home, "%s/%s", cnf->home, user);
+ return home;
+ }
+}
+
+static char *
+shell_path(char const * path, char *shells[], char *sh)
+{
+ if (sh != NULL && (*sh == '/' || *sh == '\0'))
+ return sh; /* specified full path or forced none */
+ else {
+ char *p;
+ char paths[_UC_MAXLINE];
+
+ /*
+ * We need to search paths
+ */
+ strlcpy(paths, path, sizeof(paths));
+ for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) {
+ int i;
+ static char shellpath[256];
+
+ if (sh != NULL) {
+ sprintf(shellpath, "%s/%s", p, sh);
+ if (access(shellpath, X_OK) == 0)
+ return shellpath;
+ } else
+ for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) {
+ sprintf(shellpath, "%s/%s", p, shells[i]);
+ if (access(shellpath, X_OK) == 0)
+ return shellpath;
+ }
+ }
+ if (sh == NULL)
+ errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh);
+ errx(EX_CONFIG, "no default shell available or defined");
+ return NULL;
+ }
+}
+
+
+static char *
+pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell)
+{
+ char *sh = newshell;
+ struct carg *arg = getarg(args, 's');
+
+ if (newshell == NULL && arg != NULL)
+ sh = arg->val;
+ return shell_path(cnf->shelldir, cnf->shells, sh ? sh : cnf->shell_default);
+}
+
+#define SALTSIZE 32
+
+static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
+
+char *
+pw_pwcrypt(char *password)
+{
+ int i;
+ char salt[SALTSIZE + 1];
+
+ static char buf[256];
+
+ /*
+ * Calculate a salt value
+ */
+ for (i = 0; i < SALTSIZE; i++)
+ salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)];
+ salt[SALTSIZE] = '\0';
+
+ return strcpy(buf, crypt(password, salt));
+}
+
+
+static char *
+pw_password(struct userconf * cnf, struct cargs * args, char const * user)
+{
+ int i, l;
+ char pwbuf[32];
+
+ switch (cnf->default_password) {
+ case -1: /* Random password */
+ l = (arc4random() % 8 + 8); /* 8 - 16 chars */
+ for (i = 0; i < l; i++)
+ pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)];
+ pwbuf[i] = '\0';
+
+ /*
+ * We give this information back to the user
+ */
+ if (getarg(args, 'h') == NULL && getarg(args, 'H') == NULL &&
+ getarg(args, 'N') == NULL) {
+ if (isatty(STDOUT_FILENO))
+ printf("Password for '%s' is: ", user);
+ printf("%s\n", pwbuf);
+ fflush(stdout);
+ }
+ break;
+
+ case -2: /* No password at all! */
+ return "";
+
+ case 0: /* No login - default */
+ default:
+ return "*";
+
+ case 1: /* user's name */
+ strlcpy(pwbuf, user, sizeof(pwbuf));
+ break;
+ }
+ return pw_pwcrypt(pwbuf);
+}
+
+
+static int
+print_user(struct passwd * pwd, int pretty, int v7)
+{
+ if (!pretty) {
+ char buf[_UC_MAXLINE];
+
+ fmtpwentry(buf, pwd, v7 ? PWF_PASSWD : PWF_STANDARD);
+ fputs(buf, stdout);
+ } else {
+ int j;
+ char *p;
+ struct group *grp = GETGRGID(pwd->pw_gid);
+ char uname[60] = "User &", office[60] = "[None]",
+ wphone[60] = "[None]", hphone[60] = "[None]";
+ char acexpire[32] = "[None]", pwexpire[32] = "[None]";
+ struct tm * tptr;
+
+ if ((p = strtok(pwd->pw_gecos, ",")) != NULL) {
+ strlcpy(uname, p, sizeof(uname));
+ if ((p = strtok(NULL, ",")) != NULL) {
+ strlcpy(office, p, sizeof(office));
+ if ((p = strtok(NULL, ",")) != NULL) {
+ strlcpy(wphone, p, sizeof(wphone));
+ if ((p = strtok(NULL, "")) != NULL) {
+ strlcpy(hphone, p,
+ sizeof(hphone));
+ }
+ }
+ }
+ }
+ /*
+ * Handle '&' in gecos field
+ */
+ if ((p = strchr(uname, '&')) != NULL) {
+ int l = strlen(pwd->pw_name);
+ int m = strlen(p);
+
+ memmove(p + l, p + 1, m);
+ memmove(p, pwd->pw_name, l);
+ *p = (char) toupper((unsigned char)*p);
+ }
+ if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL)
+ strftime(acexpire, sizeof acexpire, "%c", tptr);
+ if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL)
+ strftime(pwexpire, sizeof pwexpire, "%c", tptr);
+ printf("Login Name: %-15s #%-12ld Group: %-15s #%ld\n"
+ " Full Name: %s\n"
+ " Home: %-26.26s Class: %s\n"
+ " Shell: %-26.26s Office: %s\n"
+ "Work Phone: %-26.26s Home Phone: %s\n"
+ "Acc Expire: %-26.26s Pwd Expire: %s\n",
+ pwd->pw_name, (long) pwd->pw_uid,
+ grp ? grp->gr_name : "(invalid)", (long) pwd->pw_gid,
+ uname, pwd->pw_dir, pwd->pw_class,
+ pwd->pw_shell, office, wphone, hphone,
+ acexpire, pwexpire);
+ SETGRENT();
+ j = 0;
+ while ((grp=GETGRENT()) != NULL)
+ {
+ int i = 0;
+ while (grp->gr_mem[i] != NULL)
+ {
+ if (strcmp(grp->gr_mem[i], pwd->pw_name)==0)
+ {
+ printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name);
+ break;
+ }
+ ++i;
+ }
+ }
+ ENDGRENT();
+ printf("%s", j ? "\n" : "");
+ }
+ return EXIT_SUCCESS;
+}
+
+char *
+pw_checkname(u_char *name, int gecos)
+{
+ char showch[8];
+ u_char const *badchars, *ch, *showtype;
+ int reject;
+
+ ch = name;
+ reject = 0;
+ if (gecos) {
+ /* See if the name is valid as a gecos (comment) field. */
+ badchars = ":!@";
+ showtype = "gecos field";
+ } else {
+ /* See if the name is valid as a userid or group. */
+ badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\"";
+ showtype = "userid/group name";
+ /* Userids and groups can not have a leading '-'. */
+ if (*ch == '-')
+ reject = 1;
+ }
+ if (!reject) {
+ while (*ch) {
+ if (strchr(badchars, *ch) != NULL || *ch < ' ' ||
+ *ch == 127) {
+ reject = 1;
+ break;
+ }
+ /* 8-bit characters are only allowed in GECOS fields */
+ if (!gecos && (*ch & 0x80)) {
+ reject = 1;
+ break;
+ }
+ ch++;
+ }
+ }
+ /*
+ * A `$' is allowed as the final character for userids and groups,
+ * mainly for the benefit of samba.
+ */
+ if (reject && !gecos) {
+ if (*ch == '$' && *(ch + 1) == '\0') {
+ reject = 0;
+ ch++;
+ }
+ }
+ if (reject) {
+ snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127)
+ ? "`%c'" : "0x%02x", *ch);
+ errx(EX_DATAERR, "invalid character %s at position %d in %s",
+ showch, (ch - name), showtype);
+ }
+ if (!gecos && (ch - name) > LOGNAMESIZE)
+ errx(EX_DATAERR, "name too long `%s' (max is %d)", name,
+ LOGNAMESIZE);
+ return (char *)name;
+}
+
+
+static void
+rmat(uid_t uid)
+{
+ DIR *d = opendir("/var/at/jobs");
+
+ if (d != NULL) {
+ struct dirent *e;
+
+ while ((e = readdir(d)) != NULL) {
+ struct stat st;
+
+ if (strncmp(e->d_name, ".lock", 5) != 0 &&
+ stat(e->d_name, &st) == 0 &&
+ !S_ISDIR(st.st_mode) &&
+ st.st_uid == uid) {
+ char tmp[MAXPATHLEN];
+
+ sprintf(tmp, "/usr/bin/atrm %s", e->d_name);
+ system(tmp);
+ }
+ }
+ closedir(d);
+ }
+}
+
+static void
+rmopie(char const * name)
+{
+ static const char etcopie[] = "/etc/opiekeys";
+ FILE *fp = fopen(etcopie, "r+");
+
+ if (fp != NULL) {
+ char tmp[1024];
+ off_t atofs = 0;
+ int length = strlen(name);
+
+ while (fgets(tmp, sizeof tmp, fp) != NULL) {
+ if (strncmp(name, tmp, length) == 0 && tmp[length]==' ') {
+ if (fseek(fp, atofs, SEEK_SET) == 0) {
+ fwrite("#", 1, 1, fp); /* Comment username out */
+ }
+ break;
+ }
+ atofs = ftell(fp);
+ }
+ /*
+ * If we got an error of any sort, don't update!
+ */
+ fclose(fp);
+ }
+}
+
diff --git a/pw/pw_vpw.c b/pw/pw_vpw.c
new file mode 100644
index 0000000..473cbb6
--- /dev/null
+++ b/pw/pw_vpw.c
@@ -0,0 +1,316 @@
+/*-
+ * 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);
+}
+
+int vpwdb(char *arg, ...)
+{
+ arg=arg;
+ return 0;
+}
+
+
+
+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;
+ 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);
+}
+
+int
+vgrdb(char *arg, ...)
+{
+ arg=arg;
+ return 0;
+}
+
diff --git a/pw/pwupd.c b/pw/pwupd.c
new file mode 100644
index 0000000..cb8456d
--- /dev/null
+++ b/pw/pwupd.c
@@ -0,0 +1,213 @@
+/*-
+ * 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 <errno.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;
+}
+
+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;
+ }
+ return i;
+}
+
+int
+fmtpwentry(char *buf, struct passwd * pwd, int type)
+{
+ int l;
+ char *pw;
+
+ pw = (type == PWF_MASTER) ?
+ ((pwd->pw_passwd == NULL) ? "" : pwd->pw_passwd) : "*";
+
+ if (type == PWF_PASSWD)
+ l = sprintf(buf, "%s:*:%ld:%ld:%s:%s:%s\n",
+ pwd->pw_name, (long) pwd->pw_uid, (long) pwd->pw_gid,
+ pwd->pw_gecos ? pwd->pw_gecos : "User &",
+ pwd->pw_dir, pwd->pw_shell);
+ else
+ l = sprintf(buf, "%s:%s:%ld:%ld:%s:%lu:%lu:%s:%s:%s\n",
+ pwd->pw_name, pw, (long) pwd->pw_uid, (long) pwd->pw_gid,
+ pwd->pw_class ? pwd->pw_class : "",
+ (unsigned long) pwd->pw_change,
+ (unsigned long) pwd->pw_expire,
+ pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
+ return l;
+}
+
+
+int
+fmtpwent(char *buf, struct passwd * pwd)
+{
+ return fmtpwentry(buf, pwd, PWF_STANDARD);
+}
+
+static int
+pw_update(struct passwd * pwd, char const * user, int mode)
+{
+ int rc = 0;
+
+ ENDPWENT();
+
+ /*
+ * 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
+ char pfx[PWBUFSZ];
+ char pwbuf[PWBUFSZ];
+ int l = snprintf(pfx, PWBUFSZ, "%s:", user);
+#ifdef HAVE_PWDB_U
+ int isrename = pwd!=NULL && strcmp(user, pwd->pw_name);
+#endif
+
+ /*
+ * Update the passwd file first
+ */
+ if (pwd == NULL)
+ *pwbuf = '\0';
+ else
+ fmtpwentry(pwbuf, pwd, PWF_PASSWD);
+
+ if (l < 0)
+ l = 0;
+ rc = fileupdate(getpwpath(_PASSWD), 0644, pwbuf, pfx, l, mode);
+ if (rc == 0) {
+
+ /*
+ * Then the master.passwd file
+ */
+ if (pwd != NULL)
+ fmtpwentry(pwbuf, pwd, PWF_MASTER);
+ rc = fileupdate(getpwpath(_MASTERPASSWD), 0600, pwbuf, pfx, l, mode);
+ if (rc == 0) {
+#ifdef HAVE_PWDB_U
+ if (mode == UPD_DELETE || isrename)
+#endif
+ rc = pwdb(NULL);
+#ifdef HAVE_PWDB_U
+ else
+ rc = pwdb("-u", user, (char *)NULL);
+#endif
+ }
+ }
+ }
+ return rc;
+}
+
+int
+addpwent(struct passwd * pwd)
+{
+ return pw_update(pwd, pwd->pw_name, UPD_CREATE);
+}
+
+int
+chgpwent(char const * login, struct passwd * pwd)
+{
+ return pw_update(pwd, login, UPD_REPLACE);
+}
+
+int
+delpwent(struct passwd * pwd)
+{
+ return pw_update(NULL, pwd->pw_name, UPD_DELETE);
+}
diff --git a/pw/pwupd.h b/pw/pwupd.h
new file mode 100644
index 0000000..7289065
--- /dev/null
+++ b/pw/pwupd.h
@@ -0,0 +1,160 @@
+/*-
+ * 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
+
+enum updtype
+{
+ UPD_DELETE = -1,
+ UPD_CREATE = 0,
+ UPD_REPLACE = 1
+};
+
+__BEGIN_DECLS
+int fileupdate(char const * fname, mode_t fm, char const * nline, char const * pfx, int pfxlen, int updmode);
+__END_DECLS
+
+enum pwdfmttype
+{
+ PWF_STANDARD, /* MASTER format but with '*' as password */
+ PWF_PASSWD, /* V7 format */
+ PWF_GROUP = PWF_PASSWD,
+ PWF_MASTER /* MASTER format with password */
+};
+
+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);
+ int (*_pwdb)(char *arg, ...);
+ RET_SETGRENT (*_setgrent)(void);
+ void (*_endgrent)(void);
+ struct group * (*_getgrent)(void);
+ struct group * (*_getgrgid)(gid_t gid);
+ struct group * (*_getgrnam)(const char * nam);
+ int (*_grdb)(char *arg, ...);
+};
+
+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 PWDB(args) PWF._pwdb(args)
+
+#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 GRDB(args) PWF._grdb(args)
+
+#define PWALTDIR() PWF._altdir
+#ifndef _PATH_PWD
+#define _PATH_PWD "/etc"
+#endif
+#ifndef _GROUP
+#define _GROUP "group"
+#endif
+#ifndef _PASSWD
+#define _PASSWD "passwd"
+#endif
+#ifndef _MASTERPASSWD
+#define _MASTERPASSWD "master.passwd"
+#endif
+#ifndef _GROUP
+#define _GROUP "group"
+#endif
+
+__BEGIN_DECLS
+int addpwent(struct passwd * pwd);
+int delpwent(struct passwd * pwd);
+int chgpwent(char const * login, struct passwd * pwd);
+int fmtpwent(char *buf, struct passwd * pwd);
+int fmtpwentry(char *buf, struct passwd * pwd, int type);
+
+int setpwdir(const char * dir);
+char * getpwpath(char const * file);
+int pwdb(char *arg, ...);
+
+int addgrent(struct group * grp);
+int delgrent(struct group * grp);
+int chggrent(char const * name, struct group * grp);
+int fmtgrent(char **buf, int * buflen, struct group * grp);
+int fmtgrentry(char **buf, int * buflen, struct group * grp, int type);
+int editgroups(char *name, char **groups);
+
+int setgrdir(const char * dir);
+char * getgrpath(const char *file);
+int grdb(char *arg, ...);
+
+void vsetpwent(void);
+void vendpwent(void);
+struct passwd * vgetpwent(void);
+struct passwd * vgetpwuid(uid_t uid);
+struct passwd * vgetpwnam(const char * nam);
+struct passwd * vgetpwent(void);
+int vpwdb(char *arg, ...);
+
+struct group * vgetgrent(void);
+struct group * vgetgrgid(gid_t gid);
+struct group * vgetgrnam(const char * nam);
+struct group * vgetgrent(void);
+int vgrdb(char *arg, ...);
+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 */
diff --git a/pw/rm_r.c b/pw/rm_r.c
new file mode 100644
index 0000000..4ad590b
--- /dev/null
+++ b/pw/rm_r.c
@@ -0,0 +1,75 @@
+/*-
+ * 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 <sys/stat.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include "pwupd.h"
+
+void
+rm_r(char const * dir, uid_t uid)
+{
+ DIR *d = opendir(dir);
+
+ if (d != NULL) {
+ struct dirent *e;
+ struct stat st;
+ char file[MAXPATHLEN];
+
+ while ((e = readdir(d)) != NULL) {
+ if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0) {
+ sprintf(file, "%s/%s", dir, e->d_name);
+ if (lstat(file, &st) == 0) { /* Need symlinks, not
+ * linked file */
+ if (S_ISDIR(st.st_mode)) /* Directory - recurse */
+ rm_r(file, uid);
+ else {
+ if (S_ISLNK(st.st_mode) || st.st_uid == uid)
+ remove(file);
+ }
+ }
+ }
+ }
+ closedir(d);
+ if (lstat(dir, &st) == 0) {
+ if (S_ISLNK(st.st_mode))
+ remove(dir);
+ else if (st.st_uid == uid)
+ rmdir(dir);
+ }
+ }
+}