diff options
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_ */ @@ -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. @@ -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 @@ -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); + } + } +} |
