/*
 * dump novell nds database from binarie backup files
 *
 * by w.j.hengeveld  jun 1996
 *    itsme@xs4all.nl
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <share.h>
#include <dir.h>

typedef unsigned long uint32;
typedef unsigned int  uint16;
typedef unsigned char uint8;
typedef unsigned int  unicode;
typedef unsigned int  bool;

#define TRUE (0==0)
#define FALSE (!TRUE)

bool fVerbose=FALSE;

#define MIN(a,b)    ((a)>(b)?(b):(a))
#define MAX(a,b)    ((a)<(b)?(b):(a))

#define NB_LIST_END             -1L
#define NB_INVALID_POSITION     -2L

typedef struct TIMESTAMP {
        uint32 seconds;
        uint16 replicaNumber,
               event;
} TIMESTAMP;   // size=8

#define MAX_RDN_CHARS 128
//
//   entry->firstChild->parentID == entry->id
//   entry->firstChild->sibling-> ... ->sibling == entry->lastChild
//   entry->subordinateCount == # siblings from first ... last Child
//   entry->firstAttr->entryID == node
//   entry->nextFree!=-2  <==> entry->flags==0
//
//   entry->nextName     use is unclear
//   entry->self   == node
//   entry->classID  : type van dit ding.  ?? verband met value->attrID
//
typedef struct ENTRY
{
/* private, initialized by record manager */
        uint32          self,          // -> offset in entry            // 00
                        checkSum,
                        nextFree,      // -> offset in entry
                        nextName;      // -> offset in entry
        uint32          sibling,       // -> offset in entry            // 10
                        firstChild,    // -> offset in entry
                        lastChild,     // -> offset in entry
                        firstAttr;     // -> offset in value
        uint32          id;            //  ID in entry                  // 20

/* public, must be initialized before inserts */
        uint32          partitionID,   //  ID in partition
                        parentID,      //  ID in entry
                        flags;
        uint32          unused,                                         // 30
                        subordinateCount,
                        classID;       //  ID in entry
        TIMESTAMP       creationTime,
                        modificationTime;                               // 40
        unicode         name[MAX_RDN_CHARS + 1];
} ENTRY;   // size = 19*4+129*2 = 334
/* entry flags */
#define EF_PRESENT              0x0001
#define EF_PARTITIONROOT        0x0004
#define EF_AUDITED              0x0010
#define EF_BACKLINKED           0x0020
#define EF_ALIAS                0x0040
#define EF_NEVER_SKULKED        0x0080

#define ENTRY_INDEX_MASK        0x00ffffffL

/* entry IDs that have no corresponding entry record, internal use only */
#define ID_MIN_RESERVED         0xff000000L
#define ID_PUBLIC               0xff000001L
#define ID_INHERITANCE_MASK     0xff000002L
#define ID_CREATOR              0xff000003L
#define ID_SELF                 0xff000004L
#define A_ENTRY_RIGHTS          0xff000005L
#define A_SMS_RIGHTS            0xff000006L
#define A_ALL_ATTRS_RIGHTS      0xff000007L
#define A_REMOTE_ID_LIST        0xff000008L
// 9
// a
// b
#define A_SCHEMAVALUE           0xff00000cL
// d
#define A_PASSWORD              0xff00000eL
// f
#define A_TREE_NAME             0xff000010L
#define ID_ROOT_TEMPLATE        0xff000011L
#define A_MONITORED_CONNECTION	0xff000012L

char *specialEntryIDs[]={
   "ID_MIN_RESERVED", "ID_PUBLIC", "ID_INHERITANCE_MASK", "ID_CREATOR",
   "ID_SELF", "A_ENTRY_RIGHTS", "A_SMS_RIGHTS", "A_ALL_ATTRS_RIGHTS",
   "A_REMOTE_ID_LIST", "__9__", "__a__", "__b__", "A_SCHEMAVALUE",
   "__d__", "A_PASSWORD", "__f__", "A_TREE_NAME", "ID_ROOT_TEMPLATE",
   "A_MONITORED_CONNECTION", NULL
};

//    ==0xffffffff : end of list
//    ==0xfffffffe : N/A
//    <  0x1000000 : offset in file
//    < 0xff000000 : id
//    else         : ?
//
// value->self == value
// value->nextFree!=-2  <==> value->flags==0
// value->nextValue  : lijst van objecten van het zelfde attrID ??
// value->nextAttr   : lijst van attributen
// value->attrID     : -> [Class ...] in ENTRY
// value->firstBlock : when more than 16 bytes are used
// value->entryID    : object to which this attribute belongs
//
typedef struct VALUE
{
/* private, initialized by record manager */
        uint32          self,           // offset in VALUE.NDS
                        checkSum,
                        nextFree,       // offset in VALUE.NDS
                        nextValue;      // offset in VALUE.NDS
        uint32          nextAttr;       /* nextAttr's attrID guaranteed > attrID */
        uint32          firstBlock;     // offset in BLOCK.NDS

/* public, must be initialized before inserts */
        uint32          entryID,
                        attrID,
                        flags;
        TIMESTAMP       timeStamp;
        uint32          length;
        uint8           data[64 - 10 * sizeof(uint32) - sizeof(TIMESTAMP)];
/* when in RAM data may be longer if value is "whole" */
} VALUE;   // size=12*4+64-10*4-8  = 64
/* VALUE and AVA flags */
#define VF_NAMING                   0x0001
#define VF_BASECLASS                0x0002
#define VF_VALUELESS                0x0004
#define VF_PRESENT                  0x0008
#define VF_ATTRMUSTBEPRESENT        0x0010  /* only used in AVA */
#define VF_ATTRMUSTNOTBEPRESENT     0x0020  /* only used in AVA */
#define VF_EQUAL                    0x0040  /* only used in AVA */
#define VF_GREATER                  0x0080  /* only used in AVA */
#define VF_LESS                     0x0100  /* only used in AVA */
#define VF_WILD                     0x0200  /* only used in AVA */
#define VF_APPROX                   0x0400  /* only used in AVA */
#define VF_ALLOCATED                0x0800  /* AVA data has been allocated */
#define VF_PURGED                   0x1000
#define VF_DONTAUDIT                0x2000
#define VF_OVERWRITE                0x4000
#define VF_PRESENT_NOT_REQUIRED     0x8000
#define VF_SLOW_SYNC                0x10000 /* only used in AVA */

/* only these bits should be set in the files */
#define VF_VALUEMASK                (VF_NAMING | VF_BASECLASS | VF_VALUELESS \
                                     | VF_PRESENT | VF_PURGED)

// block->self == block
// block->nextFree!=-2  <==> block->flags==0
// block->next   == ptr to next 112 bytes of data
// block->valuePosition == ??
typedef struct BLOCK
{
        uint32  self,                // offset in BLOCK.NDS
                checkSum,
                nextFree,            // offset in BLOCK.NDS
                next,                // offset in BLOCK.NDS
                valuePosition;       // offset in VALUE.NDS (backlink)
        uint8   data[128 - 5 * sizeof(uint32)];
} BLOCK; // size=128

typedef struct PARTITION
{
/* private, initialized by record manager */
        uint32          self,           // offset in partition
                        checkSum,
                        nextFree,       // offset in partition

// id is one of: PI_SYSTEM, PI_SCHEMA, PI_EXTREF, PI_BINDERY, PI_COUNT
                        id;             // id in partition

/* public, must be initialized before inserts */
        uint32          rootEntryID,    // ID in entry
                        replicaType,    // ID in entry
                        state,
                        unused;
        TIMESTAMP       timeStamp;
} PARTITION;   // size = 10*4  = 40

// values for partition.state
typedef enum REPLICA_STATE
{
	RS_ON, RS_NEW_REPLICA, RS_DYING_REPLICA, RS_DYING_PARTITION, RS_REPLICA_DOWN, 
	RS_SS_0 = 48, RS_SS_1, RS_SS_2, RS_SS_3, RS_SS_4, RS_SS_5,
	RS_JS_0 = 64, RS_JS_1, RS_JS_2, RS_JS_3, RS_JS_4, RS_JS_5,
	RS_MS_0 = 80, RS_MS_1, RS_MS_2, RS_MS_3, RS_MS_4, RS_MS_5,
	RS_COUNT
} REPLICA_STATE;

FILE *fPartition;
FILE *fEntry;
FILE *fValue;
FILE *fBlock;

#define XCLOSE(f)  (f)=(FILE*)((f)?(fclose(f)):NULL)

void CloseNDS(void)
{
  XCLOSE(fPartition);
  XCLOSE(fEntry);
  XCLOSE(fValue);
  XCLOSE(fBlock);
}

FILE *ndsopen(char *fs, char *file)
{
   char buf[200];
   sprintf(buf, "\\\\%s\\sys\\_netware\\%s", fs, file);
   return _fsopen(buf, "rb", SH_DENYNONE);
}

int OpenNDS(char *svr)
{
   fPartition=ndsopen(svr, "partitio.nds");
   fEntry=    ndsopen(svr, "entry.nds");
   fValue=    ndsopen(svr, "value.nds");
   fBlock=    ndsopen(svr, "block.nds");
   if (!(fPartition && fEntry && fValue && fBlock))
   {
      CloseNDS();
      return 1;
   }
   return 0;
}

//
// all Get*By* functions return TRUE when successful, FALSE when
// record not found or an IO error occurred
//

bool GetBlockByOffset(BLOCK *pBlock, long offset)
{
   int nRc;
   if (offset%sizeof(BLOCK))
      return FALSE;

   nRc = fseek(fBlock, offset, SEEK_SET);
   if (nRc)
      return FALSE;
   nRc = fread(pBlock, sizeof(BLOCK), 1, fBlock);
   return nRc==1;
}

bool GetValueByOffset(VALUE *pValue, long offset)
{
   int nRc;
   if (offset%sizeof(VALUE))
      return FALSE;

   nRc = fseek(fValue, offset, SEEK_SET);
   if (nRc)
      return FALSE;
   nRc = fread(pValue, sizeof(VALUE), 1, fValue);
   return nRc==1;
}

#define ENTRY_INDEX_MASK  0x00ffffffL

bool GetEntryByID(ENTRY *pEntry, long id)
{
   int nRc;

   if ((id&0xff000000L)==0xff000000L)
      return FALSE;

   if ((id&0xff000000L)!=0)
      id = (id&ENTRY_INDEX_MASK)*sizeof(ENTRY);
   else if (id%sizeof(ENTRY))
      return FALSE;

   nRc = fseek(fEntry, id, SEEK_SET);
   if (nRc)
      return FALSE;
   nRc = fread(pEntry, sizeof(ENTRY), 1, fEntry);
   return nRc==1;
}

bool GetPartitionByID(PARTITION *pPartition, long id)
{
   int nRc;

   if ((id&0xff000000L)==0xff000000L)
      return FALSE;

   if (id%sizeof(PARTITION))
      id=(id&ENTRY_INDEX_MASK)*sizeof(PARTITION);

   nRc = fseek(fPartition, id, SEEK_SET);
   if (nRc)
      return FALSE;
   nRc = fread(pPartition, sizeof(PARTITION), 1, fPartition);
   return nRc==1;
}


//
// all print* functions return # characters printed
//
//
int printUnicodeName(unicode *name)
{
   int i=0;
   while (name[i] && i<MAX_RDN_CHARS)
      putchar(name[i++]);
   return i;
}

int printTimeStamp(TIMESTAMP *ts)
{
   return printf("T(%ldS,%dR,%dE)", ts->seconds, ts->replicaNumber, ts->event);
}


int xprintf(char *fmt, ...)
{
   va_list ap;
// used for conversions
   char      *pString;
   unicode   *pUnicode;
   TIMESTAMP *pTimeStamp;
//   PARTITION *pPart;
   ENTRY     entry;
   long      nEntryID;
//   VALUE     *pValue
//   BLOCK     *pBlock;
   int       nNumber;
   long      lNumber;
   int       res=0;

   va_start(ap, fmt);
   while (*fmt)
   {
      if (fmt[0]=='%')
         switch(fmt[1])
         {
            case 'E':   // print Extended
               switch(fmt[2])
               {
                  case 'U':  // unicode
                     pUnicode=va_arg(ap, unicode*);
                     res+=printUnicodeName(pUnicode);
                     break;
                  case 'N':  // entry name
                     nEntryID=va_arg(ap, long);
                     if (GetEntryByID(&entry, nEntryID))
                        res+=printUnicodeName(entry.name);
                     else if (nEntryID>=0xFF000000L && nEntryID<=0xff000012L)
                        res+=printf("<%s>", specialEntryIDs[(int)nEntryID&0xff]);
                     else
                        res+=printf("<%08lx?>", nEntryID);
                     break;
                  case 'T':  // timestamp (%d,%d)[date/time]
                     pTimeStamp=va_arg(ap, TIMESTAMP*);
                     res+=printTimeStamp(pTimeStamp);
                     break;
               }
               fmt+=3;
               break;
            case 'l':
               lNumber=va_arg(ap, long);
               res+=printf("%ld", lNumber);
               fmt+=2;
               break;
            case 'x':
               lNumber=va_arg(ap, long);
               res+=printf("%08lx", lNumber);
               fmt+=2;
               break;
            case 'd':
               nNumber=va_arg(ap, int);
               res+=printf("%d", nNumber);
               fmt+=2;
               break;
            case 's':
               pString=va_arg(ap, char*);
               res+=printf("%s", pString);
               fmt+=2;
               break;
            default:
               fmt+=2;
         }
      else
      {
         putchar(*fmt++);
         res++;
      }
   }
   va_end(ap);
   return res;
}

//
// these functions dump the NDS tree
//

void dumpData(uint8 *data, long length)
{
   while (length--)
      printf(" %02x", *data++);
}

void printData(VALUE *pAttr)
{
   BLOCK block;
   long  blockOfs;
   long  printedLength;

   printf("      Attr Value %08lx:(%4d) ", pAttr->self, pAttr->length);
   printedLength=MIN(pAttr->length, sizeof(pAttr->data));
   dumpData(pAttr->data, printedLength);
   if (fVerbose)
      for (blockOfs=pAttr->firstBlock ; blockOfs!=NB_LIST_END ; blockOfs=block.next)
      {
         GetBlockByOffset(&block, blockOfs);
         dumpData(block.data, MIN(pAttr->length-printedLength, sizeof(block.data)));
      }
   printf("\n");
}

void printAttributes(long attrID, long chkentry)
{
   VALUE attr;
   long  valID;

   if (!GetValueByOffset(&attr, attrID))
   {
      if (fVerbose)
         printf("prtAttr: Unknown Attribute: %08lx\n", attrID);
      return;
   }

   // possibly check attr.entryID==chkentry
   if (fVerbose)
      xprintf("attributes for ENTRY <%EN>\n", attr.entryID);
   while (attrID!=NB_LIST_END)
   {
      xprintf("   attribute : <%EN> flags=%l\n", attr.attrID, attr.flags);
      for (valID=attrID ; valID!=NB_LIST_END ; valID=attr.nextValue)
      {
         if (!GetValueByOffset(&attr, valID))
            break;
         printData(&attr);
      }
      attrID=attr.nextAttr;
   }
   printf("\n");
}

void printEntry(long entryID, long chkParentID, long chkPartitionID)
{
   // check chkparrentID==entry->prntID
   // check chkpartitionID==entry->pttnID
   // check if unused==0,
   // check if self==entryoffset
   // check if id==entryID
   // check checksum ??
   // check free==NB_INVALID_POSITION
   ENTRY entry;

   if (!GetEntryByID(&entry, entryID))
   {
      if (fVerbose)
         printf("Unknown entry: %08lx\n", entryID);
      return;
   }

   xprintf("ENTRY (%x):<%EU> classID=<%EN>  parent=<%EN> flags=%l\n",
          entry.id, entry.name, entry.classID, entry.parentID, entry.flags);
   xprintf("                sibbling=<%EN>  childs= <%EN> .. <%EN> (%l subs)\n",
          entry.sibling, entry.firstChild, entry.lastChild, entry.subordinateCount);
   printAttributes(entry.firstAttr, entryID);
   printEntry(entry.firstChild, entryID, chkPartitionID);
   printEntry(entry.sibling, chkParentID, chkPartitionID);
  // check sibling count == parent.subordinatecount
}

void checkPartition(PARTITION *pt, int ptID)
{
// check checksum ??
   if (pt->unused)
      printf("PARTITION(%d) : unused=%d\n", ptID, pt->unused);
   if (pt->id!=ptID)
      printf("PARTITION(%d) : id=%d\n", ptID, pt->id);
   if (pt->self!=ptID*sizeof(PARTITION))
      printf("PARTITION(%d) : self=%08lx\n", ptID, pt->self);
   if (pt->nextFree!=NB_INVALID_POSITION)
      printf("PARTITION(%d) : free==%08lx\n", ptID, pt->nextFree);
}

char *partitionNames[]={
  "PI_SYSTEM",
  "PI_SCHEMA",
  "PI_EXTREF",
  "PI_BINDERY",
  "PI_COUNT",
  "PI_?6?",
  "PI_?7?",
  "PI_?8?",
  "PI_?9?",
  NULL
};

void printNDS(void)
{
   PARTITION pt;
   int ptID=0;

   while(GetPartitionByID(&pt, ptID))
   {
//      checkPartition(&pt, ptID);
      xprintf("PARTITION:%s  rootentry=<%EN>  replicatype=<%EN>  state=%l time=<%ET>\n\n",
               partitionNames[ptID],
               pt.rootEntryID, pt.replicaType,
               pt.state, &pt.timeStamp);

      printEntry(pt.rootEntryID, NB_LIST_END, ptID);
      printEntry(pt.replicaType, NB_LIST_END, ptID);
      ptID++;
   }
}

#define bit(n)  (1<<(n))

#define seton(flags,mask)  ((flags)|=(mask))
#define ison(flags,mask)  (((flags)&(mask))!=0)

#define ACT_DUMP_PARTITION   bit(0)
#define ACT_DUMP_VALUE       bit(1)
#define ACT_DUMP_ENTRY       bit(2)
#define ACT_DUMP_BLOCK       bit(3)
#define ACT_DUMP_LOGIC       bit(4)

int main(int argc, char **argv)
{
//   FILE *f;
   int i;
   int actionFlags=0;
   char *server=NULL;
   uint32 nEntryID;

   for (i=1 ; i<argc ; i++)
   {
      if (argv[i][0]=='-')
         switch(argv[i][1])
      {
         case 'v':  fVerbose=TRUE; break;
         case 'e':
            seton(actionFlags, ACT_DUMP_ENTRY);
            nEntryID=strtoul(argv[i]+2, 0, 16);
            break;
         default:
            printf("invalid option: %s\n", argv[i]);
            exit(1);
      }
      else if (server==NULL)
      {
         server=argv[i];
      }
      else
      {
         printf("Too many parameters: %s\n", argv[i]);
         exit(1);
      }
   }
   if (server==NULL)
   {
      printf("no servername\n");
      exit(1);
   }
   if (OpenNDS(server))
   {
      perror("Directory Services");
      exit(1);
   }
/*
   if (ison(actionFlags, ACT_DUMP_PARTITION))
      dumpPartition(fPartition);
   if (ison(actionFlags, ACT_DUMP_ENTRY))
      dumpEntry(fEntry);
   if (ison(actionFlags, ACT_DUMP_VALUE))
      dumpValue(fValue);
   if (ison(actionFlags, ACT_DUMP_BLOCK))
      dumpBlock(fBlock);
*/
//   if (ison(actionFlags, ACT_DUMP_LOGIC))
   if (actionFlags==0)
      printNDS();
   else if (ison(actionFlags, ACT_DUMP_ENTRY))
      printEntry(nEntryID, NB_LIST_END, NB_LIST_END);
   CloseNDS();
   return 0;
}

/*
//
// these are all old functions
//

void printEntry(ENTRY *entry)
{
   printf("%08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx ",
           entry->self, entry->checkSum, entry->nextFree,
           entry->nextName, entry->sibling, entry->firstChild,
           entry->lastChild, entry->firstAttr, entry->id,
           entry->partitionID, entry->parentID, entry->flags,
           entry->unused, entry->subordinateCount, entry->classID,
           entry->creationTime.seconds,
           entry->modificationTime.seconds);
   printUnicodeName(entry->name);
   putchar('\n');
}

void dumpEntry(FILE *f)
{
   ENTRY entry;
   printf("-------------------------------ENTRY.NDS-----------------------------------\n");
   printf("self     chkSum   nextFree nextName sibling  1stChld  lstChld  1stAttr  id       pttnID   prntID   flags    unused   nsubs    classID  tCreat   tMod     name\n");
   while (fread(&entry, sizeof(entry), 1, f))
   {
      printEntry(&entry);
   }
}

void printValue(VALUE *value)
{
   int i;
   printf("%08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx (%4ld) ",
      value->self, value->checkSum, value->nextFree, value->nextValue,
      value->nextAttr, value->firstBlock, value->entryID, value->attrID,
      value->flags, value->timeStamp.seconds, value->length);
   for (i=0 ; i<value->length && i<sizeof(value->data) ; i++)
      putchar(value->data[i]);
   putchar('\n');
}

void dumpValue(FILE *f)
{
   VALUE value;
   printf("-------------------------------VALUE.NDS-----------------------------------\n");
   printf("self     checkSum nextFree nextVal  nextAttr 1stBlock entryID  attrID   flags    time     length   name\n");
   while (fread(&value, sizeof(value), 1, f))
   {
      printValue(&value);
   }
}

void printBlock(BLOCK *block)
{
   int i;
   printf("%08lx %08lx %08lx %08lx %08lx ",
      block->self, block->checkSum, block->nextFree, block->next,
      block->valuePosition);
   for (i=0 ; i<sizeof(block->data) ; i++)
      printf(" %02x", block->data[i]);
   putchar('\n');
}

void dumpBlock(FILE *f)
{
   BLOCK block;
   printf("-------------------------------BLOCK.NDS-----------------------------------\n");
   printf("self     checkSum nextFree next     valuePos  data\n");
   while (fread(&block, sizeof(block), 1, f))
   {
      printBlock(&block);
   }
}

void printPartition(PARTITION *partition)
{
   printf("%08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx\n",
      partition->self, partition->checkSum, partition->nextFree,
      partition->id, partition->rootEntryID, partition->replicaType,
      partition->state, partition->unused,
      partition->timeStamp.seconds);
}

void dumpPartition(FILE *f)
{
   PARTITION partition;
   printf("-------------------------------PARTITION.NDS-------------------------------\n");
   printf("self     chkSum   nextFree id       rootEnID replType state    unused   time\n");
   while (fread(&partition, sizeof(partition), 1, f))
   {
      printPartition(&partition);
   }
}

*/

