xref: /freebsd/sbin/dhclient/dhclient-script (revision 8a7df6c58d549bd2eaa5cad30b2ef93c3eca3c18)
1#!/bin/sh
2#
3# $OpenBSD: dhclient-script,v 1.6 2004/05/06 18:22:41 claudio Exp $
4#
5# Copyright (c) 2003 Kenneth R Westerback <krw@openbsd.org>
6#
7# Permission to use, copy, modify, and distribute this software for any
8# purpose with or without fee is hereby granted, provided that the above
9# copyright notice and this permission notice appear in all copies.
10#
11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18#
19#
20
21ARP=/usr/sbin/arp
22HOSTNAME=/bin/hostname
23IFCONFIG='/sbin/ifconfig -n'
24
25LOCALHOST=127.0.0.1
26
27if [ -x /usr/bin/logger ]; then
28	LOGGER="/usr/bin/logger -s -p user.notice -t dhclient"
29else
30	LOGGER=echo
31fi
32
33#
34# Helper functions that implement common actions.
35#
36
37check_hostname() {
38	current_hostname=`$HOSTNAME`
39	if [ -z "$current_hostname" ]; then
40		$LOGGER "New Hostname ($interface): $new_host_name"
41		$HOSTNAME $new_host_name
42	elif [ "$current_hostname" = "$old_host_name" -a \
43	       "$new_host_name" != "$old_host_name" ]; then
44		$LOGGER "New Hostname ($interface): $new_host_name"
45		$HOSTNAME $new_host_name
46	fi
47}
48
49arp_flush() {
50	arp -an -i $interface | \
51		sed -n -e 's/^.*(\(.*\)) at .*$/arp -d \1/p' | \
52		sh >/dev/null 2>&1
53}
54
55delete_old_address() {
56	eval "$IFCONFIG $interface inet -alias $old_ip_address $medium"
57}
58
59add_new_address() {
60	eval "$IFCONFIG $interface \
61		inet $new_ip_address \
62		netmask $new_subnet_mask \
63		broadcast $new_broadcast_address \
64		$medium"
65
66	$LOGGER "New IP Address ($interface): $new_ip_address"
67	$LOGGER "New Subnet Mask ($interface): $new_subnet_mask"
68	$LOGGER "New Broadcast Address ($interface): $new_broadcast_address"
69	$LOGGER "New Routers ($interface): $new_routers"
70}
71
72delete_old_alias() {
73	if [ -n "$alias_ip_address" ]; then
74		$IFCONFIG $interface inet -alias $alias_ip_address > /dev/null 2>&1
75		#route delete $alias_ip_address $LOCALHOST > /dev/null 2>&1
76	fi
77}
78
79add_new_alias() {
80	if [ -n "$alias_ip_address" ]; then
81		$IFCONFIG $interface inet alias $alias_ip_address netmask \
82		    $alias_subnet_mask
83		#route add $alias_ip_address $LOCALHOST
84	fi
85}
86
87add_captive_portal() {
88	# DHCP option 114 (RFC 8910), with the legacy DHCP option 160
89	# (RFC 7710) as a fallback.
90	captive_portal_url="${new_captive_portal_url:-$new_captive_portal_legacy}"
91
92	if [ -z "$captive_portal_url" ]; then
93		rm -f /var/run/captive-portal.$interface
94		return 0
95	fi
96
97	$LOGGER "New Captive Portal ($interface): $captive_portal_url"
98
99	captive_portal_tmp=/var/run/captive-portal.${interface}.tmp
100	if ! (umask 022; echo "$captive_portal_url" > $captive_portal_tmp); then
101		rm -f $captive_portal_tmp
102		return 1
103	fi
104	if ! mv $captive_portal_tmp /var/run/captive-portal.$interface; then
105		rm -f $captive_portal_tmp
106		return 1
107	fi
108	return 0
109}
110
111delete_captive_portal() {
112	rm -f /var/run/captive-portal.$interface
113}
114
115fill_classless_routes() {
116	set $1
117	while [ $# -ge 5 ]; do
118		if [ $1 -eq 0 ]; then
119			route="default"
120		elif [ $1 -le 8 ]; then
121			route="$2.0.0.0/$1"
122			shift
123		elif [ $1 -le 16 ]; then
124			route="$2.$3.0.0/$1"
125			shift; shift
126		elif [ $1 -le 24 ]; then
127			route="$2.$3.$4.0/$1"
128			shift; shift; shift
129		else
130			route="$2.$3.$4.$5/$1"
131			shift; shift; shift; shift
132		fi
133		shift
134		router="$1.$2.$3.$4"
135		classless_routes="$classless_routes $route $router"
136		shift; shift; shift; shift
137	done
138}
139
140delete_old_routes() {
141	#route delete "$old_ip_address" $LOCALHOST >/dev/null 2>&1
142	if [ -n "$old_classless_routes" ]; then
143		fill_classless_routes "$old_classless_routes"
144		set $classless_routes
145		while [ $# -gt 1 ]; do
146			route delete "$1" "$2"
147			shift; shift
148		done
149		return 0;
150	fi
151
152	# If we supported multiple default routes, we'd be removing each
153	# one here.  We don't so just delete the default route if it's
154	# through our interface.
155	if is_default_interface; then
156		route delete default >/dev/null 2>&1
157	fi
158
159	if [ -n "$old_static_routes" ]; then
160		set $old_static_routes
161		while [ $# -gt 1 ]; do
162			route delete "$1" "$2"
163			shift; shift
164		done
165	fi
166
167	arp_flush
168}
169
170add_new_routes() {
171	#route add $new_ip_address $LOCALHOST >/dev/null 2>&1
172
173	# RFC 3442: If the DHCP server returns both a Classless Static
174	# Routes option and a Router option, the DHCP client MUST ignore
175	# the Router option.
176	#
177	# DHCP clients that support this option (Classless Static Routes)
178	# MUST NOT install the routes specified in the Static Routes
179	# option (option code 33) if both a Static Routes option and the
180	# Classless Static Routes option are provided.
181
182	if [ -n "$new_classless_routes" ]; then
183		fill_classless_routes "$new_classless_routes"
184		$LOGGER "New Classless Static Routes ($interface): $classless_routes"
185		set $classless_routes
186		while [ $# -gt 1 ]; do
187			if [ "0.0.0.0" = "$2" ]; then
188				route add "$1" -iface "$interface"
189			else
190				route add "$1" "$2"
191			fi
192			shift; shift
193		done
194		return
195	fi
196
197	for router in $new_routers; do
198		if is_default_interface; then
199
200			if [ "$new_ip_address" = "$router" ]; then
201				route add default -iface $router >/dev/null 2>&1
202			else
203				if [ "$new_subnet_mask" = "255.255.255.255" ]; then
204					route add "$router" -iface "$interface" >/dev/null 2>&1
205				fi
206
207				route add default $router >/dev/null 2>&1
208			fi
209		fi
210		# 2nd and subsequent default routers error out, so explicitly
211		# stop processing the list after the first one.
212		break
213	done
214
215	if [ -n "$new_static_routes" ]; then
216		$LOGGER "New Static Routes ($interface): $new_static_routes"
217		set $new_static_routes
218		while [ $# -gt 1 ]; do
219			route add $1 $2
220			shift; shift
221		done
222	fi
223}
224
225add_new_resolv_conf() {
226	# XXX Old code did not create/update resolv.conf unless both
227	# $new_domain_name and $new_domain_name_servers were provided.  PR
228	# #3135 reported some ISP's only provide $new_domain_name_servers and
229	# thus broke the script. This code creates the resolv.conf if either
230	# are provided.
231
232	local tmpres=/var/run/resolv.conf.${interface}
233	rm -f $tmpres
234
235	if [ -n "$new_domain_search" ]; then
236		echo "search $new_domain_search" >>$tmpres
237	elif [ -n "$new_domain_name" ]; then
238		echo "search $new_domain_name" >>$tmpres
239	fi
240
241	if [ -n "$new_domain_name_servers" ]; then
242		for nameserver in $new_domain_name_servers; do
243			echo "nameserver $nameserver" >>$tmpres
244		done
245	fi
246
247	if [ -f $tmpres ]; then
248		if [ -f /etc/resolv.conf.tail ]; then
249			cat /etc/resolv.conf.tail >>$tmpres
250		fi
251
252		case $resolvconf_enable in
253		# "no", "false", "off", or "0"
254		[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
255			# When resolv.conf is not changed actually, we don't
256			# need to update it.
257			# If /usr is not mounted yet, we cannot use cmp, then
258			# the following test fails.  In such case, we simply
259			# ignore an error and do update resolv.conf.
260			if cmp -s $tmpres /etc/resolv.conf; then
261				rm -f $tmpres
262				return 0
263			fi 2>/dev/null
264
265			# In case (e.g. during OpenBSD installs)
266			# /etc/resolv.conf is a symbolic link, take
267			# care to preserve the link and write the new
268			# data in the correct location.
269
270			if [ -f /etc/resolv.conf ]; then
271				cat /etc/resolv.conf > /etc/resolv.conf.save
272			fi
273			cat $tmpres > /etc/resolv.conf
274
275			# Try to ensure correct ownership and permissions.
276			chown -RL root:wheel /etc/resolv.conf
277			chmod -RL 644 /etc/resolv.conf
278			;;
279
280		*)
281			/sbin/resolvconf -a ${interface} < $tmpres
282			;;
283		esac
284
285		rm -f $tmpres
286
287		return 0
288	fi
289
290	return 1
291}
292
293# Must be used on exit.   Invokes the local dhcp client exit hooks, if any.
294exit_with_hooks() {
295	exit_status=$1
296	if [ -f /etc/dhclient-exit-hooks ]; then
297		. /etc/dhclient-exit-hooks
298	fi
299	# probably should do something with exit status of the local script
300	exit $exit_status
301}
302
303# Get the interface with the current ipv4 default route on it using only
304# commands that are available prior to /usr being mounted.
305is_default_interface()
306{
307	routeget="`route -n get -inet default`"
308	oldifs="$IFS"
309	IFS="
310"
311	defif=
312	for line in $routeget ; do
313		case $line in
314		*interface:*)
315			defif=${line##*: }
316			;;
317		esac
318	done
319	IFS=${oldifs}
320
321	if [ -z "$defif" -o "$defif" = "$interface" ]; then
322		return 0
323	else
324		return 1
325	fi
326}
327
328#
329# Start of active code.
330#
331
332# Invoke the local dhcp client enter hooks, if they exist.
333if [ -f /etc/dhclient-enter-hooks ]; then
334	exit_status=0
335	. /etc/dhclient-enter-hooks
336	# allow the local script to abort processing of this state
337	# local script must set exit_status variable to nonzero.
338	if [ $exit_status -ne 0 ]; then
339		exit $exit_status
340	fi
341fi
342
343: ${resolvconf_enable="YES"}
344
345case $reason in
346MEDIUM)
347	eval "$IFCONFIG $interface $medium"
348	sleep 1
349	;;
350
351PREINIT)
352	delete_old_alias
353	eval "$IFCONFIG $interface up"
354	;;
355
356ARPCHECK|ARPSEND)
357	;;
358
359BOUND|RENEW|REBIND|REBOOT)
360	check_hostname
361	if [ -n "$old_ip_address" ]; then
362		if [ "$old_ip_address" != "$alias_ip_address" ]; then
363			delete_old_alias
364		fi
365		if [ "$old_ip_address" != "$new_ip_address" ]; then
366			delete_old_address
367			delete_old_routes
368		fi
369	fi
370	if [ "$reason" = BOUND ] || \
371	   [ "$reason" = REBOOT ] || \
372	   [ -z "$old_ip_address" ] || \
373	   [ "$old_ip_address" != "$new_ip_address" ]; then
374		add_new_address
375		add_new_routes
376	fi
377	if [ "$new_ip_address" != "$alias_ip_address" ]; then
378		add_new_alias
379	fi
380	add_captive_portal
381	if is_default_interface; then
382		add_new_resolv_conf
383	fi
384	;;
385
386EXPIRE|FAIL)
387	delete_old_alias
388	delete_captive_portal
389	if [ -n "$old_ip_address" ]; then
390		delete_old_address
391		delete_old_routes
392	fi
393	if [ -x $ARP ]; then
394		$ARP -d -a -i $interface
395	fi
396	# XXX Why add alias we just deleted above?
397	add_new_alias
398	if is_default_interface; then
399		case $resolvconf_enable in
400		# "no", "false", "off", or "0"
401		[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
402			if [ -f /etc/resolv.conf.save ]; then
403				cat /etc/resolv.conf.save > /etc/resolv.conf
404			fi
405			;;
406		*)
407			/sbin/resolvconf -d ${interface}
408			;;
409		esac
410	fi
411	;;
412
413TIMEOUT)
414	delete_old_alias
415	add_new_address
416	sleep 1
417	if [ -n "$new_routers" ]; then
418		$LOGGER "New Routers ($interface): $new_routers"
419		set "$new_routers"
420		if ping -q -c 1 -t 1 "$1"; then
421			if [ "$new_ip_address" != "$alias_ip_address" ]; then
422				add_new_alias
423			fi
424			add_new_routes
425			add_captive_portal
426			if ! is_default_interface; then
427				exit_with_hooks 0
428			fi
429			if add_new_resolv_conf; then
430				exit_with_hooks 0
431			fi
432		fi
433	fi
434	eval "$IFCONFIG $interface inet -alias $new_ip_address $medium"
435	delete_old_routes
436	delete_captive_portal
437	exit_with_hooks 1
438	;;
439esac
440
441exit_with_hooks 0
442