/* lookupAccountName.cpp
 *
 * Copyright (c) 2008, Charles S. Wilson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <windows.h>
#include <tchar.h>
#include <iostream>
#include "Win32Error.h"
#include "lookupAccountName.h"


/* LookupAccountName:  only on 2kPro/XP/Vista/Server2k/Server2003/Server2008 */
/* LookupAccountSid:   only on 2kPro/XP/Vista/Server2k/Server2003/Server2008 */
/* CreateWellKnownSid: only on XP/Vista/Server2003/Server2008                */

/* not declared by mingw/cygwin w32api headers */
#ifndef SECURITY_MAX_SID_SIZE
#define SECURITY_MAX_SID_SIZE 68
#endif

/* not declared by mingw.org w32api headers */
#ifndef PRODUCT_UNLICENSED
BOOL WINAPI CreateWellKnownSid(
  WELL_KNOWN_SID_TYPE WellKnownSidType,
  PSID DomainSid,
  PSID pSid,
  DWORD* cbSid
);
#endif


/* returns pointer to appropriate function on success, NULL otherwise */
FARPROC getProcAddress(LPCTSTR lpModuleName, LPCSTR lpProcName)
{
  HMODULE hModule = NULL;
  FARPROC fpProcAddress = NULL;

  hModule = ::GetModuleHandle(lpModuleName);
  if (!hModule)
  {
    hModule = ::LoadLibrary(lpModuleName);
    if (!hModule) return NULL;
  }
  fpProcAddress = ::GetProcAddress(hModule, lpProcName);
  return fpProcAddress;
}

typedef BOOL(WINAPI *PFN_CreateWellKnownSid)(WELL_KNOWN_SID_TYPE, PSID, PSID, DWORD*);\
static PFN_CreateWellKnownSid pfnCreateWellKnownSid = NULL;

typedef BOOL(WINAPI *PFN_LookupAccountName)(LPCTSTR, LPCTSTR, PSID, LPDWORD, LPTSTR, LPDWORD, PSID_NAME_USE);
static PFN_LookupAccountName pfnLookupAccountName = NULL;

typedef BOOL(WINAPI *PFN_LookupAccountSid)(LPCTSTR, PSID, LPTSTR, LPDWORD, LPTSTR, LPDWORD, PSID_NAME_USE);
static PFN_LookupAccountSid pfnLookupAccountSid = NULL;

static BOOL pfnERROR = TRUE;
/* returns FALSE on success, TRUE otherwise */
BOOL initPFN(void)
{
  if (pfnERROR)
  {
    pfnCreateWellKnownSid = (PFN_CreateWellKnownSid) getProcAddress(TEXT("advapi32"),"CreateWellKnownSid");
    pfnLookupAccountName  = (PFN_LookupAccountName)  getProcAddress(TEXT("advapi32"),"LookupAccountNameA");
    pfnLookupAccountSid   = (PFN_LookupAccountSid)   getProcAddress(TEXT("advapi32"),"LookupAccountSidA");
    if (pfnCreateWellKnownSid && pfnLookupAccountName && pfnLookupAccountSid)
    {
      pfnERROR = FALSE;
    }
  }
  return pfnERROR;
}

/* returns FALSE on success, TRUE otherwise (check GetLastError) */
/* caller's responsibility to free *accountName            */
BOOL lookupWellKnownAccountName(LPTSTR *accountName, WELL_KNOWN_SID_TYPE desiredSidType)
{
  TCHAR        computerName[MAX_COMPUTERNAME_LENGTH+1];
  PSID         NewSid = NULL;
  PSID         Sid = NULL;
  DWORD        cbSid = 0;
  LPTSTR       ReferencedDomainName = NULL;
  DWORD        cbReferencedDomainName = 0;
  DWORD        dwSize, dwResult = 0;
  SID_NAME_USE SidType;

  BOOL retErr = FALSE;
  if (accountName == NULL)
  {
    SetLastError(ERROR_INVALID_ADDRESS);
    return 1;
  }
  if (initPFN())
  {
    return 1;
  }

  /* Obtain computer name (Kernel32.lib) */
  dwSize = MAX_COMPUTERNAME_LENGTH+1;
  if( !GetComputerName(computerName, &dwSize) )
  {
    retErr = TRUE;
    goto cleanup;
  }

  /* Obtain computer SID (Advapi32.lib) */
  /* 1st call: Determine string sizes, without error handling */
  (*pfnLookupAccountName)(
    NULL,                   /* __in_opt Machine to lookup account on. */
    computerName,           /* __in Address of string for account name. */
    NULL,                   /* __out_opt Address of security identifier. */
    &cbSid,                 /* __inout Address of size (bytes) of security identifier. */
    NULL,                   /* __out_opt Address of string for referenced domain. */
    &cbReferencedDomainName,/* __inout Address of size (TCHARs) of domain string. */
    &SidType                /* __out Address of SID-type indicator. */
  );

  Sid = (PSID)malloc( cbSid );
  ReferencedDomainName = (LPTSTR)malloc( cbReferencedDomainName * sizeof(TCHAR) );

  /* 2nd call: Read strings */
  if( ! (*pfnLookupAccountName)(
         NULL,                    /* __in_opt Machine to lookup account on. */
         computerName,            /* __in Address of string for account name. */
         Sid,                     /* __out_opt Address of security identifier. */
         &cbSid,                  /* __inout Address of size (bytes) of security identifier. */
         ReferencedDomainName,    /* __out_opt Address of string for referenced domain. */
         &cbReferencedDomainName, /* __inout Address of size (TCHARs) of domain string. */
         &SidType                 /* __out Address of SID-type indicator. */
  ))
  {
    retErr = TRUE;
    goto cleanup;
  }

  /* Should never fail: Computer SID must be of type "SidTypeDomain" */
  if( SidType != SidTypeDomain )
  {
    retErr = TRUE;
    goto cleanup;
  }

  /* Create Guest SID (well-known) (Advapi32.lib) */
  /* Following code taken from MSDN "CreateWellKnownSid" example */
  cbSid = SECURITY_MAX_SID_SIZE;
  /* Allocate enough memory for the largest possible SID. */
  if( !(NewSid = LocalAlloc(LMEM_FIXED, cbSid)) )
  {
    retErr = TRUE;
    goto cleanup;
  }

  if( ! (*pfnCreateWellKnownSid)(
         desiredSidType,     /* __in WELL_KNOWN_SID_TYPE member*/
         Sid,                /* __in_opt DomainSid, identifies domain control */
         NewSid,             /* __out_opt pSid, output variable */
         &cbSid              /* __inout cbSid, no. of bytes available for pSid */
  ))
  {
    retErr = TRUE;
    goto cleanup;
  }

  /* Obtain Guest account name (Advapi32.lib) */
  free((void *)ReferencedDomainName);
  ReferencedDomainName = 0;

  /* 1st call: Determine string sizes, without error handling */
  cbReferencedDomainName = 0;
  dwSize = 0;

  (*pfnLookupAccountSid)(
    NULL,                    /* __in_opt Machine to lookup account on. */
    NewSid,                  /* __in Address of security identifier. */
    NULL,                    /* __out_opt Address of string for account name. */
    &dwSize,                 /* __inout Address of size of security identifier. */
    NULL,                    /* __out_opt Address of string for referenced domain. */
    &cbReferencedDomainName, /* __inout Address of size of domain string. */
    &SidType                 /* __out Address of SID-type indicator. */
  );

  *accountName = (LPTSTR)malloc( dwSize * sizeof(TCHAR) );
  ReferencedDomainName = (LPSTR)malloc( cbReferencedDomainName * sizeof(TCHAR) );

  /* 2nd call: Read strings */
  if( ! (*pfnLookupAccountSid)(
         NULL,                    /* __in_opt Machine to lookup account on. */
         NewSid,                  /* __in Address of security identifier. */
         *accountName,            /* __out_opt Address of string for account name. */
         &dwSize,                 /* __inout Address of size of security identifier. */
         ReferencedDomainName,    /* __out_opt Address of string for referenced domain. */
         &cbReferencedDomainName, /* __inout Address of size of domain string. */
         &SidType                 /* __out Address of SID-type indicator. */
  ))
  {
    retErr = TRUE;
    goto cleanup;
  }

cleanup:
  if (NewSid)
    LocalFree( NewSid );
  if (Sid)
    free((void *)Sid);
  if (ReferencedDomainName)
    free((void *)ReferencedDomainName);

  return retErr;
}

