git: 0f1ca95d7740 - main - dhclient-script: store the Captive Portal URI

From: Baptiste Daroussin <bapt_at_FreeBSD.org>
Date: Wed, 07 Oct 2026 14:07:46 UTC
The branch main has been updated by bapt:

URL: https://cgit.FreeBSD.org/src/commit/?id=0f1ca95d7740e0800572ed436fd61e70f76eb6c3

commit 0f1ca95d7740e0800572ed436fd61e70f76eb6c3
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-10-02 19:55:28 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-10-07 14:07:09 +0000

    dhclient-script: store the Captive Portal URI
    
    Write the URI received in DHCP option 114 to
    /var/run/captive-portal.IFACE atomically, with the legacy option 160 as
    a fallback, and remove the file on EXPIRE and FAIL.
    
    Reviewed by:    bcr (manpage)
    Differential Revision:  https://reviews.freebsd.org/D60259
---
 sbin/dhclient/dhclient-script   | 32 ++++++++++++++++++++++++++++++++
 sbin/dhclient/dhclient-script.8 | 11 ++++++++++-
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/sbin/dhclient/dhclient-script b/sbin/dhclient/dhclient-script
index 27c424e18118..b2fde6e3ed2a 100755
--- a/sbin/dhclient/dhclient-script
+++ b/sbin/dhclient/dhclient-script
@@ -84,6 +84,34 @@ add_new_alias() {
 	fi
 }
 
+add_captive_portal() {
+	# DHCP option 114 (RFC 8910), with the legacy DHCP option 160
+	# (RFC 7710) as a fallback.
+	captive_portal_url="${new_captive_portal_url:-$new_captive_portal_legacy}"
+
+	if [ -z "$captive_portal_url" ]; then
+		rm -f /var/run/captive-portal.$interface
+		return 0
+	fi
+
+	$LOGGER "New Captive Portal ($interface): $captive_portal_url"
+
+	captive_portal_tmp=/var/run/captive-portal.${interface}.tmp
+	if ! (umask 022; echo "$captive_portal_url" > $captive_portal_tmp); then
+		rm -f $captive_portal_tmp
+		return 1
+	fi
+	if ! mv $captive_portal_tmp /var/run/captive-portal.$interface; then
+		rm -f $captive_portal_tmp
+		return 1
+	fi
+	return 0
+}
+
+delete_captive_portal() {
+	rm -f /var/run/captive-portal.$interface
+}
+
 fill_classless_routes() {
 	set $1
 	while [ $# -ge 5 ]; do
@@ -349,6 +377,7 @@ BOUND|RENEW|REBIND|REBOOT)
 	if [ "$new_ip_address" != "$alias_ip_address" ]; then
 		add_new_alias
 	fi
+	add_captive_portal
 	if is_default_interface; then
 		add_new_resolv_conf
 	fi
@@ -356,6 +385,7 @@ BOUND|RENEW|REBIND|REBOOT)
 
 EXPIRE|FAIL)
 	delete_old_alias
+	delete_captive_portal
 	if [ -n "$old_ip_address" ]; then
 		delete_old_address
 		delete_old_routes
@@ -392,6 +422,7 @@ TIMEOUT)
 				add_new_alias
 			fi
 			add_new_routes
+			add_captive_portal
 			if ! is_default_interface; then
 				exit_with_hooks 0
 			fi
@@ -402,6 +433,7 @@ TIMEOUT)
 	fi
 	eval "$IFCONFIG $interface inet -alias $new_ip_address $medium"
 	delete_old_routes
+	delete_captive_portal
 	exit_with_hooks 1
 	;;
 esac
diff --git a/sbin/dhclient/dhclient-script.8 b/sbin/dhclient/dhclient-script.8
index dd3efd3e8698..00482da3731c 100644
--- a/sbin/dhclient/dhclient-script.8
+++ b/sbin/dhclient/dhclient-script.8
@@ -36,7 +36,7 @@
 .\" see ``http://www.isc.org/isc''.  To learn more about Vixie
 .\" Enterprises, see ``http://www.vix.com''.
 .\"
-.Dd September 6, 2010
+.Dd October 5, 2026
 .Dt DHCLIENT-SCRIPT 8
 .Os
 .Sh NAME
@@ -142,6 +142,15 @@ in order to make valid shell variables, and the variable names start with
 So for example, the new subnet mask would be passed in
 .Va $new_subnet_mask .
 .Pp
+As an exception, the captive portal API URI from DHCP option 114
+.Pq see Xr dhcp-options 5
+is passed as
+.Va $new_captive_portal_url .
+When such a URI is received, the script also writes it to
+.Pa /var/run/captive-portal. Ns Va $interface
+so that it can be discovered by other programs.
+The file is removed when the lease expires or is lost.
+.Pp
 When a binding has been completed, a lot of network parameters are
 likely to need to be set up.
 A new