From owner-svn-src-head@FreeBSD.ORG Fri Mar 14 03:42:06 2014 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id AC7503AF; Fri, 14 Mar 2014 03:42:06 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.freebsd.org (Postfix) with ESMTPS id 989867CC; Fri, 14 Mar 2014 03:42:06 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.8/8.14.8) with ESMTP id s2E3g61D055680; Fri, 14 Mar 2014 03:42:06 GMT (envelope-from dteske@svn.freebsd.org) Received: (from dteske@localhost) by svn.freebsd.org (8.14.8/8.14.8/Submit) id s2E3g5r7055673; Fri, 14 Mar 2014 03:42:05 GMT (envelope-from dteske@svn.freebsd.org) Message-Id: <201403140342.s2E3g5r7055673@svn.freebsd.org> From: Devin Teske Date: Fri, 14 Mar 2014 03:42:05 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r263150 - in head/usr.sbin/bsdconfig: share usermgmt usermgmt/include usermgmt/share X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.17 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 14 Mar 2014 03:42:06 -0000 Author: dteske Date: Fri Mar 14 03:42:05 2014 New Revision: 263150 URL: http://svnweb.freebsd.org/changeset/base/263150 Log: Rewrite usermgmt -- hooking it into the scripting system with dispatch commands addUser, deleteUser, and editUser. Getting rid of the awkward- to-use `userinput' bolt-on which Ron and I talked about rewriting. Added: head/usr.sbin/bsdconfig/usermgmt/share/user.subr (contents, props changed) Deleted: head/usr.sbin/bsdconfig/usermgmt/userinput Modified: head/usr.sbin/bsdconfig/share/script.subr head/usr.sbin/bsdconfig/share/variable.subr head/usr.sbin/bsdconfig/usermgmt/Makefile head/usr.sbin/bsdconfig/usermgmt/include/messages.subr head/usr.sbin/bsdconfig/usermgmt/share/Makefile head/usr.sbin/bsdconfig/usermgmt/share/user_input.subr head/usr.sbin/bsdconfig/usermgmt/useradd head/usr.sbin/bsdconfig/usermgmt/userdel head/usr.sbin/bsdconfig/usermgmt/useredit Modified: head/usr.sbin/bsdconfig/share/script.subr ============================================================================== --- head/usr.sbin/bsdconfig/share/script.subr Fri Mar 14 03:37:08 2014 (r263149) +++ head/usr.sbin/bsdconfig/share/script.subr Fri Mar 14 03:42:05 2014 (r263150) @@ -38,6 +38,7 @@ f_include $BSDCFG_SHARE/mustberoot.subr f_include $BSDCFG_SHARE/networking/services.subr f_include $BSDCFG_SHARE/packages/packages.subr f_include $BSDCFG_SHARE/usermgmt/group.subr +f_include $BSDCFG_SHARE/usermgmt/user.subr f_include $BSDCFG_SHARE/variable.subr ############################################################ GLOBALS @@ -204,6 +205,11 @@ f_resword_new addGroup f_group_add f_resword_new deleteGroup f_group_delete f_resword_new editGroup f_group_edit +# usermgmt/user.subr +f_resword_new addUser f_user_add +f_resword_new deleteUser f_user_delete +f_resword_new editUser f_user_edit + # variable.subr f_resword_new installVarDefaults f_variable_set_defaults f_resword_new dumpVariables f_dump_variables Modified: head/usr.sbin/bsdconfig/share/variable.subr ============================================================================== --- head/usr.sbin/bsdconfig/share/variable.subr Fri Mar 14 03:37:08 2014 (r263149) +++ head/usr.sbin/bsdconfig/share/variable.subr Fri Mar 14 03:42:05 2014 (r263150) @@ -283,6 +283,21 @@ f_variable_new VAR_SLOW_ETHER slowEther f_variable_new VAR_TRY_DHCP tryDHCP f_variable_new VAR_TRY_RTSOL tryRTSOL f_variable_new VAR_UFS_PATH ufs +f_variable_new VAR_USER user +f_variable_new VAR_USER_ACCOUNT_EXPIRE userAccountExpire +f_variable_new VAR_USER_DOTFILES_CREATE userDotfilesCreate +f_variable_new VAR_USER_GECOS userGecos +f_variable_new VAR_USER_GID userGid +f_variable_new VAR_USER_GROUPS userGroups +f_variable_new VAR_USER_GROUP_DELETE userGroupDelete +f_variable_new VAR_USER_HOME userHome +f_variable_new VAR_USER_HOME_CREATE userHomeCreate +f_variable_new VAR_USER_HOME_DELETE userHomeDelete +f_variable_new VAR_USER_LOGIN_CLASS userLoginClass +f_variable_new VAR_USER_PASSWORD userPassword +f_variable_new VAR_USER_PASSWORD_EXPIRE userPasswordExpire +f_variable_new VAR_USER_SHELL userShell +f_variable_new VAR_USER_UID userUid f_variable_new VAR_ZFSINTERACTIVE zfsInteractive # Modified: head/usr.sbin/bsdconfig/usermgmt/Makefile ============================================================================== --- head/usr.sbin/bsdconfig/usermgmt/Makefile Fri Mar 14 03:37:08 2014 (r263149) +++ head/usr.sbin/bsdconfig/usermgmt/Makefile Fri Mar 14 03:42:05 2014 (r263150) @@ -8,8 +8,7 @@ FILESDIR= ${LIBEXECDIR}/bsdconfig/070.us FILES= INDEX USAGE SCRIPTSDIR= ${FILESDIR} -SCRIPTS= groupadd groupdel groupedit useradd userdel useredit \ - userinput usermgmt +SCRIPTS= groupadd groupdel groupedit useradd userdel useredit usermgmt beforeinstall: mkdir -p ${DESTDIR}${FILESDIR} Modified: head/usr.sbin/bsdconfig/usermgmt/include/messages.subr ============================================================================== --- head/usr.sbin/bsdconfig/usermgmt/include/messages.subr Fri Mar 14 03:37:08 2014 (r263149) +++ head/usr.sbin/bsdconfig/usermgmt/include/messages.subr Fri Mar 14 03:42:05 2014 (r263150) @@ -33,7 +33,7 @@ hline_arrows_tab_enter="Press arrows, TA hline_num_arrows_tab_enter="Use numbers, arrows, TAB or ENTER" hline_num_tab_enter="Use numbers, TAB or ENTER" msg_account_does_not_expire="Account does not expire" -msg_account_expires_in_how_many_days="Account expires in how many days?" +msg_account_expire_manual_edit="Enter account expiration time. Format is one of:\n\n a) decimal for UNIX time since %s\n b) dd-mmm-yy[yy] for day, month, and 2- or 4-digit year\n c) +n[mhdwoy] for relative time from current date\n\nNOTE: Value of zero disables expiration." msg_account_expires_on="Account Expires on" msg_add="Add" msg_add_group="Add Group" @@ -56,6 +56,7 @@ msg_edit_group="Edit/View Group" msg_edit_login="Edit/View Login" msg_edit_view="Edit/View" msg_enter_group_members_manually="Enter Group Members manually" +msg_enter_groups_manually="Enter Groups manually" msg_enter_number_of_days_into_the_future="Enter number of days into the future" msg_enter_value_manually="Edit value manually" msg_error="ERROR!" @@ -74,9 +75,8 @@ msg_group_not_found="%s: Group not found msg_group_password="Group Password" msg_group_passwords_do_not_match="Group Passwords do not match." msg_group_updated="Group Updated" +msg_groups="Groups" msg_home_directory="Home Directory" -msg_invalid_number_of_days="Invalid number of days." -msg_invalid_number_of_seconds="Invalid number of seconds." msg_login="Login" msg_login_added="Login Added" msg_login_already_used="%s: Login is already used." @@ -85,25 +85,28 @@ msg_login_deleted="Login Deleted" msg_login_is_empty="Login is empty." msg_login_management="Login/Group Management" msg_login_must_start_with_letter="Login must start with a letter." -msg_login_not_found="Login not found." +msg_login_not_found="%s: Login not found." msg_login_updated="Login Updated" msg_member_of_groups="Member of Groups" msg_n_a="N/A" msg_no="No" msg_no_group_specified="No group specified!" +msg_no_user_specified="No user specified!" msg_number_of_seconds_since_epoch="Number of seconds since the Epoch\n(1 = %s)\nNULL or zero to disable:" msg_ok="OK" msg_password="Password" msg_password_does_not_expire="Password does not expire" -msg_password_expires_in_how_many_days="Password expires in how many days?" +msg_password_expire_manual_edit="Enter password expiration time. Format is one of:\n\n a) decimal for UNIX time since %s\n b) dd-mmm-yy[yy] for day, month, and 2- or 4-digit year\n c) +n[mhdwoy] for relative time from current date\n\nNOTE: Value of zero disables expiration." msg_password_expires_on="Password Expires on" msg_passwords_do_not_match="Passwords do not match." msg_please_enter_a_group_name="Please enter a group name!" +msg_please_enter_a_user_name="Please enter a user name!" msg_reenter_group_password="Re-enter Group Password" msg_reenter_password="Re-enter Password" msg_save="Save" msg_save_exit_or_cancel="Choose Save/Exit when finished or Cancel." msg_select_group_members_from_list="Select Group Members from a list" +msg_select_groups_from_list="Select Groups from a list" msg_select_login_shell="Select Login Shell" msg_separated_by_commas="Separated by commas" msg_shell="Shell" Modified: head/usr.sbin/bsdconfig/usermgmt/share/Makefile ============================================================================== --- head/usr.sbin/bsdconfig/usermgmt/share/Makefile Fri Mar 14 03:37:08 2014 (r263149) +++ head/usr.sbin/bsdconfig/usermgmt/share/Makefile Fri Mar 14 03:42:05 2014 (r263150) @@ -3,7 +3,7 @@ NO_OBJ= FILESDIR= ${SHAREDIR}/bsdconfig/usermgmt -FILES= group.subr group_input.subr user_input.subr +FILES= group.subr group_input.subr user.subr user_input.subr beforeinstall: mkdir -p ${DESTDIR}${FILESDIR} Added: head/usr.sbin/bsdconfig/usermgmt/share/user.subr ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/bsdconfig/usermgmt/share/user.subr Fri Mar 14 03:42:05 2014 (r263150) @@ -0,0 +1,1184 @@ +if [ ! "$_USERMGMT_USER_SUBR" ]; then _USERMGMT_USER_SUBR=1 +# +# Copyright (c) 2012 Ron McDowell +# Copyright (c) 2012-2014 Devin Teske +# 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$ +# +############################################################ INCLUDES + +BSDCFG_SHARE="/usr/share/bsdconfig" +. $BSDCFG_SHARE/common.subr || exit 1 +f_dprintf "%s: loading includes..." usermgmt/user.subr +f_include $BSDCFG_SHARE/dialog.subr +f_include $BSDCFG_SHARE/strings.subr +f_include $BSDCFG_SHARE/usermgmt/group_input.subr +f_include $BSDCFG_SHARE/usermgmt/user_input.subr + +BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt" +f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr + +############################################################ CONFIGURATION + +# set some reasonable defaults if /etc/adduser.conf does not exist. +[ -f /etc/adduser.conf ] && f_include /etc/adduser.conf +: ${defaultclass:=""} +: ${defaultshell:="/bin/sh"} +: ${homeprefix:="/home"} +: ${passwdtype:="yes"} +: ${udotdir:="/usr/share/skel"} +: ${uexpire:=""} + # Default account expire time. Format is similar to upwexpire variable. +: ${ugecos:="User &"} +: ${upwexpire:=""} + # The default password expiration time. Format of the date is either a + # UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where dd is + # the day, mmm is the month in either numeric or alphabetic format, and + # yy[yy] is either a two or four digit year. This variable also accepts + # a relative date in the form of n[mhdwoy] where 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. + +# +# uexpire and upwexpire from adduser.conf(5) differ only slightly from what +# pw(8) accepts as `date' argument(s); pw(8) requires a leading `+' for the +# relative date syntax (n[mhdwoy]). +# +case "$uexpire" in *[mhdwoy]) + f_isinteger "${uexpire%[mhdwoy]}" && uexpire="+$uexpire" +esac +case "$upwexpire" in *[mhdwoy]) + f_isinteger "${upwexpire%[mhdwoy]}" && upwexpire="+$upwexpire" +esac + +############################################################ FUNCTIONS + +# f_user_create_homedir $user +# +# Create home directory for $user. +# +f_user_create_homedir() +{ + local funcname=f_user_create_homedir + local user="$1" + + [ "$user" ] || return $FAILURE + + local user_account_expire user_class user_gecos user_gid user_home_dir + local user_member_groups user_name user_password user_password_expire + local user_shell user_uid # Variables created by f_input_user() below + f_input_user "$user" || return $FAILURE + + f_dprintf "Creating home directory \`%s' for user \`%s'" \ + "$user_home_dir" "$user" + + local _user_gid _user_home_dir _user_uid + f_shell_escape "$user_gid" _user_gid + f_shell_escape "$user_home_dir" _user_home_dir + f_shell_escape "$user_uid" _user_uid + f_eval_catch $funcname mkdir "mkdir -p '%s'" "$_user_home_dir" || + return $FAILURE + f_eval_catch $funcname chown "chown '%i:%i' '%s'" \ + "$_user_uid" "$_user_gid" "$_user_home_dir" || return $FAILURE +} + +# f_user_copy_dotfiles $user +# +# Copy `skel' dot-files from $udotdir (global inherited from /etc/adduser.conf) +# to the home-directory of $user. Attempts to create the home-directory first +# if it doesn't exist. +# +f_user_copy_dotfiles() +{ + local funcname=f_user_copy_dotfiles + local user="$1" + + [ "$udotdir" ] || return $FAILURE + [ "$user" ] || return $FAILURE + + local user_account_expire user_class user_gecos user_gid user_home_dir + local user_member_groups user_name user_password user_password_expire + local user_shell user_uid # Variables created by f_input_user() below + f_input_user "$user" || return $FAILURE + + f_dprintf "Copying dot-files from \`%s' to \`%s'" \ + "$udotdir" "$user_home_dir" + + # Attempt to create the home directory if it doesn't exist + [ -d "$user_home_dir" ] || + f_user_create_homedir "$user" || return $FAILURE + + local _user_gid _user_home_dir _user_uid + f_shell_escape "$user_gid" _user_gid + f_shell_escape "$user_home_dir" _user_home_dir + f_shell_escape "$user_uid" _user_uid + + local - # Localize `set' to this function + set +f # Enable glob pattern-matching for paths + cd "$udotdir" || return $FAILURE + + local _file file retval + for file in dot.*; do + [ -e "$file" ] || continue # no-match + + f_shell_escape "$file" "_file" + f_eval_catch $funcname cp "cp -n '%s' '%s'" \ + "$_file" "$_user_home_dir/${_file#dot}" + retval=$? + [ $retval -eq $SUCCESS ] || break + f_eval_catch $funcname chown \ + "chown -h '%i:%i' '%s'" \ + "$_user_uid" "$_user_gid" \ + "$_user_home_dir/${_file#dot}" + retval=$? + [ $retval -eq $SUCCESS ] || break + done + + cd - + return $retval +} + +# f_user_add [$user] +# +# Create a login account. If both $user (as a first argument) and $VAR_USER are +# unset or NULL and we are running interactively, prompt the end-user to enter +# the name of a new login account and (if $VAR_NO_CONFIRM is unset or NULL) +# prompt the end-user to answer some questions about the new account. Variables +# that can be used to script user input: +# +# VAR_USER [Optional if running interactively] +# The login to add. Ignored if given non-NULL first-argument. +# VAR_USER_ACCOUNT_EXPIRE [Optional] +# The account expiration time. Format is similar to +# VAR_USER_PASSWORD_EXPIRE variable below. Default is to never +# expire the account. +# VAR_USER_DOTFILES_CREATE [Optional] +# If non-NULL, populate the user's home directory with the +# template files found in $udotdir (`/usr/share/skel' default). +# VAR_USER_GECOS [Optional] +# Often the full name of the account holder. Default is NULL. +# VAR_USER_GID [Optional] +# Numerical primary-group ID to use. If NULL or unset, the group +# ID is automatically chosen. +# VAR_USER_GROUPS [Optional] +# Comma-separated list of additional groups to which the user is +# a member of. Default is NULL (no additional groups). +# VAR_USER_HOME [Optional] +# The home directory to set. If NULL or unset, the home directory +# is automatically calculated. +# VAR_USER_HOME_CREATE [Optional] +# If non-NULL, create the user's home directory if it doesn't +# already exist. +# VAR_USER_LOGIN_CLASS [Optional] +# Login class to use when creating the login. Default is NULL. +# VAR_USER_PASSWORD [Optional] +# Unencrypted password to use. If unset or NULL, password +# authentication for the login is disabled. +# VAR_USER_PASSWORD_EXPIRE [Optional] +# The password expiration time. Format of the date is either a +# UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where +# dd is the day, mmm is the month in either numeric or alphabetic +# format, and yy[yy] is either a two or four digit year. This +# variable also accepts a relative date in the form of +n[mhdwoy] +# where 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. Default is to never expire the +# account password. +# VAR_USER_SHELL [Optional] +# Path to login shell to use. Default is `/bin/sh'. +# VAR_USER_UID [Optional] +# Numerical user ID to use. If NULL or unset, the user ID is +# automatically chosen. +# +# Returns success if the user account was successfully created. +# +f_user_add() +{ + local funcname=f_user_add + local title # Calculated below + local alert=f_show_msg no_confirm= + + f_getvar $VAR_NO_CONFIRM no_confirm + [ "$no_confirm" ] && alert=f_show_info + + local input + f_getvar 3:-\$$VAR_USER input "$1" + + # + # NB: pw(8) has a ``feature'' wherein `-n name' can be taken as UID + # instead of name. Work-around is to also pass `-u UID' at the same + # time (any UID will do; but `-1' is appropriate for this context). + # + if [ "$input" ] && f_quietly pw usershow -n "$input" -u -1; then + f_show_err "$msg_login_already_used" "$input" + return $FAILURE + fi + + local user_name="$input" + while f_interactive && [ ! "$user_name" ]; do + f_dialog_input_name user_name "$user_name" || + return $SUCCESS + [ "$user_name" ] || + f_show_err "$msg_please_enter_a_user_name" + done + if [ ! "$user_name" ]; then + f_show_err "$msg_no_user_specified" + return $FAILURE + fi + + local user_account_expire user_class user_gecos user_gid user_home_dir + local user_member_groups user_password user_password_expire user_shell + local user_uid user_dotfiles_create= user_home_create= + f_getvar $VAR_USER_ACCOUNT_EXPIRE-\$uexpire user_account_expire + f_getvar $VAR_USER_DOTFILES_CREATE:+\$msg_yes user_dotfiles_create + f_getvar $VAR_USER_GECOS-\$ugecos user_gecos + f_getvar $VAR_USER_GID user_gid + f_getvar $VAR_USER_GROUPS user_member_groups + f_getvar $VAR_USER_HOME:-\${homeprefix%/}/\$user_name \ + user_home_dir + f_getvar $VAR_USER_HOME_CREATE:+\$msg_yes user_home_create + f_getvar $VAR_USER_LOGIN_CLASS-\$defaultclass user_class + f_getvar $VAR_USER_PASSWORD user_password + f_getvar $VAR_USER_PASSWORD_EXPIRE-\$upwexpire user_password_expire + f_getvar $VAR_USER_SHELL-\$defaultshell user_shell + f_getvar $VAR_USER_UID user_uid + + # Create home-dir if no script-override and does not exist + f_isset $VAR_USER_HOME_CREATE || [ -d "$user_home_dir" ] || + user_home_create="$msg_yes" + # Copy dotfiles if home-dir creation is desired, does not yet exist, + # and no script-override has been set + f_isset $VAR_USER_DOTFILES_CREATE || + [ "$user_home_create" != "$msg_yes" ] || + [ -d "$user_home_dir" ] || user_dotfiles_create="$msg_yes" + # Create home-dir if copying dotfiles but home-dir does not exist + [ "$user_dotfiles_create" -a ! -d "$user_home_dir" ] && + user_home_create="$msg_yes" + + # Set flags for meaningful NULL values if-provided + local no_account_expire= no_password_expire= null_gecos= null_members= + local user_password_disable= + f_isset $VAR_USER_ACCOUNT_EXPIRE && + [ ! "$user_account_expire" ] && no_account_expire=1 + f_isset $VAR_USER_GECOS && + [ ! "$user_gecos" ] && null_gecos=1 + f_isset $VAR_USER_GROUPS && + [ ! "$user_member_groups" ] && null_members=1 + f_isset $VAR_USER_PASSWORD && + [ ! "$user_password" ] && user_password_disable=1 + f_isset $VAR_USER_PASSWORD_EXPIRE && + [ ! "$user_password_expire" ] && no_password_expire=1 + + if f_interactive && [ ! "$no_confirm" ]; then + f_dialog_noyes \ + "$msg_use_default_values_for_all_account_details" + retval=$? + if [ $retval -eq $DIALOG_ESC ]; then + return $SUCCESS + elif [ $retval -ne $DIALOG_OK ]; then + # + # Ask series of questions to pre-fill the editor screen + # + # Defaults used in each dialog should allow the user to + # simply hit ENTER to proceed, because cancelling any + # single dialog will cause them to be returned to the + # previous menu. + # + + f_dialog_input_gecos user_gecos "$user_gecos" || + return $FAILURE + if [ "$passwdtype" = "yes" ]; then + f_dialog_input_password user_password \ + user_password_disable || + return $FAILURE + fi + f_dialog_input_uid user_uid "$user_uid" || + return $FAILURE + f_dialog_input_gid user_gid "$user_gid" || + return $FAILURE + f_dialog_input_member_groups user_member_groups \ + "$user_member_groups" || return $FAILURE + f_dialog_input_class user_class "$user_class" || + return $FAILURE + f_dialog_input_expire_password user_password_expire \ + "$user_password_expire" || return $FAILURE + f_dialog_input_expire_account user_account_expire \ + "$user_account_expire" || return $FAILURE + f_dialog_input_home_dir user_home_dir \ + "$user_home_dir" || return $FAILURE + if [ ! -d "$user_home_dir" ]; then + f_dialog_input_home_create user_home_create || + return $FAILURE + if [ "$user_home_create" = "$msg_yes" ]; then + f_dialog_input_dotfiles_create \ + user_dotfiles_create || + return $FAILURE + fi + fi + f_dialog_input_shell user_shell "$user_shell" || + return $FAILURE + fi + fi + + # + # Loop until the user decides to Exit, Cancel, or presses ESC + # + title="$msg_add $msg_user: $user_name" + if f_interactive; then + local mtag retval defaultitem= + while :; do + f_dialog_title "$title" + f_dialog_menu_user_add "$defaultitem" + retval=$? + f_dialog_title_restore + f_dialog_menutag_fetch mtag + f_dprintf "retval=%u mtag=[%s]" $retval "$mtag" + defaultitem="$mtag" + + # Return if user either pressed ESC or chose Cancel/No + [ $retval -eq $DIALOG_OK ] || return $FAILURE + + case "$mtag" in + X) # Add/Exit + local var + for var in account_expire class gecos gid home_dir \ + member_groups name password_expire shell uid \ + ; do + local _user_$var + eval f_shell_escape \"\$user_$var\" _user_$var + done + + local cmd="pw useradd -n '$_user_name'" + [ "$user_gid" ] && cmd="$cmd -g '$_user_gid'" + [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'" + [ "$user_uid" ] && cmd="$cmd -u '$_user_uid'" + [ "$user_account_expire" -o \ + "$no_account_expire" ] && + cmd="$cmd -e '$_user_account_expire'" + [ "$user_class" -o "$null_class" ] && + cmd="$cmd -L '$_user_class'" + [ "$user_gecos" -o "$null_gecos" ] && + cmd="$cmd -c '$_user_gecos'" + [ "$user_home_dir" ] && + cmd="$cmd -d '$_user_home_dir'" + [ "$user_member_groups" ] && + cmd="$cmd -G '$_user_member_groups'" + [ "$user_password_expire" -o \ + "$no_password_expire" ] && + cmd="$cmd -p '$_user_password_expire'" + + # Execute the command + if [ "$user_password_disable" ]; then + f_eval_catch $funcname pw '%s -h -' "$cmd" + elif [ "$user_password" ]; then + echo "$user_password" | f_eval_catch \ + $funcname pw '%s -h 0' "$cmd" + else + f_eval_catch $funcname pw '%s' "$cmd" + fi || continue + + # Create home directory if desired + [ "${user_home_create:-$msg_no}" != "$msg_no" ] && + f_user_create_homedir "$user_name" + + # Copy dotfiles if desired + [ "${user_dotfiles_create:-$msg_no}" != \ + "$msg_no" ] && f_user_copy_dotfiles "$user_name" + + break # to success + ;; + 1) # Login (prompt for new login name) + f_dialog_input_name input "$user_name" || + continue + if f_quietly pw usershow -n "$input" -u -1; then + f_show_err "$msg_login_already_used" "$input" + continue + fi + user_name="$input" + title="$msg_add $msg_user: $user_name" + user_home_dir="${homeprefix%/}/$user_name" + ;; + 2) # Full Name + f_dialog_input_gecos user_gecos "$user_gecos" && + [ ! "$user_gecos" ] && null_gecos=1 ;; + 3) # Password + f_dialog_input_password \ + user_password user_password_disable ;; + 4) # User ID + f_dialog_input_uid user_uid "$user_uid" ;; + 5) # Group ID + f_dialog_input_gid user_gid "$user_gid" ;; + 6) # Member of Groups + f_dialog_input_member_groups \ + user_member_groups "$user_member_groups" && + [ ! "$user_member_groups" ] && + null_members=1 ;; + 7) # Login Class + f_dialog_input_class user_class "$user_class" && + [ ! "$user_class" ] && null_class=1 ;; + 8) # Password Expires On + f_dialog_input_expire_password \ + user_password_expire "$user_password_expire" && + [ ! "$user_password_expire" ] && + no_password_expire=1 ;; + 9) # Account Expires On + f_dialog_input_expire_account \ + user_account_expire "$user_account_expire" && + [ ! "$user_account_expire" ] && + no_account_expire=1 ;; + A) # Home Directory + f_dialog_input_home_dir \ + user_home_dir "$user_home_dir" ;; + B) # Shell + f_dialog_input_shell user_shell "$user_shell" ;; + C) # Create Home Directory? + if [ "${user_home_create:-$msg_no}" != "$msg_no" ] + then + user_home_create="$msg_no" + else + user_home_create="$msg_yes" + fi ;; + D) # Create Dotfiles? + if [ "${user_dotfiles_create:-$msg_no}" != \ + "$msg_no" ] + then + user_dotfiles_create="$msg_no" + else + user_dotfiles_create="$msg_yes" + fi ;; + esac + done + else + local var + for var in account_expire class gecos gid home_dir \ + member_groups name password_expire shell uid \ + ; do + local _user_$var + eval f_shell_escape \"\$user_$var\" _user_$var + done + + # Form the command + local cmd="pw useradd -n '$_user_name'" + [ "$user_gid" ] && cmd="$cmd -g '$_user_gid'" + [ "$user_home_dir" ] && cmd="$cmd -d '$_user_home_dir'" + [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'" + [ "$user_uid" ] && cmd="$cmd -u '$_user_uid'" + [ "$user_account_expire" -o "$no_account_expire" ] && + cmd="$cmd -e '$_user_account_expire'" + [ "$user_class" -o "$null_class" ] && + cmd="$cmd -L '$_user_class'" + [ "$user_gecos" -o "$null_gecos" ] && + cmd="$cmd -c '$_user_gecos'" + [ "$user_member_groups" -o "$null_members" ] && + cmd="$cmd -G '$_user_member_groups'" + [ "$user_password_expire" -o "$no_password_expire" ] && + cmd="$cmd -p '$_user_password_expire'" + + # Execute the command + local retval err + if [ "$user_password_disable" ]; then + f_eval_catch -k err $funcname pw '%s -h -' "$cmd" + elif [ "$user_password" ]; then + err=$( echo "$user_password" | f_eval_catch -de \ + $funcname pw '%s -h 0' "$cmd" 2>&1 ) + else + f_eval_catch -k err $funcname pw '%s' "$cmd" + fi + retval=$? + if [ $retval -ne $SUCCESS ]; then + f_show_err "%s" "$err" + return $retval + fi + + # Create home directory if desired + [ "${user_home_create:-$msg_no}" != "$msg_no" ] && + f_user_create_homedir "$user_name" + + # Copy dotfiles if desired + [ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] && + f_user_copy_dotfiles "$user_name" + fi + + f_dialog_title "$title" + $alert "$msg_login_added" + f_dialog_title_restore + [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1 + + return $SUCCESS +} + +# f_user_delete [$user] +# +# Delete a user. If both $user (as a first argument) and $VAR_USER are unset or +# NULL and we are running interactively, prompt the end-user to select a user +# account from a list of those available. Variables that can be used to script +# user input: +# +# VAR_USER [Optional if running interactively] +# The user to delete. Ignored if given non-NULL first-argument. +# +# Returns success if the user account was successfully deleted. +# +f_user_delete() +{ + local funcname=f_user_delete + local title # Calculated below + local alert=f_show_msg no_confirm= + + f_getvar $VAR_NO_CONFIRM no_confirm + [ "$no_confirm" ] && alert=f_show_info + + local input + f_getvar 3:-\$$VAR_USER input "$1" + + if f_interactive && [ ! "$input" ]; then + f_dialog_menu_user_list || return $SUCCESS + f_dialog_menutag_fetch input + [ "$input" = "X $msg_exit" ] && return $SUCCESS + elif [ ! "$input" ]; then + f_show_err "$msg_no_user_specified" + return $FAILURE + fi + + local user_account_expire user_class user_gecos user_gid user_home_dir + local user_member_groups user_name user_password user_password_expire + local user_shell user_uid # Variables created by f_input_user() below + if [ "$input" ] && ! f_input_user "$input"; then + f_show_err "$msg_login_not_found" "$input" + return $FAILURE + fi + + local user_group_delete= user_home_delete= + f_getvar $VAR_USER_GROUP_DELETE:-\$msg_no user_group_delete + f_getvar $VAR_USER_HOME_DELETE:-\$msg_no user_home_delete + + # Attempt to translate user GID into a group name + local user_group + if user_group=$( pw groupshow -g "$user_gid" 2> /dev/null ); then + user_group="${user_group%%:*}" + # Default to delete the primary group if no script-override and + # exists with same name as the user (same logic used by pw(8)) + f_isset $VAR_USER_GROUP_DELETE || + [ "$user_group" != "$user_name" ] || + user_group_delete="$msg_yes" + fi + + # + # Loop until the user decides to Exit, Cancel, or presses ESC + # + title="$msg_delete $msg_user: $user_name" + if f_interactive; then + local mtag retval defaultitem= + while :; do + f_dialog_title "$title" + f_dialog_menu_user_delete "$user_name" "$defaultitem" + retval=$? + f_dialog_title_restore + f_dialog_menutag_fetch mtag + f_dprintf "retval=%u mtag=[%s]" $retval "$mtag" + defaultitem="$mtag" + + # Return if user either pressed ESC or chose Cancel/No + [ $retval -eq $DIALOG_OK ] || return $FAILURE + + case "$mtag" in + X) # Delete/Exit + f_shell_escape "$user_uid" _user_uid + + # Save group information in case pw(8) deletes it + # and we wanted to keep it (to be restored below) + if [ "${user_group_delete:-$msg_no}" = "$msg_no" ] + then + local v vars="gid members name password" + for v in $vars; do local group_$var; done + f_input_group "$user_group" + + # Remove user-to-delete from group members + # NB: Otherwise group restoration could fail + local name length=0 _members= + while [ $length -ne ${#group_members} ]; do + name="${group_members%%,*}" + [ "$name" != "$user_name" ] && + _members="$_members,$name" + length=${#group_members} + group_members="${group_members#*,}" + done + group_members="${_members#,}" + + # Create escaped variables for f_eval_catch() + for v in $vars; do + local _group_$v + eval f_shell_escape \ + \"\$group_$v\" _group_$v + done + fi + + # Delete the user (if asked to delete home directory + # display [X]dialog notification to show activity) + local cmd="pw userdel -u '$_user_uid'" + if [ "$user_home_delete" = "$msg_yes" -a \ + "$USE_XDIALOG" ] + then + local err + err=$( + exec 9>&1 + f_eval_catch -e $funcname pw \ + "%s -r" "$cmd" \ + >&$DIALOG_TERMINAL_PASSTHRU_FD 2>&9 | + f_xdialog_info \ + "$msg_deleting_home_directory" + ) + [ ! "$err" ] + elif [ "$user_home_delete" = "$msg_yes" ]; then + f_dialog_info "$msg_deleting_home_directory" + f_eval_catch $funcname pw '%s -r' "$cmd" + else + f_eval_catch $funcname pw '%s' "$cmd" + fi || continue + + # + # pw(8) may conditionally delete the primary group, + # which may not be what is desired. + # + # If we've been asked to delete the group and pw(8) + # chose not to, delete it. Otherwise, if we're told + # to NOT delete the group, we may need to restore it + # since pw(8) doesn't have a flag to tell `userdel' + # to not delete the group. + # + # NB: If primary group and user have different names + # the group may not have been deleted (again, see PR + # 169471 and SVN r263114 for details). + # + if [ "${user_group_delete:-$msg_no}" != "$msg_no" ] + then + f_quietly pw groupshow -g "$user_gid" && + f_eval_catch $funcname pw \ + "pw groupdel -g '%s'" "$_user_gid" + elif ! f_quietly pw groupshow -g "$group_gid" && + [ "$group_name" -a "$group_gid" ] + then + # Group deleted by pw(8), so restore it + local cmd="pw groupadd -n '$_group_name'" + cmd="$cmd -g '$_group_gid'" + cmd="$cmd -M '$_group_members'" + + # Get the group password (pw(8) groupshow does + # NOT provide this (even if running privileged) + local group_password_enc + group_password_enc=$( getent group | awk -F: ' + !/^[[:space:]]*(#|$)/ && \ + $1 == ENVIRON["group_name"] && \ + $3 == ENVIRON["group_gid"] && \ + $4 == ENVIRON["group_members"] \ + { print $2; exit } + ' ) + if [ "$group_password_enc" ]; then + echo "$group_password_enc" | + f_eval_catch $funcname \ + pw '%s -H 0' "$cmd" + else + f_eval_catch $funcname \ + pw '%s -h -' "$cmd" + fi + fi + + break # to success + ;; + 1) # Login (select different login from list) + f_dialog_menu_user_list "$user_name" || continue + f_dialog_menutag_fetch mtag + + [ "$mtag" = "X $msg_exit" ] && continue + + if ! f_input_user "$mtag"; then + f_show_err "$msg_login_not_found" "$mtag" + # Attempt to fall back to previous selection + f_input_user "$input" || return $FAILURE + else + input="$mtag" + fi + title="$msg_delete $msg_user: $user_name" + ;; + C) # Delete Primary Group? + if [ "${user_group_delete:-$msg_no}" != "$msg_no" ] + then + user_group_delete="$msg_no" + else + user_group_delete="$msg_yes" + fi ;; + D) # Delete Home Directory? + if [ "${user_home_delete:-$msg_no}" != "$msg_no" ] + then + user_home_delete="$msg_no" + else + user_home_delete="$msg_yes" + fi ;; + esac + done + else + f_shell_escape "$user_uid" _user_uid + + # Save group information in case pw(8) deletes it + # and we wanted to keep it (to be restored below) + if [ "${user_group_delete:-$msg_no}" = "$msg_no" ]; then + local v vars="gid members name password" + for v in $vars; do local group_$v; done + f_input_group "$user_group" + + # Remove user we're about to delete from group members + # NB: Otherwise group restoration could fail + local name length=0 _members= + while [ $length -ne ${#group_members} ]; do + name="${group_members%%,*}" + [ "$name" != "$user_name" ] && + _members="$_members,$name" + length=${#group_members} + group_members="${group_members#*,}" + done + group_members="${_members#,}" + + # Create escaped variables for later f_eval_catch() + for v in $vars; do + local _group_$v + eval f_shell_escape \"\$group_$v\" _group_$v + done + fi + + # Delete the user (if asked to delete home directory + # display [X]dialog notification to show activity) + local err cmd="pw userdel -u '$_user_uid'" + if [ "$user_home_delete" = "$msg_yes" -a "$USE_XDIALOG" ]; then + err=$( + exec 9>&1 + f_eval_catch -de $funcname pw \ + '%s -r' "$cmd" 2>&9 | f_xdialog_info \ + "$msg_deleting_home_directory" + ) + [ ! "$err" ] + elif [ "$user_home_delete" = "$msg_yes" ]; then + f_dialog_info "$msg_deleting_home_directory" + f_eval_catch -k err $funcname pw '%s -r' "$cmd" + else + f_eval_catch -k err $funcname pw '%s' "$cmd" + fi + local retval=$? + if [ $retval -ne $SUCCESS ]; then + f_show_err "%s" "$err" + return $retval + fi + + # + # pw(8) may conditionally delete the primary group, which may + # not be what is desired. + # + # If we've been asked to delete the group and pw(8) chose not + # to, delete it. Otherwise, if we're told to NOT delete the + # group, we may need to restore it since pw(8) doesn't have a + # flag to tell `userdel' to not delete the group. + # + # NB: If primary group and user have different names the group + # may not have been deleted (again, see PR 169471 and SVN + # r263114 for details). + # + if [ "${user_group_delete:-$msg_no}" != "$msg_no" ] + then + f_quietly pw groupshow -g "$user_gid" && + f_eval_catch $funcname pw \ + "pw groupdel -g '%s'" "$_user_gid" + elif ! f_quietly pw groupshow -g "$group_gid" && + [ "$group_name" -a "$group_gid" ] + then + # Group deleted by pw(8), so restore it + local cmd="pw groupadd -n '$_group_name'" + cmd="$cmd -g '$_group_gid'" + cmd="$cmd -M '$_group_members'" + local group_password_enc + group_password_enc=$( getent group | awk -F: ' + !/^[[:space:]]*(#|$)/ && \ + $1 == ENVIRON["group_name"] && \ + $3 == ENVIRON["group_gid"] && \ + $4 == ENVIRON["group_members"] \ + { print $2; exit } + ' ) + if [ "$group_password_enc" ]; then + echo "$group_password_enc" | + f_eval_catch $funcname \ + pw '%s -H 0' "$cmd" + else + f_eval_catch $funcname \ + pw '%s -h -' "$cmd" + fi + fi + fi + + f_dialog_title "$title" + $alert "$msg_login_deleted" + f_dialog_title_restore + [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1 + + return $SUCCESS +} + +# f_user_edit [$user] +# +# Modify a login account. If both $user (as a first argument) and $VAR_USER are +# unset or NULL and we are running interactively, prompt the end-user to select +# a login account from a list of those available. Variables that can be used to +# script user input: +# +# VAR_USER [Optional if running interactively] +# The login to modify. Ignored if given non-NULL first-argument. +# VAR_USER_ACCOUNT_EXPIRE [Optional] +# The account expiration time. Format is similar to +# VAR_USER_PASSWORD_EXPIRE variable below. If unset, account +# expiry is unchanged. If set but NULL, account expiration is +# disabled (same as setting a value of `0'). +# VAR_USER_DOTFILES_CREATE [Optional] +# If non-NULL, re-populate the user's home directory with the +# template files found in $udotdir (`/usr/share/skel' default). +# VAR_USER_GECOS [Optional] +# Often the full name of the account holder. If unset, the GECOS +# field is unmodified. If set but NULL, the field is blanked. +# VAR_USER_GID [Optional] *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***