/* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.

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; version 2 of the License.

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, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */


#include "sql_class.h"
#include <AclAPI.h>
#include <accctrl.h>
#include <errno.h>

#include "my_config.h"
#include "my_sys.h"
#include "mysqld_error.h"
#include "m_string.h"
#include "log.h"
#include "named_pipe.h"

bool is_existing_windows_group_name(const char *group_name)
{
  // First, let's get a SID for the given group name...
  BYTE soughtSID[SECURITY_MAX_SID_SIZE]= { 0 };
  DWORD size_sid= SECURITY_MAX_SID_SIZE;
  char referencedDomainName[MAX_PATH];
  DWORD size_referencedDomainName= MAX_PATH;
  SID_NAME_USE sid_name_use;

  if (!LookupAccountName(nullptr, group_name, soughtSID, &size_sid,
    referencedDomainName, &size_referencedDomainName,
    &sid_name_use))
  {
    return false;
  }

  // sid_name_use is SidTypeAlias when group_name is a local group
  if (sid_name_use != SidTypeAlias && sid_name_use != SidTypeWellKnownGroup)
  {
    return false;
  }
  return true;
}

/*
return false on successfully checking group, true on error.
*/
static bool check_windows_group_for_everyone(const char *group_name,
                                             bool *is_everyone_group)
{
  *is_everyone_group= false;
  if (!group_name || group_name[0] == '\0')
  {
    return false;
  }

  if (strcmp(group_name, DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP) == 0)
  {
    *is_everyone_group= true;
    return false;
  }
  else
  {
    TCHAR last_error_msg[256];
    // First, let's get a SID for the given group name...
    BYTE soughtSID[SECURITY_MAX_SID_SIZE]= {0};
    DWORD size_sought_sid= SECURITY_MAX_SID_SIZE;
    BYTE worldSID[SECURITY_MAX_SID_SIZE]= {0};
    DWORD size_world_sid= SECURITY_MAX_SID_SIZE;
    char referencedDomainName[MAX_PATH];
    DWORD size_referencedDomainName= MAX_PATH;
    SID_NAME_USE sid_name_use;

    if (!LookupAccountName(NULL, group_name, soughtSID, &size_sought_sid,
                         referencedDomainName, &size_referencedDomainName,
                         &sid_name_use))
    {
      return false;
    }

    if (!CreateWellKnownSid(WinWorldSid, NULL, worldSID, &size_world_sid))
    {
      DWORD last_error_num= GetLastError();
      FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                    NULL, last_error_num,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
                    sizeof(last_error_msg) / sizeof(TCHAR), NULL);
      my_printf_error(ER_UNKNOWN_ERROR,
                      "check_windows_group_for_everyone, CreateWellKnownSid failed: %s",
                      MYF(0), last_error_msg);
      return true;
    }

    *is_everyone_group= EqualSid(soughtSID, worldSID);
    return false;
  }
}

bool is_valid_named_pipe_full_access_group(const char *group_name)
{
  if (!group_name || group_name[0] == '\0'){
    return true;
  }

  bool is_everyone_group= false;

  if(check_windows_group_for_everyone(group_name, &is_everyone_group)){
    return false;
  }

  if (is_everyone_group || is_existing_windows_group_name(group_name))
  {
    return true;
  }
  return false;
}

// return false on success, true on failure.
bool my_security_attr_add_rights_to_group(SECURITY_ATTRIBUTES *psa,
  const char *group_name,
  DWORD group_rights)
{
  TCHAR last_error_msg[256];
  // First, let's get a SID for the given group name...
  BYTE soughtSID[SECURITY_MAX_SID_SIZE]= { 0 };
  DWORD size_sid= SECURITY_MAX_SID_SIZE;
  char referencedDomainName[MAX_PATH];
  DWORD size_referencedDomainName= MAX_PATH;
  SID_NAME_USE sid_name_use;

  bool is_everyone_group= false;

  if(check_windows_group_for_everyone(group_name, &is_everyone_group)){
    return true;
  }

  if (is_everyone_group)
  {
    sql_print_warning(ER_DEFAULT(WARN_NAMED_PIPE_ACCESS_EVERYONE), group_name);
    if (current_thd)
    {
      push_warning_printf(current_thd, Sql_condition::SL_WARNING,
        WARN_NAMED_PIPE_ACCESS_EVERYONE,
        ER(WARN_NAMED_PIPE_ACCESS_EVERYONE), group_name);
    }
  }

  // Treat the DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP value
  // as a special case: we  convert it to the "world" SID
  if (strcmp(group_name, DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP) == 0)
  {
    if (!CreateWellKnownSid(WinWorldSid, NULL, soughtSID, &size_sid))
    {
      DWORD last_error_num= GetLastError();
      FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                    NULL, last_error_num,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
                    sizeof(last_error_msg) / sizeof(TCHAR), NULL);
      my_printf_error(ER_UNKNOWN_ERROR,
                      "my_security_attr_add_rights_to_group, CreateWellKnownSid failed: %s",
                      MYF(0), last_error_msg);
      return true;
    }
  }
  else
  {
    if (!LookupAccountName(NULL, group_name, soughtSID, &size_sid,
                           referencedDomainName, &size_referencedDomainName,
                           &sid_name_use))
    {
      DWORD last_error_num= GetLastError();
      FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                    NULL, last_error_num,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
                    sizeof(last_error_msg) / sizeof(TCHAR), NULL);
      sql_print_error("my_security_attr_add_rights_to_group, LookupAccountName failed: %s",
                      last_error_msg);
      return true;
    }

    // sid_name_use is SidTypeAlias when group_name is a local group
    if (sid_name_use != SidTypeAlias && sid_name_use != SidTypeWellKnownGroup)
    {
      sql_print_error("LookupAccountName failed: unexpected sid_name_use");
      return true;
    }
  }

  PACL pNewDACL= nullptr;
  PACL pOldDACL= nullptr;
  BOOL dacl_present_in_descriptor= FALSE;
  BOOL dacl_defaulted= FALSE;
  if (!GetSecurityDescriptorDacl(psa->lpSecurityDescriptor,
    &dacl_present_in_descriptor, &pOldDACL,
    &dacl_defaulted) ||
    !dacl_present_in_descriptor)
  {
    DWORD last_error_num= GetLastError();
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
      NULL, last_error_num,
      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
      sizeof(last_error_msg) / sizeof(TCHAR), NULL);
    sql_print_error("GetSecurityDescriptorDacl failed: %s", last_error_msg);
    return true;
  }

  // Just because GetSecurityDescriptorDacl succeeded doesn't mean we're out of
  // the woods: a NULL value for pOldDACL is a bad/unexpected thing, as is
  // dacl_defaulted == TRUE
  if (pOldDACL == nullptr || dacl_defaulted)
  {
    sql_print_error("Invalid DACL on named pipe: %s",
      (pOldDACL == nullptr) ? "NULL DACL" : "Defaulted DACL");
    return true;
  }

  EXPLICIT_ACCESS ea;
  // Initialize an EXPLICIT_ACCESS structure for the new ACE.

  ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
  ea.grfAccessPermissions= group_rights;
  ea.grfAccessMode= SET_ACCESS;
  ea.grfInheritance= NO_INHERITANCE;
  ea.Trustee.TrusteeForm= TRUSTEE_IS_SID;
  ea.Trustee.ptstrName= (LPSTR)soughtSID;

  // Create a new ACL that merges the new ACE
  // into the existing DACL.
  DWORD dwRes= SetEntriesInAcl(1, &ea, pOldDACL, &pNewDACL);
  if (ERROR_SUCCESS != dwRes)
  {
    char num_buff[20];
    int10_to_str(dwRes, num_buff, 10);
    sql_print_error("SetEntriesInAcl to add group permissions failed: %s", num_buff);
    return true;
  }

  // Apply the new DACL to the existing security descriptor...
  if (!SetSecurityDescriptorDacl(psa->lpSecurityDescriptor, TRUE, pNewDACL,
    FALSE))
  {
    DWORD last_error_num= GetLastError();
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
      NULL, last_error_num,
      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
      sizeof(last_error_msg) / sizeof(TCHAR), NULL);
    sql_print_error("SetSecurityDescriptorDacl failed: %s", last_error_msg);
    return true;
  }

  return false;
}

/**
Creates an instance of a named pipe and returns a handle.

@param sec_attr    Security attributes for the pipe.
@param buffer_size Number of bytes to reserve for input and output buffers.
@param name        The name of the pipe.
@param name_buf    Output argument: null-terminated concatenation of
"\\.\pipe\" and name.
@param buflen      The size of name_buff.

@returns           Pipe handle, or INVALID_HANDLE_VALUE in case of error.

@note  The entire pipe name string can be up to 256 characters long.
Pipe names are not case sensitive.
*/
HANDLE create_server_named_pipe(SECURITY_ATTRIBUTES **ppsec_attr,
  DWORD buffer_size, const char *name,
  char *name_buf, size_t buflen,
  const char *full_access_group_name)
{
  HANDLE ret_handle= INVALID_HANDLE_VALUE;
  TCHAR last_error_msg[256];

  strxnmov(name_buf, buflen - 1, "\\\\.\\pipe\\", name, NullS);
  const char *perror= nullptr;
  // Set up security for the named pipe to provide full access to the owner
  // and minimal read/write access to others.
  if (my_security_attr_create(ppsec_attr, &perror, NAMED_PIPE_OWNER_PERMISSIONS,
    NAMED_PIPE_EVERYONE_PERMISSIONS) != 0)
  {
    sql_print_error("my_security_attr_create failed: %s", perror);
    return ret_handle;
  }

  if (full_access_group_name && full_access_group_name[0] != '\0')
  {
    if (my_security_attr_add_rights_to_group(
      *ppsec_attr, full_access_group_name,
      NAMED_PIPE_FULL_ACCESS_GROUP_PERMISSIONS))
    {
      return ret_handle;
    }
  }

  ret_handle= CreateNamedPipe(
    name_buf,
    PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
    FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC,
    PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES,
    buffer_size, buffer_size, NMPWAIT_USE_DEFAULT_WAIT, *ppsec_attr);

  if (ret_handle == INVALID_HANDLE_VALUE)
  {
    DWORD last_error_num= GetLastError();

    if (last_error_num == ERROR_ACCESS_DENIED)
    {
      my_printf_error(ER_CANT_START_SERVER_NAMED_PIPE,
        ER_DEFAULT(ER_CANT_START_SERVER_NAMED_PIPE),
        MYF(ME_FATALERROR), name);
    }
    else
    {
      FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_MAX_WIDTH_MASK,
        NULL, last_error_num,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
        sizeof(last_error_msg) / sizeof(TCHAR), NULL);
      char num_buff[20];
      int10_to_str(last_error_num, num_buff, 10);
      sql_print_error("Can't start server : %s %s", last_error_msg, num_buff);
    }
  }

  return ret_handle;
}
