#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Make Tunnelbox Configuration
# Copyright (C) 2012-2024 by Thomas Dreibholz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact: dreibh@simula.no

import shlex
import sys
import time
import adns

# NorNet
from NorNetConfiguration import *
from NorNetAPI           import *
from NorNetNodeSetup     import *



# ###### Get local node configuration string ################################
def getFileServRWSystemsConfigurationString():
   try:
      return NorNet_Configuration['NorNet_FileServ_RWSystems']
   except:
      return ''


# ###### Generate TFTP daemon configuration #################################
def makeTFTPDConfiguration():
   outputFile = codecs.open('tftpd-hpa-config', 'w', 'utf-8')
   writeAutoConfigInformation(outputFile)

   outputFile.write('TFTP_USERNAME="tftp"\n')
   outputFile.write('TFTP_DIRECTORY="/filesrv/adm/TFTP"\n')
   outputFile.write('TFTP_ADDRESS="[::]:69" \n')
   outputFile.write('TFTP_OPTIONS="--secure"\n')

   outputFile.close()


# ###### Generate NFS daemon configuration ##################################
def makeNFSDConfiguration(fullSiteList, rwSystemList, uid, gid):
   outputFile = codecs.open('nfsd-config', 'w', 'utf-8')
   writeAutoConfigInformation(outputFile)

   nfsOptions = 'no_subtree_check,async,no_wdelay,all_squash,anonuid=' + str(uid) + ',anongid=' + str(gid)

   # ====== Global shares ===================================================
   log('Writing global configuration ...')
   outputFile.write('# Global shares\n')
   outputFile.write('/filesrv/pub\t')
   outputFile.write(str(makeNorNetIP(0, 0, 0, 4)) + '(' + nfsOptions + ',rw)\t')
   outputFile.write(str(makeNorNetTunnelIP(0, 0, 0, 0, 4)) + '(' + nfsOptions + ',rw)\t')
   outputFile.write('[' + str(makeNorNetIP(0, 0, 0, 6)) + '](' + nfsOptions + ',rw)\t')
   outputFile.write('[' + str(makeNorNetTunnelIP(0, 0, 0, 0, 6)) + '](' + nfsOptions + ',rw)\t')
   outputFile.write('\n')
   outputFile.write('/filesrv/adm\t')
   for i in range(0, len(rwSystemList)):
      outputFile.write(rwSystemList[i] + '(' + nfsOptions + ',rw)\t')
   outputFile.write(str(makeNorNetIP(0, 0, 0, 4)) + '(' + nfsOptions + ',ro)\t')
   outputFile.write(str(makeNorNetTunnelIP(0, 0, 0, 0, 4)) + '(' + nfsOptions + ',ro)\t')
   outputFile.write('[' + str(makeNorNetIP(0, 0, 0, 6)) + '](' + nfsOptions + ',ro)\t')
   outputFile.write('[' + str(makeNorNetTunnelIP(0, 0, 0, 0, 6)) + '](' + nfsOptions + ',ro)\t')
   outputFile.write('\n')
   outputFile.write('/filesrv/sys\t')
   for i in range(0, len(rwSystemList)):
      outputFile.write(rwSystemList[i] + '(' + nfsOptions + ',rw)\t')
   outputFile.write('\n\n')

   # ====== Per-node private share ==========================================
   makeDir('/filesrv/sys')
   originalDirectory = changeDir('/filesrv/sys')
   obsoleteSites     = os.listdir('.')

   log('Writing per-node configuration ...')
   outputFile.write('# Per-node shares\n')
   adnsResolver = adns.init()
   for siteIndex in fullSiteList:
      # ====== Prepare site directory =======================================
      siteQueryList = [ None ]
      site          = fullSiteList[siteIndex]
      siteDirectory = str(site['site_index'])
      siteLink      = site['site_domain']
      makeDir(siteDirectory)
      makeSymlink(siteLink, siteDirectory)
      oldDirectory  = changeDir(siteDirectory)
      obsoleteNodes = os.listdir('.')
      try:
         obsoleteSites.remove(siteDirectory)
         obsoleteSites.remove(siteLink)
      except:
         pass

      # ====== Prepare node directories =====================================
      for nodeIndex in range(1, 255):
         nodeDirectory = str(nodeIndex)
         makeDir(nodeDirectory)
         try:
            obsoleteNodes.remove(nodeDirectory)
         except:
            pass

         tunnelIPs = []   # Allow tunnel IPs, but /etc/exports needs unique configuration!

         outputFile.write('/filesrv/sys/' + siteDirectory + '/' + nodeDirectory + '\t')
         siteProviderList = getNorNetProvidersForSite(site)
         for providerIndex in siteProviderList:
            provider = siteProviderList[providerIndex]
            for version in [ 4, 6 ]:
               # ====== Add entry for node ==================================
               nodeAddress = makeNorNetIP(providerIndex, siteIndex, nodeIndex, version)
               if version == 4:
                  outputFile.write(str(nodeAddress.ip) + '(' + nfsOptions + ',rw)\t')
               else:
                  outputFile.write('[' + str(nodeAddress.ip) + '](' + nfsOptions + ',rw)\t')

               # ====== Query DNS for the node's name =======================
               if ((version == 4) and
                   (providerIndex == site['site_default_provider_index'])):
                  # print str(nodeAddress.ip)
                  query = adnsResolver.submit_reverse(str(nodeAddress.ip), adns.rr.PTR)
                  siteQueryList.append(query)

            # ====== Add tunnel IP addresses for a tunnelbox ================
            # The route to the Central Site goes over the tunnel, i.e. the
            # routing will choose the tunnel IP as source address!
            if nodeIndex == NorNet_NodeIndex_Tunnelbox:
               for remoteSiteIndex in fullSiteList:
                  if remoteSiteIndex != siteIndex:
                     remoteSite = fullSiteList[remoteSiteIndex]
                     remoteProviderList = getNorNetProvidersForSite(remoteSite)
                     for remoteProviderIndex in remoteProviderList:
                        remoteProvider = remoteProviderList[remoteProviderIndex]
                        for version in [ 4, 6 ]:
                           localInnerAddress =  makeNorNetTunnelIP(siteIndex, providerIndex,
                                                                  remoteSiteIndex, remoteProviderIndex, version)

                           # The tunnel IPs may be non-unique! Make sure that NFSD gets unique addresses only!
                           if not str(localInnerAddress.ip) in tunnelIPs:
                              tunnelIPs.append(str(localInnerAddress.ip))
                              if version == 4:
                                 outputFile.write(str(localInnerAddress.ip) + '(' + nfsOptions + ',rw)\t')
                              else:
                                 outputFile.write('[' + str(localInnerAddress.ip) + '](' + nfsOptions + ',rw)\t')
                           else:
                              print('Non-unique: ' + str(localInnerAddress.ip))

         outputFile.write('\n')

      # ====== Create symlinks with host name ===============================
      resolvedNames = 0
      while resolvedNames < len(siteQueryList) - 1:
         time.sleep(0.334)
         resolvedNames = resolvedNames + len(adnsResolver.completed())
         # print resolvedNames

      # All names are resolved now (successfully or unsuccessfully)!
      successfulNameResolutions = 0
      for i in range(1,len(siteQueryList)):
         answer = siteQueryList[i].check()
         if answer[0] == 0:
            # print i,hostName,answer[3][0]
            successfulNameResolutions = successfulNameResolutions + 1
            nodeNumber = str(i)
            nodeName = getHostnameFromFQDN(answer[3][0].decode('utf-8'))
            makeSymlink(nodeName, nodeNumber)
            try:
               obsoleteNodes.remove(nodeName)
            except:
               pass

      # ====== Remove obsolete node links ======================================
      # Only perform cleanup if some name resolutions have been successful.
      # Otherwise, just a DNS problem could remove valid links!
      if successfulNameResolutions > 0:
         for obsoleteNode in obsoleteNodes:
            try:
               if os.path.islink(obsoleteNode):
                  os.unlink(obsoleteNode)
            except:
               pass

      changeDir(oldDirectory)

   outputFile.close()

   # ====== Remove obsolete site links ======================================
   for obsoleteSite in obsoleteSites:
      try:
         if os.path.islink(obsoleteSite):
            os.unlink(obsoleteSite)
         elif os.path.isdir(obsoleteSite):
            os.rmdir(obsoleteSite)
      except:
         pass

   changeDir(originalDirectory)



# ###### Main program #######################################################
try:
   loginToPLC()
except Exception as e:
   sys.stderr.write('ERROR: ' + str(e) + '!\n')
   sys.exit(1)

fullSiteList = fetchNorNetSiteList(False)
# NOTE: Site list also includes disabled sites!

rwSystemList = []
rwSystems = getFileServRWSystemsConfigurationString()
if rwSystems != None:
   parameters = shlex.split(rwSystems)
   for i in range(0, len(parameters)):
      name = unquote(parameters[i])
      # print i, name
      # print resolveHostname(name, AF_INET)
      # print resolveHostname(name, AF_INET6)
      rwSystemList.append(name)

user = pwd.getpwnam(getLocalNodeNorNetUser())
uid  = user.pw_uid
gid  = user.pw_gid

makeTFTPDConfiguration()
makeNFSDConfiguration(fullSiteList, rwSystemList, uid, gid)
