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