#!/bin/sh
#####################################################################
SCRIPT="$(readlink -f "${0}")"
script_name="${0##*/}"

ETC_OPENVPN_DIR="/etc/openvpn"
ETC_EASYRSA_DIR="/etc/easy-rsa"
# "/etc/easy-rsa" should be avoided, upgrades will overwrite it.
# Make copy and use it from different path.
# Contains 3 files on clean FW:
#   openssl-1.0.cnf
#   vars
#   x509-types

: "${VPN_SERVER_PORT:=1194}"
: "${VPN_SERVER_IP:=}"
: "${VPN_SUBNET_IP:=10.8.0.0}"
: "${VPN_SUBNET_MASK:=255.255.255.0}"

: "${EDITOR:=nano}"
: "${OPENVPN_DIR:=${ETC_OPENVPN_DIR}}"
: "${EASYRSA_DIR:=${OPENVPN_DIR}/easy-rsa}"

: "${PRODUCT_ID:=$(cat /usr/local/etc/telem/product_id 2>/dev/null)}"
: "${GEN_DH:=genDH}"

: "${ROOT_DIR:=}"

DAYS=7300
EASYRSA="easyrsa --batch"

ASK_FIRST=true

START_DIR="$(pwd)"

INITDSCRIPT='/etc/init.d/S83openvpnserver'
#####################################################################

setupEasyRSA() {
  [ -e "${EASYRSA_DIR}" ] && return
  mkdir -p "${EASYRSA_DIR}"
  if [ -r "${ETC_EASYRSA_DIR}" ]; then
    cp -r "${ETC_EASYRSA_DIR}/"* "${EASYRSA_DIR}"
    rm "${EASYRSA_DIR}/vars"
  fi
}

setupDir() {
  if [ -z "${ROOT_DIR}"]; then
    [ -e "/usr/local/etc/telem" ] || ROOT_DIR="${START_DIR}/vpn"
  fi
  if [ -n "${ROOT_DIR}" ]; then
    mkdir -p "${ROOT_DIR}/"
  fi
  if [ -w "${ROOT_DIR}/" ]; then
    OPENVPN_DIR="${ROOT_DIR}${ETC_OPENVPN_DIR}"
    EASYRSA_DIR="${ROOT_DIR}${ETC_OPENVPN_DIR}/easy-rsa"
    mkdir -p "${OPENVPN_DIR}"
    setupEasyRSA
  fi
}

initVariables() {
  setupDir
  OPENVPN_DIR="$(readlink -f "${OPENVPN_DIR}")"
  EASYRSA_DIR="$(readlink -f "${EASYRSA_DIR}")"
  EASYRSA="easyrsa --batch --vars=${EASYRSA_DIR}/vars"
  PRODUCT_ID="${PRODUCT_ID:-testserver}"
}

die() { echo >&2 "$@"; exit 1; }
needRoot() { [ "$(id -u)" != "0" ] && die "This script must be run as root"; }
testcmd() { command -v "$1" &>/dev/null; }
testcmd2() { testcmd "$1" || die "Missing $1"; }

#####################################################################
# pre-defined DHE groups as recommended by IETF RFC 7919:
# ffdhe2048, ffdhe3072 or ffdhe4096.
ffdhe2048() {
cat << "EOF"
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----
EOF
}
ffdhe3072(){
cat << "EOF"
-----BEGIN DH PARAMETERS-----
MIIBiAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3
7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32
nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu
N///////////AgEC
-----END DH PARAMETERS-----
EOF
}
ffdhe4096(){
cat << "EOF"
-----BEGIN DH PARAMETERS-----
MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3
7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32
nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e
8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx
iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K
zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=
-----END DH PARAMETERS-----
EOF
}
gen_ffdhe2048() { ffdhe2048 > "${EASYRSA_DIR}/pki/dh.pem"; }
gen_ffdhe3072() { ffdhe3072 > "${EASYRSA_DIR}/pki/dh.pem"; }
gen_ffdhe4096() { ffdhe4096 > "${EASYRSA_DIR}/pki/dh.pem"; }
genDH() { $EASYRSA gen-dh; }
#####################################################################
# Base server and client settings

gen_base_server() {
cat << EOF
# Change port and OpenVPN network
port ${VPN_SERVER_PORT}
server ${VPN_SUBNET_IP} ${VPN_SUBNET_MASK}

proto udp
dev tun101
topology subnet

persist-key
persist-tun

client-to-client
ifconfig-pool-persist /var/log/openvpn.server.ipp
client-config-dir /etc/openvpn/ccd
ccd-exclusive

cipher AES-256-CBC
auth SHA256

keepalive 10 120
max-clients 100
user nobody
group nogroup
key-direction 0

explicit-exit-notify 1

# Set output verbosity (default=1):
# 0 -- No output except fatal errors.
# 1 to 4 -- Normal usage range.
verb 1

log /var/log/openvpn.server.log
status /var/log/openvpn.server.st 60
EOF
}

gen_base_client() {
cat << EOF
# Change server IP/Domain and port
remote ${VPN_SERVER_IP} ${VPN_SERVER_PORT}

client
dev tun
proto udp
resolv-retry infinite
persist-key
persist-tun
nobind

cipher AES-256-CBC
auth SHA256

remote-cert-tls server
key-direction 1

# Set output verbosity (default=1):
# 0 -- No output except fatal errors.
# 1 to 4 -- Normal usage range.
# 5 -- Output R and W characters, uppercase for
# TCP/UDP packets and lowercase for TUN/TAP packets.
# 6 to 11 -- Debug info range.
verb 1

#log /var/log/openvpn.tunX.log
#status /var/log/openvpn.tunX.st 60
EOF
}

gen_startup() {
cat << "EOF"
#!/bin/sh
# Generated
VPN_SERVER_CONF='/etc/openvpn/server.conf'
start() {
	logger -s -p "user.info" -t "$0" "OpenVPN server:start ... "

	if [ -r "${VPN_SERVER_CONF}" ]; then
		mkdir -p /etc/openvpn/ccd
		chmod 755 /etc/openvpn/ccd
		chmod 644 /etc/openvpn/ccd/*

		server_port=$(awk '/^port/{print $2}' "${VPN_SERVER_CONF}")
		/usr/sbin/iptables -t filter -A INPUT -p udp --dport "${server_port}" -m conntrack --ctstate NEW -j ACCEPT
		/usr/local/bin/openvpn/StartOpenVPNClient "${VPN_SERVER_CONF}" &> /dev/null &
	fi

	logger -s -p "user.info" -t "$0" "done"
}

stop() {
	logger -s -p "user.info" -t "$0" "OpenVPN server:stop ... "
	ps aux | grep -i "${VPN_SERVER_CONF}" | awk '{print $2}' | xargs kill -9 || true
	logger -s -p "user.info" -t "$0" "done"
}

restart() { stop; start; }

case "$1" in
	start) start; ;;
	stop) stop; ;;
	restart|reload) restart; ;;
	*)
		echo "Usage: $0 {start|stop|restart}"
		exit 1
esac

exit $?
EOF
}

getEditorComment() {
[ 'nano' = "${EDITOR:(-4)}" ] && \
cat << "EOF"
# Ctrl+S to save, Ctrl+X to exit
EOF
[ 'vi' = "${EDITOR:(-2)}" ] && \
cat << "EOF"
# i: start insert mode, ESC: start command mode
# Save and exit: ESC and type :wq
EOF
}

gen_vars() {
cat << EOF
# EDITOR=${EDITOR}
$(getEditorComment)

set_var EASYRSA_REQ_CN          "${SERVER_NAME}"

set_var EASYRSA_DN              "org"
set_var EASYRSA_REQ_COUNTRY     "EE"
set_var EASYRSA_REQ_PROVINCE    "Harjumaa"
set_var EASYRSA_REQ_CITY        "Tallinn"
set_var EASYRSA_REQ_ORG         "Martem AS"
set_var EASYRSA_REQ_EMAIL       "martem@martem.ee"
set_var EASYRSA_REQ_OU          "Networking"
set_var EASYRSA_BATCH           "yes"

# For elliptic curve:
#set_var EASYRSA_ALGO ec
#set_var EASYRSA_CURVE secp521r1
# For Twisted Edwards curve:
#set_var EASYRSA_ALGO  ed
#set_var EASYRSA_CURVE ed25519

EOF
}

getFirstIP() {
ip -4 addr | awk -F'[ /]' '
!/inet/{next}
/127(\.[0-9]{1,3}){3}/{next}
{print $6;exit}
'
}
#####################################################################
valid_any() {
  true
}

valid_text() {
  P='[0-9a-zA-Z]\+$'
  expr match "${1}" "${P}" &>/dev/null
}
valid_text_m(){
  valid_text "${1}" && return
  echo "Invalid text: '${1}'"
  return 1
}

valid_name() {
  # Valid server or client name
  expr match "${1}" '[a-zA-Z0-9_-]\+$' &>/dev/null
}
valid_name_m() {
  [ -z "${1}" ] && return
  valid_name "${1}" && return
  echo "Invalid name: '${1}'"
  return 1
}

valid_new_client_name() {
  valid_name "$1" || return 1
  [ -e "${EASYRSA_DIR}/pki/issued/$1.crt" ] && return 1
  [ "${1::6}" = 'server' ] && return 1
  return 0
}
valid_new_client_name_m() {
  [ -z "${1}" ] && return
  if ! valid_name "$1"; then
    echo "Invalid client name: '${1}'"
    return 1
  fi
  if [ -e "${EASYRSA_DIR}/pki/issued/$1.crt" ]; then
    echo "Name already exists: '${1}'"
    return 1
  fi
  if [ "${1::6}" = 'server' ]; then
    echo "Do not use 'server' in the name: '${1}'"
    return 1
  fi
  return 0
}

valid_client_name() {
  N="${1:-${CLIENT_NAME}}"
  [ "${N}" = "${SERVER_NAME}" ] && return 1
  expr match "$N" '[a-zA-Z0-9_-]\+$' &>/dev/null || return 1
  [ -e "${EASYRSA_DIR}/pki/issued/$N.crt" ] || return 1
  return 0
}
valid_client_name_m() {
  [ -z "${1}" ] && return
  valid_client_name "${1}" && return
  echo "Client does not exist: '${1}'"
  return 1
}

valid_IPv4() {
  P='[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$'
  if expr match "$1" "$P" &>/dev/null; then
    for i in 1 2 3 4; do
      if [ $(echo "$1" | cut -d. -f$i) -gt 255 ]; then
        return 1
      fi
    done
    return 0
  else
    return 1
  fi
}
valid_IPv4_m() {
  [ -z "${1}" ] && return
  valid_IPv4 "${1}" && return
  echo "Invalid IP: '${1}'"
  return 1
}

valid_domain() {
  # Using simple pattern, not always valid
  P='[0-9a-zA-Z.-]\+$'
  expr match "${1}" "${P}" &>/dev/null
}
valid_domain_m() {
  [ -z "${1}" ] && return
  valid_domain "${1}" && return
  echo "Invalid domain name: '${1}'"
  return 1
}

valid_domainIP_m() {
  [ -z "${1}" ] && return
  valid_domain_m "${1}" && return
  valid_IPv4_m "${1}" && return
}

valid_port() {
  expr match "$1" "[0-9]\+$" &>/dev/null \
  && [ "$1" -lt 65536 ]
}
valid_port_m() {
  [ -z "${1}" ] && return
  valid_port "${1}" && return
  echo "Invalid port: '${1}'"
  return 1
}

valid_ny() {
  expr match "${1}" '[ny]$' &>/dev/null
}
valid_ny_m() {
  [ -z "${1}" ] && return
  valid_ny "${1}" && return
  echo "Invalid answer: '${1}'"
  return 1
}
#####################################################################
ask1value() {
  # $1: prompt
  # $2: default
  # $3: test function
  d1="${2}"
  t1="${3:-valid_text_m}"
  v1=
  AF=$ASK_FIRST
  [ "${d1}" = '?' ] && { d1=; AF=true; }
  $AF && read -p "$1${d1:+ [${d1}]}: " v1
  v1="${v1:-${d1}}"
  until $t1 "${v1}"; do
    read -p "$1${d1:+ [${d1}]}: " v1
    v1="${v1:-${d1}}"
  done
  [ -n "$v1" ]
}

ask2values() {
  # $1: prompt
  # $2: default1
  # $3: default2
  # $4: test function 1
  # $5: test function 2
  d1="${2}"
  d2="${3}"
  t1="${4:-valid_text_m}"
  t2="${5:-valid_text_m}"
  v1=
  v2=
  AF=$ASK_FIRST
  [ "${d1}" = '?' ] && { d1=; AF=true; }
  $AF && read -p "$1${d1:+ [${d1}${d2:+ $d2}]}: " v1 v2 r
  v1="${v1:-${d1}}"
  v2="${v2:-${d2}}"
  until $t1 "${v1}" && $t2 "${v2}"; do
    read -p "$1${d1:+ [${d1}${d2:+ $d2}]}: " v1 v2 r
    v1="${v1:-${d1}}"
    v2="${v2:-${d2}}"
  done
  [ -n "$v1" ] && [ -n "$v2" ] || [ -n "$v1" ]
}

ask_ny() {
  ask1value "$1 (n/y)" "$2" valid_ny_m
  [ "$v1" = 'y' ]
}
#####################################################################
load_settings() {
  F1="${EASYRSA_DIR}/server.conf"
  F2="${EASYRSA_DIR}/client.conf"
  if [ -r "${F1}" ] && [ -r "${F2}" ]; then
    VPN_SERVER_PORT=$(awk '/^port /{print $2}' "${F1}")
    VPN_SUBNET_IP=$(awk '/^server /{print $2}' "${F1}")
    VPN_SUBNET_MASK=$(awk '/^server /{print $3}' "${F1}")
    VPN_SERVER_IP=$(awk '/^remote /{print $2}' "${F2}")
    return 0
  fi
  VPN_SERVER_PORT=${VPN_SERVER_PORT:-1194}
  VPN_SERVER_IP=${VPN_SERVER_IP:-$(getFirstIP)}
  VPN_SUBNET_IP=${VPN_SUBNET_IP:-10.8.0.0}
  VPN_SUBNET_MASK=${VPN_SUBNET_MASK:-255.255.255.0}
  return 1
}

print_settings() {
cat << EOF
Current settings:
  Server address '${VPN_SERVER_IP}' and port '${VPN_SERVER_PORT}'.
  VPN subnet '${VPN_SUBNET_IP}' and mask '${VPN_SUBNET_MASK}'.
EOF
}

ask_settings() {
  if load_settings; then
    echo
    print_settings
    echo
    ask_ny "Change settings" n || return
  fi
  ask_address_and_port "Provide server ip or domain and optional port" \
  "${VPN_SERVER_IP}" "${VPN_SERVER_PORT}"
  VPN_SERVER_IP="${v1:-${VPN_SERVER_IP}}"
  VPN_SERVER_PORT="${v2:-${VPN_SERVER_PORT}}"

  ask_ip "Provide VPN subnet address and optional mask" \
  "${VPN_SUBNET_IP}" "${VPN_SUBNET_MASK}"
  VPN_SUBNET_IP="${v1:-${VPN_SUBNET_IP}}"
  VPN_SUBNET_MASK="${v2:-${VPN_SUBNET_MASK}}"

  write_base
}

load_vars() {
  F="${EASYRSA_DIR}/vars"
  if [ -r "$F" ]; then
    SERVER_NAME=$(
      awk '/^set_var EASYRSA_REQ_CN\s+/{gsub(/"/, "", $3);print $3}' "${F}"
    )
    return 0
  fi
  SERVER_NAME="${SERVER_NAME:-${PRODUCT_ID}}"
  return 1
}

print_vars() {
  F="${EASYRSA_DIR}/vars"
  echo "Current vars:"
  awk '/^#|^\s*$/{next}{print "  "$0}' "$F"
}

ask_vars() {
  F="${EASYRSA_DIR}/vars"
  if load_vars; then
    echo
    print_vars
    echo
    ask_ny "Change vars" n || return
  fi
  write_vars
  $EDITOR "$F"
  load_vars
}

ask_dh_gen() {
  GEN_DH=genDH
  echo
  ask_ny "Generate DH (can take 2 hours)" y && return
  echo "Using pre-defined DHE groups as recommended by IETF RFC 7919."
  GEN_DH=gen_ffdhe2048
}

has_server_keys() {
  [ -r "${EASYRSA_DIR}/pki/ca.crt" ] && \
  [ -r "${EASYRSA_DIR}/pki/issued/${SERVER_NAME}.crt" ] && \
  [ -r "${EASYRSA_DIR}/pki/private/${SERVER_NAME}.key" ] && \
  [ -r "${EASYRSA_DIR}/pki/private/ta.key" ] && \
  [ -r "${EASYRSA_DIR}/pki/dh.pem" ]
}

has_client_keys() {
  [ -r "${EASYRSA_DIR}/pki/ca.crt" ] && \
  [ -r "${EASYRSA_DIR}/pki/issued/{CLIENT_NAME}.crt" ] && \
  [ -r "${EASYRSA_DIR}/pki/private/{CLIENT_NAME}.key" ] && \
  [ -r "${EASYRSA_DIR}/pki/private/ta.key" ]
}

ask_server_gen() {
  [ -z "${GEN_SERVER_CERTS}" ] || return
  GEN_SERVER_CERTS=false
  if has_server_keys; then
    echo
    echo "Already have certs and keys"
    ask_ny 'Regenerate' n || return    
  else
    :
  fi
  GEN_SERVER_CERTS=true
  ask_dh_gen
}

get_free_client_name() {
  for i in $(seq 99); do
    FREE_CLIENT_NAME="client${i}"
    [ -r "${EASYRSA_DIR}/pki/private/${FREE_CLIENT_NAME}.key" ] || return 0
  done
  return 1
}

ask_new_client_name() {
  echo
  echo "Client needs a name (ex: GWM-1234)."
  ask1value "Name" "${CLIENT_NAME}" valid_new_client_name_m
  CLIENT_NAME="${v1:-${CLIENT_NAME}}"
  [ -n "${CLIENT_NAME}" ]
}

ask_server_name() {
  F="${EASYRSA_DIR}/vars"
  if ! load_vars; then
    ask1value "Provide server name" "${SERVER_NAME}" valid_name_m
    SERVER_NAME="${v1:-${SERVER_NAME}}"
    write_vars
  fi
  [ -n "${SERVER_NAME}" ]
}

ask_client_name() {
  ask1value "Client name" "${CLIENT_NAME}" valid_client_name_m
  CLIENT_NAME="${v1:-${CLIENT_NAME}}"
  [ -n "${CLIENT_NAME}" ]
}

ask_address_and_port() {
  ask2values "$1" "$2" "$3" valid_domainIP_m valid_port_m
}
ask_ip() {
  ask2values "$1" "$2" "$3" valid_IPv4_m valid_IPv4_m
}

ask_backup_file_name() {
  BKPFILE="${BKPFILE:-"$(ls -1t "${START_DIR}/vpn-"*.tar.gz | head -n1)"}"
  ask1value "Backup file name" "${BKPFILE}" valid_any
  BKPFILE="${v1:-${BKPFILE}}"
  [ -e "${BKPFILE}" ] && return
  BKPFILE="${START_DIR}/${BKPFILE}"
  [ -e "${BKPFILE}" ] || die "File does not exist!"
}

ask_server_restart() {
  [ -e "${INITDSCRIPT}" ] || return
  ask_ny "Start/Restart VPN server" n || return
  "${INITDSCRIPT}" restart
}
#####################################################################

inline() {
  # $1: name
  # $2: file or stdin
  echo
  echo "<${1}>"
  cat -- "${2:--}"
  echo "</${1}>"
}

gen_ServerInlineFiles() {
  cd "${EASYRSA_DIR}"
  inline 'ca'         'pki/ca.crt'
  inline 'cert'       'pki/issued/server.crt'
  inline 'key'        'pki/private/server.key'
  inline 'tls-auth'   'pki/private/ta.key'
  inline 'dh'         'pki/dh.pem'
  inline 'crl-verify' 'pki/crl.pem'
}
gen_ServerOVPN() {
  cd "${EASYRSA_DIR}"
  [ -e "server.conf" ] || gen_base_server > server.conf
  gen_ServerInlineFiles > server.inline
  cat server.conf server.inline
}
createServerOVPN() {
  FNAME="${OPENVPN_DIR}/server.conf"
  gen_ServerOVPN > "${FNAME}"
}

gen_ClientOVPN() {
  cd "${EASYRSA_DIR}"
  [ -e "client.conf" ] || gen_base_client > client.conf
  cat client.conf
  inline 'ca' pki/ca.crt
  sed -ne '/BEGIN CERTIFICATE/,$ p' "pki/issued/${CLIENT_NAME}.crt" | inline 'cert'
  inline 'key' "pki/private/${CLIENT_NAME}.key"
  inline 'tls-auth' 'pki/private/ta.key'
}
createClientOVPN() {
  mkdir -p "${OPENVPN_DIR}/clients/"
  FNAME="${OPENVPN_DIR}/clients/${CLIENT_NAME}.ovpn"
  gen_ClientOVPN > "${FNAME}"
}

createStartup() {
  F="${INITDSCRIPT}"
  if [ -e "${F}" ]; then
    echo
    echo "Startup script already exists."
    read -p "Overwrite? (type uppercase YES): " VAL
    [ "$VAL" = "YES" ] || { echo "Cancel overwrite."; return 1; }
  fi
  gen_startup > "${F}" && \
  chmod +x "${F}" && \
  echo "Script created."
  ask_server_restart
}
#####################################################################

gen_server_certs() {
  cd "${EASYRSA_DIR}"
  # Building CA
  $EASYRSA init-pki
  $EASYRSA build-ca nopass

  # Building server cert & keys
  $EASYRSA --days=${DAYS} --req-cn=ChangeMe build-server-full "${SERVER_NAME}" nopass
  $EASYRSA --days=${DAYS} gen-crl
  ${GEN_DH}
  openvpn --genkey --secret pki/private/ta.key

  cp "pki/issued/${SERVER_NAME}.crt" "pki/issued/server.crt"
  cp "pki/private/${SERVER_NAME}.key" "pki/private/server.key"
}

gen_server() {
  $GEN_SERVER_CERTS && gen_server_certs

  mkdir -p "${OPENVPN_DIR}/ccd"
  chmod 755 "${OPENVPN_DIR}/ccd"

  createServerOVPN

  echo "Created: '${FNAME}'"
}

write_base() {
  F1="${EASYRSA_DIR}/server.conf"
  F2="${EASYRSA_DIR}/client.conf"

  if [ -r "${F1}" ]; then
    tmpF="$(mktemp)"
    awk '
      /^port /{print "port '"${VPN_SERVER_PORT}"'";next}
      /^server /{print "server '"${VPN_SUBNET_IP}"' '"${VPN_SUBNET_MASK}"'";next}
      {print}
    ' "${F1}" > "${tmpF}"
    mv "${tmpF}" "${F1}"
  else
    gen_base_server > "${F1}"
  fi

  if [ -r "${F2}" ]; then
    tmpF="$(mktemp)"
    awk '
      /^remote /{print "remote '"${VPN_SERVER_IP}"' '"${VPN_SERVER_PORT}"'";next}
      {print}
    ' "${F2}" > "${tmpF}"
    mv "${tmpF}" "${F2}"
  else
    gen_base_client > "${F2}"
  fi
}

write_vars() {
  F1="${EASYRSA_DIR}/vars"
  if [ -r "${F1}" ]; then
    tmpF="$(mktemp)"
    awk '
      /^set_var EASYRSA_REQ_CN\s+/{gsub(/".*"/, "\"'"${SERVER_NAME}"'\"")}
      {print}
    ' "${F1}" > "${tmpF}"
    mv "${tmpF}" "${F1}"
  else
    gen_vars > "${F1}"
  fi
}

createServer() {
  echo
  echo "Create server"

  setupEasyRSA

  ask_settings
  ask_server_name
  ask_vars
  ask_server_gen

  set -e
  gen_server
  set +e
  ask_server_restart
}

createClient() {
  CLIENT_NAME="${1:-${CLIENT_NAME}}"
  has_server_keys || die "Setup server first"
  ask_new_client_name || return

  set -e

  load_settings

  cd "${EASYRSA_DIR}"
  $EASYRSA --days=${DAYS} --req-cn=ChangeMe build-client-full "${CLIENT_NAME}" nopass

  createClientOVPN

  touch "${OPENVPN_DIR}/ccd/${CLIENT_NAME}"
  chmod 644 "${OPENVPN_DIR}/ccd/${CLIENT_NAME}"

  echo "Configuration is here: ${FNAME}"

  set +e
}

removeAll() {
  echo
  echo "We are going to remove all previously generated files."
  read -p "Remove all. Are you sure? (type uppercase YES): " VAL
  [ "$VAL" = "YES" ] || { echo "Cancel removing."; return 1; }

  rm -vrf \
  "${EASYRSA_DIR}/" \
  "${OPENVPN_DIR}/server.conf" \
  "${OPENVPN_DIR}/clients/" \
  "${OPENVPN_DIR}/ccd/"
  echo "Done removing."
}

runTestSetup() {
  if has_server_keys; then
    echo
    echo "Server already configured!"
    removeAll || return
  fi

  setupEasyRSA

  ASK_FIRST=false
  GEN_DH=gen_ffdhe2048
  GEN_SERVER_CERTS=true

  load_settings || write_base
  load_vars || write_vars
  print_settings
  print_vars

  set -e
  gen_server
  createClient "client1"
  createClient "client2"
  setClientStaticIP 10.8.0.22
  createClient "client3"
  set +e
  ASK_FIRST=true
  ask_server_restart
}

runInfo() {
  echo
  echo "Server conf:"
  for f in "${EASYRSA_DIR}/server.conf" "${OPENVPN_DIR}/server.conf"; do
    [ -e "${f}" ] && echo "  '${f}'"
  done || echo '  NONE'

  echo
  echo "Client configs:"
  for f in "${EASYRSA_DIR}/client.conf" "${OPENVPN_DIR}/clients/"*.ovpn; do
    [ -e "${f}" ] && echo "  '${f}'"
  done || echo '  NONE'
}
#####################################################################
setClientStaticIP() {
  new_ip="${1:-${new_ip}}"
  new_mask="${2:-${new_mask}}"

  ask_client_name || die "Need a client name!"

  CCD="${OPENVPN_DIR}/ccd/${CLIENT_NAME}"
  if [ ! -f "${CCD}" ]; then
    echo "Client '${CLIENT_NAME}' override file is not found in '${OPENVPN_DIR}/ccd'"
    return 1
  fi

  ask_ip "Provide IP address and optional mask" "${new_ip:-?}" "${new_mask}"
  new_ip="${v1:-${new_ip}}"
  new_mask="${v2:-${new_mask}}"
  valid_IPv4 "${new_ip}" || die "IP address is required!"

  if grep -q -F 'ifconfig-push' "${CCD}" ; then
    prev_ip=$(awk '/ifconfig-push/ {print $2}' "${CCD}")
    prev_mask=$(awk '/ifconfig-push/ {print $3}' "${CCD}")
    new_mask="${new_mask:-${prev_mask:-255.255.255.0}}"
    sed -i "s/.*ifconfig-push.*/ifconfig-push ${new_ip} ${new_mask}/g" "${CCD}"
    echo "Changed client '${CLIENT_NAME}' static IP from ${prev_ip} to ${new_ip}"
    echo "'${CCD}'"
  else
    new_mask="${new_mask:-255.255.255.0}"
    echo "ifconfig-push ${new_ip} ${new_mask}" >> "${CCD}"
    echo "Set client '${CLIENT_NAME}' IP to ${new_ip}"
    echo "'${CCD}'"
  fi
}

addNetRoute() {
  new_net="${1:-${new_net}}"
  new_mask="${2:-${new_mask:-255.255.255.0}}"

  ask_client_name || die "Need a client name!"

  CCD="${OPENVPN_DIR}/ccd/${CLIENT_NAME}"
  if [ ! -f "${CCD}" ]; then
    echo "Client '${CLIENT_NAME}' override file is not found in '${OPENVPN_DIR}/ccd'"
    return 1
  fi

  ask_ip "Provide Network address and optional mask" "${new_net:-?}" "${new_mask}"
  new_net="${v1:-${new_net}}"
  new_mask="${v2:-${new_mask}}"
  valid_IPv4 "${new_net}" || die "Network address is required!"

  if grep -q -F "iroute ${new_net} ${new_mask}" "${CCD}" ; then
    echo "Client '${CLIENT_NAME}' has this network route added. Finishing"
  else
    echo "iroute ${new_net} ${new_mask}" >> "${CCD}"
    echo -e "\npush \"route ${new_net} ${new_mask}\"" >> "${EASYRSA_DIR}/server.conf"
    echo "Added route to network ${new_net} ${new_mask} through client '${CLIENT_NAME}'"
    echo "'${CCD}'"
    echo "'${EASYRSA_DIR}/server.conf'"
    createServerOVPN
    ask_server_restart
  fi
}

revokeClient() {
  ask_client_name || die "Need a client name!"
  cd "${EASYRSA_DIR}"
  $EASYRSA revoke "${CLIENT_NAME}"
  $EASYRSA --days=${DAYS} gen-crl
  createServerOVPN
  echo "${CLIENT_NAME} revoked!"
  # Restart could be avoided if it would not be inlined?
  ask_server_restart
}

#####################################################################

version() {
  echo "FW VERSION:"
  cat /usr/local/etc/telem/version 2>/dev/null
  echo "SCRIPT VERSION:"
  sha256sum "${SCRIPT}" 2>/dev/null
  echo "PRODUCT:"
  cat /usr/local/etc/telem/product_id
  cat /usr/local/etc/telem/order_code
  echo ""
}

makeBackup() {
  P="$(cat /usr/local/etc/telem/product_id 2>/dev/null)"
  P="${P:-PC}"
  TARGET="${START_DIR}/vpn-${P}-$(hostname)-$(date -I).tar.gz"
  mkdir -p "${ROOT_DIR}/tmp"
  (
    set -x
    cd "${ROOT_DIR}/" || return
    version
    ls -laR -- ".${ETC_OPENVPN_DIR}/"
  ) &> "${ROOT_DIR}/tmp/backup-info.txt"
  [ "$(id -u)" != "0" ] && ug='--owner=0 --group=0'
  rm "${TARGET}" 2>/dev/null
  tar -cvzf "${TARGET}" -C "${ROOT_DIR}/" \
  ${ug} \
  ".${ETC_OPENVPN_DIR}"/easy-rsa \
  ".${ETC_OPENVPN_DIR}"/clients \
  ".${ETC_OPENVPN_DIR}"/ccd \
  ".${ETC_OPENVPN_DIR}"/server.conf \
  "./tmp/backup-info.txt"
  
  # List files:
  # tar -tvf vpn-*.tar.gz
  # View info:
  # tar -Oxf vpn-*.tar.gz ./tmp/backup-info.txt
}

restoreBackup() {
  ask_backup_file_name || die "Need a backup file name!"
  SOURCE="${BKPFILE:-${1}}"
  tar -xvzf "${SOURCE}" -C "${ROOT_DIR}/"
}

#####################################################################
runValidOpt() {
  P='[123459cirstx]$\|2[abc]$\|[cr]b$'
  P="$P"'\|testsetup$\|client$\|server$\|info$\|remove$\|startup$'
  P="$P"'\|backup$\|create-backup$\|restore-backup$'
  P="$P"'\|set-static-ip$\|add-network-route$\|revoke$'
  expr match "$option" "$P" &>/dev/null && return
  echo "Invalid option: '$option'"
  return 1
}

runMenu() {
cat << EOL
Select an option:

  1) Setup server
  2) Add a new client
    2a) Set static IP
    2b) Add route
    2c) Revoke
  3) Add startup script

  4) Quick test setup
  5) Show info
  9) Remove all

  cb/rb) Create/Restore backup

  x) Exit

EOL

  read -p "Option: " option
  until [ -z "$option" ] || runValidOpt; do
    read -p "Option: " option
  done
}

runOpt() {
  case "$option" in
    1|s|server)
      echo "=== Setup server ==="
      ASK_FIRST=true
      VPN_SERVER_PORT="${opt2:-${VPN_SERVER_PORT}}"     
      VPN_SUBNET_IP="${opt3:-${VPN_SUBNET_IP}}"
      VPN_SERVER_IP="${opt4:-${VPN_SERVER_IP}}"
      createServer
    ;;
    2|c|client)
      echo "=== Add client ==="
      get_free_client_name
      CLIENT_NAME="${opt2:-${FREE_CLIENT_NAME}}"
      new_ip="${opt3}"
      new_mask="${opt4}"
      createClient
      [ -n "${new_ip}" ] && setClientStaticIP
    ;;
    2a|set-static-ip)
      echo "=== Add static IP address ==="
      CLIENT_NAME="${opt2:-${CLIENT_NAME}}"
      new_ip="${opt3}"
      new_mask="${opt4}"
      setClientStaticIP
    ;;
    2b|add-network-route)
      echo "=== Add network route ==="
      CLIENT_NAME="${opt2:-${CLIENT_NAME}}"
      new_net="${opt3}"
      new_mask="${opt4}"
      addNetRoute
    ;;
    2c|revoke)
      CLIENT_NAME="${opt2:-${CLIENT_NAME}}"
      revokeClient
    ;;
    3|startup)
      echo "=== Add startup script ==="
      createStartup
    ;;
    4|t|testsetup)
      echo "=== Test setup ==="
      runTestSetup
    ;;
    5|i|info)
      echo "=== Show info ==="
      runInfo
    ;;
    9|r|remove)
      echo "=== Remove all ==="
      removeAll
    ;;
    cb|backup|create-backup)
      echo "=== Create backup ==="
      makeBackup
    ;;
    rb|restore-backup)
      echo "=== Restore from backup ==="
      BKPFILE="${opt2}"
      restoreBackup
    ;;
    *) exit
  esac
}

run() {
  while :; do
    ASK_FIRST=true
    GEN_SERVER_CERTS=
    runMenu
    runOpt
    ask_ny "Back to menu?" y || return
    echo
  done
}

#####################################################################
testcmd2 easyrsa
testcmd2 openvpn
initVariables

[ -O "${OPENVPN_DIR}" ] && \
[ -O "${EASYRSA_DIR}" ] && \
[ -r '/etc/easy-rsa' ] || \
needRoot

version

load_settings
load_vars

option="${1}"
if [ -n "$option" ] && runValidOpt; then
  opt2="${2}"
  opt3="${3}"
  opt4="${4}"
  [ -n "${opt2}" ] && ASK_FIRST=false
  runOpt
else
  run
fi
#####################################################################
