Created
January 14, 2015 02:00
-
-
Save kurahaupo/a038ffe04cc46103cd0d to your computer and use it in GitHub Desktop.
Manually change /etc/resolv.conf but protect it from meddling by NetworkManager et al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Update /etc/resolv.conf but keep it unwritable, even by root-owned processes. | |
# Useful to stop WiFi and/or DHCP from messing with it, and quite essential when | |
# there are multiple managers for different interface types. | |
# | |
# Because symlinks don't work, instead we bind-mount an appropriate file onto | |
# /etc/resolv.conf, chosen from those in ~/.resolvconf.d/. | |
# | |
# Since BIND is running locally, almost all the time that will work, so it's the | |
# default if nothing else is loaded. | |
erc=/etc/resolv.conf | |
xdir=${HOME:-~martin.k}/.resolvconf.d/ | |
(( EUID == 0 )) || exec sudo $0 "$@" | |
false=0 true=1 | |
dhcp=true | |
role= | |
verbose=true _v= | |
dry_run=false _n= | |
debug=false _x= | |
die() { | |
RESULT=$? SUCCESS=0 FAILURE=1 OK=0 USAGE=64 DATAERR=65 NOINPUT=66 NOUSER=67 | |
NOHOST=68 UNAVAILABLE=69 SOFTWARE=70 OSERR=71 OSFILE=72 CANTCREAT=73 | |
IOERR=74 TEMPFAIL=75 PROTOCOL=76 NOPERM=77 CONFIG=78 | |
((e=${1#EX*_})) ; shift | |
echo >&2 "$*" | |
exit $e | |
} | |
[[ "$*" = "-h" || "$*" = "--h"* && "--help" = "$*"* ]] && { cat <<EndOfHelp ; exit 0 ; } | |
$0 --help | |
$0 [-q | -v[v...]] [-d DIR] [-r ROLE] | |
Roles are: $( cd $xdir ; echo * ) | |
EndOfHelp | |
true=1 false=0 | |
while (($#)) ; do | |
case $1 in | |
(--) shift ; break ;; | |
(-) role=localhost ;; | |
(-q) verbose=false _v= ;; | |
(-v) verbose=true _v=${_v:--}v ;; | |
(-D|--dir) xdir=$2 ; shift ;; | |
(-d|--dhcp) dhcp=true ;; | |
(-n|--dry*run) dry_run=true _n=-n ;; | |
(-nd|--no-dhcp|--nodhcp) dhcp=false ;; | |
(-r|--role) role=$2 ; shift ;; | |
(-x|--debug) debug=true _x=-x ;; | |
(-[Dr]?*) set -- "${1:0:2}" "${1:2}" "${@:2}" ; continue ;; | |
(-[^-]?*) set -- "${1:0:2}" "-${1:2}" "${@:2}" ; continue ;; | |
(--*=*) set -- "${1%%=*}" "${1#*=}" "${@:2}" ; continue ;; | |
(-*) die EX_USAGE "Invalid option '$1'; try '${0##*/} --help'" ;; | |
(*) break ;; | |
esac | |
shift | |
done | |
: ${role:=$1} | |
:|sort_array() { | |
local ___cmp=$1 ___v=$2 ___n ___i ___j a b | |
eval "((___n=\${!$___v[@]}))" | |
local ___vi="$___v[___i]" | |
local ___vj="$___v[___j]" | |
# stretch the size for sparse arrays | |
for (( ___i=0 ; ___i<___n ; ++___i )) do | |
[[ -n "${!___vi+X}" ]] || ((++___n)) | |
done | |
# plain bubble-sort | |
for (( ___i=0 ; ___i<___n ; ++___i )) do | |
[[ -n "${!___vi+X}" ]] || continue | |
for (( ___j=___i+1 ; ___j<___n ; ++___j )) do | |
[[ -n "${!___vj+X}" ]] || continue | |
set -- "${!___vi}" "${!___vj}" | |
eval "$___cmp" || { | |
printf -v "$___vi" -- %s "$2" | |
printf -v "$___vj" -- %s "$1" | |
} | |
done | |
done | |
} | |
:!sort_array() { | |
local ___cmp=$1 ___vptr=$2 ___changed=0 | |
local ___vref="$___vptr[@]" | |
local -a ___array=("${!___vref}") | |
local ___n ___i ___j a b | |
for ((___n=${#___array[@]},___i=0;___i<___n;___i++)) do | |
for ((___j=___i+1;___j<___n;___j++)) do | |
set -- "${___array[___i]}" "${___array[___j]}" | |
eval "$___cmp" || | |
___array[___i]="$b" \ | |
___array[___j]="$a" \ | |
___changed=1 | |
done | |
done | |
((___changed)) && eval "$___vptr=(\"\${___array[@]}\")" | |
} | |
___eswap() { | |
# NB relies on dynamic scoping for ___array | |
local x="${___array[$1]}" | |
___array[$1]="${___array[$2]}" | |
___array[$2]="$x" | |
} | |
sort_array() { | |
local ___cmp=$1 | |
local -n ___array=$2 | |
local ___n ___i ___j | |
for ((___n=${#___array[@]},___i=0;___i<___n;___i++)) do | |
for ((___j=___i+1;___j<___n;___j++)) do | |
set -- "${___array[___i]}" "${___array[___j]}" | |
eval "$___cmp" || | |
___eswap $___i $___j | |
done | |
done | |
} | |
if $debug || : | |
then | |
no_debug() { | |
local _oldx=$- | |
set +x | |
"$@" | |
local r=$? | |
if [[ $_oldx = *x* ]] ; then set -x ; else set +x ; fi | |
return $r | |
} | |
with_debug() { | |
local _oldx=$- | |
set -x | |
"$@" | |
local r=$? | |
if [[ $_oldx = *x* ]] ; then set -x ; else set +x ; fi | |
return $r | |
} | |
else | |
no_debug() { | |
"$@" | |
} | |
with_debug() { | |
"$@" | |
} | |
fi | |
case $role in | |
none) | |
ns=() | |
;; | |
dhcp|'') | |
rolefiles=($( find /var/lib/dhcp /var/lib/NetworkManager \( -name \*.leases -o -name \*.lease \) -mmin -1440 -print )) | |
(( ${#rolefiles[@]} )) || die EX_TEMPFAIL "Can't find a recent DHCP lease (less than 2 hours old)" | |
no_debug sort_array '[[ $2 -nt $1 ]]' rolefiles | |
$debug && ls -ldU "${rolefiles[@]}" | |
ns=() | |
printf -v tn '%(%Y%m%d%H%M%S)T' -1 | |
while read t lease | |
do | |
(( t>=tn )) && | |
IFS=, read -r -a ns <<<"$lease" | |
done < <( | |
# extract all the known leases; fold up each one into a single line, | |
# then prefix that line with its expiry time as yyyymmddHHMMSS; then | |
# sort by those times newest-last, so that later ones will override | |
# older ones | |
sed -n 's#^ *## | |
/^#/d | |
/{/{ | |
s#.*## | |
x | |
d | |
} | |
/}/!{ | |
H | |
d | |
} | |
x | |
s#\n# #g | |
s#\(.*;\) expire[^;]*\([12][09][0-9][0-9]\)/\([01][0-9]\)/\([0-3][0-9]\) \([0-2][0-9]\):\([0-5][0-9]\):\([0-5][0-9]\)#\2\3\4\5\6\7 &# | |
s# .* option domain-name-servers *\([0-9.,]*\);.*# \1#p | |
' "${rolefiles[@]}" </dev/null | | |
sort -r | |
) | |
# # Sort rolefiles into time-order | |
# ns=($( sed -ne ' | |
# /^}/bo | |
# s/^ *// | |
# /^option /!d | |
# s/^option *// | |
# /^domain-name-servers *[0-9.,]*;*$/!d | |
# /^domain-name[^ ]* *[0-9.,]*;*$/!d | |
# s/^[^ ]* *// | |
# s/;$// | |
# s/,/ /g | |
# H | |
# bl | |
# :o | |
# s/.*// | |
# x | |
# bl | |
# :l | |
# $p | |
# ' "${rolefiles[@]}" </dev/null )) | |
(( ${#ns[@]} )) || echo >&2 "No 'option domain-name*' in ${rolefiles[*]}" | |
;; | |
*) ns=( ${role//,/\ } ) ;; | |
esac | |
# (Sort and) prune duplicates | |
for i in ${!ns[@]} | |
do | |
[[ ${ns[i]-__UnSeT__uNsEt__} = __UnSeT__uNsEt__ ]] || | |
for j in ${!ns[@]} | |
do | |
(( i<j )) || continue | |
if [[ ${ns[i]} = ${ns[j]} ]] | |
then | |
unset 'ns[j]' | |
elif [[ ${ns[i]} > ${ns[j]} ]] && false | |
then | |
ti=${ns[i]} tj=${ns[j]} | |
ns[i]=$tj ns[j]=$ti | |
fi | |
done | |
done | |
any_match() { | |
local f="$1" g ; shift | |
for g do | |
[[ $f =~ $g ]] && return 0 | |
done | |
return 1 | |
} | |
all_match() { | |
local f="$1" g ; shift | |
for g do | |
[[ $f =~ $g ]] || return 1 | |
done | |
return 0 | |
} | |
if all_match 'localhost|0.0.0.0|127.0.0.1' "${ns[@]}" | |
then | |
# includes case where ns array is empty | |
frc= | |
if $dry_run | |
then | |
echo >&2 "Would use local resolver" | |
elif $verbose | |
then | |
echo >&2 "Using local resolver" | |
fi | |
else | |
nsx="${ns[*]}" | |
frc=$xdir${nsx//\ /+} | |
if [[ -e $frc ]] | |
then | |
if [[ ! -f $frc ]] | |
then die EX_NOINPUT "'$frc' is not a regular file" | |
elif ! grep -qs '^nameserver ' "$frc" | |
then die EX_PROTOCOL "'$frc' does not contain a 'nameserver' statement" | |
fi | |
if $dry_run | |
then | |
echo >&2 "Would use $frc" | |
elif $verbose | |
then | |
echo >&2 "Using '$frc'" | |
fi | |
else | |
for ip in ${ns[@]} ; do [[ -z ${ip//[.0-9]/} && $ip = *.*.*.* && $ip != *.*.*.*.* && .$ip != *..* ]] || die EX_PROTOCOL "Nameserver '$ip' isn't IPv4 addresses" ; done | |
if $dry_run | |
then | |
echo >&2 "Would create '$frc' with [${ns[*]}]" | |
else | |
$verbose && echo >&2 "Creating '$frc' with [${ns[*]}]" | |
printf 'nameserver %s\n' "${ns[@]}" >"$frc" | |
#for ip in ${ns[@]} ; do echo "nameserver $ip" ; done >"$frc" | |
fi | |
fi | |
if $dry_run | |
then | |
echo >&2 "Would make '$frc' unwritable" | |
# if [[ -h $frc ]] | |
# then hrc=$( readlink -e "$frc" ) || die EX_OSERR "Can't canonicalize '$frc'" | |
# [[ $frc != $hrc ]] && echo >&2 "Have canonicalized '$frc' to '$hrc'" | |
# else hrc=$frc | |
# fi | |
# echo >&2 "Would enforce immutability on '$hrc'" | |
else | |
[[ ! -w $frc ]] || { | |
$verbose && echo >&2 "Making '$frc' unwritable" | |
chmod -c a-xw "$frc" || die EX_OSERR "Can't fix permissions on '$frc'" | |
} | |
# if [[ -h $frc ]] | |
# then hrc=$( readlink -e "$frc" ) || die EX_OSERR "Can't canonicalize $frc" | |
# $verbose && [[ $frc != $hrc ]] && echo >&2 "Have canonicalized '$frc' to '$hrc'" | |
# else hrc=$frc | |
# fi | |
# $verbose && echo >&2 "Enforcing immutability on '$hrc'" | |
# chattr +i "$hrc" || die EX_OSERR "Can't enforce immutability on '$hrc'" | |
fi | |
fi | |
if $dry_run | |
then | |
! grep -qs "$erc" /proc/mounts || echo >&2 "Would unmount current '$erc'" | |
[[ -z $frc ]] || echo >&2 "Would mount '$frc' on '$erc'" | |
else | |
! grep -qs "$erc" /proc/mounts || { echo >&2 "Unmounting current '$erc'" | |
umount "$erc" || | |
die EX_OSERR "Can't unmount old '$erc'" ; } | |
# Note: bind-mounting with *different* options requires *two* mount | |
# commands; see the man page excerpt at the foot of this script. | |
[[ -z $frc ]] || { echo >&2 "Mounting '$frc' on '$erc'" | |
mount --bind "$frc" "$erc" || | |
die EX_OSERR "Can't bind-mount '$frc' as new '$erc'" | |
mount -o remount,bind,ro "$frc" "$erc" || | |
die EX_OSERR "Can't remount '$erc' with ro" | |
} | |
fi | |
((verbose)) && grep '^nameserver ' "$erc" | |
: <<EOF | |
[From «man mount» apropos «--bind»] | |
Note that the filesystem mount options will remain the same as those on | |
the original mount point, and cannot be changed by passing the -o option | |
along with --bind/--rbind. The mount options can be changed by a separate | |
remount command, for example: | |
mount --bind olddir newdir | |
mount -o remount,ro newdir | |
Note that behavior of the remount operation depends on the /etc/mtab file. | |
The first command stores the 'bind' flag to the /etc/mtab file and the | |
second command reads the flag from the file. If you have a system | |
without the /etc/mtab file or if you explicitly define source and target | |
for the remount command (then mount(8) does not read /etc/mtab), then you | |
have to use bind flag (or option) for the remount command too. For | |
example: | |
mount --bind olddir newdir | |
mount -o remount,ro,bind olddir newdir | |
EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment