Wren Linux

wren-linux / lib / net.sh

# shellcheck shell=bash
# lib/net.sh: Wi-Fi and network config helpers. Sourced by lib.sh.

# drop_ifupdown_iface IF: comment out IF's stanza in /etc/network/interfaces
drop_ifupdown_iface() {
    local ifc=$1 file=/etc/network/interfaces tmp
    [ -f "$file" ] || return 0
    $SUDO grep -Eq "^[[:space:]]*(auto|allow-hotplug|iface)[[:space:]]+$ifc([[:space:]]|$)" "$file" ||
        return 0
    [ -e "$file.wren.bak" ] || $SUDO cp -a "$file" "$file.wren.bak"
    tmp=$(mktemp)
    $SUDO cat "$file" | awk -v i="$ifc" '
        /^[^[:space:]#]/ { inside = 0 }
        $1 == "iface" && $2 == i { inside = 1 }
        ($1 == "auto" || $1 == "allow-hotplug") && $2 == i && NF == 2 { print "#" $0; next }
        inside && !/^#/ { print "#" $0; next }
        { print }' >"$tmp"
    $SUDO tee "$file" <"$tmp" >/dev/null
    rm -f "$tmp"
    note "Commented out $ifc in $file (backup: $file.wren.bak)"
}

wifi_ifaces() {
    local d
    for d in /sys/class/net/*; do
        if [ -d "$d/wireless" ] || [ -e "$d/phy80211" ]; then
            basename "$d"
        fi
    done
}

# nm_write_wifi SSID PSK HIDDEN(1|0) IF: a NetworkManager connection written
# straight to its config folder, so the password never shows up in the
# command line of a running nmcli (anyone on the machine can read those).
# An empty PSK is an open network. Returns 1, and writes nothing, for names
# or passwords the file format can't hold; the caller then uses nmcli.
nm_write_wifi() {
    local ssid=$1 psk=$2 hidden=$3 ifc=$4 bytes hex id key
    local edge='^[[:space:]]|[[:space:]]$'
    [[ $ssid != *[[:cntrl:]]* && $psk != *[[:cntrl:]]* ]] || return 1
    [[ ! $ssid =~ $edge && ! $psk =~ $edge ]] || return 1
    # The SSID is written as bytes, so it can hold anything
    bytes=$(printf '%s' "$ssid" | od -An -tu1 -v | tr -s ' \n' ' ' | sed 's/^ //; s/ $//; s/ /;/g;')
    hex=$(printf '%s' "$ssid" | od -An -tx1 -v | tr -d ' \n')
    id=${ssid//\\/\\\\}
    key=${psk//\\/\\\\}
    {
        printf '[connection]\nid=%s\nuuid=%s\ntype=wifi\ninterface-name=%s\n\n' \
            "$id" "$(cat /proc/sys/kernel/random/uuid)" "$ifc"
        printf '[wifi]\nmode=infrastructure\nssid=%s;\n' "$bytes"
        if [ "$hidden" = 1 ]; then printf 'hidden=true\n'; fi
        printf '\n'
        if [ -n "$psk" ]; then printf '[wifi-security]\nkey-mgmt=wpa-psk\npsk=%s\n\n' "$key"; fi
        printf '[ipv4]\nmethod=auto\n\n[ipv6]\naddr-gen-mode=default\nmethod=auto\n'
    } | $SUDO install -D -m 0600 /dev/stdin "/etc/NetworkManager/system-connections/wren-$hex.nmconnection"
}

# ssh_pubkey_ok LINE: one ssh public key, as the .pub file holds it: a key type,
# the key, maybe a comment. Nothing in front (an authorized_keys line can start
# with options such as command= or permitopen=, which a pasted line must not
# smuggle in), and ssh-keygen must be able to read it.
ssh_pubkey_ok() {
    local tmp rc=0
    [[ $1 =~ ^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)|sk-ssh-ed25519@openssh\.com|sk-ecdsa-sha2-nistp256@openssh\.com)\ [A-Za-z0-9+/]+={0,3}(\ [^[:cntrl:]]*)?$ ]] || return 1
    tmp=$(mktemp)
    printf '%s\n' "$1" >"$tmp"
    ssh-keygen -l -f "$tmp" >/dev/null 2>&1 || rc=1
    rm -f "$tmp"
    return "$rc"
}

# sshd_problems USER: reads `sshd -T` (what sshd will really do) on stdin and
# prints one line for each setting that is not what extras/remote-access.sh
# wants: key login only, no root, USER only, and listening on 127.0.0.1 only.
# sshd takes the first value it finds for a setting, so a file read earlier
# can beat Wren Linux's drop-in; this catches that.
sshd_problems() {
    awk -v user="$1" '
        BEGIN {
            # settings that must be "no" (sshd -T prints every one, in lower case)
            split("AllowTcpForwarding AllowStreamLocalForwarding GatewayPorts AllowAgentForwarding X11Forwarding PermitTunnel PermitUserEnvironment", names, " ")
            for (i in names) off[tolower(names[i])] = names[i]
        }
        ($1 in off) { got[$1] = 1; if ($2 != "no") print off[$1] " is " $2 }
        $1 == "maxauthtries" { tries = 1; if ($2 + 0 > 3) print "MaxAuthTries is " $2 }
        $1 == "passwordauthentication" && $2 != "no" { print "PasswordAuthentication is " $2 }
        $1 == "kbdinteractiveauthentication" && $2 != "no" { print "KbdInteractiveAuthentication is " $2 }
        $1 == "permitrootlogin" && $2 != "no" { print "PermitRootLogin is " $2 }
        $1 == "listenaddress" && $2 !~ /^127\.0\.0\.1:/ { print "ListenAddress " $2 }
        $1 == "allowusers" { seen = 1; if ($0 != "allowusers " user) print "AllowUsers includes " $2 }
        END {
            if (!seen) print "AllowUsers is not set"
            for (k in off) if (!(k in got)) print off[k] " is not set"
            if (!tries) print "MaxAuthTries is not set"
        }'
}

# parts/secure-dns.sh's settings for Unbound (secure DNS is set up), and its
# switch: while it is on, /etc/resolv.conf points at Unbound (and says so in
# its first line) and nothing of Wren Linux's may rewrite it (wifi.sh would put
# the network's DNS server back, which its firewall rule then refuses). An
# older Wren Linux wrote "extras/". WREN_ETC is where /etc is, for tests.
SECURE_DNS_CONF=/etc/unbound/unbound.conf.d/wren-secure-dns.conf
secure_dns_installed() { [ -e "${WREN_ETC:-/etc}${SECURE_DNS_CONF#/etc}" ]; }
secure_dns_on() {
    local r=${WREN_ETC:-/etc}/resolv.conf
    [ ! -L "$r" ] && grep -qsE '^# Written by Wren Linux (parts|extras)/secure-dns\.sh' "$r"
}

# remove_wifi_config: undoes what wifi.sh wrote for a Wi-Fi it set up by hand
# (run as root): the wpa_supplicant files it marked as its own and their
# per-interface services, its ifupdown stanzas, its systemd-networkd files, and
# the DNS file it saved. It leaves alone what NetworkManager has (that is
# parts/network-manager.sh) and every package. Prints what it removed.
# WREN_ETC is where /etc is, for tests.
remove_wifi_config() {
    local etc=${WREN_ETC:-/etc} f ifc networkd=false
    for f in "$etc"/wpa_supplicant/wpa_supplicant-*.conf; do
        [ -f "$f" ] && [ "$(head -1 "$f")" = "# Written by Wren Linux wifi.sh" ] || continue
        ifc=${f##*/wpa_supplicant-}
        ifc=${ifc%.conf}
        systemctl disable --now "wpa_supplicant@$ifc.service" >/dev/null 2>&1 || true
        rm -f "$f"
        note "Removed $f"
    done
    for f in "$etc"/network/interfaces.d/wren-wifi-*; do
        [ -f "$f" ] || continue
        ifc=${f##*/wren-wifi-}
        ifdown "$ifc" >/dev/null 2>&1 || true
        rm -f "$f"
        note "Removed $f"
    done
    for f in "$etc"/systemd/network/50-wren-*.network; do
        [ -f "$f" ] || continue
        rm -f "$f"
        networkd=true
        note "Removed $f"
    done
    if $networkd; then systemctl restart systemd-networkd >/dev/null 2>&1 || true; fi
    # (not while secure DNS points it at Unbound)
    if [ -e "$etc/resolv.conf.wren.bak" ] && [ ! -L "$etc/resolv.conf" ] && ! secure_dns_on; then
        cp -a "$etc/resolv.conf.wren.bak" "$etc/resolv.conf" && rm -f "$etc/resolv.conf.wren.bak"
        note "Put back $etc/resolv.conf"
    fi
}

# set_bluetooth_autoenable FILE false|true: bluez's main.conf decides whether
# the adapter powers up by itself at start. "false" keeps it off until you
# switch it on (the Wren menu or wrenctl --bluetooth on); "true" puts bluez's
# own default back. Lines this wrote end in "# Wren Linux" so they can be undone.
set_bluetooth_autoenable() {
    local f=$1 want=$2 tmp
    if [ "$want" = false ]; then
        if grep -qE '^[#[:space:]]*AutoEnable=' "$f" 2>/dev/null; then
            tmp=$(mktemp)
            sed -E 's/^[#[:space:]]*AutoEnable=.*/AutoEnable=false # Wren Linux/' "$f" >"$tmp"
            cat "$tmp" >"$f"
            rm -f "$tmp"
        else
            printf '\n[Policy]\nAutoEnable=false # Wren Linux\n' >>"$f"
        fi
    else
        tmp=$(mktemp)
        sed -E 's/^AutoEnable=false # Wren Linux$/#AutoEnable=true/' "$f" >"$tmp"
        cat "$tmp" >"$f"
        rm -f "$tmp"
    fi
}

# dhcp_no_name on|off: dhcpcd and dhclient send this computer's name to every network,
# since Debian's settings tell them to (seen in a DHCP server's log). This takes that
# out of those settings, for wifi.sh's connections and for cables, and off puts it back.
# NetworkManager has a setting of its own (parts/random-mac.sh, bin/tether). (WREN_ETC is
# where /etc is, for tests.)
DHCP_NO_NAME_MARK='#(no name sent: Wren Linux) '
dhcp_no_name() {
    local etc=${WREN_ETC:-/etc} f
    for f in "$etc/dhcpcd.conf" "$etc/dhcp/dhclient.conf"; do
        [ -f "$f" ] || continue
        if [ "$1" = off ]; then
            $SUDO sed -i "s/^$DHCP_NO_NAME_MARK//" "$f"
        else
            $SUDO sed -i -E "s/^(hostname[[:space:]]*|send host-name = gethostname\(\);[[:space:]]*)\$/$DHCP_NO_NAME_MARK\1/" "$f"
        fi
    done
}