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