local.m4: Fix whitespace oddity.
[firewall] / prologue.m4
index 60fcf4d..9f5d084 100644 (file)
@@ -1,4 +1,4 @@
-### -*-m4-*-
+### -*-sh-*-
 ###
 ### Failsafe prologue for firewall scripts
 ###
@@ -26,8 +26,21 @@ m4_divert(10)m4_dnl
 ### Failsafe prologue.
 
 revert () {
-  echo "$1!  Retreating to safe version..."
-  if ! "$firewall_failsafe" revert; then
+  escape=$1 badness=$2
+  ## Report a firewall script failure and retreat to a safe place.
+
+  echo "$2!  Retreating to safe version..."
+  if [ -f /var/run/firewall.save ] && [ -f /var/run/firewall6.save ]; then
+    echo "Trying to loading saved firewall state..."
+    if iptables-restore </var/run/firewall.save &&
+       ip6tables-restore </var/run/firewall6.save; then
+      echo "Previous firewall state restored."
+      return
+    else
+      echo "Failed!  Falling back to plan B."
+    fi
+  fi
+  if ! "$1" revert; then
     echo >&2 "Safe firewall failed.  You're screwed.  Good luck."
     exit 1
   fi
@@ -40,40 +53,118 @@ finished () {
   exit 0
 }
 
+try () {
+  old=$1 new=$2
+  ## Install the NEW firewall rules.  If it fails, revert to the OLD ones.
+  ## Updating firewall rules can fail spectacularly, so be careful.  Leave a
+  ## timebomb in the form of NEW.errors: if this isn't removed in 10 seconds
+  ## after the NEW rules complete successfully, then revert.  Write errors to
+  ## NEW.errors.
+
+  ## Make sure we have an escape route.
+  iptables-save >/var/run/firewall.save.new
+  ip6tables-save >/var/run/firewall6.save.new
+  mv /var/run/firewall.save.new /var/run/firewall.save
+  mv /var/run/firewall6.save.new /var/run/firewall6.save
+
+  ## Clear the air and make the errors file.
+  rm -f "$new.errors" "$new.timebomb" "$new.grabbed"
+  exec >"$new.errors" 2>&1
+
+  ## Now try to install the new firewall.
+  "$new" install || revert "$old" "Failed"
+
+  ## Set up the time bomb.  Leave the errors file there if we failed.
+  (sleep 10
+   if [ -f "$new.errors" ]; then
+     mv "$new.errors" "$new.timebomb"
+     revert "$old" "Time bomb"
+   fi)&
+}
+
+catch () {
+  new=$1
+  ## Report successful installation of the script.
+
+  if mv "$new.errors" "$new.grabbed" 2>/dev/null; then
+    rc=0
+    echo "Installed OK."
+  else
+    mv "$new.timebomb" "$new.grabbed"
+    echo "Timebomb went off."
+    rc=1
+  fi
+  cat "$new.grabbed" >&2
+  rm -f "$new.grabbed"
+  return $rc
+}
+
 exit_after_clearing=:
 export FWCOOKIE=magical
-case "${1-update}" in
-  start | restart | reload | force-reload)
+case "$#,${1-update}" in
+  1,start | 1,restart | 1,reload | 1,force-reload)
     echo -n "Starting up firewall... "
-    "$firewall_script" install || revert "Failed"
+    "$firewall_script" install || revert "$firewall_failsafe" "Failed"
     finished
     ;;
-  stop)
+  1,stop)
     echo -n "Shutting down firewall... "
     exit_after_clearing=finished
     ;;
-  update)
-    echo -n "Installing new firewall... "
-    "$firewall_script" install || revert "Failed"
-    echo "Done."
+  1,replace | 1,test)
+    echo -n "Running new firewall... "
+    if ! (try "$firewall_script" "$0"); then
+      echo "FAILED."
+      cat "$0.errors" >&2
+      exit
+    fi
+    echo "done."
     echo "Can you hear me?"
-    parent=$$
-    (sleep 5; kill $parent; revert "Timeout")&
-    child=$!
+    (trap 'exit 127' TERM
+     while :; do
+       if [ -f "$0.timebomb" ]; then
+        kill $$
+        echo "Timebomb went off!"
+        cat "$0.timebomb"
+        exit 1
+       fi
+       sleep 1
+     done)&
     read answer
-    kill $child
-    case "$answer" in
-      y* | Y*)
-       echo "Cool.  We're done here."
+    kill $!
+    catch "$0"
+    case "$1,$answer" in
+      replace,y* | replace,Y*)
+       install -m755 "$0" "$firewall_script"
+       echo "Cool.  Firewall script replaced."
+       exit 0
+       ;;
+      test,y* | test,Y*)
+       echo "Cool.  Everything seems good."
        exit 0
        ;;
+      *)
+       revert "$firewall_script" "Bogus"
+       ;;
     esac
-    revert "Bogus"
     ;;
-  install | revert)
+  1,remote-prepare)
+    try "$firewall_script" "$0"
+    exit 0
+    ;;
+  1,remote-commit)
+    catch "$0"
+    install -m755 "$0" "$firewall_script"
+    exit 0
+    ;;
+  1,install | 1,revert)
     ;;
   *)
-    echo >&2 "Usage: firewall start|stop|reload|restart|force-reload|update|install|revert"
+    cat >&2 <<EOF
+Usage:
+       $0 start|stop|reload|restart|force-reload
+       $0 replace|test|remote-prepare|remote-commit
+EOF
     exit 1
     ;;
 esac