xref: /freebsd/usr.sbin/bsdconfig/share/mustberoot.subr (revision 12f110aa1ad3c9d0ead55bf80f2f994b4b845ffb)
1if [ ! "$_MUSTBEROOT_SUBR" ]; then _MUSTBEROOT_SUBR=1
2#
3# Copyright (c) 2006-2012 Devin Teske
4# All Rights Reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27# $FreeBSD$
28#
29############################################################ INCLUDES
30
31BSDCFG_SHARE="/usr/share/bsdconfig"
32. $BSDCFG_SHARE/common.subr || exit 1
33f_include $BSDCFG_SHARE/dialog.subr
34
35BSDCFG_LIBE="/usr/libexec/bsdconfig"
36f_include_lang $BSDCFG_LIBE/include/messages.subr
37
38############################################################ CONFIGURATION
39# NOTE: These are not able to be overridden/inherited for security purposes.
40
41#
42# Number of tries a user gets to enter his/her password before we log the
43# sudo(8) failure and exit.
44#
45PASSWD_TRIES=3
46
47#
48# While in SECURE mode, should authentication as `root' be allowed? Set to
49# non-NULL to enable authentication as `root', otherwise disabled.
50#
51# WARNING:
52# Unless using a custom sudo(8) configuration, user `root' should not be
53# allowed because no password is required to become `root' when already `root'
54# and therefore, any value entered as password will work.
55#
56SECURE_ALLOW_ROOT=
57
58#
59# While in SECURE mode, should we divulge (through error message) when the
60# requested authentication user does not exist? Set to non-NULL to enable,
61# otherwise a non-existent user is treated like an invalid password.
62#
63SECURE_DIVULGE_UNKNOWN_USER=
64
65############################################################ FUNCTIONS
66
67# f_become_root_via_sudo
68#
69# If not running as root, prompt for sudo(8) credentials to become root.
70# Re-execution of the current program via sudo is automatically handled.
71#
72# The following environment variables effect functionality:
73#
74# 	USE_XDIALOG   Either NULL or Non-NULL. If given a value will indicate
75# 	              that Xdialog(1) should be used instead of dialog(1).
76#
77f_become_root_via_sudo()
78{
79	local msg hline size
80
81	[ "$( id -u )" = "0" ] && return $SUCCESS
82
83	f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm"
84
85	#
86	# Ask the user if it's OK to become root via sudo(8) and give them
87	# the option to save this preference (by touch(1)ing a file in the
88	# user's $HOME directory).
89	#
90	local checkpath="${HOME%/}/.bsdconfig_uses_sudo"
91	if [ ! -e "$checkpath" ]; then
92		msg=$( printf "$msg_always_try_sudo_when_run_as" "$USER" )
93		local menu_list="
94			'X' '$msg_cancel_exit'
95			'1' '$msg'
96			'2' '$msg_try_sudo_only_this_once'
97		" # END-QUOTE
98		msg=$( printf "$msg_you_are_not_root_but" bsdconfig )
99		hline="$hline_arrows_tab_enter"
100		size=$( eval f_dialog_menu_size \
101		        	\"\$DIALOG_TITLE\"     \
102		        	\"\$DIALOG_BACKTITLE\" \
103		        	\"\$msg\"              \
104		        	\"\$hline\"            \
105		        	$menu_list             )
106
107		local dialog_menu mtag retval
108		dialog_menu=$( eval $DIALOG \
109			--title \"\$DIALOG_TITLE\"         \
110			--backtitle \"\$DIALOG_BACKTITLE\" \
111			--hline \"\$hline\"                \
112			--ok-label \"\$msg_ok\"            \
113			--cancel-label \"\$msg_cancel\"    \
114			--menu \"\$msg\" $size             \
115			$menu_list                         \
116			2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
117		)
118		retval=$?
119		setvar DIALOG_MENU_$$ "$dialog_menu"
120		mtag=$( f_dialog_menutag )
121
122		[ $retval -eq 0 ] || f_die
123
124		case "$mtag" in
125		X) # Cancel/Exit
126		   f_die ;;
127		1) # Always try sudo(8) when run as $user
128			local err
129			if ! err=$( touch "$checkpath" 2>&1 ); then
130				f_show_msg "%s" "$err"
131			else
132				f_show_msg "$msg_created_path" "$checkpath"
133			fi
134		esac
135	else
136		#
137		# This user has created the path signing-off on sudo(8)-use
138		# but let's still give them a short/quick/unobtrusive reminder
139		#
140		f_dialog_info "$msg_becoming_root_via_sudo"
141		[ "$USE_XDIALOG" ] || sleep 0.6
142	fi
143
144	#
145	# Check sudo(8) access before prompting for password.
146	#
147	:| sudo -S -v 2> /dev/null
148	if [ $? -ne $SUCCESS ]; then
149		#
150		# sudo(8) access denied. Prompt for their password.
151		#
152		msg="$msg_please_enter_password"
153		hline="$hline_alnum_punc_tab_enter"
154		size=$( f_dialog_inputbox_size \
155		        	"$DIALOG_TITLE"     \
156		        	"$DIALOG_BACKTITLE" \
157		        	"$msg"              \
158		        	"$hline"            )
159
160		#
161		# Continue prompting until they either Cancel, succeed
162		# or exceed the number of allowed failures.
163		#
164		local password nfailures=0 retval
165		while [ $nfailures -lt $PASSWD_TRIES ]; do
166			if [ "$USE_XDIALOG" ]; then
167				password=$( $DIALOG \
168					--title "$DIALOG_TITLE"            \
169					--backtitle "$DIALOG_BACKTITLE"    \
170					--hline "$hline"                   \
171					--ok-label "$msg_ok"               \
172					--cancel-label "$msg_cancel"       \
173					--password --inputbox "$msg" $size \
174					2>&1 > /dev/null )
175				retval=$?
176
177				# Catch X11-related errors
178				[ $retval -eq 255 ] &&
179					f_die $retval "$password"
180			else
181				local dialog_inputbox
182				dialog_inputbox=$( $DIALOG \
183					--title "$DIALOG_TITLE"         \
184					--backtitle "$DIALOG_BACKTITLE" \
185					--hline "$hline"                \
186					--ok-label "$msg_ok"            \
187					--cancel-label "$msg_cancel"    \
188					--insecure                      \
189					--passwordbox "$msg" $size      \
190					2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
191				)
192				retval=$?
193				setvar DIALOG_INPUTBOX_$$ "$dialog_inputbox"
194				password=$( f_dialog_inputstr )
195			fi
196
197			# Exit if the user cancelled.
198			[ $retval -eq $SUCCESS ] || exit $retval
199
200			#
201			# Validate sudo(8) credentials
202			#
203			sudo -S -v 2> /dev/null <<-EOF
204			$password
205			EOF
206			retval=$?
207			unset password # scrub memory
208			if [ $retval -eq $SUCCESS ]; then
209				# Access granted...
210				break
211			else
212				# Access denied...
213				nfailures=$(( $nfailures + 1 ))
214
215				# introduce a short delay
216				if [ $nfailures -lt $PASSWD_TRIES ]; then
217					f_dialog_info "$msg_sorry_try_again"
218					sleep 1
219				fi
220			fi
221		done
222
223		#
224		# If user exhausted number of allowed password tries, log
225		# the security event and exit immediately.
226		#
227		if [ $nfailures -ge $PASSWD_TRIES ]; then
228			msg=$( printf "$msg_nfailed_attempts" "$nfailures" )
229			logger -p auth.notice -t sudo " " \
230				"$USER : $msg" \
231				"; TTY=$(tty)" \
232				"; PWD=$PWD"   \
233				"; USER=root"  \
234				"; COMMAND=$0"
235			f_die 1 "sudo: $msg"
236		fi
237	fi
238
239	# Use xauth(1) to grant root the ability to use this X11/SSH session
240	if [ "$USE_XDIALOG" -a "$SSH_CONNECTION" -a "$DISPLAY" ]; then
241		f_have xauth || f_die 1 \
242			"$msg_no_such_file_or_directory" "$pgm" "xauth"
243		local HOSTNAME displaynum
244		HOSTNAME=$(hostname)
245		displaynum="${DISPLAY#*:}"
246		xauth -f ~/.Xauthority extract - $HOSTNAME/unix:$displaynum \
247			$HOSTNAME:$displaynum | sudo sh -c 'xauth -ivf \
248			~root/.Xauthority merge - > /dev/null 2>&1'
249	fi
250
251	# Re-execute ourselves with sudo(8)
252	if [ $ARGC -gt 0 ]; then
253		exec sudo "$0" $ARGV
254	else
255		exec sudo "$0"
256	fi
257	exit $? # Never reached unless error
258}
259
260# f_authenticate_some_user
261#
262# Only used if running as root and requires X11 (see USE_XDIALOG below).
263# Prompts the user to enter a username and password to be authenticated via
264# sudo(8) to proceed.
265#
266# The following environment variables effect functionality:
267#
268# 	USE_XDIALOG   Either NULL or Non-NULL. If given a value will indicate
269# 	              that Xdialog(1) should be used instead of dialog(1).
270#
271f_authenticate_some_user()
272{
273	local msg hline size width height
274
275	f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm"
276
277	#
278	# Secure-mode has been requested.
279	#
280
281	[ "$USE_XDIALOG" ] || f_die 1 "$msg_secure_mode_requires_x11"
282	[ "$(id -u)" = "0" ] || f_die 1 "$msg_secure_mode_requires_root"
283
284	#
285	# Prompt for sudo(8) credentials.
286	#
287
288	msg="$msg_please_enter_username_password"
289	hline="$hline_alnum_punc_tab_enter"
290	size=$( f_xdialog_2inputsbox_size \
291	        	"$DIALOG_TITLE"      \
292	        	"$DIALOG_BACKTITLE"  \
293	        	"$msg"               \
294	        	"$field_username" "" \
295	        	"$field_password" "" )
296	width="${size##*[$IFS]}"
297	height="${size%%[$IFS]*}"
298	height=$(( $height + 2 )) # Add height for --password
299
300	#
301	# Continue prompting until they either Cancel, succeed or exceed the
302	# number of allowed failures.
303	#
304	local user_pass nfailures=0 retval
305	while [ $nfailures -lt $PASSWD_TRIES ]; do
306		user_pass=$( $DIALOG \
307			--title "$DIALOG_TITLE"         \
308			--backtitle "$DIALOG_BACKTITLE" \
309			--hline "$hline"                \
310			--ok-label "$msg_ok"            \
311			--cancel-label "$msg_cancel"    \
312			--password --2inputsbox "$msg"  \
313			$height $width                  \
314			"$field_username" ""            \
315			"$field_password" ""            \
316			2>&1 > /dev/null )
317		retval=$?
318
319		# Catch X11-related errors
320		[ $retval -eq 255 ] && f_die $retval "$user_pass"
321
322		# Exit if the user cancelled.
323		[ $retval -eq $SUCCESS ] || exit $retval
324
325		#
326		# Make sure the user exists and is non-root
327		#
328		local user password
329		user="${user_pass%%/*}"
330		password="${user_pass#*/}"
331		unset user_pass # scrub memory
332		if [ ! "$user" ]; then
333			nfailures=$(( $nfailures + 1 ))
334			f_dialog_msgbox "$msg_no_username"
335			continue
336		fi
337		if [ ! "$SECURE_ALLOW_ROOT" ]; then
338			case "$user" in
339			root|toor)
340				nfailures=$(( $nfailures + 1 ))
341				f_show_msg "$msg_user_disallowed" "$user"
342				continue
343			esac
344		fi
345		if ! f_quietly id "$user"; then
346			nfailures=$(( $nfailures + 1 ))
347			if [ "$SECURE_DIVULGE_UNKNOWN_USER" ]; then
348				f_show_msg "$msg_unknown_user" "$user"
349			elif [ $nfailures -lt $PASSWD_TRIES ]; then
350				f_dialog_info "$msg_sorry_try_again"
351				sleep 1
352			fi
353			continue
354		fi
355
356		#
357		# Validate sudo(8) credentials for given user
358		#
359		su -m "$user" <<-EOF
360			sh <<EOS
361				sudo -k
362				sudo -S -v 2> /dev/null <<EOP
363				$password
364				EOP
365			EOS
366		EOF
367		retval=$?
368		unset user
369		unset password # scrub memory
370
371		if [ $retval -eq $SUCCESS ]; then
372			# Access granted...
373			break
374		else
375			# Access denied...
376			nfailures=$(( $nfailures + 1 ))
377
378			# introduce a short delay
379			if [ $nfailures -lt $PASSWD_TRIES ]; then
380				f_dialog_info "$msg_sorry_try_again"
381				sleep 1
382			fi
383		fi
384	done
385
386	#
387	# If user exhausted number of allowed password tries, log
388	# the security event and exit immediately.
389	#
390	if [ $nfailures -ge $PASSWD_TRIES ]; then
391		msg=$( printf "$msg_nfailed_attempts" "$nfailures" )
392		logger -p auth.notice -t sudo " " \
393			"${SUDO_USER:-$USER} : $msg" \
394			"; TTY=$(tty)"               \
395			"; PWD=$PWD"                 \
396			"; USER=root"                \
397			"; COMMAND=$0"
398		f_die 1 "sudo: $message"
399	fi
400}
401
402# f_mustberoot_init
403#
404# If not already root, make the switch to root by re-executing ourselves via
405# sudo(8) using user-supplied credentials.
406#
407# The following environment variables effect functionality:
408#
409# 	SECURE        Either NULL or Non-NULL. If given a value will indicate
410# 	              that (while running as root) sudo(8) authentication is
411# 	              required to proceed.
412#
413f_mustberoot_init()
414{
415	if [ "$(id -u)" != "0" -a ! "$SECURE" ]; then
416		f_become_root_via_sudo
417	elif [ "$SECURE" ]; then
418		f_authenticate_some_user
419	fi
420}
421
422fi # ! $_MUSTBEROOT_SUBR
423