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