#!/bin/sh /etc/rc.common
#
# Copyright (C) 2015 Vitaly Protsko <villy@sft.ru>

#set -vx

USE_PROCD=1

START=60
STOP=40

let connWait=2/2
confDir=/var/racoon
confExtZone=
confIntZone=
confPort=
confNATPort=
confIPMode=

confPh1ID=0

log="logger -t init.d/racoon[$$] "

. /etc/racoon/functions.sh

setup_load() {
  config_get confExtZone "$1" ext_zone wan
  config_get confIntZone "$1" int_zone lan
  config_get confPort    "$1" port 500
  config_get confNATPort "$1" natt_port 4500
  config_get confIPMode  "$1" ipversion ""

  case X$confIPMode in
    X4|X6) ;;
    *) unset confIPMode ;;
  esac
}

write_header() {
  echo "
# autogenerated, don't edit, look at /etc/config/racoon
#
path certificate \"$confDir/cert\";
path script \"/etc/racoon\";
path pre_shared_key \"$confDir/psk.txt\";
path pidfile \"$confDir/racoon.pid\";
padding { maximum_length 20; randomize off; strict_check off; exclusive_tail off; }
timer { counter 5; interval 20 sec; persend 1; phase1 30 sec; phase2 15 sec; }
"
}

setup_conf() {
  local conf=$confDir/racoon.conf
  local peerconf=$confDir/peers.txt
  local pskconf=$confDir/psk.txt
  local item
  local data

  data="$(get_zoneiplist $confExtZone)"
  if [ "X$data" = X ]; then
    $log "No IP addresses found for zone $confExtZone, exitng"
    errno=2; return 2
  fi

  write_header > $conf
  echo -n > $peerconf
  echo -n > $pskconf
  chmod 0600 $conf $peerconf $pskconf

  echo "listen {" >> $conf
  for item in $data ; do
    echo "  isakmp $item [$confPort]; isakmp_natt $item [$confNATPort];" >> $conf
  done
  echo "}" >> $conf

  config_get_bool item "$1" debug 0
  data=warning
  test $item -ne 0 && data=debug
  echo "log $data;" >> $conf

  setup_fw add
}

setup_p1() {
  local conf=$confDir/racoon.conf
  local data

  echo "  proposal {" >> $conf
  config_get data "$1" lifetime 28800
  echo "    lifetime time $data sec;" >> $conf

  config_get data "$1" enc_alg
  test -n "$data" && echo "    encryption_algorithm $data;" >> $conf

  config_get data "$1" hash_alg
  test -n "$data" && echo "    hash_algorithm $data;" >> $conf

  config_get data "$1" auth_method
  test -n "$data" && echo "    authentication_method $data;" >> $conf

  config_get data "$1" dh_group 2
  echo -e "    dh_group $data;\n  }" >> $conf
}

setup_fw() {
  local cmd=/usr/sbin/iptables
  local mode

  case "$1" in
    add|up|1) mode=A ;;
    del|down|0) mode=D ;;
    *) return 3 ;;
  esac

  $cmd -$mode input_${confExtZone}_rule -p AH -j ACCEPT
  $cmd -$mode input_${confExtZone}_rule -p ESP -j ACCEPT
  $cmd -$mode input_${confExtZone}_rule -p UDP --dport $confPort -j ACCEPT
  $cmd -$mode input_${confExtZone}_rule -p UDP --dport $confNATPort -j ACCEPT
}

setup_sa() {
  local conf=$confDir/racoon.conf
  local remote="${2/ *}"
  local client="${2#* }"
  local locnet
  local remnet
  local p2
  local data

  test "$2" = "$client" && unset client

  if [ -z "$client" ]; then
    config_get locnet "$1" local_net
    config_get remnet "$1" remote_net
    if [ -z "$locnet" ] || [ -z "$remnet" ]; then
      $log "Remote and local networks for $1 must be configured ($2)"
      errno=4; return 4
    fi

    if [ "$remote" = "anonymous" ]; then
      echo "sainfo anonymous {" >> $conf
     else
      echo "sainfo address $locnet any address $remnet any {" >> $conf
    fi
  else
    echo "sainfo anonymous {" >> $conf
  fi

  config_get p2 "$1" p2_proposal
  if [ -z "$p2" ]; then
    $log "Phase2 proposal must be configured in $1 sainfo"
    errno=5; return 5
  fi

  echo "  remoteid $confPh1ID;" >> $conf

  config_get data "$p2" pfs_group
  test -n "$data" && echo "  pfs_group $data;" >> $conf
  config_get data "$p2" lifetime 14400
  test -n "$data" && echo "  lifetime time $data sec;" >> $conf
  config_get data "$p2" enc_alg
  test -n "$data" && echo "  encryption_algorithm $data;" >> $conf
  config_get data "$p2" auth_alg
  test -n "$data" && echo "  authentication_algorithm $data;" >> $conf

  echo -e "  compression_algorithm deflate;\n}" >> $conf

  if [ "$remote" = "anonymous" ]; then
    echo -e "mode_cfg {\n  auth_source system;\n  conf_source local;" >> $conf

    config_get data "$1" dns4
    test -n "$data" && echo "  dns4 $data;" >> $conf
    config_get data "$1" defdomain
    test -n "$data" && echo "  default_domain \"$data\";" >> $conf

    data=${remnet%/*}
    let "data=${data##*.}+1"
    echo "  network4 ${remnet%.*}.$data;" >> $conf

    let "data=255<<(24-${remnet#*/}+8)&255"
    echo "  netmask4 255.255.255.$data;" >> $conf

    echo -e "  split_network include $locnet;\n}" >> $conf

  elif [ -z "$client" ]; then
    config_list_foreach "$1" remote_exclude manage_nonesa add remote "$locnet"
    config_list_foreach "$1" local_exclude manage_nonesa add local "$remnet"
    manage_sa add "$locnet" "$remnet" $remote
    test $? -gt 0 -o $errno -gt 0 && return $errno

    manage_fw add $confIntZone $confExtZone "$remnet"
  fi
}

setup_tunnel() {
  local conf=$confDir/racoon.conf
  local peerconf=$confDir/peers.txt
  local data
  local remote
  local xauth

  config_get_bool data "$1" enabled 0
  test "$data" = "0" && return 0

  config_get remote "$1" remote
  if [ "$remote" = "anonymous" ]; then
    echo -e "remote anonymous {\n  generate_policy on;" >> $conf
  else
    data=$(nslookup "$remote" | awk 'NR == 5 {print $3}')
    test -n "$data" && remote="$data"
    echo -e "remote \"$1\" {\n  remote_address $remote;" >> $conf
    echo "$data" >> $peerconf
  fi

  config_get data "$1" pre_shared_key ""
  if [ -n "$data" ]; then
    if [ "$remote" != "anonymous" ]; then
      echo "$remote $data" >> $confDir/psk.txt
    else
      echo "* $data" >> $confDir/psk.txt
    fi
  fi

  let confPh1ID=$confPh1ID+1
  echo "  ph1id $confPh1ID;" >> $conf

  config_get xauth "$1" username ""

  config_get data "$1" certificate ""
  if [ -n "$data" ]; then
    echo -en "  verify_cert on;\n  my_identifier asn1dn;\n  certificate_type x509 " >> $conf
    echo -en "\"$data.crt\" \"$data.key\";\n  send_cr off;\n  peers_identifier " >> $conf
  else
    config_get data "$1" my_id_type ""
    if [ -n "$data" ]; then
      echo -n "  my_identifier $data" >> $conf
      config_get data "$1" my_id ""
      if [ -n "$data" ]; then
	echo " \"$data\";" >> $conf
      elif [ -n "$xauth" ]; then
	echo " \"$xauth\";" >> $conf
      else
	echo ";" >> $conf
      fi
    elif [ -n "$xauth" ]; then
      echo "  my_identifier user_fqdn \"$xauth\";" >> $conf
    fi
    echo -n "  peers_identifier " >> $conf
  fi

  if [ "$remote" = "anonymous" ]; then
    echo "user_fqdn;" >> $conf
  else
    config_get data "$1" peer_id_type "asn1dn"
    echo -n "$data" >> $conf

    config_get data "$1" peer_id ""
    test -n "$data" && echo -n " \"$data\"" >> $conf

    echo ";" >> $conf
  fi

  if [ -n "$xauth" ]; then
    config_get data "$1" password
    if [ -z "$data" ]; then
      $log "Password must be given in $1 tunnel"
      errno=7; return 7
    fi
    echo "$xauth $data" >> $confDir/psk.txt

    echo "  xauth_login \"$xauth\";" >> $conf
    echo -e "  script \"p1client-up\" phase1_up;\n  script \"p1client-down\" phase1_down;" >> $conf
  fi

  config_get data "$1" exchange_mode
  if [ -z "$data" ]; then
    data=main
    test -n "$xauth" && data="${data},aggressive"
  fi
  echo -e "  exchange_mode $data;\n  nat_traversal on;\n  support_proxy on;" >> $conf

  config_get data "$1" prop_check "obey"
  test -n "$data" && echo "  proposal_check $data;" >> $conf

  config_get_bool data "$1" weak_p1check 1
  if [ $data -eq 0 ]; then data=off; else data=on; fi
  echo "  weak_phase1_check $data;" >> $conf

  config_get_bool data "$1" verify_id 1
  if [ $data -eq 0 ]; then data=off; else data=on; fi
  echo "  verify_identifier $data;" >> $conf

  config_get data "$1" dpd_delay ""
  test -n "$data" && echo "  dpd_delay $data;" >> $conf

  unset data
  test -n "$xauth" && data="on"
  config_get data "$1" mode_cfg "$data"
  test -n "$data" && echo "  mode_cfg $data;" >> $conf

  config_get_bool data "$1" init 0
  if [ $data -eq 0 ]; then data=off; else data=on; fi
  echo "  initial_contact $data;" >> $conf


  config_list_foreach "$1" p1_proposal setup_p1
  echo "}" >> $conf

  config_list_foreach "$1" sainfo setup_sa "$remote $xauth"
}

setup_cert() {
  local item
  local data

  for item in key crt ; do
    config_get data "$1" $item ""
    test -z "$data" && continue

    echo "$data" |\
    sed 's/-\+[A-Z ]\+-\+/\n&\n/g' | sed 's/.\{50,50\}/&\n/g' | sed '/^$/d'\
    > $confDir/cert/$1.$item

    chmod 600 $confDir/cert/$1.$item
  done

  if [ -s $confDir/cert/$1.crt ]; then
    data=$(openssl x509 -noout -hash -in $confDir/cert/$1.crt)
    ln -sf $confDir/cert/$1.crt $confDir/cert/$data.0
  fi
}

destroy_sa() {
  local locnet
  local remnet

  config_get locnet "$1" local_net
  config_get remnet "$1" remote_net
  if [ -z "$locnet" ] || [ -z "$remnet" ]; then
    $log "Remote and local networks for $1 must be configured"
    errno=4; return 4
  fi

  config_list_foreach "$1" remote_exclude manage_nonesa del remote "$locnet"
  config_list_foreach "$1" local_exclude manage_nonesa del local "$remnet"
  manage_sa del "$locnet" "$remnet" $2
  manage_fw del $confIntZone $confExtZone "$remnet"
}

destroy_tunnel() {
  local data

  config_get_bool data "$1" enabled 0
  test "$data" = "0" && return 0

  config_get remote "$1" remote
  data=$(nslookup "$remote" | awk 'NR == 5 {print $3}')
  test -n "$data" && remote="$data"

  config_get data "$1" username ""
  if [ -z "$data" ]; then
    config_list_foreach "$1" sainfo destroy_sa $remote
  fi
}

destroy_conf() {
  setup_fw del
}

check_software() {
  local item

  for item in /usr/sbin/setkey /usr/bin/openssl /usr/sbin/ip ; do
    if [ ! -x $item ]; then
      $log "Needed program $item not found, exiting"
      errno=9; return 9
    fi
  done
}

cleanup_conf() {
  config_load racoon
  config_foreach setup_load racoon
  config_foreach destroy_conf racoon
  config_foreach destroy_tunnel tunnel

  /usr/sbin/setkey -P -F
  /usr/sbin/setkey -F
}

check_dir() {
  local item

  for item in $confDir $confDir/cert ; do
    if [ ! -d $item ]; then
      mkdir -m 0700 -p $item
    fi
  done
}

wait4wanzone() {
  local item=$connWait
  local data

  data="$(get_zoneiplist $confExtZone)"
  while [ $item -gt 0 ]; do
    test -n "$data" && break
    sleep 2
    let "item=$item-1"
    data="$(get_zoneiplist $confExtZone)"
  done

  test -z "$data" && return 10
}

start_service() {
  check_software
  test $? -gt 0 -o $errno -gt 0 && exit $errno

  check_dir

  config_load racoon
  config_foreach setup_load racoon

  config_foreach wait4wanzone racoon
  if [ $? -gt 0 ] || [ $errno -gt 0 ]; then
    $log "No active interfaces in $confExtZone zone found, exiting"
    exit $errno
  fi

  config_foreach setup_conf racoon
  test $? -gt 0 -o $errno -gt 0 && exit $errno

  config_foreach setup_tunnel tunnel
  test $? -gt 0 -o $errno -gt 0 && exit $errno

  config_foreach setup_cert certificate

  procd_open_instance
  procd_set_param command /usr/sbin/racoon
  test -n "$confIPMode" && procd_append_param command -$confIPMode
  procd_append_param command -F -f $confDir/racoon.conf
  procd_set_param file $confDir/racoon.conf
  procd_close_instance

  if [ -x /etc/racoon/vpnctl ]; then
    let connWait=$connWait*2+2
    ( sleep $connWait; /etc/racoon/vpnctl up ) &
  fi
}

service_triggers() {
  local item
  local data

  procd_add_reload_trigger "racoon" "network"

  config_load racoon
  config_foreach setup_load racoon

  data=$(get_zoneiflist $confExtZone)
  if [ $? -gt 0 ] || [ $errno -gt 0 ] || [ -z "$data" ]; then
    $log "Can not find interfaces for $confExtZone zone"
  else
    for item in $data ; do
      procd_add_reload_interface_trigger $item
    done
  fi
}

stop_service() {
  cleanup_conf
  procd_kill racoon 
}

trap "cleanup_conf" 1 2 3 4 5 6 7 8 9 10


# EOF /etc/init.d/racoon
