mustberoot.subr revision 251190
1226031Sstasif [ ! "$_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 251190 2013-05-31 19:07:17Z 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 height width rows
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		eval f_dialog_menu_size height width rows \
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\"                   \
116			$height $width $rows               \
117			$menu_list                         \
118			2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
119		)
120		retval=$?
121		setvar DIALOG_MENU_$$ "$dialog_menu"
122		mtag=$( f_dialog_menutag )
123
124		[ $retval -eq 0 ] || f_die
125
126		case "$mtag" in
127		X) # Cancel/Exit
128		   f_die ;;
129		1) # Always try sudo(8) when run as $user
130			local err
131			if ! err=$( touch "$checkpath" 2>&1 ); then
132				f_dialog_msgbox "$err"
133			else
134				f_show_msg "$msg_created_path" "$checkpath"
135			fi
136		esac
137	else
138		#
139		# This user has created the path signing-off on sudo(8)-use
140		# but let's still give them a short/quick/unobtrusive reminder
141		#
142		f_dialog_info "$msg_becoming_root_via_sudo"
143		[ "$USE_XDIALOG" ] || sleep 0.6
144	fi
145
146	#
147	# Check sudo(8) access before prompting for password.
148	#
149	:| sudo -S -v 2> /dev/null
150	if [ $? -ne $SUCCESS ]; then
151		#
152		# sudo(8) access denied. Prompt for their password.
153		#
154		msg="$msg_please_enter_password"
155		hline="$hline_alnum_punc_tab_enter"
156		f_dialog_inputbox_size height width \
157		                       "$DIALOG_TITLE"     \
158		                       "$DIALOG_BACKTITLE" \
159		                       "$msg"              \
160		                       "$hline"
161
162		#
163		# Continue prompting until they either Cancel, succeed
164		# or exceed the number of allowed failures.
165		#
166		local password nfailures=0 retval
167		while [ $nfailures -lt $PASSWD_TRIES ]; do
168			if [ "$USE_XDIALOG" ]; then
169				password=$( $DIALOG \
170					--title "$DIALOG_TITLE"         \
171					--backtitle "$DIALOG_BACKTITLE" \
172					--hline "$hline"                \
173					--ok-label "$msg_ok"            \
174					--cancel-label "$msg_cancel"    \
175					--password --inputbox "$msg"    \
176					$height $width                  \
177					2>&1 > /dev/null )
178				retval=$?
179
180				# Catch X11-related errors
181				[ $retval -eq 255 ] &&
182					f_die $retval "$password"
183			else
184				local dialog_inputbox
185				dialog_inputbox=$( $DIALOG \
186					--title "$DIALOG_TITLE"         \
187					--backtitle "$DIALOG_BACKTITLE" \
188					--hline "$hline"                \
189					--ok-label "$msg_ok"            \
190					--cancel-label "$msg_cancel"    \
191					--insecure                      \
192					--passwordbox "$msg"            \
193					$height $width                  \
194					2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
195				)
196				retval=$?
197				setvar DIALOG_INPUTBOX_$$ "$dialog_inputbox"
198				password=$( f_dialog_inputstr )
199			fi
200
201			# Exit if the user cancelled.
202			[ $retval -eq $SUCCESS ] || exit $retval
203
204			#
205			# Validate sudo(8) credentials
206			#
207			sudo -S -v 2> /dev/null <<-EOF
208			$password
209			EOF
210			retval=$?
211			unset password # scrub memory
212			if [ $retval -eq $SUCCESS ]; then
213				# Access granted...
214				break
215			else
216				# Access denied...
217				nfailures=$(( $nfailures + 1 ))
218
219				# introduce a short delay
220				if [ $nfailures -lt $PASSWD_TRIES ]; then
221					f_dialog_info "$msg_sorry_try_again"
222					sleep 1
223				fi
224			fi
225		done
226
227		#
228		# If user exhausted number of allowed password tries, log
229		# the security event and exit immediately.
230		#
231		if [ $nfailures -ge $PASSWD_TRIES ]; then
232			msg=$( printf "$msg_nfailed_attempts" "$nfailures" )
233			logger -p auth.notice -t sudo " " \
234				"$USER : $msg" \
235				"; TTY=$(tty)" \
236				"; PWD=$PWD"   \
237				"; USER=root"  \
238				"; COMMAND=$0"
239			f_die 1 "sudo: $msg"
240		fi
241	fi
242
243	# Use xauth(1) to grant root the ability to use this X11/SSH session
244	if [ "$USE_XDIALOG" -a "$SSH_CONNECTION" -a "$DISPLAY" ]; then
245		f_have xauth || f_die 1 \
246			"$msg_no_such_file_or_directory" "$pgm" "xauth"
247		local HOSTNAME displaynum
248		HOSTNAME=$(hostname)
249		displaynum="${DISPLAY#*:}"
250		xauth -f ~/.Xauthority extract - $HOSTNAME/unix:$displaynum \
251			$HOSTNAME:$displaynum | sudo sh -c 'xauth -ivf \
252			~root/.Xauthority merge - > /dev/null 2>&1'
253	fi
254
255	# Re-execute ourselves with sudo(8)
256	f_dprintf "%s: Becoming root via sudo(8)..." mustberoot.subr
257	if [ $ARGC -gt 0 ]; then
258		exec sudo "$0" $ARGV
259	else
260		exec sudo "$0"
261	fi
262	exit $? # Never reached unless error
263}
264
265# f_authenticate_some_user
266#
267# Only used if running as root and requires X11 (see USE_XDIALOG below).
268# Prompts the user to enter a username and password to be authenticated via
269# sudo(8) to proceed.
270#
271# The following environment variables effect functionality:
272#
273# 	USE_XDIALOG   Either NULL or Non-NULL. If given a value will indicate
274# 	              that Xdialog(1) should be used instead of dialog(1).
275#
276f_authenticate_some_user()
277{
278	local msg hline height width
279
280	f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm"
281
282	#
283	# Secure-mode has been requested.
284	#
285
286	[ "$USE_XDIALOG" ] || f_die 1 "$msg_secure_mode_requires_x11"
287	[ "$(id -u)" = "0" ] || f_die 1 "$msg_secure_mode_requires_root"
288
289	#
290	# Prompt for sudo(8) credentials.
291	#
292
293	msg="$msg_please_enter_username_password"
294	hline="$hline_alnum_punc_tab_enter"
295	f_xdialog_2inputsbox_size height width \
296	                          "$DIALOG_TITLE"      \
297	                          "$DIALOG_BACKTITLE"  \
298	                          "$msg"               \
299	                          "$field_username" "" \
300	                          "$field_password" ""
301	height=$(( $height + 2 )) # Add height for --password
302
303	#
304	# Continue prompting until they either Cancel, succeed or exceed the
305	# number of allowed failures.
306	#
307	local user_pass nfailures=0 retval
308	while [ $nfailures -lt $PASSWD_TRIES ]; do
309		user_pass=$( $DIALOG \
310			--title "$DIALOG_TITLE"         \
311			--backtitle "$DIALOG_BACKTITLE" \
312			--hline "$hline"                \
313			--ok-label "$msg_ok"            \
314			--cancel-label "$msg_cancel"    \
315			--password --2inputsbox "$msg"  \
316			$height $width                  \
317			"$field_username" ""            \
318			"$field_password" ""            \
319			2>&1 > /dev/null )
320		retval=$?
321
322		# Catch X11-related errors
323		[ $retval -eq 255 ] && f_die $retval "$user_pass"
324
325		# Exit if the user cancelled.
326		[ $retval -eq $SUCCESS ] || exit $retval
327
328		#
329		# Make sure the user exists and is non-root
330		#
331		local user password
332		user="${user_pass%%/*}"
333		password="${user_pass#*/}"
334		unset user_pass # scrub memory
335		if [ ! "$user" ]; then
336			nfailures=$(( $nfailures + 1 ))
337			f_dialog_msgbox "$msg_no_username"
338			continue
339		fi
340		if [ ! "$SECURE_ALLOW_ROOT" ]; then
341			case "$user" in
342			root|toor)
343				nfailures=$(( $nfailures + 1 ))
344				f_show_msg "$msg_user_disallowed" "$user"
345				continue
346			esac
347		fi
348		if ! f_quietly id "$user"; then
349			nfailures=$(( $nfailures + 1 ))
350			if [ "$SECURE_DIVULGE_UNKNOWN_USER" ]; then
351				f_show_msg "$msg_unknown_user" "$user"
352			elif [ $nfailures -lt $PASSWD_TRIES ]; then
353				f_dialog_info "$msg_sorry_try_again"
354				sleep 1
355			fi
356			continue
357		fi
358
359		#
360		# Validate sudo(8) credentials for given user
361		#
362		su -m "$user" <<-EOF
363			sh <<EOS
364				sudo -k
365				sudo -S -v 2> /dev/null <<EOP
366				$password
367				EOP
368			EOS
369		EOF
370		retval=$?
371		unset user
372		unset password # scrub memory
373
374		if [ $retval -eq $SUCCESS ]; then
375			# Access granted...
376			break
377		else
378			# Access denied...
379			nfailures=$(( $nfailures + 1 ))
380
381			# introduce a short delay
382			if [ $nfailures -lt $PASSWD_TRIES ]; then
383				f_dialog_info "$msg_sorry_try_again"
384				sleep 1
385			fi
386		fi
387	done
388
389	#
390	# If user exhausted number of allowed password tries, log
391	# the security event and exit immediately.
392	#
393	if [ $nfailures -ge $PASSWD_TRIES ]; then
394		msg=$( printf "$msg_nfailed_attempts" "$nfailures" )
395		logger -p auth.notice -t sudo " " \
396			"${SUDO_USER:-$USER} : $msg" \
397			"; TTY=$(tty)"               \
398			"; PWD=$PWD"                 \
399			"; USER=root"                \
400			"; COMMAND=$0"
401		f_die 1 "sudo: $message"
402	fi
403}
404
405# f_mustberoot_init
406#
407# If not already root, make the switch to root by re-executing ourselves via
408# sudo(8) using user-supplied credentials.
409#
410# The following environment variables effect functionality:
411#
412# 	SECURE        Either NULL or Non-NULL. If given a value will indicate
413# 	              that (while running as root) sudo(8) authentication is
414# 	              required to proceed.
415#
416f_mustberoot_init()
417{
418	if [ "$(id -u)" != "0" -a ! "$SECURE" ]; then
419		f_become_root_via_sudo
420	elif [ "$SECURE" ]; then
421		f_authenticate_some_user
422	fi
423}
424
425############################################################ MAIN
426
427f_dprintf "%s: Successfully loaded." mustberoot.subr
428
429fi # ! $_MUSTBEROOT_SUBR
430