Home | History | Annotate | Line # | Download | only in hooks
      1 #!/bin/sh
      2 # SPDX-License-Identifier: BSD-2-Clause
      3 # Copyright (c) 2017-2022 Roy Marples <roy (at] marples.name>
      4 
      5 # dhcpcd client configuration script 
      6 
      7 # Handy variables and functions for our hooks to use
      8 ifname="$interface${protocol+.}$protocol"
      9 from=from
     10 signature_base="# Generated by dhcpcd"
     11 signature="$signature_base $from $ifname"
     12 signature_base_end="# End of dhcpcd"
     13 signature_end="$signature_base_end $from $ifname"
     14 state_dir=/var/run/dhcpcd/hook-state
     15 _detected_init=false
     16 
     17 : ${if_up:=false}
     18 : ${if_down:=false}
     19 : ${syslog_debug:=false}
     20 
     21 # Ensure that all arguments are unique
     22 uniqify()
     23 {
     24 	result=
     25 	for i do
     26 		case " $result " in
     27 			*" $i "*);;
     28 			*) result="$result${result:+ }$i";;
     29 		esac
     30 	done
     31 	printf '%s\n' "$result"
     32 }
     33 
     34 # List interface config files in a directory.
     35 # If dhcpcd is running as a single instance then it will have a list of
     36 # interfaces in the preferred order.
     37 # Otherwise we just use what we have.
     38 list_interfaces()
     39 {
     40 	ifaces=
     41 	for i in $interface_order; do
     42 		for x in "$1"/$i.*; do
     43 			[ -f "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}"
     44 		done
     45 	done
     46 	for x in "$1"/*; do
     47 		[ -f "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}"
     48 	done
     49 	uniqify $ifaces
     50 }
     51 
     52 # Trim function
     53 trim()
     54 {
     55 	var="$*"
     56 	var=${var#"${var%%[![:space:]]*}"}
     57 	var=${var%"${var##*[![:space:]]}"}
     58 	if [ -z "$var" ]; then
     59 		# So it seems our shell doesn't support wctype(3) patterns
     60 		# Fall back to sed
     61 		var=$(printf '%s\n' "$*" | sed -e 's/^[[:space:]]*//;s/[[:space:]]*$//')
     62 	fi
     63 	printf %s "$var"
     64 }
     65 
     66 # We normally use sed to extract values using a key from a list of files
     67 # but sed may not always be available at the time.
     68 key_get_value()
     69 {
     70 	key="$1"
     71 	shift
     72 
     73 	if command -v sed >/dev/null 2>&1; then
     74 		sed -n "s/^$key//p" $@
     75 	else
     76 		for x do
     77 			while read line; do
     78 				case "$line" in
     79 				"$key"*) printf '%s\n' "${line##$key}";;
     80 				esac
     81 			done < "$x"
     82 		done
     83 	fi
     84 }
     85 
     86 # We normally use sed to remove markers from a configuration file
     87 # but sed may not always be available at the time.
     88 remove_markers()
     89 {
     90 	m1="$1"
     91 	m2="$2"
     92 	in_marker=0
     93 
     94 	shift; shift
     95 	if command -v sed >/dev/null 2>&1; then
     96 		sed "/^$m1/,/^$m2/d" $@
     97 	else
     98 		for x do
     99 			while read line; do
    100 				case "$line" in
    101 				"$m1"*) in_marker=1;;
    102 				"$m2"*) in_marker=0;;
    103 				*) [ $in_marker = 0 ] && printf '%s\n' "$line";;
    104 				esac
    105 			done < "$x"
    106 		done
    107 	fi
    108 }
    109 
    110 # Compare two files.
    111 comp_file()
    112 {
    113 	[ -e "$1" ] && [ -e "$2" ] || return 1
    114 
    115 	if command -v cmp >/dev/null 2>&1; then
    116 		cmp -s "$1" "$2"
    117 	elif command -v diff >/dev/null 2>&1; then
    118 		diff -q "$1" "$2" >/dev/null
    119 	else
    120 		# Hopefully we're only working on small text files ...
    121 		[ "$(cat "$1")" = "$(cat "$2")" ]
    122 	fi
    123 }
    124 
    125 # Compare two files.
    126 # If different, replace first with second otherwise remove second.
    127 change_file()
    128 {
    129 	if [ -e "$1" ]; then
    130 		if comp_file "$1" "$2"; then
    131 			rm -f "$2"
    132 			return 1
    133 		fi
    134 	fi
    135 	cat "$2" > "$1"
    136 	rm -f "$2"
    137 	return 0
    138 }
    139 
    140 # Compare two files.
    141 # If different, copy or link depending on target type
    142 copy_file()
    143 {
    144 	if [ -h "$2" ]; then
    145 		[ "$(readlink "$2")" = "$1" ] && return 1
    146 		ln -sf "$1" "$2"
    147 	else
    148 		comp_file "$1" "$2" && return 1
    149 		cat "$1" >"$2"
    150 	fi
    151 }
    152 
    153 # Save a config file
    154 save_conf()
    155 {
    156 	if [ -f "$1" ]; then
    157 		rm -f "$1-pre.$interface"
    158 		cat "$1" > "$1-pre.$interface"
    159 	fi
    160 }
    161 
    162 # Restore a config file
    163 restore_conf()
    164 {
    165 	[ -f "$1-pre.$interface" ] || return 1
    166 	cat "$1-pre.$interface" > "$1"
    167 	rm -f "$1-pre.$interface"
    168 }
    169 
    170 # Write a syslog entry
    171 syslog()
    172 {
    173 	lvl="$1"
    174 
    175 	if [ "$lvl" = debug ]; then
    176 		${syslog_debug} || return 0
    177 	fi
    178 	[ -n "$lvl" ] && shift
    179 	[ -n "$*" ] || return 0
    180 	case "$lvl" in
    181 	err|error)	printf '%s\n' "$interface: $*" >&2;;
    182 	*)		printf '%s\n' "$interface: $*";;
    183 	esac
    184 	if command -v logger >/dev/null 2>&1; then
    185 		logger -i -p daemon."$lvl" -t dhcpcd-run-hooks "$interface: $*"
    186 	fi
    187 }
    188 
    189 # Check for a valid name as per RFC952 and RFC1123 section 2.1
    190 valid_domainname()
    191 {
    192 	name="$1"
    193 	[ -z "$name" ] || [ ${#name} -gt 255 ] && return 1
    194 	
    195 	while [ -n "$name" ]; do
    196 		label="${name%%.*}"
    197 		[ -z "$label" ] || [ ${#label} -gt 63 ] && return 1
    198 		case "$label" in
    199 		-*|_*|*-|*_)		return 1;;
    200 		*[![:alnum:]_-]*)	return 1;;
    201 		"$name")		return 0;;
    202 		esac
    203 		name="${name#*.}"
    204 	done
    205 	return 0
    206 }
    207 
    208 valid_domainname_list()
    209 {
    210 	for name do
    211 		valid_domainname "$name" || return $?
    212 	done
    213 	return 0
    214 }
    215 
    216 # With the advent of alternative init systems, it's possible to have
    217 # more than one installed. So we need to try to guess what one we're
    218 # using unless overridden by configure.
    219 detect_init()
    220 {
    221 	_service_exists=""
    222 	_service_cmd=""
    223 	_service_status=""
    224 
    225 	[ -n "$_service_cmd" ] && return 0
    226 
    227 	if $_detected_init; then
    228 		[ -n "$_service_cmd" ]
    229 		return $?
    230 	fi
    231 
    232 	# Detect the running init system.
    233 	# As systemd and OpenRC can be installed on top of legacy init
    234 	# systems we try to detect them first.
    235 	status=""
    236 	: ${status:=status}
    237 	if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then
    238 		_service_exists="/bin/systemctl --quiet is-enabled \$1.service"
    239 		_service_status="/bin/systemctl --quiet is-active \$1.service"
    240 		_service_cmd="/bin/systemctl \$2 --no-block \$1.service"
    241 	elif [ -x /usr/bin/systemctl ] && [ -S /run/systemd/private ]; then
    242 		_service_exists="/usr/bin/systemctl --quiet is-enabled \$1.service"
    243 		_service_status="/usr/bin/systemctl --quiet is-active \$1.service"
    244 		_service_cmd="/usr/bin/systemctl \$2 --no-block \$1.service"
    245 	elif [ -x /sbin/rc-service ] &&
    246 	     { [ -s /libexec/rc/init.d/softlevel ] ||
    247 	     [ -s /run/openrc/softlevel ]; }
    248 	then
    249 		_service_exists="/sbin/rc-service -e \$1"
    250 		_service_cmd="/sbin/rc-service \$1 -- -D \$2"
    251 	elif [ -x /usr/sbin/invoke-rc.d ]; then
    252 		_service_exists="/usr/sbin/invoke-rc.d --query --quiet \$1 start >/dev/null 2>&1 || [ \$? = 104 ]"
    253 		_service_cmd="/usr/sbin/invoke-rc.d \$1 \$2"
    254 	elif [ -x /sbin/service ]; then
    255 		_service_exists="/sbin/service \$1 >/dev/null 2>&1"
    256 		_service_cmd="/sbin/service \$1 \$2"
    257 	elif [ -x /usr/sbin/service ]; then
    258 		_service_exists="/usr/sbin/service \$1 $status >/dev/null 2>&1"
    259 		_service_cmd="/usr/sbin/service \$1 \$2"
    260 	elif [ -x /bin/sv ]; then
    261 		_service_exists="/bin/sv status \$1 >/dev/null 2>&1"
    262 		_service_cmd="/bin/sv \$2 \$1"
    263 	elif [ -x /usr/bin/sv ]; then
    264 		_service_exists="/usr/bin/sv status \$1 >/dev/null 2>&1"
    265 		_service_cmd="/usr/bin/sv \$2 \$1"
    266 	elif [ -e /etc/slackware-version ] && [ -d /etc/rc.d ]; then
    267 		_service_exists="[ -x /etc/rc.d/rc.\$1 ]"
    268 		_service_cmd="/etc/rc.d/rc.\$1 \$2"
    269 		_service_status="/etc/rc.d/rc.\$1 status >/dev/null 2>&1"
    270 	else
    271 		for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
    272 			if [ -d $x ]; then
    273 				_service_exists="[ -x $x/\$1 ]"
    274 				_service_cmd="$x/\$1 \$2"
    275 				_service_status="$x/\$1 $status >/dev/null 2>&1"
    276 				break
    277 			fi
    278 		done
    279 		if [ -e /etc/arch-release ]; then
    280 			_service_status="[ -e /var/run/daemons/\$1 ]"
    281 		elif [ "$x" = "/etc/rc.d" ] && [ -e /etc/rc.d/rc.subr ]; then
    282 			_service_status="$x/\$1 check >/dev/null 2>&1"
    283 		fi
    284 	fi
    285 
    286 	_detected_init=true
    287 	if [ -z "$_service_cmd" ]; then
    288 		syslog err "could not detect a useable init system"
    289 		return 1
    290 	fi
    291 	return 0
    292 }
    293 
    294 # Check a system service exists 
    295 service_exists()
    296 {
    297 	if [ -z "$_service_exists" ]; then
    298 		detect_init || return 1
    299 	fi
    300 	eval $_service_exists
    301 }
    302 
    303 # Send a command to a system service
    304 service_cmd()
    305 {
    306 	if [ -z "$_service_cmd" ]; then
    307 		detect_init || return 1
    308 	fi
    309 	eval $_service_cmd
    310 }
    311 
    312 # Send a command to a system service if it is running
    313 service_status()
    314 {
    315 	if [ -z "$_service_cmd" ]; then
    316 		detect_init || return 1
    317 	fi
    318 	if [ -n "$_service_status" ]; then
    319 		eval $_service_status
    320 	else
    321 		service_command $1 status >/dev/null 2>&1
    322 	fi
    323 }
    324 
    325 # Handy macros for our hooks
    326 service_command()
    327 {
    328 	service_exists $1 && service_cmd $1 $2
    329 }
    330 service_condcommand()
    331 {
    332 	service_exists $1 && service_status $1 && service_cmd $1 $2
    333 }
    334 
    335 # We source each script into this one so that scripts run earlier can
    336 # remove variables from the environment so later scripts don't see them.
    337 # Thus, the user can create their dhcpcd.enter/exit-hook script to configure
    338 # /etc/resolv.conf how they want and stop the system scripts ever updating it.
    339 for hook in \
    340 	/etc/dhcpcd.enter-hook \
    341 	/libexec/dhcpcd-hooks/* \
    342 	/etc/dhcpcd.exit-hook
    343 do
    344 	case "$hook" in
    345 		*/*~)	continue;;
    346 	esac
    347 	for skip in $skip_hooks; do
    348 		case "$hook" in
    349 			*/"$skip")			continue 2;;
    350 			*/[0-9][0-9]"-$skip")		continue 2;;
    351 			*/[0-9][0-9]"-$skip.sh")	continue 2;;
    352 		esac
    353 	done
    354 	if [ -f "$hook" ]; then
    355 		. "$hook"
    356 	fi
    357 done
    358