/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 *
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <winsock2.h>
#include <ws2tcpip.h>

extern "C" {
#include <afs/param.h>
#include <afs/stds.h>
}

#include "afs_config.h"
#include <WINNT\afsreg.h>

extern "C" {

#include "../afsd/fs_utils.h"

#define __CM_CONFIG_INTERFACES_ONLY__
#include "../afsd/cm_config.h"

#include "../afsd/cm_nls.h"
#define __CM_IOCTL_INTERFACES_ONLY__
#include "../afsd/cm_ioctl.h"

} // extern "C"


#define cREALLOC_PREFS       32

#define cSERVERPREFS_CHUNK  256

#define PIOCTL_MAXSIZE     2048



/*
 * ROUTINES ___________________________________________________________________
 *
 */

static DWORD log2 (DWORD dwValue)
{
   DWORD dwLog;
   for (dwLog = 0; (DWORD)(1<<dwLog) < dwValue; ++dwLog)
      ;
   return dwLog;
}

DWORD Config_GetServiceState (void)
{
   if (!g.fIsWinNT)
      {
      TCHAR szGateway[ cchRESOURCE ] = TEXT("");
      Config_GetGatewayName (szGateway);
      return (szGateway[0]) ? SERVICE_RUNNING : SERVICE_STOPPED;
      }

   SERVICE_STATUS Status;
   memset (&Status, 0x00, sizeof(Status));

   SC_HANDLE hManager;
   if ((hManager = OpenSCManager (NULL, NULL, GENERIC_READ)) != NULL)
      {
      SC_HANDLE hService;
      if ((hService = OpenService (hManager, TEXT("TransarcAFSDaemon"), GENERIC_READ)) != NULL)
         {
         QueryServiceStatus (hService, &Status);
         CloseServiceHandle (hService);
         }

      CloseServiceHandle (hManager);
      }

   return Status.dwCurrentState;
}


/*
 * Configuration Routine ______________________________________________________
 *
 */

void Config_GetGatewayFlag (BOOL *pfFlag)
{
    if (!Config_ReadGlobalNum (TEXT("IsGateway"), (DWORD*)pfFlag))
        *pfFlag = FALSE;
}


BOOL Config_SetGatewayFlag (BOOL fFlag, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("IsGateway"), fFlag);
    g.fNeedRestart = TRUE;
    return TRUE;
}


void Config_GetGatewayName (LPTSTR pszName)
{
    if (!Config_ReadGlobalString (TEXT("Gateway"), pszName, MAX_PATH))
        GetString (pszName, IDS_GATEWAY_UNKNOWN);
    else if (!*pszName)
        GetString (pszName, IDS_GATEWAY_UNKNOWN);
}


BOOL Config_SetGatewayName (LPCTSTR pszName, ULONG *pStatus)
{
   TCHAR szBogus[ cchRESOURCE ];
   GetString (szBogus, IDS_GATEWAY_UNKNOWN);
   if (!lstrcmpi (szBogus, pszName))
      {
      Config_WriteGlobalString (TEXT("Gateway"), TEXT(""));
      }
   else
      {
      Config_WriteGlobalString (TEXT("Gateway"), pszName);
      }

   return TRUE;
}


void Config_FixGatewayDrives (void)
{
   // Zip through the user's network drives and reconnect
   // them as necessary.
   //
   for (TCHAR chDrive = chDRIVE_A; chDrive <= chDRIVE_Z; ++chDrive)
      {
      TCHAR szSubmount[ MAX_PATH ];
      if (!GetDriveSubmount (chDrive, szSubmount))
         continue;

      // We've got a mapping into AFS!  Remove it, rather forcefully.
      //
      TCHAR szDrive[] = "@:";
      szDrive[0] = chDrive;
      WNetCancelConnection (szDrive, TRUE);
      }

   // Now recreate our drive mappings, based on the user's already-
   // specified preferences.
   //
   DRIVEMAPLIST List;
   QueryDriveMapList (&List);

   for (size_t ii = 0; ii < 26; ++ii)
      {
      if (!List.aDriveMap[ii].szMapping[0])
         continue;
      ActivateDriveMap (List.aDriveMap[ii].chDrive, List.aDriveMap[ii].szMapping, List.aDriveMap[ii].szSubmount, List.aDriveMap[ii].fPersistent);
      }
}


void Config_GetCellName (LPTSTR pszName)
{
   if (!Config_ReadGlobalString (TEXT("Cell"), pszName, MAX_PATH))
      GetString (pszName, IDS_CELL_UNKNOWN);
   else if (!*pszName)
      GetString (pszName, IDS_CELL_UNKNOWN);
}


BOOL Config_ContactGateway (LPTSTR pszGateway, LPTSTR pszCell)
{
   // pszCell is MAX_PATH
   BOOL rc = FALSE;

   BYTE OutData[ PIOCTL_MAXSIZE ];
   memset (OutData, 0x00, sizeof(OutData));

   struct ViceIoctl IOInfo;
   IOInfo.in_size = 0;
   IOInfo.in = 0;
   IOInfo.out = (char *)OutData;
   IOInfo.out_size = PIOCTL_MAXSIZE;

   TCHAR szOldGateway[ MAX_PATH ];
   Config_GetGatewayName (szOldGateway);
   Config_SetGatewayName (pszGateway);

   ULONG status;
   if ((status = pioctl (0, VIOC_GET_WS_CELL, &IOInfo, 1)) == 0)
   {
       OutData[min(PIOCTL_MAXSIZE, MAX_PATH) - 1] = '\0';
       if (OutData[0])
       {
           lstrcpy (pszCell, (LPCTSTR)OutData);
           rc = TRUE;
       }
   }

   Config_SetGatewayName (szOldGateway);

   return rc;
}


BOOL Config_SetCellName (LPCTSTR pszName, ULONG *pStatus)
{
   TCHAR szBogus[ cchRESOURCE ];
   GetString (szBogus, IDS_CELL_UNKNOWN);
   if (lstrcmpi (szBogus, pszName))
      {
      Config_WriteGlobalString (TEXT("Cell"), pszName);
      g.fNeedRestart = TRUE;
      }
   return TRUE;
}

void Config_GetTrayIconFlag (BOOL *pfFlag)
{
   if (!Config_ReadUserNum (TEXT("ShowTrayIcon"), (DWORD*)pfFlag))
      *pfFlag = FALSE;
}


BOOL Config_SetTrayIconFlag (BOOL fFlag, ULONG *pStatus)
{
   Config_WriteUserNum (TEXT("ShowTrayIcon"), fFlag);

   HWND hSearch;
   for (hSearch = GetWindow (GetDesktopWindow(), GW_CHILD);
        hSearch && IsWindow(hSearch);
        hSearch = GetWindow (hSearch, GW_HWNDNEXT))
      {
      TCHAR szClassName[ cchRESOURCE ];
      if (GetClassName (hSearch, szClassName, cchRESOURCE))
         {
         if (!lstrcmpi (szClassName, TEXT("AfsCreds")))
            break;
         }
      }

   if (hSearch && IsWindow(hSearch))
      {
      UINT msgCheckTerminate = RegisterWindowMessage (TEXT("AfsCredsCheckTerminate"));
      PostMessage (hSearch, msgCheckTerminate, 0, 0);
      }
   else if (fFlag && !(hSearch && IsWindow(hSearch)))
      {
      WinExec (TEXT("AfsCreds.exe /quiet"), SW_SHOW);
      }

   return TRUE;
}


PSERVERPREFS Config_GetServerPrefs (BOOL fVLServers)
{
   PSERVERPREFS pPrefs = New (SERVERPREFS);
   memset (pPrefs, 0x00, sizeof(SERVERPREFS));
   pPrefs->fVLServers = fVLServers;

   if (Config_GetServiceState() == SERVICE_RUNNING)
      {
      // We retrieve server prefs in batches--start that loop now.
      //
      size_t iOut = 0;
      for (int iOffset = 0; ; )
         {
         cm_SPrefRequest_t InData;
         memset (&InData, 0x00, sizeof(InData));
         InData.offset = iOffset;
         InData.flags = (pPrefs->fVLServers) ? CM_SPREF_VLONLY : 0;
         InData.num_servers = cSERVERPREFS_CHUNK;

         BYTE OutDataStorage[ sizeof(cm_SPrefInfo_t) + cSERVERPREFS_CHUNK * sizeof(cm_SPref_t) ];
         memset (OutDataStorage, 0x00, sizeof(OutDataStorage));
         cm_SPrefInfo_t *pOutData = (cm_SPrefInfo_t *)OutDataStorage;

         struct ViceIoctl IOInfo;
         IOInfo.in_size = sizeof(InData);
         IOInfo.in = (char *)&InData;
         IOInfo.out = (char *)pOutData;
         IOInfo.out_size = sizeof(cm_SPrefInfo_t) + cSERVERPREFS_CHUNK * sizeof(cm_SPref_t);

         if (pioctl (0, VIOC_GETSPREFS, &IOInfo, 1) != 0)
            break;

         if (!REALLOC (pPrefs->aPrefs, pPrefs->cPrefs, iOut + pOutData->num_servers, cREALLOC_PREFS))
            break;

         for (size_t ii = 0; ii < pOutData->num_servers; ++ii)
            {
            pPrefs->aPrefs[ iOut ].ipServer = pOutData->servers[ ii ].host.s_addr;
            pPrefs->aPrefs[ iOut ].iRank = pOutData->servers[ ii ].rank;
            iOut ++;
            }

         if ((iOffset = pOutData->next_offset) == 0)
            break;
         }
     }

   return pPrefs;
}


BOOL Config_SetServerPrefs (PSERVERPREFS pPrefs, ULONG *pStatus)
{
   BOOL rc = TRUE;
   ULONG status = 0;

   if (pPrefs)
      {
      size_t cChanged = 0;
      size_t ii;
      for (ii = 0; ii < pPrefs->cPrefs; ++ii)
         {
         if (pPrefs->aPrefs[ ii ].fChanged)
            ++cChanged;
         }

      if (cChanged)
         {
         if (Config_GetServiceState() != SERVICE_RUNNING)
            {
            rc = FALSE;
            status = ERROR_SERVICE_NOT_ACTIVE;
            }
         else
            {
            size_t cbInDataStorage = sizeof(cm_SSetPref_t) + cChanged * sizeof(cm_SPref_t);

            PBYTE pInDataStorage;
            if ((pInDataStorage = (PBYTE)Allocate (cbInDataStorage)) == NULL)
               {
               rc = FALSE;
               status = ERROR_NOT_ENOUGH_MEMORY;
               }
            else
               {
               memset (pInDataStorage, 0x00, sizeof(cbInDataStorage));

               cm_SSetPref_t *pInData = (cm_SSetPref_t*)pInDataStorage;
               pInData->flags = (pPrefs->fVLServers) ? CM_SPREF_VLONLY : 0;
               pInData->num_servers = cChanged;

               size_t iOut = 0;
               for (ii = 0; ii < pPrefs->cPrefs; ++ii)
                  {
                  if (pPrefs->aPrefs[ ii ].fChanged)
                     {
                     pInData->servers[ iOut ].host.s_addr = pPrefs->aPrefs[ ii ].ipServer;
                     pInData->servers[ iOut ].rank = (unsigned short)pPrefs->aPrefs[ ii ].iRank;
                     iOut++;
                     }
                  }

               BYTE OutDataStorage[ PIOCTL_MAXSIZE ];

               struct ViceIoctl IOInfo;
               IOInfo.in_size = cbInDataStorage;
               IOInfo.in = (char *)pInData;
               IOInfo.out = (char *)OutDataStorage;
               IOInfo.out_size = PIOCTL_MAXSIZE;

               if ((status = pioctl (0, VIOC_SETSPREFS, &IOInfo, 1)) != 0)
                  {
                  rc = FALSE;
                  }

               Free (pInDataStorage);
               }
            }
         }
      }

   if (pStatus && !rc)
      *pStatus = status;
   if (!rc)
      Message (MB_ICONHAND, GetErrorTitle(), IDS_FAILCONFIG_PREFS, TEXT("%ld"), status);
   return rc;
}


void Config_FreeServerPrefs (PSERVERPREFS pPrefs)
{
   if (pPrefs->aPrefs)
      Free (pPrefs->aPrefs);
   memset (pPrefs, 0xFD, sizeof(SERVERPREFS));
   Delete (pPrefs);
}


void Config_GetCacheSize (ULONG *pckCache)
{
    if (!Config_ReadGlobalNum (TEXT("CacheSize"), (DWORD*)pckCache))
        *pckCache = CM_CONFIGDEFAULT_CACHESIZE;
}


BOOL Config_SetCacheSize (ULONG ckCache, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("CacheSize"), ckCache);
    g.fNeedRestart = TRUE;
    return TRUE;
}



void Config_GetChunkSize (ULONG *pckChunk)
{
    if (!Config_ReadGlobalNum (TEXT("ChunkSize"), (DWORD*)pckChunk))
        *pckChunk = CM_CONFIGDEFAULT_CHUNKSIZE;
    *pckChunk = max (*pckChunk, 10);
    *pckChunk = (1 << ((*pckChunk)-10));
}


BOOL Config_SetChunkSize (ULONG ckChunk, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("ChunkSize"), log2(ckChunk * 1024));
    g.fNeedRestart = TRUE;
    return TRUE;
}



void Config_GetStatEntries (ULONG *pcEntries)
{
    if (!Config_ReadGlobalNum (TEXT("Stats"), (DWORD*)pcEntries))
        *pcEntries = CM_CONFIGDEFAULT_STATS;
}


BOOL Config_SetStatEntries (ULONG cEntries, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("Stats"), cEntries);
    g.fNeedRestart = TRUE;
    return TRUE;
}



void Config_GetProbeInt (ULONG *pcsecProbe)
{
    *pcsecProbe = 30;
    // TODO: NEED REGISTRY SETTING
}


BOOL Config_SetProbeInt (ULONG csecProbe, ULONG *pStatus)
{
    BOOL rc = TRUE;
    ULONG status = 0;

    // TODO: NEED REGISTRY SETTING
    if (Config_GetServiceState() == SERVICE_RUNNING)
    {
        struct chservinfo checkserv;
        memset (&checkserv, 0x00, sizeof(checkserv));
        checkserv.magic = 0x12345678;
        checkserv.tinterval = csecProbe;

        BYTE OutData[ PIOCTL_MAXSIZE ];
        memset (OutData, 0x00, sizeof(OutData));

        struct ViceIoctl IOInfo;
        IOInfo.in_size = sizeof(checkserv);
        IOInfo.in = (char *)&checkserv;
        IOInfo.out = (char *)OutData;
        IOInfo.out_size = PIOCTL_MAXSIZE;

        if ((status = pioctl (0, VIOCCKSERV, &IOInfo, 1)) != 0)
        {
            rc = FALSE;
        }
    }

    if (pStatus && !rc)
        *pStatus = status;
    if (!rc)
        Message (MB_ICONHAND, GetErrorTitle(), IDS_FAILCONFIG_PROBE, TEXT("%ld"), status);
    return rc;
}



void Config_GetNumThreads (ULONG *pcThreads)
{
    if (!Config_ReadGlobalNum (TEXT("ServerThreads"), (DWORD*)pcThreads))
        *pcThreads = CM_CONFIGDEFAULT_SVTHREADS;
}


BOOL Config_SetNumThreads (ULONG cThreads, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("ServerThreads"), cThreads);
    g.fNeedRestart = TRUE;
    return TRUE;
}



void Config_GetNumDaemons (ULONG *pcDaemons)
{
    if (!Config_ReadGlobalNum (TEXT("Daemons"), (DWORD*)pcDaemons))
        *pcDaemons = CM_CONFIGDEFAULT_DAEMONS;
}


BOOL Config_SetNumDaemons (ULONG cDaemons, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("Daemons"), cDaemons);
    g.fNeedRestart = TRUE;
    return TRUE;
}



void Config_GetSysName (LPTSTR pszName)
{
    if (!Config_ReadGlobalString (TEXT("SysName"), pszName, MAX_PATH))
        lstrcpy (pszName, TEXT("i386_nt40"));
}


BOOL Config_SetSysName (LPCTSTR pszName, ULONG *pStatus)
{
    BOOL rc = TRUE;
    ULONG status = 0;

    if (Config_GetServiceState() == SERVICE_RUNNING)
    {
        struct {
            ULONG cbData;
            TCHAR szData[ PIOCTL_MAXSIZE ];
        } InData;
        memset (&InData, 0x00, sizeof(InData));
        USHORT i=0, j=0, len=lstrlen(pszName);

        if ( len == 0 ) {
            Message (MB_ICONHAND, GetErrorTitle(), IDS_FAILCONFIG_SYSNAME, TEXT("A sysname must be specified"));
            return(-1);
        }

        while ( pszName[i] ) {
            if ( !isspace(pszName[i]) ) {
                InData.szData[j++] = pszName[i];
            } else if (InData.szData[j-1] != '\0') {
                InData.szData[j++] = '\0';
                InData.cbData++;
            }
            i++;
        }
        InData.szData[j++] = '\0';
        InData.cbData++;      /* one word */

        BYTE OutData[ PIOCTL_MAXSIZE ];
        memset (OutData, 0x00, sizeof(OutData));

        struct ViceIoctl IOInfo;
        IOInfo.in_size = sizeof(ULONG) + j;
        IOInfo.in = (char *)&InData;
        IOInfo.out = (char *)OutData;
        IOInfo.out_size = PIOCTL_MAXSIZE;

        if ((status = pioctl (0, VIOC_AFS_SYSNAME, &IOInfo, 1)) != 0)
        {
            rc = FALSE;
        }
    }

    if (rc)
    {
        Config_WriteGlobalString (TEXT("SysName"), pszName);
    }

    if (pStatus && !rc)
        *pStatus = status;
    if (!rc)
        Message (MB_ICONHAND, GetErrorTitle(), IDS_FAILCONFIG_SYSNAME, TEXT("%ld"), status);
    return rc;
}



void Config_GetRootVolume (LPTSTR pszName)
{
    if (!Config_ReadGlobalString (TEXT("RootVolume"), pszName, MAX_PATH))
        lstrcpy (pszName, TEXT("root.afs"));
}


BOOL Config_SetRootVolume (LPCTSTR pszName, ULONG *pStatus)
{
    Config_WriteGlobalString (TEXT("RootVolume"), pszName);
    g.fNeedRestart = TRUE;
    return TRUE;
}



void Config_GetMountRoot (LPTSTR pszPath)
{
    if (!Config_ReadGlobalString (TEXT("MountRoot"), pszPath, MAX_PATH))
        lstrcpy (pszPath, TEXT("/afs"));
}


BOOL Config_SetMountRoot (LPCTSTR pszPath, ULONG *pStatus)
{
    Config_WriteGlobalString (TEXT("MountRoot"), pszPath);
    g.fNeedRestart = TRUE;
    return TRUE;
}


BOOL Config_GetCacheInUse (ULONG *pckCacheInUse, ULONG *pStatus)
{
    BOOL rc = TRUE;
    ULONG status = 0;

    *pckCacheInUse = 0;

    if (Config_GetServiceState() != SERVICE_RUNNING)
    {
        rc = FALSE;
        status = ERROR_SERVICE_NOT_ACTIVE;
    }
    else
    {
        BYTE OutData[ PIOCTL_MAXSIZE ];
        memset (OutData, 0x00, sizeof(OutData));

        struct ViceIoctl IOInfo;
        IOInfo.in_size = 0;
        IOInfo.in = (char *)0;
        IOInfo.out = (char *)OutData;
        IOInfo.out_size = PIOCTL_MAXSIZE;

        if ((status = pioctl (0, VIOCGETCACHEPARMS, &IOInfo, 1)) != 0)
        {
            rc = FALSE;
        }
        else
        {
            *pckCacheInUse = ((LONG*)OutData)[1];
        }
    }

    if (pStatus && !rc)
        *pStatus = status;
    return rc;
}

void Config_GetCachePath (LPTSTR pszCachePath)
{
    if (!Config_ReadGlobalString (TEXT("CachePath"), pszCachePath, MAX_PATH)) {
        HKEY hk;
        TCHAR szPath[ MAX_PATH ] = TEXT("");

        if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, TEXT("System\\CurrentControlSet\\Control\\Session Manager\\Environment"), 0, KEY_QUERY_VALUE, &hk) == ERROR_SUCCESS)
        {
            DWORD dwSize = sizeof(szPath);
            DWORD dwType = 0;

            if (RegQueryValueEx (hk, TEXT("TEMP"), NULL, &dwType, (PBYTE)szPath, &dwSize) == ERROR_SUCCESS)
            {
                if ( dwType == REG_EXPAND_SZ ) {
                    TCHAR szTemp[ MAX_PATH ];
                    lstrcpy(szTemp, szPath);
                    ExpandEnvironmentStrings(szTemp, szPath, MAX_PATH);
                }
            }
            RegCloseKey (hk);
            lstrcat(szPath, "\\AFSCache");
        }

        if ( !szPath[0] ) {
            GetWindowsDirectory(szPath, sizeof(szPath));
            lstrcat(szPath, "\\TEMP\\AFSCache");
        }
        lstrcpy (pszCachePath, szPath);
    }
}

BOOL Config_SetCachePath(LPCTSTR pszPath, ULONG *pStatus)
{
    Config_WriteGlobalString (TEXT("CachePath"), pszPath);
    g.fNeedRestart = TRUE;
    return TRUE;
}

void Config_GetLanAdapter (ULONG *pnLanAdapter)
{
    if (!Config_ReadGlobalNum (TEXT("LANadapter"), (DWORD*)pnLanAdapter))
        *pnLanAdapter = -1;
}

BOOL Config_SetLanAdapter (ULONG nLanAdapter, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("LANadapter"), nLanAdapter);
    g.fNeedRestart = TRUE;
    return TRUE;
}

void Config_GetTrapOnPanic (BOOL *pfFlag)
{
    if (!Config_ReadGlobalNum (TEXT("TrapOnPanic"), (DWORD*)pfFlag))
        *pfFlag = TRUE;
}

BOOL Config_SetTrapOnPanic (BOOL fFlag, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("TrapOnPanic"), fFlag);
    g.fNeedRestart = TRUE;
    return TRUE;
}

void Config_GetTraceBufferSize (ULONG *pnBufSize)
{
    if (!Config_ReadGlobalNum (TEXT("TraceBufferSize"), (DWORD*)pnBufSize))
        *pnBufSize = 5000;
}

BOOL Config_SetTraceBufferSize (ULONG nBufSize, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("TraceBufferSize"), nBufSize);
    g.fNeedRestart = TRUE;
    return TRUE;
}

void Config_GetLoginRetryInterval (ULONG *pnInterval)
{
    if (!Config_ReadGlobalNum (TEXT("LoginRetryInterval"), (DWORD*)pnInterval))
        *pnInterval = 30;
}

BOOL Config_SetLoginRetryInterval (ULONG nInterval, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("LoginRetryInterval"), nInterval);
    return TRUE;
}

void Config_GetFailLoginsSilently (BOOL *pfFlag)
{
    if (!Config_ReadGlobalNum (TEXT("FailLoginsSilently"), (DWORD*)pfFlag))
        *pfFlag = FALSE;
}

BOOL Config_SetFailLoginsSilently (BOOL fFlag, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("FailLoginsSilently"), fFlag);
    return TRUE;
}

void Config_GetReportSessionStartups (BOOL *pfFlag)
{
    if (!Config_ReadGlobalNum (TEXT("ReportSessionStartups"), (DWORD*)pfFlag))
        *pfFlag = FALSE;
}

BOOL Config_SetReportSessionStartups (BOOL fFlag, ULONG *pStatus)
{
    Config_WriteGlobalNum (TEXT("ReportSessionStartups"), fFlag);
    return TRUE;
}

