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