mustberoot.subr revision 240783
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: head/usr.sbin/bsdconfig/share/mustberoot.subr 240783 2012-09-21 19:03:25Z dteske $
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	# Check sudo(8) access before prompting for password.
87	#
88	:| sudo -S -v 2> /dev/null
89	if [ $? -ne $SUCCESS ]; then
90		#
91		# sudo(8) access denied. Prompt for their password.
92		#
93		msg="$msg_please_enter_password"
94		hline="$hline_alnum_punc_tab_enter"
95		size=$( f_dialog_inputbox_size \
96		        	"$DIALOG_TITLE"     \
97		        	"$DIALOG_BACKTITLE" \
98		        	"$msg"              \
99		        	"$hline"            )
100
101		#
102		# Continue prompting until they either Cancel, succeed
103		# or exceed the number of allowed failures.
104		#
105		local password nfailures=0 retval
106		while [ $nfailures -lt $PASSWD_TRIES ]; do
107			if [ "$USE_XDIALOG" ]; then
108				password=$( $DIALOG \
109					--title "$DIALOG_TITLE"            \
110					--backtitle "$DIALOG_BACKTITLE"    \
111					--hline "$hline"                   \
112					--ok-label "$msg_ok"               \
113					--cancel-label "$msg_cancel"       \
114					--password --inputbox "$msg" $size \
115					2>&1 > /dev/null )
116				retval=$?
117
118				# Catch X11-related errors
119				[ $retval -eq 255 ] &&
120					f_die $retval "$password"
121			else
122				local dialog_inputbox
123				dialog_inputbox=$( $DIALOG \
124					--title "$DIALOG_TITLE"         \
125					--backtitle "$DIALOG_BACKTITLE" \
126					--hline "$hline"                \
127					--ok-label "$msg_ok"            \
128					--cancel-label "$msg_cancel"    \
129					--insecure                      \
130					--passwordbox "$msg" $size      \
131					2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
132				)
133				retval=$?
134				setvar DIALOG_INPUTBOX_$$ "$dialog_inputbox"
135				password=$( f_dialog_inputstr )
136			fi
137
138			# Exit if the user cancelled.
139			[ $retval -eq $SUCCESS ] || exit $retval
140
141			#
142			# Validate sudo(8) credentials
143			#
144			sudo -S -v 2> /dev/null <<-EOF
145			$password
146			EOF
147			retval=$?
148			unset password # scrub memory
149			if [ $retval -eq $SUCCESS ]; then
150				# Access granted...
151				break
152			else
153				# Access denied...
154				nfailures=$(( $nfailures + 1 ))
155
156				# introduce a short delay
157				if [ $nfailures -lt $PASSWD_TRIES ]; then
158					f_dialog_info "$msg_sorry_try_again"
159					sleep 1
160				fi
161			fi
162		done
163
164		#
165		# If user exhausted number of allowed password tries, log
166		# the security event and exit immediately.
167		#
168		if [ $nfailures -ge $PASSWD_TRIES ]; then
169			msg=$( printf "$msg_nfailed_attempts" "$nfailures" )
170			logger -p auth.notice -t sudo " " \
171				"$USER : $msg" \
172				"; TTY=$(tty)" \
173				"; PWD=$PWD"   \
174				"; USER=root"  \
175				"; COMMAND=$0"
176			f_die 1 "sudo: $msg"
177		fi
178	fi
179
180	# Use xauth(1) to grant root the ability to use this X11/SSH session
181	if [ "$USE_XDIALOG" -a "$SSH_CONNECTION" -a "$DISPLAY" ]; then
182		f_have xauth || f_die 1 \
183			"$msg_no_such_file_or_directory" "$pgm" "xauth"
184		local HOSTNAME displaynum
185		HOSTNAME=$(hostname)
186		displaynum="${DISPLAY#*:}"
187		xauth -f ~/.Xauthority extract - $HOSTNAME/unix:$displaynum \
188			$HOSTNAME:$displaynum | sudo sh -c 'xauth -ivf \
189			~root/.Xauthority merge - > /dev/null 2>&1'
190	fi
191
192	# Re-execute ourselves with sudo(8)
193	if [ $ARGC -gt 0 ]; then
194		exec sudo "$0" $ARGV
195	else
196		exec sudo "$0"
197	fi
198	exit $? # Never reached unless error
199}
200
201# f_authenticate_some_user
202#
203# Only used if running as root and requires X11 (see USE_XDIALOG below).
204# Prompts the user to enter a username and password to be authenticated via
205# sudo(8) to proceed.
206#
207# The following environment variables effect functionality:
208#
209# 	USE_XDIALOG   Either NULL or Non-NULL. If given a value will indicate
210# 	              that Xdialog(1) should be used instead of dialog(1).
211#
212f_authenticate_some_user()
213{
214	local msg hline size width height
215
216	f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm"
217
218	#
219	# Secure-mode has been requested.
220	#
221 
222	[ "$USE_XDIALOG" ] || f_die 1 "$msg_secure_mode_requires_x11"
223	[ "$(id -u)" = "0" ] || f_die 1 "$msg_secure_mode_requires_root"
224
225	#
226	# Prompt for sudo(8) credentials.
227	#
228
229	msg="$msg_please_enter_username_password"
230	hline="$hline_alnum_punc_tab_enter"
231	size=$( f_xdialog_2inputsbox_size \
232	        	"$DIALOG_TITLE"      \
233	        	"$DIALOG_BACKTITLE"  \
234	        	"$msg"               \
235	        	"$field_username" "" \
236	        	"$field_password" "" )
237	width="${size##*[$IFS]}"
238	height="${size%%[$IFS]*}"
239	height=$(( $height + 2 )) # Add height for --password
240
241	#
242	# Continue prompting until they either Cancel, succeed or exceed the
243	# number of allowed failures.
244	#
245	local user_pass nfailures=0 retval
246	while [ $nfailures -lt $PASSWD_TRIES ]; do
247		user_pass=$( $DIALOG \
248			--title "$DIALOG_TITLE"         \
249			--backtitle "$DIALOG_BACKTITLE" \
250			--hline "$hline"                \
251			--ok-label "$msg_ok"            \
252			--cancel-label "$msg_cancel"    \
253			--password --2inputsbox "$msg"  \
254			$height $width                  \
255			"$field_username" ""            \
256			"$field_password" ""            \
257			2>&1 > /dev/null )
258		retval=$?
259
260		# Catch X11-related errors
261		[ $retval -eq 255 ] && f_die $retval "$user_pass"
262
263		# Exit if the user cancelled.
264		[ $retval -eq $SUCCESS ] || exit $retval
265
266		#
267		# Make sure the user exists and is non-root
268		#
269		local user password
270		user="${user_pass%%/*}"
271		password="${user_pass#*/}"
272		unset user_pass # scrub memory
273		if [ ! "$user" ]; then
274			nfailures=$(( $nfailures + 1 ))
275			f_dialog_msgbox "$msg_no_username"
276			continue
277		fi
278		if [ ! "$SECURE_ALLOW_ROOT" ]; then
279			case "$user" in
280			root|toor)
281				nfailures=$(( $nfailures + 1 ))
282				f_dialog_msgbox "$( printf \
283					"$msg_user_disallowed" "$user" )"
284				continue
285			esac
286		fi
287		if ! f_quietly id "$user"; then
288			nfailures=$(( $nfailures + 1 ))
289			if [ "$SECURE_DIVULGE_UNKNOWN_USER" ]; then
290				f_dialog_msgbox "$( printf \
291					"$msg_unknown_user" "$user" )"
292			elif [ $nfailures -lt $PASSWD_TRIES ]; then
293				f_dialog_info "$msg_sorry_try_again"
294				sleep 1
295			fi
296			continue
297		fi
298
299		#
300		# Validate sudo(8) credentials for given user
301		#
302		su -m "$user" <<-EOF
303			sh <<EOS
304				sudo -k
305				sudo -S -v 2> /dev/null <<EOP
306				$password
307				EOP
308			EOS
309		EOF
310		retval=$?
311		unset user
312		unset password # scrub memory
313
314		if [ $retval -eq $SUCCESS ]; then
315			# Access granted...
316			break
317		else
318			# Access denied...
319			nfailures=$(( $nfailures + 1 ))
320
321			# introduce a short delay
322			if [ $nfailures -lt $PASSWD_TRIES ]; then
323				f_dialog_info "$msg_sorry_try_again"
324				sleep 1
325			fi
326		fi
327	done
328
329	#
330	# If user exhausted number of allowed password tries, log
331	# the security event and exit immediately.
332	#
333	if [ $nfailures -ge $PASSWD_TRIES ]; then
334		msg=$( printf "$msg_nfailed_attempts" "$nfailures" )
335		logger -p auth.notice -t sudo " " \
336			"${SUDO_USER:-$USER} : $msg" \
337			"; TTY=$(tty)"               \
338			"; PWD=$PWD"                 \
339			"; USER=root"                \
340			"; COMMAND=$0"
341		f_die 1 "sudo: $message"
342	fi
343}
344
345# f_mustberoot_init
346#
347# If not already root, make the switch to root by re-executing ourselves via
348# sudo(8) using user-supplied credentials.
349#
350# The following environment variables effect functionality:
351#
352# 	SECURE        Either NULL or Non-NULL. If given a value will indicate
353# 	              that (while running as root) sudo(8) authentication is
354# 	              required to proceed.
355#
356f_mustberoot_init()
357{
358	if [ "$(id -u)" != "0" -a ! "$SECURE" ]; then
359		f_become_root_via_sudo
360	elif [ "$SECURE" ]; then
361		f_authenticate_some_user
362	fi
363}
364
365fi # ! $_MUSTBEROOT_SUBR
366