
/**
 * @file object_directory.c
 *
 * Tento soubor je soucasti frameworku pro praci s datovymi strukturami spravce objektu.
 * Obsahuje rutiny pro praci s objektem adresare (Directory).
 */

#include <ntddk.h>
#include "version.h"
#include "driver.h"
#include "pushlock.h"
#include "object_header.h"
#include "object.h"
#include "object_directory.h"

// Adresa tela objektu typu reprezentujiciho adresar
static POBJECT_TYPE DirectoryTypeObject = NULL;

// Adresa tela korenoveho adresare
static PVOID RootDirectoryObject = NULL;

// Cislo verze operacniho systemu, na kterem kod aktualne bezi
static ULONG VersionCode = VERSION_UNKNOWN;



/** Vrati adresu tela objektu korenoveho adresare.
 *
 * Nedochazi k zvysovani poctu referenci ciloveho objektu.
 *
 * @return Vraci adresu tela objektu korenoveho adresare. Tato funkce by mela vzdy po
 * uspesne inicializaci modulu uspet.
 */
PVOID DORootDirectory(VOID)
{
  return RootDirectoryObject;
}


/** Vraci adresu tela objektu typu reprezentujiciho adresare.
 *
 * Funkce nijak nemanipuluje s poctem referenci ciloveho objektu.
 *
 * @return Vraci adresu tela objektu typu reprezentujiciho adresare. Po uspesne inicializaci
 * by tato rutina mela vzdy vratit platny vysledek.
 */
POBJECT_TYPE DODirectoryTypeObject(VOID)
{
  return DirectoryTypeObject;
}



/** Vrati adresu pole slotu hashovaci tabulky, ve ktere je pomoci jednosmernych spojovych
 * seznamu ulozen seznam polozek adresare.
 *
 * @param Object Adresa tela objektu adresare.
 *
 * @return Funkce vraci adresu hashovaci tabulky s polozkami adresare. Toto pole obsahuje
 * vzdy 37 polozek. Rutina by mela vzdy uspet.
 */
PDIRECTORY_ENTRY *DOObjectHashBuckets(PVOID Object)
{
  PDIRECTORY_ENTRY *Ret = NULL;
  KdPrint(("object_directory.c: DOObjectHashBuckets(Object=0x%p)\n", Object));

  switch (VersionCode) {
    case VERSION_W2K: Ret = (PDIRECTORY_ENTRY *)DOObjectHashBucketsW2K(Object); break;
    case VERSION_WXP: Ret = (PDIRECTORY_ENTRY *)DOObjectHashBucketsWXP(Object); break;
    case VERSION_W2K3: Ret = (PDIRECTORY_ENTRY *)DOObjectHashBucketsW2K3(Object); break;
    case VERSION_VISTA: Ret = (PDIRECTORY_ENTRY *)DOObjectHashBucketsVista(Object); break;
    case VERSION_W7: Ret = (PDIRECTORY_ENTRY *)DOObjectHashBucketsW7(Object); break;
    default: 
      KdPrint(("object_directory.c: DOObjectHashBuckets(): This version of OS is not supported\n")); 
      break;
  }

  KdPrint(("object_directory.c: DOObjectHashBuckets(-):0x%p\n", Ret));
  return Ret;
}



/** Zamkne objekt adresare pro sdileny pristup.
 *
 * @param DirectoryObject Adresa tela objektu adresare, ktery chce volajici zamknout.
 */
VOID DODirObjectLockShared(PVOID DirectoryObject)
{
  PVOID *Lock = NULL;
  
  switch (VersionCode) {
    case VERSION_WXP: Lock = DOObjectLockWXP(DirectoryObject); break;
    case VERSION_W2K3: Lock = DOObjectLockW2K3(DirectoryObject); break;
    case VERSION_VISTA: Lock = DOObjectLockVista(DirectoryObject); break;
    case VERSION_W7: Lock = DOObjectLockW7(DirectoryObject); break;
  }

  if (Lock != NULL)
    PushLockAcquireShared(Lock);


  return;
}


/** odemkne objekt adresare zamceny pro sdileny pristup.
 *
 * @param DirectoryObject Adresa tela objektu adresare, ktery chce volajici odemknout.
 */
VOID DODirObjectUnlockShared(PVOID DirectoryObject)
{
  PVOID *Lock = NULL;
  
  switch (VersionCode) {
    case VERSION_WXP: Lock = DOObjectLockWXP(DirectoryObject); break;
    case VERSION_W2K3: Lock = DOObjectLockW2K3(DirectoryObject); break;
    case VERSION_VISTA: Lock = DOObjectLockVista(DirectoryObject); break;
    case VERSION_W7: Lock = DOObjectLockW7(DirectoryObject); break;
  }

  if (Lock != NULL)
    PushLockReleaseShared(Lock);

  return;
}


/** Zamkne objekt adresare pro exkluzivni pristup.
 *
 * @param DirectoryObject Adresa tela objektu adresare, ktery chce volajici zamknout.
 */
VOID DODirObjectLockExclusive(PVOID DirectoryObject)
{
  PVOID *Lock = NULL;
  
  switch (VersionCode) {
    case VERSION_WXP: Lock = DOObjectLockWXP(DirectoryObject); break;
    case VERSION_W2K3: Lock = DOObjectLockW2K3(DirectoryObject); break;
    case VERSION_VISTA: Lock = DOObjectLockVista(DirectoryObject); break;
    case VERSION_W7: Lock = DOObjectLockW7(DirectoryObject); break;
  }

  if (Lock != NULL)
    PushLockAcquireExclusive(Lock);

  return;
}


/** Odemkne objekt adresare pro exkluzivni pristup.
 *
 * @param DirectoryObject Adresa tela objektu adresare, ktery chce volajici odemknout.
 */
VOID DODirObjectUnlockExclusive(PVOID DirectoryObject)
{
  PVOID *Lock = NULL;
  
  switch (VersionCode) {
    case VERSION_WXP: Lock = DOObjectLockWXP(DirectoryObject); break;
    case VERSION_W2K3: Lock = DOObjectLockW2K3(DirectoryObject); break;
    case VERSION_VISTA: Lock = DOObjectLockVista(DirectoryObject); break;
    case VERSION_W7: Lock = DOObjectLockW7(DirectoryObject); break;
  }

  if (Lock != NULL)
    PushLockReleaseExclusive(Lock);

  return;
}



/** Zjisti, zda je cilovy objekt adresar.
 *
 * Funkce tuto skutecnost zjistuje tak, ze porovna adresu tela typu ciloveho objektu s
 * adresou tela typu objektu adresare zjistenou pri inicializaci modulu.
 *
 * @param Object Objekt, ktery si volajici preje otestovat.
 *
 * @return Funkce vraci TRUE, pokud je cilovy objekt adresar. V opacnem pripade vraci FALSE.
 */
BOOLEAN DOIsDirectory(PVOID Object)
{
  BOOLEAN Ret = FALSE;
  PVOID ObjectType = NULL;
  PVOID ObjectHeader = NULL;
  KdPrint(("DOIsDirectory(Object=0x%p)\n", Object));

  ObjectHeader = OHGetObjectHeader(Object);
  if (Object != NULL) {
    ObjectType = OHGetObjectType(ObjectHeader);
	Ret = (ObjectType != NULL && ObjectType == DirectoryTypeObject);
  }

  KdPrint(("DOIsDirectory(-):%u\n", Ret));
  return Ret;
}



/** Zjisti jmena vsech polozek adresare a zapise je ve forme Unicode retezcu do vystupniho
 * bufferu.
 *
 * Format vystupniho bufferu je tvaru>
 * Jmeno1#0#Jmeno2#0#...#0#JmenoN#0#
 * #0# reprezentuje nulovy znak.
 *
 * Rutina pri sve praci zamyka cilovy adresar pro sdileni.
 *
 * @param Object Adresar, jehoz obsah si volajici preje znat.
 * @param Buffer Adresa promenne, ktera v pripade uspechu obdrzi adresu bufferu se
 * jmeny polozek adresare.
 * @param BufferLength Delka bufferu se jmeny polozek adresare v bajtech.
 *
 * @return Vraci hodnotu NTSTATUS indikujici uspech ci neuspech operace. V pripade neuspechu
 * je adresa bufferu nastavena na NULL a jeho delka na 0 bajtu.
 */
NTSTATUS DOGetDirectoryInfo(PVOID Object, PWCHAR *Buffer, PULONG32 BufferLength)
{
  LONG i = 0;
  PVOID NameInfo = NULL;
  PVOID Obj = NULL;
  PVOID ObjHdr = NULL;
  PDIRECTORY_ENTRY Bucket = NULL;
  PDIRECTORY_ENTRY *Buckets = NULL;
  PWCHAR Tmp = NULL;
  UNICODE_STRING ObjectName;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("object_directory.c: DOGetDirectoryInfo(Object=0x%p; Buffer=0x%p; BufferLength=0x%p)\n", Object, Buffer, BufferLength));

  *Buffer = NULL;
  *BufferLength = 0;
  Status = STATUS_SUCCESS;
  Buckets = DOObjectHashBuckets(Object);
  if (Buckets != NULL) {
    DODirObjectLockShared(Object);
	for (i = 0; i < 37; ++i) {
	  Bucket = Buckets[i];
	  while (Bucket != NULL && NT_SUCCESS(Status)) {
        Obj = Bucket->Object;
		Status = OBGetObjectName(Obj, &ObjectName);
        if (NT_SUCCESS(Status)) {
          Tmp = (PWCHAR)ExAllocatePoolWithTag(PagedPool, *BufferLength + ObjectName.Length + sizeof(WCHAR), POOL_TAG);
		  if (Tmp != NULL) {
            RtlZeroMemory(Tmp, *BufferLength + ObjectName.Length + sizeof(WCHAR));
            if (*Buffer != NULL)
              RtlCopyMemory(Tmp, *Buffer, *BufferLength);

            RtlCopyMemory(&Tmp[*BufferLength / sizeof(WCHAR)], ObjectName.Buffer, ObjectName.Length);
            *BufferLength += ObjectName.Length + sizeof(WCHAR);
            if (*Buffer != NULL)
              ExFreePoolWithTag(*Buffer, POOL_TAG);

            *Buffer = Tmp;
          } else Status = STATUS_INSUFFICIENT_RESOURCES;
		
		  ExFreePoolWithTag(ObjectName.Buffer, POOL_TAG);
		}

        Bucket = Bucket->Next;
	  }

      if (!NT_SUCCESS(Status)) {
        if (*Buffer != NULL)
          ExFreePoolWithTag(*Buffer, POOL_TAG);

        *Buffer = NULL;
        *BufferLength = 0;
        break;
	  }
	}

    DODirObjectUnlockShared(Object);
  } else Status = STATUS_UNSUCCESSFUL;

  KdPrint(("object_directory.c: DOGetDirectoryInfo(-):0x%x, *BufferLength=%u\n", Status, *BufferLength));
  return Status;
}



/** Zjisti adresu korenoveho adresare a zapise ji do promenne RootDirectoryObject.
 *
 * Tato rutina predpoklada, ze ovladac byl nacten bud pomoci SCM ci primo rutinou
 * NtLoadDriver, a tudiz ma vlastni struktru DRIVER_OBJECT, ktera je obalena objektem
 * lezicim v adresari \Driver. Rutina z tohoto objektu ovladace projde az na korenovy adresar,
 * zvysi jeho pocet referenci a zapise adresu jeho tela do promenne RootdirectoryObject.
 *
 * @param DriverObject Adresa struktury DRIVER_OBJECT reprezentujici aktualni ovladac.
 * 
 * @return Vraci hodnotu NTSTATUS indikujici uspech ci neuspech operace.
 */
static NTSTATUS DOFindRootDirectory(PDRIVER_OBJECT DriverObject)
{
  PVOID Tmp = NULL;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("object_directory.c: DOFindRootDirectory(DriverObject=0x%p)\n", DriverObject));

  Tmp = OHGetObjectHeader(DriverObject);
  if (Tmp != NULL) {
    KdPrint(("object_directory.c DOFindRootDirectory(): \\Driver\\objview header 0x%p\n", Tmp));
    Tmp = OHGetInfoHeader(Tmp, OH_HEADER_NAME_INFO);
	if (Tmp != NULL) {
      KdPrint(("object_directory.c DOFindRootDirectory(): \\Driver\\objview name subheader 0x%p\n", Tmp));
	  Tmp = OHNameInfoGetDirectory(Tmp);
	  if (Tmp != NULL) {
        KdPrint(("object_directory.c DOFindRootDirectory(): \\Driver directory object 0x%p\n", Tmp));
	    Tmp = OHGetObjectHeader(Tmp);
		if (Tmp != NULL) {
          KdPrint(("object_directory.c DOFindRootDirectory(): \\Driver directory header 0x%p\n", Tmp));
		  Tmp = OHGetInfoHeader(Tmp, OH_HEADER_NAME_INFO);
		  if (Tmp != NULL) {
            KdPrint(("object_directory.c DOFindRootDirectory(): \\Driver directory name subheader 0x%p\n", Tmp));
		    RootDirectoryObject = OHNameInfoGetDirectory(Tmp);
			ObReferenceObject(RootDirectoryObject);
            KdPrint(("object_directory.c DOFindRootDirectory(): \\ Directory object (Root) 0x%p\n", RootDirectoryObject));
			Status = STATUS_SUCCESS;
		  }
		}
	  }
	}
  }

  KdPrint(("object_directory.c: DOFindRootDirectory(-):0x%x\n", Status));
  return Status;
}


/** Zjisti adresu objektu typu reprezentujiciho adresare a zapise adresu jeho tela do
 * promenne DirectoryObjectType.
 *
 * Rutina predoklada, ze promenna RootDirectoryObject jiz obsahuje platnou adresu tela
 * objektu korenoveho adresare. 
 *
 * Pocet referenci na objekt typu adresare je zvysen o jednicku.
 *
 * @return Vraci hodnotu NTSTATUS indikujici uspech ci neuspech operace.
 */
static NTSTATUS DOFindDirectoryTypeObject(VOID)
{
  PVOID Tmp;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("object_directory.c: DOFindDirectoryTypeObject()\n"));

  Tmp = OHGetObjectHeader(RootDirectoryObject);
  if (Tmp != NULL) {
    Tmp = OHGetObjectType(Tmp);
	if (Tmp != NULL)
      ObReferenceObject(Tmp);
      DirectoryTypeObject = Tmp;
      Status = STATUS_SUCCESS;
  }

  KdPrint(("object_directory.c: DOFindDirectoryTypeObject(-):0x%x\n", Status));
  return Status;
}


/** Inicializuje modul pro praci s objekty adresaru.
 *
 * Rutina zjisti cislo aktualni verze operacniho systemu a najde adresy tel korenoveho
 * adresare a objektu typu reprezentujiciho adresare.
 *
 * @param DriverObject Adresa struktury DRIVER_OBJECT reprezentujici aktualne bezici
 * ovladac.
 *
 * @return Rutina vraci kod NTSTATUS indikujici uspech ci neuspech operace.
 */
NTSTATUS DirObjectInit(PDRIVER_OBJECT DriverObject)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("object_directory.c: DirObjectInit(DriverObject=0x%p)\n", DriverObject));

  VersionCode = GetVersionCode();
  if (VersionCode != VERSION_UNKNOWN) {
    Status = DOFindRootDirectory(DriverObject);
    if (NT_SUCCESS(Status)) {
      Status = DOFindDirectoryTypeObject();
	  if (!NT_SUCCESS(Status))
        ObDereferenceObject(RootDirectoryObject);
    }
  } else Status = STATUS_NOT_SUPPORTED;
  
  KdPrint(("object_directory.c: DirObjectInit(-):0x%x\n", Status));
  return Status;
}


/** Provede uklid po modulu pracujicim s objekty adresaru.
 *
 * Rutina snizi pocet referenci na korenovy adresar a objekt typu reprezentujici adresare
 * o jednicku a do promennych RootDirectoryObject a DirectoryObjectType, jenz na tyto 
 * objekty ukazuji, dosadi hodnotu NULL.
 */
VOID DirObjectFinit(VOID)
{
  KdPrint(("object_directory.c: DirObjectFinit()\n"));

  ObDereferenceObject(DirectoryTypeObject);
  DirectoryTypeObject = NULL;

  ObDereferenceObject(RootDirectoryObject);
  RootDirectoryObject = NULL;

  KdPrint(("object_directory.c: DirObjectFinit(-)\n"));
  return;
}
