/************************************************************************************
   Copyright (C) 2013,2016 MariaDB Corporation AB
   
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
   
   This library 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
   Library General Public License for more details.
   
   You should have received a copy of the GNU Library General Public
   License along with this library; if not see <http://www.gnu.org/licenses>
   or write to the Free Software Foundation, Inc., 
   51 Franklin St., Fifth Floor, Boston, MA 02110, USA
*************************************************************************************/
#include <ma_odbc.h>


#define DSNKEY_OPTIONS_INDEX   3
#define DSNKEY_OPTION_INDEX    4
#define DSNKEY_NAMEDPIPE_INDEX 5
#define DSNKEY_TCPIP_INDEX     6
#define DSNKEY_SERVER_INDEX    7
#define DSNKEY_UID_INDEX       8
#define DSNKEY_PWD_INDEX       9
#define DSNKEY_DATABASE_INDEX 10

MADB_DsnKey DsnKeys[]=
{
  {"DSN",            offsetof(MADB_Dsn, DSNName),           DSN_TYPE_STRING, 0, 0}, /* 0 */
  {"DESCRIPTION",    offsetof(MADB_Dsn, Description),       DSN_TYPE_STRING, 0, 0},
  {"DRIVER",         offsetof(MADB_Dsn, Driver),            DSN_TYPE_STRING, 0, 0},
  /* OPTIONS should go above all DSN_TYPE_OPTION. They are not saved in DSN separately, and then DSN is read, corresponding
     properties are filled from OPTIONS. Also putting its alias here - it should not appear on Windows(unless somebody edits
     registry manually), but on *nix we can expect everything. Array index used in some places to decide if the key is OPTIONS */
  {"OPTIONS",        offsetof(MADB_Dsn, Options),           DSN_TYPE_INT,    0, 0}, /* DSNKEY_OPTIONS_INDEX */
  {"OPTION",         DSNKEY_OPTIONS_INDEX,                  DSN_TYPE_INT,    0, 1}, /* DSNKEY_OPTION_INDEX  */

  {"NamedPipe",      offsetof(MADB_Dsn, IsNamedPipe),       DSN_TYPE_OPTION, MADB_OPT_FLAG_NAMED_PIPE, 0}, /* MADB_DSNKEY_NAMEDPIPE_INDEX */
  {"TCPIP",          offsetof(MADB_Dsn, IsTcpIp),           DSN_TYPE_BOOL,   0, 0}, /* DSNKEY_TCPIP_INDEX */
  {"SERVER",         offsetof(MADB_Dsn, ServerName),        DSN_TYPE_STRING, 0, 0}, /* DSNKEY_SERVER_INDEX     */
  {"UID",            offsetof(MADB_Dsn, UserName),          DSN_TYPE_STRING, 0, 0}, /* DSNKEY_UID_INDEX        */
  {"PWD",            offsetof(MADB_Dsn, Password),          DSN_TYPE_STRING, 0, 0}, /* DSNKEY_PWD_INDEX        */
  {"DATABASE",       offsetof(MADB_Dsn, Catalog),           DSN_TYPE_COMBO,  0, 0}, /* 10 DSNKEY_DATABASE_INDEX */
  {"PORT",           offsetof(MADB_Dsn, Port),              DSN_TYPE_INT,    0, 0},
  {"INITSTMT",       offsetof(MADB_Dsn, InitCommand),       DSN_TYPE_STRING, 0, 0},
  {"CONN_TIMEOUT",   offsetof(MADB_Dsn, ConnectionTimeout), DSN_TYPE_INT,    0, 0},
  {"AUTO_RECONNECT", offsetof(MADB_Dsn, Reconnect),         DSN_TYPE_OPTION, MADB_OPT_FLAG_AUTO_RECONNECT,0},
  {"NO_PROMPT",      offsetof(MADB_Dsn, ConnectPrompt),     DSN_TYPE_OPTION, MADB_OPT_FLAG_NO_PROMPT,0},
  {"CHARSET",        offsetof(MADB_Dsn, CharacterSet),      DSN_TYPE_COMBO,  0, 0},
  {"TRACE",          offsetof(MADB_Dsn, TraceFile),         DSN_TYPE_STRING, 0, 0},
  {"PLUGIN_DIR",     offsetof(MADB_Dsn, ConnCPluginsDir),   DSN_TYPE_STRING, 0, 0},
  /* SSL */
  {"SSLKEY",         offsetof(MADB_Dsn, SslKey),            DSN_TYPE_STRING, 0, 0},
  {"SSLCERT",        offsetof(MADB_Dsn, SslCert),           DSN_TYPE_STRING, 0, 0}, /* 20 */
  {"SSLCA",          offsetof(MADB_Dsn, SslCa),             DSN_TYPE_STRING, 0, 0},
  {"SSLCAPATH",      offsetof(MADB_Dsn, SslCaPath),         DSN_TYPE_STRING, 0, 0},
  {"SSLCIPHER",      offsetof(MADB_Dsn, SslCipher),         DSN_TYPE_STRING, 0, 0},
  {"SSLVERIFY",      offsetof(MADB_Dsn, SslVerify),         DSN_TYPE_BOOL,   0, 0},
  {"SSLFP",          offsetof(MADB_Dsn, SslFp),             DSN_TYPE_STRING, 0, 0},
  {"SSLFPLIST",      offsetof(MADB_Dsn, SslFpList),         DSN_TYPE_STRING, 0, 0},
  {"SSLCRL",         offsetof(MADB_Dsn, SslCrl),            DSN_TYPE_STRING, 0, 0},
  {"SSLCRLPATH",     offsetof(MADB_Dsn, SslCrlPath),        DSN_TYPE_STRING, 0, 0},
  {"SOCKET",         offsetof(MADB_Dsn, Socket),            DSN_TYPE_STRING, 0, 0},
  {"SAVEFILE",       offsetof(MADB_Dsn, SaveFile),            DSN_TYPE_STRING, 0, 0}, /* 30 */
  /* Aliases. Here offset is index of aliased key */
  {"SERVERNAME",     DSNKEY_SERVER_INDEX,                   DSN_TYPE_STRING, 0, 1},
  {"USER",           DSNKEY_UID_INDEX,                      DSN_TYPE_STRING, 0, 1},
  {"PASSWORD",       DSNKEY_PWD_INDEX,                      DSN_TYPE_STRING, 0, 1},
  {"DB",             DSNKEY_DATABASE_INDEX,                 DSN_TYPE_COMBO,  0, 1},

  /* Terminating Null */
  {NULL, 0, DSN_TYPE_BOOL,0,0}
};

#define IS_OPTIONS_BITMAP(key_index) (key_index == DSNKEY_OPTIONS_INDEX || key_index == DSNKEY_OPTIONS_INDEX)

#define GET_FIELD_PTR(DSN, DSNKEY, TYPE) ((TYPE *)((char*)(DSN) + (DSNKEY)->DsnOffset))

typedef struct
{
  unsigned int Key;
  unsigned int Dependent;
  BOOL         Same;      /* Should dependent be switched same way, or in reverse */
} MADB_DsnKeyDep;

/* Define pairs of keys that are switches, i.e. setting one should reset the other.
   Transitive dependencies have to be defined as dierect dependencies here as well */
const MADB_DsnKeyDep DsnKeysSwitch[]=
{
  {DSNKEY_NAMEDPIPE_INDEX, DSNKEY_TCPIP_INDEX,     0},
  {DSNKEY_TCPIP_INDEX,     DSNKEY_NAMEDPIPE_INDEX, 0}
};


/* {{{ MADB_DSN_SetDefaults() */
void MADB_DSN_SetDefaults(MADB_Dsn *Dsn)
{
  Dsn->IsTcpIp= 1;
}
/* }}} */

/* {{{ MADB_Dsn_Init() */
MADB_Dsn *MADB_DSN_Init()
{
  MADB_Dsn *Dsn;

  if ((Dsn= (MADB_Dsn *)MADB_CALLOC(sizeof(MADB_Dsn))))
  {
    Dsn->FreeMe= TRUE;
    Dsn->Keys= (MADB_DsnKey *)&DsnKeys;
  }
  return Dsn;
}
/* }}} */

/* {{{ MADB_Dsn_Free */
void MADB_DSN_Free(MADB_Dsn *Dsn)
{
  if (!Dsn)
    return;

  MADB_FREE(Dsn->DSNName);
  MADB_FREE(Dsn->Driver);
  MADB_FREE(Dsn->Description);
  MADB_FREE(Dsn->ServerName);
  MADB_FREE(Dsn->UserName);
  MADB_FREE(Dsn->Password);
  MADB_FREE(Dsn->Catalog);
  MADB_FREE(Dsn->CharacterSet);
  MADB_FREE(Dsn->InitCommand);
  MADB_FREE(Dsn->TraceFile);
  MADB_FREE(Dsn->Socket);
  MADB_FREE(Dsn->ConnCPluginsDir);
  MADB_FREE(Dsn->SslKey);
  MADB_FREE(Dsn->SslCert);
  MADB_FREE(Dsn->SslCa);
  MADB_FREE(Dsn->SslCaPath);
  MADB_FREE(Dsn->SslCipher);
  MADB_FREE(Dsn->SslCrl);
  MADB_FREE(Dsn->SslCrlPath);
  MADB_FREE(Dsn->SslFp);
  MADB_FREE(Dsn->SslFpList);
  MADB_FREE(Dsn->SaveFile);

  if (Dsn->FreeMe)
    MADB_FREE(Dsn); 
}
/* }}} */


void MADB_SetOptionValue(MADB_Dsn *Dsn, MADB_DsnKey *DsnKey, my_bool value)
{
  *GET_FIELD_PTR(Dsn, DsnKey, my_bool)= value;
  if (value)
  {
    Dsn->Options |= DsnKey->FlagValue;
  }
  else
  {
    Dsn->Options &= ~DsnKey->FlagValue;
  }
}


my_bool MADB_DsnStoreValue(MADB_Dsn *Dsn, unsigned int DsnKeyIdx, char *Value, my_bool OverWrite);

/* {{{ MADB_DsnSwitchDependents */
   /* If TCPIP selected, we have to reset NAMEDPIPE */
BOOL MADB_DsnSwitchDependents(MADB_Dsn *Dsn, unsigned int Changed)
{
  int i;

  for (i= 0; i < sizeof(DsnKeysSwitch)/sizeof(MADB_DsnKeyDep); ++i)
  {
    if (DsnKeysSwitch[i].Key == Changed)
    {
      my_bool KeySet;

      switch (DsnKeys[Changed].Type)
      {
      case DSN_TYPE_STRING:
      case DSN_TYPE_COMBO:
        {
          char *str= *GET_FIELD_PTR(Dsn, &DsnKeys[Changed], char*);
          KeySet= str && *str;
        }
        break;
      case DSN_TYPE_OPTION:
      case DSN_TYPE_BOOL:
        {
          KeySet= *GET_FIELD_PTR(Dsn, &DsnKeys[Changed], my_bool);
        }
        break;
      case DSN_TYPE_INT:
        {
          KeySet= *GET_FIELD_PTR(Dsn, &DsnKeys[Changed], int) != 0;
        }
      }

      /* No problem to deal with aliases here as well, but let's keep things simple */
      if (DsnKeys[DsnKeysSwitch[i].Dependent].IsAlias != 0)
      {
        return FALSE;
      }

      switch(DsnKeys[DsnKeysSwitch[i].Dependent].Type)
      {
      case DSN_TYPE_BOOL:
        *GET_FIELD_PTR(Dsn, &DsnKeys[DsnKeysSwitch[i].Dependent], my_bool)= DsnKeysSwitch[i].Same == KeySet ? 1 : 0;
        break;
      case DSN_TYPE_OPTION:
         MADB_SetOptionValue(Dsn, &DsnKeys[DsnKeysSwitch[i].Dependent], DsnKeysSwitch[i].Same == KeySet ? 1 : 0);
        break;
      default:
        return FALSE; /* Only boolean fields are supported as dependent atm */ 
      }
    }
  }

  return TRUE;
}
/* }}} */


/* {{{ MADB_DsnStoreValue */
my_bool MADB_DsnStoreValue(MADB_Dsn *Dsn, unsigned int DsnKeyIdx, char *Value, my_bool OverWrite)
{
  MADB_DsnKey *DsnKey= &DsnKeys[DsnKeyIdx];
  if (!Dsn || DsnKey->IsAlias)
    return FALSE;

  switch(DsnKey->Type) {
  case DSN_TYPE_STRING:
  case DSN_TYPE_COMBO:
    {
      char **p= GET_FIELD_PTR(Dsn, DsnKey, char*);

      if (*p && OverWrite == FALSE)
        break;
      /* For the case of making copy of currently stored values */
       MADB_RESET(*p, Value);
    }
    break;
  case DSN_TYPE_BOOL:
    if (*GET_FIELD_PTR(Dsn, DsnKey, my_bool) && OverWrite == FALSE)
      break;
    *GET_FIELD_PTR(Dsn, DsnKey, my_bool)= atoi(Value);
    break;
  case DSN_TYPE_INT:
    if (*GET_FIELD_PTR(Dsn, DsnKey, int) && OverWrite == FALSE)
      break;
     *GET_FIELD_PTR(Dsn, DsnKey, int)= strtoul(Value, NULL, 10);
     break;
  case DSN_TYPE_OPTION:
    if (*GET_FIELD_PTR(Dsn, DsnKey, my_bool) && OverWrite == FALSE)
      break;
    MADB_SetOptionValue(Dsn, DsnKey, strtoul(Value, NULL, 10) != 0 ? 1 : 0);

    break;
  }
  return MADB_DsnSwitchDependents(Dsn, DsnKeyIdx);
}
/* }}} */

/* {{{ MADB_DsnUpdateOptionsFields */
void MADB_DsnUpdateOptionsFields(MADB_Dsn *Dsn)
{
  int i= 0;

  while (DsnKeys[i].DsnKey != NULL)
  { 
    if (DsnKeys[i].IsAlias == 0)
    {
      if (DsnKeys[i].Type == DSN_TYPE_OPTION)
      {
        *GET_FIELD_PTR(Dsn, &DsnKeys[i], my_bool)= (my_bool)(DSN_OPTION(Dsn, DsnKeys[i].FlagValue) ? 1 : 0);
        MADB_DsnSwitchDependents(Dsn, i);
      }
    }
    ++i;
  }
}
/* }}} */

/* {{{ MADB_ReadDSN */
my_bool MADB_ReadDSN(MADB_Dsn *Dsn, const char *KeyValue, my_bool OverWrite)
{
  char *Value;
  /* if no key/value pair was specified, we will try to read Dsn->DSNName */
  if (!KeyValue)
  {
    Value= Dsn->DSNName;
  }
  else 
  {
    if (Value= strchr(KeyValue, '='))
    {
      ++Value;
      MADB_RESET(Dsn->DSNName, Value);
    }
  }
  
  if (Value)
  {
    int  i= 1;
    char KeyVal[1024];

    while (DsnKeys[i].DsnKey)
    {
      unsigned int KeyIdx= DsnKeys[i].IsAlias ? DsnKeys[i].DsnOffset : i;

      if (SQLGetPrivateProfileString(Dsn->DSNName, DsnKeys[i].DsnKey, "", KeyVal, 1024, "ODBC.INI") > 0)
      {
        if (!MADB_DsnStoreValue(Dsn, KeyIdx, KeyVal, OverWrite))
          return FALSE;
      }
      else if (DsnKeys[i].Type == DSN_TYPE_OPTION)
      {
        *GET_FIELD_PTR(Dsn, &DsnKeys[KeyIdx], my_bool)= (my_bool)(DSN_OPTION(Dsn, DsnKeys[KeyIdx].FlagValue) ? 1 : 0);
      }
      ++i;
    }
    return TRUE;
  }
  return FALSE;
}
/* }}} */

my_bool MADB_DSN_Exists(const char *DsnName)
{
  my_bool ret;
  char buffer[1024];
  char *p= "";

  if (!DsnName)
    return FALSE;

  ret= (SQLGetPrivateProfileString(DsnName, NULL, p, buffer, 1024, "ODBC.INI") > 0);
  return ret;
}

/* {{{ MADB_SaveDSN */
my_bool MADB_SaveDSN(MADB_Dsn *Dsn)
{
  int     i= 1;
  char    Value[32];
  my_bool ret;
  DWORD   ErrNum;

  if (!SQLValidDSN(Dsn->DSNName))
  {
    strcpy_s(Dsn->ErrorMsg, SQL_MAX_MESSAGE_LENGTH, "Invalid Data Source Name");
    return FALSE;
  }

  if (!SQLRemoveDSNFromIni(Dsn->DSNName))
  {
    SQLInstallerError(1,&ErrNum, Dsn->ErrorMsg, SQL_MAX_MESSAGE_LENGTH, NULL);
    return FALSE;
  }
  if (!SQLWriteDSNToIni(Dsn->DSNName, Dsn->Driver))
  {
    SQLInstallerError(1,&ErrNum, Dsn->ErrorMsg, SQL_MAX_MESSAGE_LENGTH, NULL);
    return FALSE;
  }

  while(DsnKeys[i].DsnKey)
  {
    /* Skipping aliases - options are saved by primary name only */
    if (!DsnKeys[i].IsAlias)
    {
      ret= TRUE;
      /* We do not save DSN_TYPE_OPTION - they are saved as OPTIONS bits */
      switch(DsnKeys[i].Type){
      case DSN_TYPE_BOOL:
          ret= SQLWritePrivateProfileString(Dsn->DSNName, DsnKeys[i].DsnKey, 
          *GET_FIELD_PTR(Dsn, &DsnKeys[i], my_bool) ? "1" : "0", "ODBC.INI");
        break;
      case DSN_TYPE_INT:
        {
          _snprintf(Value ,32, "%d", *(int *)((char *)Dsn + DsnKeys[i].DsnOffset));
          ret= SQLWritePrivateProfileString(Dsn->DSNName, DsnKeys[i].DsnKey, Value, "ODBC.INI");
        }
        break;
      case DSN_TYPE_STRING:
      case DSN_TYPE_COMBO:
        {
          char *Val= *GET_FIELD_PTR(Dsn, &DsnKeys[i], char*);
          if (Val && Val[0])
            ret= SQLWritePrivateProfileString(Dsn->DSNName, DsnKeys[i].DsnKey, Val, "ODBC.INI");
        }
        break;
      }
      if (!ret)
      {
        SQLInstallerError(1,&ErrNum, Dsn->ErrorMsg, SQL_MAX_MESSAGE_LENGTH, NULL);
        return FALSE;
      }
    }
    i++;
  }
  /* Save Options */
  _snprintf(Value ,32, "%d", Dsn->Options);
  if (!(ret= SQLWritePrivateProfileString(Dsn->DSNName, "OPTIONS", Value, "ODBC.INI")))
  {
    SQLInstallerError(1,&ErrNum, Dsn->ErrorMsg, SQL_MAX_MESSAGE_LENGTH, NULL);
    return FALSE;
  }
  return TRUE;
}
/* }}} */


size_t ConnStringLength(const char * String, char Delimiter)
{
  size_t      result= strlen(String);
  const char *p=      String + result + 1;

  if (Delimiter != '\0')
  {
    return result;
  }
  /* else - we have string with null terminated key=value pairs with additional NULL after last pair */
  while (*p)
  {
    p+= strlen(p) + 1;
  }

  return p - String /* Length without ending NULL */;
}

/* {{{ MADB_ParseConnString */
my_bool MADB_ParseConnString(MADB_Dsn *Dsn, const char *String, size_t Length, char Delimiter)
{
  char    *Buffer, *Key, *Value;
  my_bool ret;

  if (!String)
    return FALSE;

  if (Length == SQL_NTS)
  {
    Length= ConnStringLength(String, Delimiter);
  }

  Buffer= MADB_ALLOC(Length + 1);
  Buffer= memcpy(Buffer, String, Length + 1);
  Key=    Buffer;

  while (Key && Key < ((char *)Buffer + Length))
  {
    int i= 0;
    if (!(Value= strchr(Key, '=')))
    {
      ret= FALSE;
      break;
    }

    *Value= 0;
    ++Value;
    Key= trim(Key);

    while (DsnKeys[i].DsnKey)
    {
      if (_stricmp(DsnKeys[i].DsnKey, Key) == 0)
      {
        char    *p;
        my_bool special= FALSE;

        if (DsnKeys[i].IsAlias)
        {
          i= DsnKeys[i].DsnOffset; /* For aliases DsnOffset is index of aliased "main" key */
        }

        Value= trim(Value);

        if (Value[0] == '{')
        {
          ++Value;
          if ((p = strchr(Value, '}')))
          {
            *p= 0;
            special= TRUE;
          }
        }
        else if ((p= strchr(Value, Delimiter)))
        {
          *p= 0;
        }
        Value= trim(Value);

        /* Overwriting here - if an option repeated more than once in the string, its last entrance will determine the value */
        if (!MADB_DsnStoreValue(Dsn, i, Value, TRUE))
          return FALSE;
        if (IS_OPTIONS_BITMAP(i))
        {
          MADB_DsnUpdateOptionsFields(Dsn);
        }

        if (p)
        {
          *p= (special) ? ' ' : Delimiter;
        }
        break;
      }
      ++i;
    }
    if ((Key= strchr(Value, Delimiter)))
    {
      ++Key;
    }
  }
  MADB_FREE(Buffer);
  return TRUE;
}
/* }}} */

/* {{{ MADB_ReadConnString */
/* Like ParseConnString, but expands DSN if needed, preserving connection string values precedence.
   Or in other words - it is combination of ReadDsn and ParseConnString */
BOOL MADB_ReadConnString(MADB_Dsn *Dsn, const char *String, size_t Length, char Delimiter)
{
  /* Basically at this point we need DSN name only */
  if (!MADB_ParseConnString(Dsn, String, Length, Delimiter))
  {
    return FALSE;
  }

  /* "If the connection string contains the DRIVER keyword, the driver cannot retrieve information about the data source
     from the system information." https://msdn.microsoft.com/en-us/library/ms715433%28v=vs.85%29.aspx */
  if (Dsn->DSNName && MADB_IS_EMPTY(Dsn->Driver))
  {
    MADB_ReadDSN(Dsn, NULL, FALSE);
    /* This redundancy is needed to be able to reset options set in the DSN, e.g. if DSN has Reconnect option selected, and
       connection string has AUTO_RECONNECT=0. Connection string should have precedence */
    MADB_ParseConnString(Dsn, String, Length, Delimiter);
  }
  return TRUE;
}
/* }}} */

/* {{{ MADB_DsnToString */
SQLULEN MADB_DsnToString(MADB_Dsn *Dsn, char *OutString, SQLULEN OutLength)
{
  int     i=           0;
  SQLULEN TotalLength= 0;
  char    *p=          OutString;
  char    *Value=      NULL;
  char    TmpStr[1024];
  char    IntVal[12];
  int     CpyLength;

  if (OutLength && OutString)
    OutString[0]= '\0';
  
  while (DsnKeys[i].DsnKey)
  {
    Value= NULL;

    if (!DsnKeys[i].IsAlias)
    {
      switch (DsnKeys[i].Type) {
      case DSN_TYPE_STRING:
      case DSN_TYPE_COMBO:
         Value= *GET_FIELD_PTR(Dsn, &DsnKeys[i], char*);
         if (MADB_IS_EMPTY(Value))
         {
           ++i;
           continue;
         }
         break;
      case DSN_TYPE_INT:
        if (*GET_FIELD_PTR(Dsn, &DsnKeys[i], int))
        {
          _snprintf(IntVal, sizeof(IntVal), "%d",*(int *)((char *)Dsn + DsnKeys[i].DsnOffset));
          Value= IntVal;
        }
        break;
      case DSN_TYPE_BOOL:
        if (*GET_FIELD_PTR(Dsn, &DsnKeys[i], my_bool))
        {
          Value= "1";
        }
        break;
      }
    }

    if (Value)
    {
      my_bool isSpecial= (strchr(Value, ' ') ||  strchr(Value, ';') || strchr(Value, '@'));
      CpyLength= _snprintf(TmpStr + TotalLength, 1024 - TotalLength, "%s%s=%s%s%s", (TotalLength) ? ";" : "",
                             DsnKeys[i].DsnKey, isSpecial ? "{" : "", Value, isSpecial ? "}" : "");
      TotalLength+= CpyLength;
    }
    ++i;
  }

  if (OutLength && OutString)
    strncpy_s(OutString, OutLength, TmpStr,  TotalLength);
  return TotalLength;
}
/* }}} */
