#!/bin/bash
# by Dominic 10 Aug 2018 / 14 Sep 2018 for timedicer2
# to prevent wifi state from being flipped by systemd also needs kernel command line param: systemd.restore_state=0

VERSION="1.1 [17 Sep 2025]"
THIS=$(basename "$0")
set -o pipefail
MAXLOOPS=2
COLUMNS=$(stty size 2>/dev/null||echo 80); COLUMNS=${COLUMNS##* }

while getopts ":hlqw" optname; do
	case "$optname" in
		"h")	HELP="y";;
		"l")	CHANGELOG="y";;
		"q")	QUIET="y";;
		"w")	COLUMNS=30000;; #suppress line-breaking
		"?")	echo "Unknown option $OPTARG"; exit 1;;
		*)		echo "Unknown error while processing options"; exit 1;;
	esac
done
shift $((OPTIND-1))
[[ -z $QUIET || -n $HELP$CHANGELOG ]] && echo -e "\n$THIS v$VERSION by Dominic (try -h for help)\n${THIS//?/=}\n"
if [[ -n $HELP ]]; then
	echo -e "$THIS brings up or takes down a wifi network interface depending \
on whether there is a live wired network interface. For use under GNU/Linux. \
Intended to be run at boot time (e.g. from /etc/rc.local) as a way to \
reduce the attack surface for a machine which has both wired and wifi \
interfaces connecting to the same network.

$THIS can be run by a non-root user to show the situation, but not to \
make changes.

Notes: 1. Taking down the wifi interface with $THIS may leave the \
machine with no default route, so that DNS lookups fail - to check/fix \
this, run https://www.timedicer.co.uk/programs/help/routefix.sh.php after $THIS.
       2. To maintain consistent initial wifi state at boot time (i.e. on), and \
prevent it from being flipped by systemd, you may also need \
to specify kernel command line parameter 'systemd.restore_state=0' \
(e.g. by setting/modifying GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub)

Options: -h  show this help and exit
         -l  show changelog and exit
         -q  be a little quieter (i.e. no header)

Exit Codes: A non-zero exit code indicates a problem. Wifi is enabled using \
'rfkill' then 'ip link', and disabled using the same in reverse order. \
Where a change is attempted, the exit code for $THIS is 10 x the exit \
code from 'rfkill' plus the exit code from 'ip link'.

Dependencies: logger[util-linux] rfkill[util-linux]

License: Copyright © 2025 Dominic Raferd. Licensed under the Apache License, \
Version 2.0 (the \"License\"); you may not use this file except in compliance \
with the License. You may obtain a copy of the License at \
https://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable \
law or agreed to in writing, software distributed under the License is \
distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY \
KIND, either express or implied. See the License for the specific language \
governing permissions and limitations under the License.
"|fold -sw"$COLUMNS"
fi
if [[ -n $CHANGELOG ]]; then
	[[ -n $HELP ]] && echo "Changelog:"
	echo "\
1.1 [17 Sep 2025] - add PID to log message, fully shellcheck-compliant
1.0 [05 Feb 2025] - add a message at the end of the datetime that it finished, add datetime for each loop
0.9 [18 Jan 2025] - add a loop so that a transient change can be auto-corrected, fix for change in 'ip link show' text output
0.8 [26 Oct 2023] - minor changes to exit codes and to help text, full shellcheck conformity
0.7 [09 Oct 2023] - add display of start date/time
0.6 [28 Apr 2022] - small change to help text
0.5 [07 Jun 2020] - recognise any device with name beginning with 'w' as wifi
0.4 [30 Jan 2020] - add use of rfkill (as well as ip link set up/down)
0.3 [19 Dec 2019] - remove dependency on ifup/ifdown
0.2 [09 Nov 2018] - first public release
0.1 [05 Nov 2018] - first version with help/changelog"
fi
[[ -n $HELP$CHANGELOG ]] && exit 0
for PROG in ip logger rfkill; do
	command -v $PROG >/dev/null 2>&1 || { echo "command '$PROG' not available, $THIS cannot work" >&2; exit 1; }
done

echo "Started at $(date +'%F %T')"
for ((LOOP=1; LOOP<=MAXLOOPS; LOOP++)); do
	echo -n "Loop $LOOP/$MAXLOOPS"
	[[ $LOOP -gt 1 ]] && LOOPTEXT=" (loop $LOOP)" && sleep 4s && echo -n " at $(date +'%F %T')"
	echo
	WIFI_INTERFACES=$(ip link show|awk -F"[: <>]" '$3 ~ /^w.*$/  {print $3}')
	CONNECTED_WIFI=$(ip link show up|awk -F"[: <>]" '$6 ~ /,UP/ && $3 ~ /^w.*$/ && $0 !~ /DOWN/ {print $3}')
	CONNECTED_WIRED=$(ip link show up|awk -F"[: <>]" '$6 ~ /,UP/ && $3 ~ /^(eth|p|en).*$/ && $0 !~ /DOWN/ {printf $3" "}'|sed 's/ $//')
	echo "Live and connected wired network device(s) (if any): '$CONNECTED_WIRED'"
	if [[ -z $WIFI_INTERFACES ]]; then
		echo "No wifi network device found"
	else
		echo "Live and connected wifi network device(s): '$CONNECTED_WIFI'"
	fi
	if [[ -z $CONNECTED_WIRED && -n $CONNECTED_WIFI ]]; then
		echo "Wired connection is off, wifi already live and connected: nothing to do"; exit 0
	elif [[ -n $CONNECTED_WIRED && -z $CONNECTED_WIFI ]]; then
		echo "Wired connection is live, wifi is disconnected: nothing to do"; exit 0
	fi
	[[ ${#WIFI_INTERFACES[@]} -eq 1 && -n $WIFI_INTERFACES ]] || { echo "${#WIFI_INTERFACES[@]} wifi network devices found, need 1, not sure how to fix the problem, aborting" >&2; exit 1; }
	echo "Wifi network device interface change required:"
	[[ $(id -u) -eq 0 ]] || { echo "Unable to proceed, need to be root" >&2; exit 1; }
	if [[ -z $CONNECTED_WIRED && -z $CONNECTED_WIFI ]]; then
		MESSAGE="No wired or wifi connections were live, enabl"
		echo "${MESSAGE}ing wifi"
		rfkill unblock wlan; EXITCODE=$?
		ip link set "$WIFI_INTERFACES" up 2>&1|sed 's/^/  /'; EXITCODE=$((10*EXITCODE+$?))
	else
		MESSAGE="Both wifi and wired connections were live, disabl"
		echo "${MESSAGE}ing wifi"
		ip link set "$WIFI_INTERFACES" down 2>&1|sed 's/^/  /'; EXITCODE=$?
		rfkill block wlan; EXITCODE=$((EXITCODE+10*$?))
	fi
	MESSAGE="${MESSAGE}ed wifi, exit code $EXITCODE"
	echo "$MESSAGE"
	logger -t "${THIS}[$$]" "$MESSAGE$LOOPTEXT"
done
echo "Finished at $(date +'%F %T')"
exit "$EXITCODE"
