#!/opt/vmware/bin/python

# Copyright 2016-2020 VMware, Inc.  All rights reserved. -- VMware Confidential

import os
import sys
sys.dont_write_bytecode = True
sys.path.append(os.environ['VMWARE_PYTHON_PATH'])

import argparse
import atexit
import logging
import ssl
import time
import platform

from collections import defaultdict as DefaultDict
from getpass import getpass as GetPassword

if platform.uname()[0] == 'Linux':
   WARN_MSG = '\033[1;33;40m *** WARNING: Disabling' \
              ' all the TLS options can be harmful to the ESXi. ***\n \033[0m'

LOG_DIR = os.path.join(os.getenv('VMWARE_LOG_DIR'),
                       'vmware',
                       'vSphere-TlsReconfigurator')

if not os.path.exists(LOG_DIR):
   os.makedirs(LOG_DIR)

LOG_FILE_NAME = os.path.join(LOG_DIR, 'EsxTlsReconfigurator.log')
#The letter Z is added unconditionally because we use only UTC time
LOG_FORMAT = '%(asctime)s.%(msecs)03dZ %(levelname)s %(message)s'
LOG_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S'

logging.Formatter.converter = time.gmtime
logging.basicConfig(filename=LOG_FILE_NAME,
                    format=LOG_FORMAT,
                    datefmt=LOG_DATE_FORMAT,
                    level=logging.DEBUG)
Logger = logging.getLogger('main')
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.INFO)
Logger.addHandler(consoleHandler)


class LoggerProxy(object):
   def __init__(self):
      self.logging = logging

   def __getattribute__(self, name):
      return getattr(object.__getattribute__(self, 'logging'), "debug")


# intercept pyVmomi, pyVim logging
sys.modules['logging'] = LoggerProxy()


# import pyVim, pyVmomi after proxying logging
from pyVim.connect import SmartConnect, Disconnect
from pyVim.task import WaitForTask
from pyVmomi import vim, vmodl
import pyVim.nfclib


VC_IP = 'localhost'

ESX_ADV_OPTION = 'UserVars.ESXiVPsDisabledProtocols'
ESX_ADV_CIPHER_OPTION = 'UserVars.ESXiVPsAllowedCiphers'
CLUSTER_ADV_OPTION = 'das.config.vmacore.ssl.protocols'
ESX_60_ADV_OPTIONS = ['UserVars.ESXiVPsDisabledProtocols',
                      'UserVars.ESXiRhttpproxyDisabledProtocols',
                      'UserVars.VMAuthdDisabledProtocols']

HOST_67_VERSION = '6.7'
HOST_65_VERSION = '6.5'
HOST_60_VERSION = '6.0'

COMMAND_LINE_TLSv10 = 'TLSv1.0'
COMMAND_LINE_TLSv11 = 'TLSv1.1'
COMMAND_LINE_TLSv12 = 'TLSv1.2'

CMD_TLS_OPTIONS = (COMMAND_LINE_TLSv10,
                   COMMAND_LINE_TLSv11,
                   COMMAND_LINE_TLSv12)

TLS_OPTIONS = {COMMAND_LINE_TLSv10: 'tlsv1',
               COMMAND_LINE_TLSv11: 'tlsv1.1',
               COMMAND_LINE_TLSv12: 'tlsv1.2'}

DAS_TLS_OPTIONS = {COMMAND_LINE_TLSv10: 'tls1.0',
                   COMMAND_LINE_TLSv11: 'tls1.1',
                   COMMAND_LINE_TLSv12: 'tls1.2'}


def GetDasProtocols(protocols):
   return [DAS_TLS_OPTIONS[protocol] for protocol in protocols]


def GetESXiVPsDisabledProtocols(protocols):
   disabledProtocols = set(CMD_TLS_OPTIONS) - set(protocols)
   return ['sslv3'] + sorted([TLS_OPTIONS[p] for p in disabledProtocols])


class CommandLineParser(argparse.ArgumentParser):
   def error(self, message):
      self.print_help()

      subparserActions = [action for action in self._actions
                          if isinstance(action, argparse._SubParsersAction)]

      for action in subparserActions:
         for _, subparser in action.choices.items():
            print('%s\n%s' % ('-' * 80, subparser.format_help()))

      sys.exit(1)


# Argument parsers
def GetArgumentParser():
   description = 'ESXi Transport Layer Security reconfigurator, ' \
                 'version=7.0.0, build=15952498\n' \
                 'For more information refer to the following article: ' \
                 'https://kb.vmware.com/kb/2147469'

   formatter = argparse.RawTextHelpFormatter
   parser = CommandLineParser(description=description, add_help=False,
                              formatter_class=formatter)

   subparser = parser.add_subparsers()
   vCenterClusterParser = VCenterClusterParser(subparser)
   vCenterHostParser = VCenterHostParser(subparser)
   standaloneHostParser = StandaloneHostParser(subparser)

   for p in (vCenterClusterParser, vCenterHostParser, standaloneHostParser):
      if p is standaloneHostParser:
         helpMsg = 'User name to be used when connecting to an ESXi Host.'
      else:
         helpMsg = 'User name to be used when connecting to a vCenter Server.'
      p.add_argument('-u', '--user', required=True, action='store',
                     help=helpMsg)
      p.add_argument('-p', '--protocols', action='store', nargs='+',
                     choices=CMD_TLS_OPTIONS,
                     help='The TLS protocols to be enabled only.\n\n ' + WARN_MSG)
   return parser


def VCenterClusterParser(subparsers):
   description = 'vCenter Server cluster TLS reconfigurator.'
   parser = subparsers.add_parser('vCenterCluster',
                                  description=description,
                                  help=description,
                                  add_help=False,
                                  formatter_class=argparse.RawTextHelpFormatter)
   parser.add_argument('-c', '--cluster', required=True, action='store',
                       nargs='+', help='List of cluster names from the local '
                                       'vCenter Server inventory to be '
                                       'reconfigured.')
   parser.add_argument('--process-all', default=False, action='store_true',
                       help='Allow processing of all clusters with the same '
                            'name')
   parser.set_defaults(func=ReconfigureVCenterClusters)
   return parser


def VCenterHostParser(subparsers):
   description = 'vCenter Server host TLS reconfigurator.'
   parser = subparsers.add_parser('vCenterHost',
                                  description=description,
                                  help=description,
                                  add_help=False,
                                  formatter_class=argparse.RawTextHelpFormatter)
   parser.add_argument('-h', '--host', required=True, action='store',
                       nargs='+', help='List of host names from the local '
                                       'vCenter Server inventory to be '
                                       'reconfigured.')
   parser.set_defaults(func=ReconfigureVCenterHosts)
   return parser


def StandaloneHostParser(subparsers):
   description = 'Standalone ESXi host TLS reconfigurator.'
   parser = subparsers.add_parser('ESXiHost',
                                  description=description,
                                  help=description,
                                  add_help=False,
                                  formatter_class=argparse.RawTextHelpFormatter)
   parser.add_argument('-h', '--host', required=True, action='store',
                       nargs='+', help='List of FQDNs or IP addresses of '
                                       'standalone ESXi hosts to be '
                                       'reconfigured.')
   parser.set_defaults(func=ReconfigureStandaloneHost)
   return parser

def LocateVCenterEntities(serviceInstance, type, names, processAll):
   Logger.debug('LocateVCenterEntities type, names: "%s", "%s".', type, names)
   content = serviceInstance.RetrieveContent()
   viewManager = content.GetViewManager()
   viewContainer = viewManager.CreateContainerView(content.rootFolder,
                                                   [type],
                                                   True)
   entities = viewContainer.view
   viewContainer.Destroy()
   Logger.debug('vCenter Server located entities of type "%s": "%s".',
                type,
                entities)
   result = DefaultDict(list)
   for entity in entities:
      if entity.name in names:
         result[entity.name].append(entity)
   if not processAll:
      for key, values in result.items():
         if len(values) > 1:
            Logger.error('Multiple entities named "%s" exist in the local '
                         'vCenter Server inventory.',
                         key)
            Logger.info('Please provide --process-all option to reconfigure '
                        'all entities with the same name.')
            sys.exit(1)
   return result


def ReconfigureVCenterClusters(args):
   Logger.debug('ReconfigureVCenterClusters args: "%s".', args)
   Logger.info('Connecting to vCenter Server at: "%s".', VC_IP)
   serviceInstance = GetConnection(args, VC_IP)
   nfc = serviceInstance.RetrieveInternalContent().GetNfcService()
   clustersDict = LocateVCenterEntities(serviceInstance,
                                        vim.ClusterComputeResource,
                                        args.cluster,
                                        args.process_all)
   for clusterName in args.cluster:
      clusters = clustersDict.get(clusterName)
      Logger.debug('vCenter Server clusters named "%s": "%s".',
                   clusterName,
                   clusters)
      if clusters:
         for cluster in clusters:
            if CheckCluster(cluster):
               # Skip args.ciphers with False
               ReconfigureCluster(cluster, args.protocols, False, nfc)
      else:
         Logger.warning('Couldn\'t locate vCenter Server cluster "%s" '
                        'in local vCenter Server inventory.',
                        clusterName)


def ReconfigureVCenterHosts(args):
   Logger.debug('ReconfigureVCenterHosts args: "%s".', args)
   Logger.info('Connecting to vCenter Server at: "%s".', VC_IP)
   serviceInstance = GetConnection(args, VC_IP)
   nfc = serviceInstance.RetrieveInternalContent().GetNfcService()
   hostsDict = LocateVCenterEntities(serviceInstance,
                                     vim.HostSystem,
                                     args.host,
                                     None)
   for hostName in args.host:
      hosts = hostsDict.get(hostName)
      Logger.debug('vCenter Server hosts named "%s": "%s".',
                   hostName,
                   hosts)
      if hosts:
         for host in hosts:
            if CheckHost(host, host.name):
               # Skip args.ciphers with False
               ReconfigureHost(host, False, True, args.protocols,
                               False, nfc)
      else:
         Logger.warning('Couldn\'t locate vCenter Server host "%s" '
                        'in local vCenter Server inventory.',
                        hostName)


def ReconfigureCluster(cluster, protocols, ciphers, nfc):
   Logger.info('Reconfiguring vCenter Server cluster: "%s" (%s).',
               cluster.name,
               cluster._moId)
   try:
      currentOptions = cluster.GetConfiguration().dasConfig.option
      currentValues = [option for option in currentOptions
                              if option.key == CLUSTER_ADV_OPTION]
      currentValue = currentValues[0].value if currentValues else None

      if protocols is None:
         newValue = currentValue
      else:
         newValue = ','.join(GetDasProtocols(protocols))

      Logger.debug('vCenter Server cluster "%s" (%s) HA advanced option "%s" '
                   'current value: "%s".',
                   cluster.name,
                   cluster._moId,
                   CLUSTER_ADV_OPTION,
                   currentValue)
      Logger.debug('vCenter Server cluster "%s" (%s) HA advanced option "%s" '
                   'new value: "%s".',
                   cluster.name,
                   cluster._moId,
                   CLUSTER_ADV_OPTION,
                   newValue)
      if currentValue != newValue:
         Logger.info('Updating vCenter Server cluster "%s" (%s) HA advanced '
                     'option "%s" from "%s" to "%s"',
                     cluster.name,
                     cluster._moId,
                     CLUSTER_ADV_OPTION,
                     currentValue,
                     newValue)
         option = vim.option.OptionValue(key=CLUSTER_ADV_OPTION,
                                         value=newValue)
         spec = vim.Cluster.ConfigSpec()
         spec.dasConfig = vim.Cluster.DasConfigInfo()
         spec.dasConfig.option = [option]
         Logger.debug('ReconfigureCluster_Task spec: "%s".', spec)
         WaitForTask(cluster.ReconfigureCluster_Task(spec, True))
      else:
         Logger.debug('Reconfiguration is not needed for cluster: "%s" (%s).',
                      cluster.name,
                      cluster._moId)
      successfulHosts = 0
      for host in cluster.host:
         if ReconfigureHost(host, True, True, protocols, ciphers, nfc,
                            forceUpdateOption=currentValue != newValue):
            successfulHosts += 1
      Logger.info('Reconfigured vCenter Server cluster: "%s" (%s).',
                  cluster.name,
                  cluster._moId)
      if successfulHosts < len(cluster.host):
         Logger.warning('Reconfiguration FAILED for %s (out of %s) ESXi host(s)'
                        ' part of vCenter Server cluster: "%s" (%s).',
                        len(cluster.host) - successfulHosts,
                        len(cluster.host),
                        cluster.name,
                        cluster._moId)
      else:
         Logger.info('Reconfiguration succeeded for all (%s) ESXi host(s) part '
                     'of vCenter Server cluster: "%s" (%s).',
                     successfulHosts,
                     cluster.name,
                     cluster._moId)
   except vmodl.MethodFault as e:
      Logger.error('Reconfiguration FAILED for vCenter Server cluster "%s" (%s)'
                   ': %s.',
                   cluster.name,
                   cluster._moId,
                   e.msg)


def CheckCluster(cluster):
   Logger.info('Validating vCenter Server cluster: "%s" (%s).',
               cluster.name,
               cluster._moId)
   for host in cluster.host:
      if not CheckHost(host, host.name):
         Logger.warning('Skipping reconfiguration of cluster: "%s" (%s).',
                        cluster.name,
                        cluster._moId)
         return False
   return True


def CheckHost(host, hostName):
   Logger.info('Validating ESXi host: "%s".', hostName)
   if not CheckHostState(host, hostName):
      Logger.warning('Skipping reconfiguration of ESXi host "%s".', hostName)
      return False
   return True


def GetHostVersion(host, hostName):
   Logger.debug('Validating ESXi host "%s" version.', hostName)
   version = host.GetConfig().product.version[:3]
   Logger.debug('ESXi host "%s" version: "%s".', hostName, version)
   return version


def CheckHostState(host, hostName):
   Logger.debug('Validating ESXi host "%s" power state.', hostName)
   powerState = host.GetRuntime().powerState
   Logger.debug('ESXi host "%s" power state: "%s".', hostName, powerState)
   if powerState != 'poweredOn':
      Logger.error('Host "%s" is not in powered on state.', hostName)
      return False
   return True


def CheckHostOwner(host, inCluster, inVCenter, hostName):
   Logger.debug('Validating ESXi host "%s" ownership.', hostName)

   if not inVCenter:
      connectionInfo = host.QueryConnectionInfo()
      Logger.debug('ESXi host "%s" connectionInfo: "%s".',
                   hostName,
                   connectionInfo)
      if connectionInfo.serverIp:
         Logger.error('ESXi host "%s" is managed by vCenter Server instance at:'
                      ' %s and should be reconfigured by running this script'
                      ' on that vCenter Server instance using the "vCenterHost"'
                      ' or "vCenterCluster" command.',
                      hostName,
                      connectionInfo.serverIp)
         return False
   elif not inCluster:
      parent = host.GetParent()
      Logger.debug('ESXi host "%s" parent: "%s".',
                   hostName,
                   parent)
      if isinstance(parent, vim.ClusterComputeResource):
         Logger.error('ESXi host "%s" should be reconfigured by running this '
                      'script using the "vCenterCluster" command and passing '
                      'to it "%s".',
                      hostName,
                      parent.name)
         return False
   return True


def ReconfigureStandaloneHost(args):
   Logger.debug('ReconfigureStandaloneHost args: "%s".', args)
   for address in args.host:
      Logger.info('Connecting to standalone ESXi host at: "%s".', address)
      serviceInstance = GetConnection(args, address, False)
      if serviceInstance:
         nfc = serviceInstance.RetrieveInternalContent().GetNfcService()
         content = serviceInstance.RetrieveContent()
         hostFolder = content.rootFolder.childEntity[0].hostFolder
         host = hostFolder.childEntity[0].host[0]
         if CheckHost(host, address):
            # Skip args.ciphers with False
            ReconfigureHost(host, False, False, args.protocols, False,
                            nfc, address)


def ReconfigureHost(host, inCluster, inVCenter, protocols, ciphers, nfc,
                    address=None, forceUpdateOption=False):
   tls_success = True
   cipher_success = True

   hostName = address if address is not None else host.name
   hostVersion = GetHostVersion(host,hostName)
   Logger.info('Reconfiguring ESXi host: "%s" of version "%s"',
               hostName, hostVersion)

   if (ciphers and hostVersion >= HOST_67_VERSION):
      cipher_success = CipherReconfigure67AboveHost(host, inCluster, inVCenter,
                                           ciphers, nfc, hostName, hostVersion,
                                           address, forceUpdateOption)

   if (protocols):
      if hostVersion >= HOST_65_VERSION:
         tls_success = Reconfigure65AboveHost(host, inCluster, inVCenter, protocols,
                                              nfc, hostName, hostVersion, address,
                                              forceUpdateOption)
      elif hostVersion == HOST_60_VERSION:
         tls_success = Reconfigure60Host(host, inCluster, inVCenter, protocols,
                                         nfc, hostName, hostVersion, address,
                                         forceUpdateOption)
      else:
         Logger.error('ESXi host "%s" reconfiguration Failed. The ESXi host '
                      'version "%s" is unknown or below 6.0.',
                      hostName, hostVersion)
         return False
   return (tls_success and cipher_success)


def Reconfigure65AboveHost(host, inCluster, inVCenter, protocols, nfc,
                           hostName, hostVersion, address=None,
                           forceUpdateOption=False):
   try:
      if CheckHostOwner(host, inCluster, inVCenter, hostName):
         optionManager = host.GetConfigManager().GetAdvancedOption()
         currentValues = optionManager.QueryOptions(ESX_ADV_OPTION)
         currentValue = currentValues[0].value if currentValues else None
         Logger.debug('ESXi host "%s" advanced option "%s" current value: '
                      '"%s".',
                      hostName,
                      ESX_ADV_OPTION,
                      currentValue)
         newValue = ','.join(GetESXiVPsDisabledProtocols(protocols))
         Logger.debug('ESXi host "%s" advanced option "%s" new value: "%s".',
                      hostName,
                      ESX_ADV_OPTION,
                      newValue)
         if forceUpdateOption or currentValue != newValue:
            Logger.info('Updating ESXi host "%s" advanced option "%s" from '
                        '"%s" to "%s"',
                        hostName,
                        ESX_ADV_OPTION,
                        currentValue,
                        newValue)
            option = vim.option.OptionValue(key=ESX_ADV_OPTION, value=newValue)
            Logger.debug('UpdateOptions option: "%s".', [option])
            optionManager.UpdateOptions([option])
         else:
            Logger.debug('Reconfiguration is not needed for ESXi host: "%s".',
                         hostName)

         ReconfigureRhttpproxy(host, nfc, address)
         if hostVersion >= '6.6':
            ReconfigureVvold(host, nfc, address)
         elif hostVersion == '6.5':
            ReconfigureSfcbd(host, nfc, address)

         Logger.info('ESXi host "%s" TLS reconfigured successfully. The ESXi host '
                     'has to be restarted for the new TLS configuration to take'
                     ' effect!',
                     hostName)
         return True
   except vmodl.MethodFault as e:
      Logger.error('TLS Reconfiguration FAILED for ESXi host "%s": %s.',
                   hostName,
                   e.msg)
   return False


def CipherReconfigure67AboveHost(host, inCluster, inVCenter, ciphers, nfc, hostName,
                                 hostVersion, address=None, forceUpdateOption=False):
   try:
      if CheckHostOwner(host, inCluster, inVCenter, hostName):
         optionManager = host.GetConfigManager().GetAdvancedOption()
         currentValues = optionManager.QueryOptions(ESX_ADV_CIPHER_OPTION)
         currentValue = currentValues[0].value if currentValues else None
         newValue = ciphers
         Logger.debug('ESXi host "%s" advanced option "%s" current value: '
                      '"%s".',
                      hostName,
                      ESX_ADV_CIPHER_OPTION,
                      currentValue)
         Logger.debug('ESXi host "%s" advanced option "%s" new value: "%s".',
                      hostName,
                      ESX_ADV_CIPHER_OPTION,
                      newValue)
         if forceUpdateOption or currentValue != newValue:
            Logger.info('Updating ESXi host "%s" advanced option "%s" from '
                        '"%s" to "%s"',
                        hostName,
                        ESX_ADV_CIPHER_OPTION,
                        currentValue,
                        newValue)
            option = vim.option.OptionValue(key=ESX_ADV_CIPHER_OPTION, value=newValue)
            Logger.debug('UpdateOptions option: "%s".', [option])
            optionManager.UpdateOptions([option])
         else:
            Logger.debug('Reconfiguration is not needed for ESXi host: "%s".',
                         hostName)

         ReconfigureCiphersConf(host, nfc, address)

         Logger.info('ESXi host "%s" Cipher reconfigured successfully. The ESXi host '
                     'has to be restarted for the new Cipher configuration to take'
                     ' effect!',
                     hostName)
         return True
   except vmodl.MethodFault as e:
      Logger.error('Cipher Reconfiguration FAILED for ESXi host "%s": %s.',
                   hostName,
                   e.msg)
   return False


def Reconfigure60Host(host, inCluster, inVCenter, protocols, nfc, hostName,
                      address, forceUpdateOption):
    try:
        if CheckHostOwner(host, inCluster, inVCenter, hostName):
            newValue = ','.join(GetESXiVPsDisabledProtocols(protocols))
            curVPsValue = None
            for ESX_ADV_OPTION in ESX_60_ADV_OPTIONS:
                optionManager = host.GetConfigManager().GetAdvancedOption()
                currentValues = optionManager.QueryOptions(ESX_ADV_OPTION)
                currentValue = currentValues[0].value if currentValues else None
                if ESX_ADV_OPTION == 'UserVars.ESXiVPsDisabledProtocols':
                    curVPsValue = currentValue
                Logger.debug('ESXi host "%s" advanced option "%s" current '
                             'value: "%s".',
                             hostName,
                             ESX_ADV_OPTION,
                             currentValue)
                Logger.debug('ESXi host "%s" advanced option "%s" new value: '
                             '"%s".',
                             hostName,
                             ESX_ADV_OPTION,
                             newValue)
                if forceUpdateOption or currentValue != newValue:
                    Logger.info('Updating ESXi host "%s" advanced option "%s" '
                                'from "%s" to "%s"',
                                hostName,
                                ESX_ADV_OPTION,
                                currentValue,
                                newValue)
                    option = vim.option.OptionValue(key=ESX_ADV_OPTION,
                                                    value=newValue)
                    Logger.debug('UpdateOptions option: "%s".', [option])
                    optionManager.UpdateOptions([option])
                else:
                    Logger.debug('Reconfiguration is not needed for ESXi host '
                                 '"%s" advanced option "%s".',
                                 hostName,
                                 ESX_ADV_OPTION)
            if forceUpdateOption or curVPsValue != newValue:
                ReconfigureSfcbd(host, nfc, address)
            Logger.info('ESXi host "%s" TLS reconfigured successfully. The ESXi '
                        'host has to be restarted for the new TLS '
                        'configuration to take effect!', hostName)
            return True
    except vmodl.MethodFault as e:
        Logger.error('TLS Reconfiguration FAILED for ESXi host "%s": %s.',
                     hostName,
                     e.msg)
    return False


def ReconfigureRhttpproxy(host, nfc, address):
   hostName = address if address is not None else host.name
   Logger.info('Removing the <sslOptions> tag (if exists) from the reverse '
               'HTTP proxy configuration file on ESXi host: "%s".',
               hostName)
   agentManager = host.RetrieveInternalConfigManager().GetAgentManager()
   path = agentManager.PrepareToUpgrade()
   scriptDir = os.path.dirname(os.path.realpath(__file__))
   # fileset = [(src, dst), ...], dst must be a posix path
   fileset = [(os.path.join(scriptDir, f), '/'.join((path, f)))
              for f in ('reconfigure-rhttpproxy',
                        'reconfigure-rhttpproxy.sig')]
   ticket = nfc.SystemManagement(host)
   if not ticket.GetHost():
      assert address
      ticket.SetHost(address)
   client = pyVim.nfclib.NfcClient(ticket)
   client.Connect()
   client.PutFiles(fileset, client.NFC_CREATE_OVERWRITE)
   agentManager.Upgrade('reconfigure-rhttpproxy',
                        'reconfigure-rhttpproxy.sig')

def ReconfigureVvold(host, nfc, address):
   hostName = address if address is not None else host.name
   Logger.info('Reconfiguring vvold on ESXi host: "%s".', hostName)
   agentManager = host.RetrieveInternalConfigManager().GetAgentManager()
   path = agentManager.PrepareToUpgrade()
   scriptDir = os.path.dirname(os.path.realpath(__file__))

   # fileset = [(src, dst), ...], dst must be a posix path
   fileset = [(os.path.join(scriptDir, f), '/'.join((path, f)))
              for f in ('reconfigure-vvold',
                        'reconfigure-vvold.sig')]
   ticket = nfc.SystemManagement(host)

   if not ticket.GetHost():
      assert address
      ticket.SetHost(address)
   client = pyVim.nfclib.NfcClient(ticket)
   client.Connect()
   client.PutFiles(fileset, client.NFC_CREATE_OVERWRITE)
   agentManager.Upgrade('reconfigure-vvold',
                        'reconfigure-vvold.sig')


def ReconfigureSfcbd(host, nfc, address):
   hostName = address if address is not None else host.name
   Logger.info('Reconfiguring sfcbd on ESXi host: "%s".', hostName)
   agentManager = host.RetrieveInternalConfigManager().GetAgentManager()
   path = agentManager.PrepareToUpgrade()
   scriptDir = os.path.dirname(os.path.realpath(__file__))
   # fileset = [(src, dst), ...], dst must be a posix path
   fileset = [(os.path.join(scriptDir, f), '/'.join((path, f)))
              for f in ('reconfigure-sfcbd',
                        'reconfigure-sfcbd.sig')]
   ticket = nfc.SystemManagement(host)
   if not ticket.GetHost():
      assert address
      ticket.SetHost(address)
   client = pyVim.nfclib.NfcClient(ticket)
   client.Connect()
   client.PutFiles(fileset, client.NFC_CREATE_OVERWRITE)
   agentManager.Upgrade('reconfigure-sfcbd',
                        'reconfigure-sfcbd.sig')


def ReconfigureCiphersConf(host, nfc, address):
   hostName = address if address is not None else host.name
   Logger.info('Reconfiguring ciphers config on ESXi host: "%s".', hostName)
   agentManager = host.RetrieveInternalConfigManager().GetAgentManager()
   path = agentManager.PrepareToUpgrade()
   scriptDir = os.path.dirname(os.path.realpath(__file__))
   # fileset = [(src, dst), ...], dst must be a posix path
   fileset = [(os.path.join(scriptDir, f), '/'.join((path, f)))
              for f in ('reconfigure-esx-ciphers',
                        'reconfigure-esx-ciphers.sig')]
   ticket = nfc.SystemManagement(host)
   if not ticket.GetHost():
      assert address
      ticket.SetHost(address)
   client = pyVim.nfclib.NfcClient(ticket)
   client.Connect()
   client.PutFiles(fileset, client.NFC_CREATE_OVERWRITE)
   agentManager.Upgrade('reconfigure-esx-ciphers',
                        'reconfigure-esx-ciphers.sig')


def GetConnection(args, address, exitOnFailure=True):
   try:
      password = GetPassword()
      context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
      si = SmartConnect(host=address,
                        user=args.user,
                        pwd=password,
                        sslContext=context)
      atexit.register(Disconnect, si)
      Logger.debug('Product serviceIntance: "%s"', si)
      if CheckProductVersion(si, address):
         return si
   except vim.fault.NoPermission as e:
      Logger.error(e.msg)
      Logger.info('Note: Access to ESXi host may be denied if it is managed '
                  'by vCenter Server instance in lockdown mode.')
      Logger.info('      If this is the case please reconfigure the ESXi '
                  'host through the corresponding vCenter Server instance.')
   except vim.fault.InvalidLogin as e:
      Logger.error(e.msg)
   except IOError as e:
      Logger.error(e.strerror)
   if exitOnFailure:
      sys.exit(1)
   return None


def CheckProductVersion(serviceInstance, address):
   Logger.info('Validating product version at: "%s".', address)
   try:
      version = serviceInstance.RetrieveContent().about.version[:3]
      Logger.debug('Product at "%s" version: "%s".',
                   address,
                   version)
      if version >= HOST_65_VERSION:
         return True
      Logger.error('Unsupported product version: %s.\n'
                   'Least supported version: %s.\n'
                   'To reconfigure 6.0 host, you need to use'
                   '6.0 EsxTlsReconfigurator.',
                   version, HOST_65_VERSION)
   except vmodl.MethodFault as e:
      Logger.error('FAILED to validate the product version at "%s": %s.',
                   address,
                   e.msg)
   return False


def main():
   parser = GetArgumentParser()
   args = parser.parse_args()
   # Skip args.ciphers with False
   if not (False or args.protocols):
      args.protocols = [COMMAND_LINE_TLSv12]

   # Log description as log file header
   for line in parser.description.split('\n'):
      Logger.info(line)

   Logger.info('Log file: "%s".', LOG_FILE_NAME)
   args.func(args)


if __name__ == '__main__':
   main()
