#!/bin/sh
#
# Copyright 2016 VMware, Inc. All rights reserved.
#
# nsx-proxy:
#   Start/Stop the NSX Proxy
#
# Tell chkconfig to start us with starting rank 98 and shutdown rank 2.
# chkconfig: 2345 98 2
# description: NSX Proxy
#

# Load the LSB shell functions
[ -f /lib/lsb/init-functions ] && . /lib/lsb/init-functions

# variables
PATH=/bin:/sbin:/usr/bin:/usr/local/bin:$PATH

PROG=/usr/bin/nsx-proxy
PROG_TAG=NSX-Proxy

MAKE_CERT=/opt/vmware/nsx-common/python/nsx_utils/make_cert.py
PROXY_SOCK_DIR=/var/run/vmware/nsx-proxy
PROXY_LOG_DIR=/var/log/vmware
PROXY_MAX_QUICK_RESTARTS="100"
PROXY_MAX_RESTARTS="1000"
PROXY_CFG_FILE=/etc/vmware/nsx/nsx-proxy.xml
CCP_CFG_FILE=/etc/vmware/nsx/controller-info.xml
APPLIANCE_INFO_FILE=/etc/vmware/nsx/appliance-info.xml
PROXY_ARGS="-c /etc/vmware/nsx/nsx-proxy.xml"
WATCHDOG_ARGS="-d -s ${PROG_TAG} -q ${PROXY_MAX_QUICK_RESTARTS} -t ${PROXY_MAX_RESTARTS}"
WATCHDOG=/opt/vmware/nsx-proxy/bin/watchdog.sh
nsx_proxy_user="nsx-proxy"
nsx_proxy_group="nsx-proxy"
nsx_group="nsx"
nsx_opsagent_group="nsx-opsagent"
nsx_nestdb_group="nestdb"
nsx_agent_group="nsx-agent"
PROXY_CERT_DIR=/etc/vmware/nsx/
PROXY_SSL_CNF=$PROXY_CERT_DIR"openssl-proxy.cnf"
PROXY_KEY_FILE=$PROXY_CERT_DIR"host-privkey.pem"
PROXY_CERT_FILE=$PROXY_CERT_DIR"host-cert.pem"
HOST_CFG_FILE=/etc/vmware/nsx/host-cfg.xml
MPA_CONFIG_DIR=/etc/vmware/nsx-mpa/
MPA_CONFIG_FILE=$MPA_CONFIG_DIR"mpaconfig.json"

SHUTDOWN_TIMEOUT=5
TERMINATE_ATTEMPTS=5

# <instrumentation> #

prog_log() {
   echo "${1}"
   logger -p daemon.info -t NSX "${1}"
}

create_certificate() {
   prog_log "Generating host certificate with TN uuid = ${1}."
   uuid=''
   if [ ! -z "$1" ]; then
      uuid="UID = $1"
   fi
   ADD_REQ_EXT="-extensions req_ext"
   # Keep retrying temp openssl config file generation if it fails.
   # This should succeed for the certs to be created.
   while [ 1 ]; do
      TMP_OPENSSL_CNF=`mktemp -q`
      if [ $? -ne 0 ]; then
         prog_log "Error creating temporary openssl config file, retrying"
         sleep 2
         continue
      fi
      cp $PROXY_SSL_CNF $TMP_OPENSSL_CNF
      cat <<EOF >> $TMP_OPENSSL_CNF
         $uuid

         [ req_ext ]
         basicConstraints     = CA:FALSE
         extendedKeyUsage     = clientAuth
         subjectKeyIdentifier = hash
         authorityKeyIdentifier = keyid,issuer
EOF
      if [ $? -ne 0 ]; then
         prog_log "Error creating temporary openssl config file, retrying"
         rm -f $TMP_OPENSSL_CNF
         sleep 2
         continue
      fi
      break
   done
   SSL_CONFIG_FILE=$TMP_OPENSSL_CNF

   prog_log "Deleting the ${PROXY_KEY_FILE} and ${PROXY_CERT_FILE}."
   rm -f $PROXY_KEY_FILE
   rm -f $PROXY_CERT_FILE

   # Keep retrying if host certificate generation fails
   while [ 1 ]; do

      if [ -f $MAKE_CERT ]; then
         prog_log "Generating certificate using make_cert.py"
         $MAKE_CERT openssl genrsa -out $PROXY_KEY_FILE 2048
         $MAKE_CERT openssl req -days 3650 -nodes -x509 -generatedby TN -key $PROXY_KEY_FILE -out $PROXY_CERT_FILE -config $SSL_CONFIG_FILE $ADD_REQ_EXT
      else
         #If make_cert.py is not available, fall back to openssl mechanism
         openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -keyout \
                    $PROXY_KEY_FILE -out $PROXY_CERT_FILE -config $SSL_CONFIG_FILE \
                    $ADD_REQ_EXT
      fi

      if [ $? -ne 0 ]; then
         prog_log "openssl req command execution failed, retrying"
         # key and cert file maybe incomplete, delete it
         rm -f $PROXY_KEY_FILE
         rm -f $PROXY_CERT_FILE
         sleep 2
      else
         if [ -s $PROXY_KEY_FILE -a -s $PROXY_CERT_FILE ]; then
            break
         else
            prog_log "Empty key/certificate file detected, re-generating key and certficate file"
            rm -f $PROXY_KEY_FILE
            rm -f $PROXY_CERT_FILE
            sleep 2
         fi
      fi
   done
   if [ ! -z "$TMP_OPENSSL_CNF" ]; then
      rm -f $TMP_OPENSSL_CNF
   fi
}

write_host_cfg_xml() {
   cat <<EOF > $HOST_CFG_FILE
<config>
   <uuid>$1</uuid>
   <hostPrivateKeyFile>$PROXY_KEY_FILE</hostPrivateKeyFile>
   <hostCertificateFile>$PROXY_CERT_FILE</hostCertificateFile>
</config>
EOF
}


check_certificate() {
   update_uuid_to_host_cfg_xml
   tnuuid=$(get_uuid_from_host_cfg_xml)
   prog_log "tnuuid = ${tnuuid}."

   if [ `wc -l < "$PROXY_CERT_FILE"` -gt 2 -a `wc -l < "$PROXY_KEY_FILE"` -gt 2 ]; then
      prog_log "NSX Host Certificate exists"

      cert_text=`openssl x509 -in $PROXY_CERT_FILE -text 2>/dev/null`
      if [ $? -ne 0 ]; then
         prog_log "Cannot read certificate contents"
         # Recreate certificate as contents are not readable
         create_certificate $tnuuid
      fi
   else
      if [ ! -z "$tnuuid" ]; then
         create_certificate $tnuuid
      else
         prog_log "Generating the certificate without TN uuid."
         create_certificate
      fi
   fi
}

start() {
   groupadd -f $nsx_group
   groupadd -f $nsx_opsagent_group
   groupadd -f $nsx_nestdb_group
   groupadd -f $nsx_proxy_group
   groupadd -f $nsx_agent_group
   if ! getent passwd $nsx_proxy_user > /dev/null; then
      useradd --system -N \
      -g $nsx_proxy_group \
      --shell /usr/sbin/nologin -M \
      --comment "NSX Proxy" $nsx_proxy_user
   fi
   usermod -a -G $nsx_group,$nsx_opsagent_group,$nsx_nestdb_group,$nsx_agent_group $nsx_proxy_user

   if [ ! -z "$(pidof -s "${PROG}")" ] ; then
      prog_log "${PROG_TAG} is already running"
      return
   fi

   if [ ! -f "${PROG}" ]; then
      prog_log "${PROG_TAG} binary is missing"
      exit 1
   fi

   if [ ! -f "${CCP_CFG_FILE}" ]; then
      prog_log "${CCP_CFG_FILE} file is missing"
      prog_log "creating empty ${CCP_CFG_FILE}"
      touch $CCP_CFG_FILE
   fi
   mkdir -p "${PROXY_SOCK_DIR}"
   chmod 0770 "${PROXY_SOCK_DIR}"
   chown -R $nsx_proxy_user:$nsx_proxy_group "${PROXY_SOCK_DIR}"
   mkdir -p "${PROXY_LOG_DIR}"
   chmod 777 "${PROXY_LOG_DIR}"
   chmod 640 "${PROXY_CFG_FILE}"
   chown $nsx_proxy_user:$nsx_proxy_group "${PROXY_CFG_FILE}"
   chmod 660 "${CCP_CFG_FILE}"
   chown $nsx_proxy_user:$nsx_proxy_group "${CCP_CFG_FILE}"
   chmod 664 "${APPLIANCE_INFO_FILE}"
   chown $nsx_proxy_user:$nsx_proxy_group "${APPLIANCE_INFO_FILE}"
   chmod 664 "${HOST_CFG_FILE}"
   chown $nsx_proxy_user:$nsx_proxy_group "${HOST_CFG_FILE}"

   /sbin/restorecon -v ${PROXY_SOCK_DIR} &> /dev/null || :

   check_certificate
   chown $nsx_proxy_user:$nsx_proxy_group "${PROXY_SSL_CNF}"
   chown $nsx_proxy_user:$nsx_proxy_group "${PROXY_KEY_FILE}"
   chown $nsx_proxy_user:$nsx_proxy_group "${PROXY_CERT_FILE}"
   chmod 660 "${PROXY_SSL_CNF}"
   chmod 660 "${PROXY_KEY_FILE}"
   chmod 660 "${PROXY_CERT_FILE}"

   chown $nsx_proxy_user:$nsx_group "${PROG}"
   chown $nsx_proxy_user:$nsx_group "${WATCHDOG}"

   ulimit -c unlimited

   export OPENSSL_MODULES=/opt/vmware/nsx-shared/lib64
   export OPENSSL_CONF=/opt/vmware/nsx-shared/lib64/openssl.cnf

   # gperftools memory profiling needs this environment
   export TCMALLOC_SAMPLE_PARAMETER="524288"
   su -m $nsx_proxy_user -s /bin/bash \
      -c "${WATCHDOG} ${WATCHDOG_ARGS} '${PROG} ${PROXY_ARGS}' >/dev/null 2>&1"
   prog_log "${PROG_TAG} starts"
}

do_stop() {
   PIDOF="pidof -s -z"
   $PIDOF "${PROG}"
   if [ $? -ne 0 ]; then
     # Either "-z" is not supported or PROG is not running. In either case, it is
     # ok to switch PIDOF to "pidof -s". This is because if "-z" is actually supported,
     # PROG is not running, which is good.
     # We are forced to do this because systemd does not let us capture pidof's stderr
     # to determine if -z is unsupported.
     prog_log "${PROG_TAG} -z may not be supported for pidof"
     PIDOF="pidof -s"
   fi
   prog_log "${PROG_TAG} Using $PIDOF"

   if [ -z $($PIDOF "${PROG}") ]; then
     prog_log "${PROG_TAG} service is not running"
     return 0
   fi

   # This only stops the watchdog process.
   prog_log "Shutting down ${PROG_TAG} watchdog"
   ${WATCHDOG} -k "${PROG_TAG}"

   prog_log "Shutting down ${PROG_TAG} service"
   kill $($PIDOF "${PROG}") >/dev/null 2>&1

   local TERMINATE_ATTEMPT=0
   while [ $((TERMINATE_ATTEMPT)) -le $((TERMINATE_ATTEMPTS)) ] ; do
      local TIMEOUT=$SHUTDOWN_TIMEOUT
      while [ $((TIMEOUT)) -gt 0 ]; do
         if [ -z $($PIDOF "${PROG}") ]; then
            prog_log "${PROG_TAG} service is stopped, breaking from loop"
            break
         fi
         sleep 1
         echo -n "."
         TIMEOUT=$((TIMEOUT-1))
      done
      echo ""

      if [ -z $($PIDOF "${PROG}") ]; then
         prog_log "${PROG_TAG} service is stopped"
         break
      fi
      if [ $((TERMINATE_ATTEMPT)) -eq $((TERMINATE_ATTEMPTS)) ];then
         prog_log "Terminating ${PROG_TAG} Already attempted $TERMINATE_ATTEMPT/$TERMINATE_ATTEMPTS"
         break
      fi

      prog_log "Terminating ${PROG_TAG} (attempt $((TERMINATE_ATTEMPT+1))/$TERMINATE_ATTEMPTS)"
      kill -9 $($PIDOF "${PROG}")

      TERMINATE_ATTEMPT=$((TERMINATE_ATTEMPT+1))
   done

   if [ -z $($PIDOF "${PROG}") ]; then
      prog_log "${PROG_TAG} service is stopped"
      return 0
   else
      prog_log "Unable to stop ${PROG_TAG} service"
      return 1
   fi
}

stop() {
   do_stop
}

status() {
   local NAME="${1:-${PROG_TAG}}"
   if [ -n "$(pidof -xs "${PROG}")" ] ; then
      echo "${NAME} is running"
      exit 0
   else
      echo "${NAME} is not running"
      exit 3
   fi
}

#
# main
#
case "${1}" in
   "start")
      start
   ;;
   "stop")
      stop
   ;;
   "status")
      status
   ;;
   "restart")
      stop
      start
   ;;
   *)
      prog_log "Usage: $(basename ${0}) {start|stop|status|restart}"
      exit 1
   ;;
esac
