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