mustberoot.subr revision 251190
1238438Sdteskeif [ ! "$_MUSTBEROOT_SUBR" ]; then _MUSTBEROOT_SUBR=1
2238438Sdteske#
3249746Sdteske# Copyright (c) 2006-2013 Devin Teske
4238438Sdteske# All Rights Reserved.
5238438Sdteske#
6238438Sdteske# Redistribution and use in source and binary forms, with or without
7238438Sdteske# modification, are permitted provided that the following conditions
8238438Sdteske# are met:
9238438Sdteske# 1. Redistributions of source code must retain the above copyright
10238438Sdteske#    notice, this list of conditions and the following disclaimer.
11238438Sdteske# 2. Redistributions in binary form must reproduce the above copyright
12238438Sdteske#    notice, this list of conditions and the following disclaimer in the
13238438Sdteske#    documentation and/or other materials provided with the distribution.
14238438Sdteske#
15238438Sdteske# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16238438Sdteske# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE
17238438Sdteske# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18238438Sdteske# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19238438Sdteske# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20238438Sdteske# DAMAGES (INLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21238438Sdteske# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22238438Sdteske# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23238438Sdteske# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24238438Sdteske# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25238438Sdteske# SUCH DAMAGE.
26238438Sdteske#
27238438Sdteske# $FreeBSD: head/usr.sbin/bsdconfig/share/mustberoot.subr 251190 2013-05-31 19:07:17Z dteske $
28238438Sdteske#
29238438Sdteske############################################################ INCLUDES
30238438Sdteske
31240684SdteskeBSDCFG_SHARE="/usr/share/bsdconfig"
32240684Sdteske. $BSDCFG_SHARE/common.subr || exit 1
33244675Sdteskef_dprintf "%s: loading includes..." mustberoot.subr
34240684Sdteskef_include $BSDCFG_SHARE/dialog.subr
35240684Sdteske
36238438SdteskeBSDCFG_LIBE="/usr/libexec/bsdconfig"
37238438Sdteskef_include_lang $BSDCFG_LIBE/include/messages.subr
38238438Sdteske
39238438Sdteske############################################################ CONFIGURATION
40238438Sdteske# NOTE: These are not able to be overridden/inherited for security purposes.
41238438Sdteske
42238438Sdteske#
43238438Sdteske# Number of tries a user gets to enter his/her password before we log the
44238438Sdteske# sudo(8) failure and exit.
45238438Sdteske#
46238438SdteskePASSWD_TRIES=3
47238438Sdteske
48238438Sdteske#
49238438Sdteske# While in SECURE mode, should authentication as `root' be allowed? Set to
50238438Sdteske# non-NULL to enable authentication as `root', otherwise disabled.
51238438Sdteske#
52238438Sdteske# WARNING: 
53238438Sdteske# Unless using a custom sudo(8) configuration, user `root' should not be
54238438Sdteske# allowed because no password is required to become `root' when already `root'
55238438Sdteske# and therefore, any value entered as password will work.
56238438Sdteske#
57238438SdteskeSECURE_ALLOW_ROOT=
58238438Sdteske
59238438Sdteske#
60238438Sdteske# While in SECURE mode, should we divulge (through error message) when the
61238438Sdteske# requested authentication user does not exist? Set to non-NULL to enable,
62238438Sdteske# otherwise a non-existent user is treated like an invalid password.
63238438Sdteske#
64238438SdteskeSECURE_DIVULGE_UNKNOWN_USER=
65238438Sdteske
66238438Sdteske############################################################ FUNCTIONS
67238438Sdteske
68238438Sdteske# f_become_root_via_sudo
69238438Sdteske#
70238438Sdteske# If not running as root, prompt for sudo(8) credentials to become root.
71238438Sdteske# Re-execution of the current program via sudo is automatically handled.
72238438Sdteske#
73238438Sdteske# The following environment variables effect functionality:
74238438Sdteske#
75238438Sdteske# 	USE_XDIALOG   Either NULL or Non-NULL. If given a value will indicate
76238438Sdteske# 	              that Xdialog(1) should be used instead of dialog(1).
77238438Sdteske#
78238438Sdteskef_become_root_via_sudo()
79238438Sdteske{
80251190Sdteske	local msg hline height width rows
81238438Sdteske
82238438Sdteske	[ "$( id -u )" = "0" ] && return $SUCCESS
83238438Sdteske
84238438Sdteske	f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm"
85238438Sdteske
86238438Sdteske	#
87241653Sdteske	# Ask the user if it's OK to become root via sudo(8) and give them
88241653Sdteske	# the option to save this preference (by touch(1)ing a file in the
89241653Sdteske	# user's $HOME directory).
90241653Sdteske	#
91241653Sdteske	local checkpath="${HOME%/}/.bsdconfig_uses_sudo"
92241653Sdteske	if [ ! -e "$checkpath" ]; then
93241653Sdteske		msg=$( printf "$msg_always_try_sudo_when_run_as" "$USER" )
94241653Sdteske		local menu_list="
95241653Sdteske			'X' '$msg_cancel_exit'
96241653Sdteske			'1' '$msg'
97241653Sdteske			'2' '$msg_try_sudo_only_this_once'
98241653Sdteske		" # END-QUOTE
99241653Sdteske		msg=$( printf "$msg_you_are_not_root_but" bsdconfig )
100241653Sdteske		hline="$hline_arrows_tab_enter"
101251190Sdteske		eval f_dialog_menu_size height width rows \
102251190Sdteske		                        \"\$DIALOG_TITLE\"     \
103251190Sdteske		                        \"\$DIALOG_BACKTITLE\" \
104251190Sdteske		                        \"\$msg\"              \
105251190Sdteske		                        \"\$hline\"            \
106251190Sdteske		                        $menu_list
107241653Sdteske
108241653Sdteske		local dialog_menu mtag retval
109241653Sdteske		dialog_menu=$( eval $DIALOG \
110241653Sdteske			--title \"\$DIALOG_TITLE\"         \
111241653Sdteske			--backtitle \"\$DIALOG_BACKTITLE\" \
112241653Sdteske			--hline \"\$hline\"                \
113241653Sdteske			--ok-label \"\$msg_ok\"            \
114241653Sdteske			--cancel-label \"\$msg_cancel\"    \
115251190Sdteske			--menu \"\$msg\"                   \
116251190Sdteske			$height $width $rows               \
117241653Sdteske			$menu_list                         \
118241653Sdteske			2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
119241653Sdteske		)
120241653Sdteske		retval=$?
121241653Sdteske		setvar DIALOG_MENU_$$ "$dialog_menu"
122241653Sdteske		mtag=$( f_dialog_menutag )
123241653Sdteske
124241653Sdteske		[ $retval -eq 0 ] || f_die
125241653Sdteske
126241653Sdteske		case "$mtag" in
127241653Sdteske		X) # Cancel/Exit
128241653Sdteske		   f_die ;;
129241653Sdteske		1) # Always try sudo(8) when run as $user
130241653Sdteske			local err
131241653Sdteske			if ! err=$( touch "$checkpath" 2>&1 ); then
132245437Sdteske				f_dialog_msgbox "$err"
133241653Sdteske			else
134241653Sdteske				f_show_msg "$msg_created_path" "$checkpath"
135241653Sdteske			fi
136241653Sdteske		esac
137241653Sdteske	else
138241653Sdteske		#
139241653Sdteske		# This user has created the path signing-off on sudo(8)-use
140241653Sdteske		# but let's still give them a short/quick/unobtrusive reminder
141241653Sdteske		#
142241653Sdteske		f_dialog_info "$msg_becoming_root_via_sudo"
143241653Sdteske		[ "$USE_XDIALOG" ] || sleep 0.6
144241653Sdteske	fi
145241653Sdteske
146241653Sdteske	#
147238438Sdteske	# Check sudo(8) access before prompting for password.
148238438Sdteske	#
149240783Sdteske	:| sudo -S -v 2> /dev/null
150238438Sdteske	if [ $? -ne $SUCCESS ]; then
151238438Sdteske		#
152238438Sdteske		# sudo(8) access denied. Prompt for their password.
153238438Sdteske		#
154238438Sdteske		msg="$msg_please_enter_password"
155238438Sdteske		hline="$hline_alnum_punc_tab_enter"
156251190Sdteske		f_dialog_inputbox_size height width \
157251190Sdteske		                       "$DIALOG_TITLE"     \
158251190Sdteske		                       "$DIALOG_BACKTITLE" \
159251190Sdteske		                       "$msg"              \
160251190Sdteske		                       "$hline"
161238438Sdteske
162238438Sdteske		#
163238438Sdteske		# Continue prompting until they either Cancel, succeed
164238438Sdteske		# or exceed the number of allowed failures.
165238438Sdteske		#
166238438Sdteske		local password nfailures=0 retval
167238438Sdteske		while [ $nfailures -lt $PASSWD_TRIES ]; do
168238438Sdteske			if [ "$USE_XDIALOG" ]; then
169238438Sdteske				password=$( $DIALOG \
170251190Sdteske					--title "$DIALOG_TITLE"         \
171251190Sdteske					--backtitle "$DIALOG_BACKTITLE" \
172251190Sdteske					--hline "$hline"                \
173251190Sdteske					--ok-label "$msg_ok"            \
174251190Sdteske					--cancel-label "$msg_cancel"    \
175251190Sdteske					--password --inputbox "$msg"    \
176251190Sdteske					$height $width                  \
177240783Sdteske					2>&1 > /dev/null )
178238438Sdteske				retval=$?
179238438Sdteske
180238438Sdteske				# Catch X11-related errors
181238438Sdteske				[ $retval -eq 255 ] &&
182238438Sdteske					f_die $retval "$password"
183238438Sdteske			else
184240768Sdteske				local dialog_inputbox
185240768Sdteske				dialog_inputbox=$( $DIALOG \
186238438Sdteske					--title "$DIALOG_TITLE"         \
187238438Sdteske					--backtitle "$DIALOG_BACKTITLE" \
188238438Sdteske					--hline "$hline"                \
189238438Sdteske					--ok-label "$msg_ok"            \
190238438Sdteske					--cancel-label "$msg_cancel"    \
191238438Sdteske					--insecure                      \
192251190Sdteske					--passwordbox "$msg"            \
193251190Sdteske					$height $width                  \
194240768Sdteske					2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
195240768Sdteske				)
196238438Sdteske				retval=$?
197240768Sdteske				setvar DIALOG_INPUTBOX_$$ "$dialog_inputbox"
198238438Sdteske				password=$( f_dialog_inputstr )
199238438Sdteske			fi
200238438Sdteske
201238438Sdteske			# Exit if the user cancelled.
202238438Sdteske			[ $retval -eq $SUCCESS ] || exit $retval
203238438Sdteske
204238438Sdteske			#
205238438Sdteske			# Validate sudo(8) credentials
206238438Sdteske			#
207240783Sdteske			sudo -S -v 2> /dev/null <<-EOF
208238438Sdteske			$password
209238438Sdteske			EOF
210238438Sdteske			retval=$?
211238438Sdteske			unset password # scrub memory
212238438Sdteske			if [ $retval -eq $SUCCESS ]; then
213238438Sdteske				# Access granted...
214238438Sdteske				break
215238438Sdteske			else
216238438Sdteske				# Access denied...
217238438Sdteske				nfailures=$(( $nfailures + 1 ))
218238438Sdteske
219238438Sdteske				# introduce a short delay
220238438Sdteske				if [ $nfailures -lt $PASSWD_TRIES ]; then
221238438Sdteske					f_dialog_info "$msg_sorry_try_again"
222238438Sdteske					sleep 1
223238438Sdteske				fi
224238438Sdteske			fi
225238438Sdteske		done
226238438Sdteske
227238438Sdteske		#
228238438Sdteske		# If user exhausted number of allowed password tries, log
229238438Sdteske		# the security event and exit immediately.
230238438Sdteske		#
231238438Sdteske		if [ $nfailures -ge $PASSWD_TRIES ]; then
232238438Sdteske			msg=$( printf "$msg_nfailed_attempts" "$nfailures" )
233238438Sdteske			logger -p auth.notice -t sudo " " \
234238438Sdteske				"$USER : $msg" \
235238438Sdteske				"; TTY=$(tty)" \
236238438Sdteske				"; PWD=$PWD"   \
237238438Sdteske				"; USER=root"  \
238238438Sdteske				"; COMMAND=$0"
239238438Sdteske			f_die 1 "sudo: $msg"
240238438Sdteske		fi
241238438Sdteske	fi
242238438Sdteske
243238438Sdteske	# Use xauth(1) to grant root the ability to use this X11/SSH session
244238438Sdteske	if [ "$USE_XDIALOG" -a "$SSH_CONNECTION" -a "$DISPLAY" ]; then
245238438Sdteske		f_have xauth || f_die 1 \
246238438Sdteske			"$msg_no_such_file_or_directory" "$pgm" "xauth"
247238438Sdteske		local HOSTNAME displaynum
248238438Sdteske		HOSTNAME=$(hostname)
249238438Sdteske		displaynum="${DISPLAY#*:}"
250238438Sdteske		xauth -f ~/.Xauthority extract - $HOSTNAME/unix:$displaynum \
251238438Sdteske			$HOSTNAME:$displaynum | sudo sh -c 'xauth -ivf \
252240783Sdteske			~root/.Xauthority merge - > /dev/null 2>&1'
253238438Sdteske	fi
254238438Sdteske
255238438Sdteske	# Re-execute ourselves with sudo(8)
256249746Sdteske	f_dprintf "%s: Becoming root via sudo(8)..." mustberoot.subr
257238438Sdteske	if [ $ARGC -gt 0 ]; then
258238438Sdteske		exec sudo "$0" $ARGV
259238438Sdteske	else
260238438Sdteske		exec sudo "$0"
261238438Sdteske	fi
262238438Sdteske	exit $? # Never reached unless error
263238438Sdteske}
264238438Sdteske
265238438Sdteske# f_authenticate_some_user
266238438Sdteske#
267238438Sdteske# Only used if running as root and requires X11 (see USE_XDIALOG below).
268238438Sdteske# Prompts the user to enter a username and password to be authenticated via
269238438Sdteske# sudo(8) to proceed.
270238438Sdteske#
271238438Sdteske# The following environment variables effect functionality:
272238438Sdteske#
273238438Sdteske# 	USE_XDIALOG   Either NULL or Non-NULL. If given a value will indicate
274238438Sdteske# 	              that Xdialog(1) should be used instead of dialog(1).
275238438Sdteske#
276238438Sdteskef_authenticate_some_user()
277238438Sdteske{
278251190Sdteske	local msg hline height width
279238438Sdteske
280238438Sdteske	f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm"
281238438Sdteske
282238438Sdteske	#
283238438Sdteske	# Secure-mode has been requested.
284238438Sdteske	#
285243476Sdteske
286238438Sdteske	[ "$USE_XDIALOG" ] || f_die 1 "$msg_secure_mode_requires_x11"
287238438Sdteske	[ "$(id -u)" = "0" ] || f_die 1 "$msg_secure_mode_requires_root"
288238438Sdteske
289238438Sdteske	#
290238438Sdteske	# Prompt for sudo(8) credentials.
291238438Sdteske	#
292238438Sdteske
293238438Sdteske	msg="$msg_please_enter_username_password"
294238438Sdteske	hline="$hline_alnum_punc_tab_enter"
295251190Sdteske	f_xdialog_2inputsbox_size height width \
296251190Sdteske	                          "$DIALOG_TITLE"      \
297251190Sdteske	                          "$DIALOG_BACKTITLE"  \
298251190Sdteske	                          "$msg"               \
299251190Sdteske	                          "$field_username" "" \
300251190Sdteske	                          "$field_password" ""
301238438Sdteske	height=$(( $height + 2 )) # Add height for --password
302238438Sdteske
303238438Sdteske	#
304238438Sdteske	# Continue prompting until they either Cancel, succeed or exceed the
305238438Sdteske	# number of allowed failures.
306238438Sdteske	#
307238438Sdteske	local user_pass nfailures=0 retval
308238438Sdteske	while [ $nfailures -lt $PASSWD_TRIES ]; do
309238438Sdteske		user_pass=$( $DIALOG \
310238438Sdteske			--title "$DIALOG_TITLE"         \
311238438Sdteske			--backtitle "$DIALOG_BACKTITLE" \
312238438Sdteske			--hline "$hline"                \
313238438Sdteske			--ok-label "$msg_ok"            \
314238438Sdteske			--cancel-label "$msg_cancel"    \
315238438Sdteske			--password --2inputsbox "$msg"  \
316238438Sdteske			$height $width                  \
317238438Sdteske			"$field_username" ""            \
318238438Sdteske			"$field_password" ""            \
319240783Sdteske			2>&1 > /dev/null )
320238438Sdteske		retval=$?
321238438Sdteske
322238438Sdteske		# Catch X11-related errors
323238438Sdteske		[ $retval -eq 255 ] && f_die $retval "$user_pass"
324238438Sdteske
325238438Sdteske		# Exit if the user cancelled.
326238438Sdteske		[ $retval -eq $SUCCESS ] || exit $retval
327238438Sdteske
328238438Sdteske		#
329238438Sdteske		# Make sure the user exists and is non-root
330238438Sdteske		#
331238438Sdteske		local user password
332238438Sdteske		user="${user_pass%%/*}"
333238438Sdteske		password="${user_pass#*/}"
334238438Sdteske		unset user_pass # scrub memory
335238438Sdteske		if [ ! "$user" ]; then
336238438Sdteske			nfailures=$(( $nfailures + 1 ))
337238438Sdteske			f_dialog_msgbox "$msg_no_username"
338238438Sdteske			continue
339238438Sdteske		fi
340238438Sdteske		if [ ! "$SECURE_ALLOW_ROOT" ]; then
341238438Sdteske			case "$user" in
342238438Sdteske			root|toor)
343238438Sdteske				nfailures=$(( $nfailures + 1 ))
344244554Sdteske				f_show_msg "$msg_user_disallowed" "$user"
345238438Sdteske				continue
346238438Sdteske			esac
347238438Sdteske		fi
348238438Sdteske		if ! f_quietly id "$user"; then
349238438Sdteske			nfailures=$(( $nfailures + 1 ))
350238438Sdteske			if [ "$SECURE_DIVULGE_UNKNOWN_USER" ]; then
351244554Sdteske				f_show_msg "$msg_unknown_user" "$user"
352238438Sdteske			elif [ $nfailures -lt $PASSWD_TRIES ]; then
353238438Sdteske				f_dialog_info "$msg_sorry_try_again"
354238438Sdteske				sleep 1
355238438Sdteske			fi
356238438Sdteske			continue
357238438Sdteske		fi
358238438Sdteske
359238438Sdteske		#
360238438Sdteske		# Validate sudo(8) credentials for given user
361238438Sdteske		#
362238438Sdteske		su -m "$user" <<-EOF
363238438Sdteske			sh <<EOS
364238438Sdteske				sudo -k
365240783Sdteske				sudo -S -v 2> /dev/null <<EOP
366238438Sdteske				$password
367238438Sdteske				EOP
368238438Sdteske			EOS
369238438Sdteske		EOF
370238438Sdteske		retval=$?
371238438Sdteske		unset user
372238438Sdteske		unset password # scrub memory
373238438Sdteske
374238438Sdteske		if [ $retval -eq $SUCCESS ]; then
375238438Sdteske			# Access granted...
376238438Sdteske			break
377238438Sdteske		else
378238438Sdteske			# Access denied...
379238438Sdteske			nfailures=$(( $nfailures + 1 ))
380238438Sdteske
381238438Sdteske			# introduce a short delay
382238438Sdteske			if [ $nfailures -lt $PASSWD_TRIES ]; then
383238438Sdteske				f_dialog_info "$msg_sorry_try_again"
384238438Sdteske				sleep 1
385238438Sdteske			fi
386238438Sdteske		fi
387238438Sdteske	done
388238438Sdteske
389238438Sdteske	#
390238438Sdteske	# If user exhausted number of allowed password tries, log
391238438Sdteske	# the security event and exit immediately.
392238438Sdteske	#
393238438Sdteske	if [ $nfailures -ge $PASSWD_TRIES ]; then
394238438Sdteske		msg=$( printf "$msg_nfailed_attempts" "$nfailures" )
395238438Sdteske		logger -p auth.notice -t sudo " " \
396238438Sdteske			"${SUDO_USER:-$USER} : $msg" \
397238438Sdteske			"; TTY=$(tty)"               \
398238438Sdteske			"; PWD=$PWD"                 \
399238438Sdteske			"; USER=root"                \
400238438Sdteske			"; COMMAND=$0"
401238438Sdteske		f_die 1 "sudo: $message"
402238438Sdteske	fi
403238438Sdteske}
404238438Sdteske
405238438Sdteske# f_mustberoot_init
406238438Sdteske#
407238438Sdteske# If not already root, make the switch to root by re-executing ourselves via
408238438Sdteske# sudo(8) using user-supplied credentials.
409238438Sdteske#
410238438Sdteske# The following environment variables effect functionality:
411238438Sdteske#
412238438Sdteske# 	SECURE        Either NULL or Non-NULL. If given a value will indicate
413238438Sdteske# 	              that (while running as root) sudo(8) authentication is
414238438Sdteske# 	              required to proceed.
415238438Sdteske#
416238438Sdteskef_mustberoot_init()
417238438Sdteske{
418238438Sdteske	if [ "$(id -u)" != "0" -a ! "$SECURE" ]; then
419238438Sdteske		f_become_root_via_sudo
420238438Sdteske	elif [ "$SECURE" ]; then
421238438Sdteske		f_authenticate_some_user
422238438Sdteske	fi
423238438Sdteske}
424238438Sdteske
425244675Sdteske############################################################ MAIN
426244675Sdteske
427244675Sdteskef_dprintf "%s: Successfully loaded." mustberoot.subr
428244675Sdteske
429238438Sdteskefi # ! $_MUSTBEROOT_SUBR
430