xref: /freebsd/usr.sbin/bsdconfig/usermgmt/share/group.subr (revision 22cf89c938886d14f5796fc49f9f020c23ea8eaf)
1if [ ! "$_USERMGMT_GROUP_SUBR" ]; then _USERMGMT_GROUP_SUBR=1
2#
3# Copyright (c) 2012 Ron McDowell
4# Copyright (c) 2012-2014 Devin Teske
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27#
28#
29############################################################ INCLUDES
30
31BSDCFG_SHARE="/usr/share/bsdconfig"
32. $BSDCFG_SHARE/common.subr || exit 1
33f_dprintf "%s: loading includes..." usermgmt/group.subr
34f_include $BSDCFG_SHARE/dialog.subr
35f_include $BSDCFG_SHARE/usermgmt/group_input.subr
36
37BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
38f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
39
40############################################################ CONFIGURATION
41
42# set some reasonable defaults if /etc/adduser.conf does not exist.
43[ -f /etc/adduser.conf ] && f_include /etc/adduser.conf
44: ${passwdtype:="yes"}
45
46############################################################ FUNCTIONS
47
48# f_group_add [$group]
49#
50# Add a group. If both $group (as a first argument) and $VAR_GROUP are unset
51# or NULL and we are running interactively, prompt the user to enter the name
52# of a new group and (if $VAR_NO_CONFIRM is unset or NULL) prompt the user to
53# answer some questions about the new group. Variables that can be used to
54# script user input:
55#
56# 	VAR_GROUP [Optional if running interactively]
57# 		The group to add. Ignored if given non-NULL first-argument.
58#	VAR_GROUP_GID [Optional]
59# 		Numerical group ID to use. If NULL or unset, the group ID is
60# 		automatically chosen.
61# 	VAR_GROUP_MEMBERS [Optional]
62# 		Comma separated list of users that are a member of this group.
63# 	VAR_GROUP_PASSWORD [Optional]
64# 		newgrp(1) password to set for the group. Default if NULL or
65# 		unset is to disable newgrp(1) password authentication.
66#
67# Returns success if the group was successfully added.
68#
69f_group_add()
70{
71	local funcname=f_group_add
72	local title # Calculated below
73	local alert=f_show_msg no_confirm=
74
75	f_getvar $VAR_NO_CONFIRM no_confirm
76	[ "$no_confirm" ] && alert=f_show_info
77
78	local input
79	f_getvar 3:-\$$VAR_GROUP input "$1"
80
81	#
82	# NB: pw(8) has a ``feature'' wherein `-n name' can be taken as GID
83	# instead of name. Work-around is to also pass `-g GID' at the same
84	# time (the GID is ignored in this case, so any GID will do).
85	#
86	if [ "$input" ] && f_quietly pw groupshow -n "$input" -g 1337; then
87		f_show_err "$msg_group_already_used" "$input"
88		return $FAILURE
89	fi
90
91	local group_name="$input"
92	while f_interactive && [ ! "$group_name" ]; do
93		f_dialog_input_group_name group_name "$group_name" ||
94			return $SUCCESS
95		[ "$group_name" ] ||
96			f_show_err "$msg_please_enter_a_group_name"
97	done
98	if [ ! "$group_name" ]; then
99		f_show_err "$msg_no_group_specified"
100		return $FAILURE
101	fi
102
103	local group_password group_gid group_members
104	f_getvar $VAR_GROUP_PASSWORD	group_password
105	f_getvar $VAR_GROUP_GID		group_gid
106	f_getvar $VAR_GROUP_MEMBERS	group_members
107
108	local group_password_disable=
109	f_interactive || [ "$group_password" ] || group_password_disable=1
110
111	if f_interactive && [ ! "$no_confirm" ]; then
112		f_dialog_noyes \
113			"$msg_use_default_values_for_all_account_details"
114		retval=$?
115		if [ $retval -eq $DIALOG_ESC ]; then
116			return $SUCCESS
117		elif [ $retval -ne $DIALOG_OK ]; then
118			#
119			# Ask series of questions to pre-fill the editor screen
120			#
121			# Defaults used in each dialog should allow the user to
122			# simply hit ENTER to proceed and cancelling a single
123			# dialog cause them to return to the previous menu.
124			#
125
126			if [ "$passwdtype" = "yes" ]; then
127				f_dialog_input_group_password group_password \
128					group_password_disable ||
129					return $FAILURE
130			fi
131			f_dialog_input_group_gid group_gid "$group_gid" ||
132				return $FAILURE
133			f_dialog_input_group_members group_members \
134				"$group_members" || return $FAILURE
135		fi
136	fi
137
138	#
139	# Loop until the user decides to Exit, Cancel, or presses ESC
140	#
141	title="$msg_add $msg_group: $group_name"
142	if f_interactive; then
143		local mtag retval defaultitem=
144		while :; do
145			f_dialog_title "$title"
146			f_dialog_menu_group_add "$defaultitem"
147			retval=$?
148			f_dialog_title_restore
149			f_dialog_menutag_fetch mtag
150			f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
151			defaultitem="$mtag"
152
153			# Return if user either pressed ESC or chose Cancel/No
154			[ $retval -eq $DIALOG_OK ] || return $FAILURE
155
156			case "$mtag" in
157			X) # Add/Exit
158			   local var
159			   for var in gid members name; do
160			   	local _group_$var
161			   	eval f_shell_escape \
162			   		\"\$group_$var\" _group_$var
163			   done
164
165			   local cmd="pw groupadd -n '$_group_name'"
166			   [ "$group_gid" ] && cmd="$cmd -g '$_group_gid'"
167			   [ "$group_members" ] &&
168			   	cmd="$cmd -M '$_group_members'"
169
170			   # Execute the command (break on success)
171			   if [ "$group_password_disable" ]; then
172			   	f_eval_catch $funcname pw '%s -h -' "$cmd"
173			   elif [ "$group_password" ]; then
174			   	echo "$group_password" |
175			   		f_eval_catch $funcname \
176			   			pw '%s -h 0' "$cmd"
177			   else
178			   	f_eval_catch $funcname pw '%s' "$cmd"
179			   fi && break
180			   ;;
181			1) # Group Name (prompt for new group name)
182			   f_dialog_input_group_name input "$group_name" ||
183			   	continue
184			   if f_quietly pw groupshow -n "$input" -g 1337; then
185			   	f_show_err "$msg_group_already_used" "$input"
186			   	continue
187			   fi
188			   group_name="$input"
189			   title="$msg_add $msg_group: $group_name"
190			   ;;
191			2) # Password
192			   f_dialog_input_group_password group_password \
193			   	group_password_disable
194			   ;;
195			3) # Group ID
196			   f_dialog_input_group_gid group_gid "$group_gid"
197			   ;;
198			4) # Group Members
199			   f_dialog_input_group_members group_members \
200					"$group_members"
201			   ;;
202			esac
203		done
204	else
205		local var
206		for var in gid members name; do
207			local _group_$var
208			eval f_shell_escape \"\$group_$var\" _group_$var
209		done
210
211		# Form the command
212		local cmd="pw groupadd -n '$_group_name'"
213		[ "$group_gid" ] && cmd="$cmd -g '$_group_gid'"
214		[ "$group_members" ] && cmd="$cmd -M '$_group_members'"
215
216		# Execute the command
217		local retval err
218		if [ "$group_password_disable" ]; then
219			f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
220		elif [ "$group_password" ]; then
221			err=$( echo "$group_password" | f_eval_catch -de \
222				$funcname pw '%s -h 0' "$cmd" 2>&1 )
223		else
224			f_eval_catch -k err $funcname pw '%s' "$cmd"
225		fi
226		retval=$?
227		if [ $retval -ne $SUCCESS ]; then
228			f_show_err "%s" "$err"
229			return $retval
230		fi
231	fi
232
233	f_dialog_title "$title"
234	$alert "$msg_group_added"
235	f_dialog_title_restore
236	[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
237
238	return $SUCCESS
239}
240
241# f_group_delete [$group]
242#
243# Delete a group. If both $group (as a first argument) and $VAR_GROUP are unset
244# or NULL and we are running interactively, prompt the user to select a group
245# from a list of available groups. Variables that can be used to script user
246# input:
247#
248# 	VAR_GROUP [Optional if running interactively]
249# 		The group to delete. Ignored if given non-NULL first-argument.
250#
251# Returns success if the group was successfully deleted.
252#
253f_group_delete()
254{
255	local funcname=f_group_delete
256	local title # Calculated below
257	local alert=f_show_msg no_confirm=
258
259	f_getvar $VAR_NO_CONFIRM no_confirm
260	[ "$no_confirm" ] && alert=f_show_info
261
262	local input
263	f_getvar 3:-\$$VAR_GROUP input "$1"
264
265	local group_name group_password group_gid group_members
266	if [ "$input" ] && ! f_input_group "$input"; then
267		f_show_err "$msg_group_not_found" "$input"
268		return $FAILURE
269	fi
270
271	#
272	# Loop until the user decides to Exit, Cancel, or presses ESC
273	#
274	title="$msg_delete $msg_group: $group_name"
275	if f_interactive; then
276		local mtag retval defaultitem=
277		while :; do
278        		f_dialog_title "$title"
279			f_dialog_menu_group_delete "$group_name" "$defaultitem"
280			retval=$?
281			f_dialog_title_restore
282			f_dialog_menutag_fetch mtag
283			f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
284			defaultitem="$mtag"
285
286			# Return if user either pressed ESC or chose Cancel/No
287			[ $retval -eq $DIALOG_OK ] || return $FAILURE
288
289			case "$mtag" in
290			X) # Delete/Exit
291			   local _group_name
292			   f_shell_escape "$group_name" _group_name
293			   f_eval_catch $funcname pw 'pw groupdel "%s"' \
294					"$_group_name" && break
295			   ;;
296			1) # Group Name (select different group from list)
297			   f_dialog_menu_group_list "$group_name" || continue
298			   f_dialog_menutag_fetch mtag
299
300			   [ "$mtag" = "X $msg_exit" ] && continue
301
302			   if ! f_input_group "$mtag"; then
303				f_show_err "$msg_group_not_found" "$mtag"
304				# Attempt to fall back to previous selection
305				f_input_group "$input" || return $FAILURE
306			   else
307				input="$mtag"
308			   fi
309			   ;;
310			esac
311		done
312	else
313		local retval err _group_name
314		f_shell_escape "$group_name" _group_name
315		f_eval_catch -k err $funcname pw \
316			"pw groupdel '%s'" "$_group_name"
317		retval=$?
318		if [ $retval -ne $SUCCESS ]; then
319			f_show_err "%s" "$err"
320			return $retval
321		fi
322	fi
323
324        f_dialog_title "$title"
325	$alert "$msg_group_deleted"
326	f_dialog_title_restore
327	[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
328
329	return $SUCCESS
330}
331
332# f_group_edit [$group]
333#
334# Modify a group. If both $group (as a first argument) and $VAR_GROUP are unset
335# or NULL and we are running interactively, prompt the user to select a group
336# from a list of available groups. Variables that can be used to script user
337# input:
338#
339# 	VAR_GROUP [Optional if running interactively]
340# 		The group to modify. Ignored if given non-NULL first-argument.
341#	VAR_GROUP_GID [Optional]
342# 		Numerical group ID to set. If NULL or unset, the group ID is
343# 		unchanged.
344# 	VAR_GROUP_MEMBERS [Optional]
345# 		Comma separated list of users that are a member of this group.
346# 		If set but NULL, group memberships are reset (no users will be
347# 		a member of this group). If unset, group membership is
348# 		unmodified.
349# 	VAR_GROUP_PASSWORD [Optional]
350# 		newgrp(1) password to set for the group. If unset, the password
351# 		is unmodified. If NULL, the newgrp(1) password is disabled.
352#
353# Returns success if the group was successfully modified.
354#
355f_group_edit()
356{
357	local funcname=f_group_edit
358	local title # Calculated below
359	local alert=f_show_msg no_confirm=
360
361	f_getvar $VAR_NO_CONFIRM no_confirm
362	[ "$no_confirm" ] && alert=f_show_info
363
364	local input
365	f_getvar 3:-\$$VAR_GROUP input "$1"
366
367	#
368	# NB: pw(8) has a ``feature'' wherein `-n name' can be taken as GID
369	# instead of name. Work-around is to also pass `-g GID' at the same
370	# time (the GID is ignored in this case, so any GID will do).
371	#
372	if [ "$input" ] && ! f_quietly pw groupshow -n "$input" -g 1337; then
373		f_show_err "$msg_group_not_found" "$input"
374		return $FAILURE
375	fi
376
377	if f_interactive && [ ! "$input" ]; then
378		f_dialog_menu_group_list || return $SUCCESS
379		f_dialog_menutag_fetch input
380		[ "$input" = "X $msg_exit" ] && return $SUCCESS
381	elif [ ! "$input" ]; then
382		f_show_err "$msg_no_group_specified"
383		return $FAILURE
384	fi
385
386	local group_name group_password group_gid group_members
387	if ! f_input_group "$input"; then
388		f_show_err "$msg_group_not_found" "$input"
389		return $FAILURE
390	fi
391
392	f_isset $VAR_GROUP_GID && f_getvar $VAR_GROUP_GID group_gid
393	local null_members=
394	if f_isset $VAR_GROUP_MEMBERS; then
395		f_getvar $VAR_GROUP_MEMBERS group_members
396		[ "$group_members" ] || null_members=1
397	fi
398	local group_password_disable=
399	if f_isset $VAR_GROUP_PASSWORD; then
400		f_getvar $VAR_GROUP_PASSWORD group_password
401		[ "$group_password" ] || group_password_disable=1
402	fi
403
404	#
405	# Loop until the user decides to Exit, Cancel, or presses ESC
406	#
407	title="$msg_edit_view $msg_group: $group_name"
408	if f_interactive; then
409		local mtag retval defaultitem=
410		while :; do
411			f_dialog_title "$title"
412			f_dialog_menu_group_edit "$defaultitem"
413			retval=$?
414			f_dialog_title_restore
415			f_dialog_menutag_fetch mtag
416			f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
417			defaultitem="$mtag"
418
419			# Return if user either pressed ESC or chose Cancel/No
420			[ $retval -eq $DIALOG_OK ] || return $FAILURE
421
422			case "$mtag" in
423			X) # Save/Exit
424			   local var
425			   for var in gid members name; do
426			   	local _group_$var
427			   	eval f_shell_escape \
428			   		\"\$group_$var\" _group_$var
429			   done
430
431			   local cmd="pw groupmod -n '$_group_name'"
432			   [ "$group_gid" ] && cmd="$cmd -g '$_group_gid'"
433			   [ "$group_members" -o "$null_members" ] &&
434			   	cmd="$cmd -M '$_group_members'"
435
436			   # Execute the command (break on success)
437			   if [ "$group_password_disable" ]; then
438			   	f_eval_catch $funcname pw '%s -h -' "$cmd"
439			   elif [ "$group_password" ]; then
440			   	echo "$group_password" | f_eval_catch \
441			   		$funcname pw '%s -h 0' "$cmd"
442			   else
443			   	f_eval_catch $funcname pw '%s' "$cmd"
444			   fi && break
445			   ;;
446			1) # Group Name (select different group from list)
447			   f_dialog_menu_group_list "$group_name" || continue
448			   f_dialog_menutag_fetch mtag
449
450			   [ "$mtag" = "X $msg_exit" ] && continue
451
452			   if ! f_input_group "$mtag"; then
453			   	f_show_err "$msg_group_not_found" "$mtag"
454			   	# Attempt to fall back to previous selection
455			   	f_input_group "$input" || return $FAILURE
456			   else
457			   	input="$mtag"
458			   fi
459			   title="$msg_edit_view $msg_group: $group_name"
460			   ;;
461			2) # Password
462			   f_dialog_input_group_password group_password \
463			   	group_password_disable
464			   ;;
465			3) # Group ID
466			   f_dialog_input_group_gid group_gid "$group_gid"
467			   ;;
468			4) # Group Members
469			   f_dialog_input_group_members group_members \
470			   	"$group_members" && [ ! "$group_members" ] &&
471			   	null_members=1
472			   ;;
473			esac
474		done
475	else
476		local var
477		for var in gid members name; do
478			local _group_$var
479			eval f_shell_escape \"\$group_$var\" _group_$var
480		done
481
482		# Form the command
483		local cmd="pw groupmod -n '$_group_name'"
484		[ "$group_gid" ] && cmd="$cmd -g '$_group_gid'"
485		[ "$group_members" -o "$null_members" ] &&
486			cmd="$cmd -M '$_group_members'"
487
488		# Execute the command
489		local retval err
490		if [ "$group_password_disable" ]; then
491			f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
492		elif [ "$group_password" -o "$null_password" ]; then
493			err=$( echo "$group_password" | f_eval_catch -de \
494				$funcname pw '%s -h 0' "$cmd" 2>&1 )
495		else
496			f_eval_catch -k err $funcname pw '%s' "$cmd"
497		fi
498		retval=$?
499		if [ $retval -ne $SUCCESS ]; then
500			f_show_err "%s" "$err"
501			return $retval
502		fi
503	fi
504
505	f_dialog_title "$title"
506	$alert "$msg_group_updated"
507	f_dialog_title_restore
508	[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
509
510	return $SUCCESS
511}
512
513############################################################ MAIN
514
515f_dprintf "%s: Successfully loaded." usermgmt/group.subr
516
517fi # ! $_USERMGMT_GROUP_SUBR
518