/**************************************************************************

  Copyright (c) 1995, 1997 Novell, Inc.  All Rights Reserved.

  With respect to this file, Novell hereby grants to Developer a 
  royalty-free, non-exclusive license to include this sample code 
  and derivative binaries in its product. Novell grants to Developer 
  worldwide distribution rights to market, distribute or sell this 
  sample code file and derivative binaries as a component of 
  Developer's product(s).  Novell shall have no obligations to 
  Developer or Developer's customers with respect to this code.
  
  DISCLAIMER:
  
  Novell disclaims and excludes any and all express, implied, and 
  statutory warranties, including, without limitation, warranties 
  of good title, warranties against infringement, and the implied 
  warranties of merchantibility and fitness for a particular purpose.  
  Novell does not warrant that the software will satisfy customer's 
  requirements or that the licensed works are without defect or error 
  or that the operation of the software will be uninterrupted.  
  Novell makes no warranties respecting any technical services or 
  support tools provided under the agreement, and disclaims all other 
  warranties, including the implied warranties of merchantability and 
  fitness for a particular purpose.  */

/**************************************************************************
  DSEXTSNC.C
***************************************************************************

  DSEXTSNC.C searches a DS database and stores to a file (NAMESDB.DBT)
  all object and attribute information that meets the search filter
  and time stamp criteria.  This code demonstrates how to synchronize 
  external flat database attribute values with the DS database.  The 
  search only returns information that has changed since the last time 
  the database was synchronized (which is determined by reading the time 
  stamp value in TIMESMP.DB).

**************************************************************************/

/* Macros */
#define S_IREAD    0000400    /* read permission, owner  */
#define S_IWRITE   0000200    /* write permission, owner */

/* Headers */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <fcntl.h>
#include <io.h>
#include <ntypes.h>
#include <nwnet.h>
#include <nwcalls.h>
#include <nwlocale.h>

/* Prototypes */
int ActOnData(pBuf_T pBuffer);
void SearchForObjects(void);
void PutDBData(pnstr8 attrName, pnstr8 attrVal); 

/* Globals */
char *attrList[] = {"Full Name",
                    "CN",
                    "Surname",
                    "Telephone Number"};

struct dbRecord
{
   char   fdn[MAX_DN_CHARS];
   char   FullName[100];
   char   CName[40];
   char   SurName[40];
   char   Telephone[20];
};

FILE                *dbStream;
struct dbRecord     namesDB;
int                 tshandle;
TimeStamp_T         lastTimeStamp;
NWDSContextHandle   dContext;

/****************************************************************************
** Function: Main (void)
**
** This function performs the necessary preliminary DS operations, loading
** the unicode tables, creating a context, etc. It then opens the
** TIMESMP.DB file to retrieve the last "sync'ed up to" value. If this file
** does not exist it assumes a timestamp value of 0. This will force the
** search function to return all objects that fit search critera.
** Subsequent searches will only return attributes for objects that have been
** modified since the saved time stamp.
*/

void main(void)
{
   NWDSCCODE   dsccode;
   nuint32     flags;
   nstr8       strUserName[NW_MAX_USER_NAME_LEN];
   nstr8       strUserPassword[50];
   nbool8      bbDoLogout = N_FALSE;
   LCONV       lconvInfo;

   dsccode = NWCallsInit(NULL, NULL);
   if(dsccode)
   {
      printf("\nCall to NWCallsInit returned: %04X", dsccode);
      exit(1);
   }

   NWLsetlocale(LC_ALL,"");
   NWLlocaleconv(&lconvInfo);

   dsccode = NWInitUnicodeTables(lconvInfo.country_id, lconvInfo.code_page);
   if(dsccode)
   {
      printf("NWInitUnicodeTables() returned: %04X\n", dsccode);
      goto _FreeUnicodeTables;
   }

   dsccode = NWDSCreateContextHandle(&dContext);
   if(dsccode)
   {
      printf("\nNWDSCreateContextHandle returned: %04lX\n", dsccode);
      goto _FreeUnicodeTables;
   }

   /*  Must authenticate if not already authenticated to NDS
       (which will always be the case if this example is 
       compiled and run as an NLM). */

   if(!NWIsDSAuthenticated())
   {
      printf("\nMust authenticate to NDS");
      printf("\nEnter User Name: ");
      gets(strUserName);
      printf("Enter User Password: ");
      gets(strUserPassword);

      dsccode = NWDSLogin(dContext, 0, strUserName, strUserPassword, 0);
      if(dsccode)
      {
         printf("\nNWDSLogin returned %X", dsccode);
       goto _FreeContext;
      }
      else
      {  /* If example logs in, it will also log out */
         bbDoLogout = N_TRUE;
      }
   }

   /* Get the current directory context flags so we can modify them. */

   dsccode = NWDSGetContext(
            /* Contxt Handle */ dContext,
            /* Key           */ DCK_FLAGS,
            /* Context Flags */ &flags);
   if(dsccode)
   {
      printf("NWDSGetContext returned: %04X\n", dsccode);
      goto _LogoutNDS;
   }

   /* Turn typeless naming on.  Turn canonicalize names off.  This means 
      we will get full names. */

   flags |= DCV_TYPELESS_NAMES;   
   flags &= ~DCV_CANONICALIZE_NAMES;

   /* Set the new directory context flags */

   dsccode = NWDSSetContext(
            /* Context Handle */ dContext,
            /* Key            */ DCK_FLAGS,
            /* Set Flag Value */ &flags);
   if(dsccode)
   {
      printf("NWDSSetContext returned: %04X\n", dsccode);
      goto _LogoutNDS;
   }

   tshandle = open("TIMESMP.DB", O_RDWR | O_BINARY);
   if (tshandle == -1)
   {
      /* No timestamp file, start from scratch. */
      memset(&lastTimeStamp, 0, sizeof(lastTimeStamp));
      tshandle = open("TIMESMP.DB", O_CREAT | O_BINARY | O_RDWR, 
                        S_IREAD | S_IWRITE);
      if(tshandle == -1)
      {
         perror("Error:");
         goto _LogoutNDS;
      }
   }
   else
   {
      read(tshandle, &lastTimeStamp, sizeof(lastTimeStamp));
      lseek(tshandle, 0L, SEEK_SET);
   }

   /* Open a file to store returned object names and attributes */
   dbStream = fopen("NAMESDB.DBT", "wt");   
   if(!dbStream)
   {
      fprintf(stderr, "Can't open NAMESDB.DBT file\n");
      goto _LogoutNDS;
   }

   SearchForObjects();

   fclose(dbStream);
   write(tshandle, &lastTimeStamp, sizeof(lastTimeStamp));
   close(tshandle);

_LogoutNDS:
   if(bbDoLogout == N_TRUE)
      NWDSLogout(dContext);
_FreeContext:
   NWDSFreeContext(dContext);
_FreeUnicodeTables:
   NWFreeUnicodeTables();

}

/****************************************************************************
** Function: int SearchForObjects(void)
**
** This function initializes the search filter and issues the request to
** return all attributes that meet the search criteria and the time stamp
** criteria.
*/

void SearchForObjects(void)
{
   NWDSCCODE          dsccode;
   nbool8             searchAliases = FALSE;
   nbool8             allAttrs = FALSE;
   nuint32            infoType = 1;
   nuint32            syntaxID;
   nint32             iterHandle = (nint32)-1;
   nint32             cntObjectsSearched;
   nptr               val;
   TimeStamp_T        timeFilter;
   pBuf_T             searchFilter = NULL;
   pBuf_T             attrNames = NULL;
   pBuf_T             retBuf = NULL;
   pFilter_Cursor_T   cur = NULL;
   int                i,iterCnt = 0;
   int                numAttrNames = sizeof(attrList)/sizeof(attrList[0]);

   /* Initialize the time filter */
   timeFilter = lastTimeStamp;

   /* Allocate a buffer to restrict search to certain attributes. */

   dsccode = NWDSAllocBuf(
              /* Buffer Size */ DEFAULT_MESSAGE_LEN,
              /* Buff. Point.*/ &attrNames);
   if(dsccode)
   {
      printf("NWDSAllocBuf returned: %04X\n", dsccode);
      goto Out;
   }

   /* Initialize ALL input buffers. */

   dsccode = NWDSInitBuf(
            /* Context  */ dContext,
            /* Operation*/ DSV_SEARCH,
            /* buffer   */ attrNames);
   if(dsccode)
   {
      printf("NWDSInitBuf returned: %04X\n", dsccode);
      goto Out;
   }

   /* attributes attrList defined in globals  */

   for (i = 0; i < numAttrNames; i++)
   {
      dsccode = NWDSPutAttrName(
                 /* context   */ dContext,
                 /* in buffer */ attrNames,
                 /* attr. name*/ (pnstr8)attrList[i]);
      if(dsccode)
      {
         printf("NWDSPutAttrName returned: %04X\n", dsccode);
         goto Out;
      }
   }

   /* Allocate a buffer to contain the search expression */

   dsccode = NWDSAllocBuf(
              /* Buffer Size */ DEFAULT_MESSAGE_LEN,
              /* Buff. Point.*/ &searchFilter);
   if(dsccode)
   {
      printf("NWDSAllocBuf returned: %04X\n", dsccode);
      goto Out;
   }

   /* Initialize the searchFilter buffer  */

   dsccode = NWDSInitBuf(
              /* Context  */ dContext,
              /* Operation*/ DSV_SEARCH_FILTER,
              /* buffer   */ searchFilter);
   if(dsccode)
   {
      printf("NWDSInitBuf returned: %04X\n", dsccode);
      goto Out;
   }

   /* Allocate a filter cursor to put the search expression */

   dsccode = NWDSAllocFilter(
                 /* Cursor */ &cur);
   if(dsccode)
   {
      printf("NWDSAllocFilter returned: %04X\n", dsccode);
      goto Out;
   }

   /*  Build the expression tree. Filter on Object Class of "User". */

   dsccode = NWDSAddFilterToken(
             /* Cursor (exp tree) */ cur,
             /* TOKEN             */ FTOK_ANAME,
             /* Name or Value     */ "Object Class",
             /* Syntax ID         */ SYN_CLASS_NAME);
   if(dsccode)
   {
      printf("NWDSAddFilterToken returned: %04X\n", dsccode);
      goto Out;
   }

   dsccode = NWDSAddFilterToken(
            /* Cursor (exp tree) */ cur,
            /* TOKEN             */ FTOK_EQ,
            /* Name or Value     */ NULL,
            /* Syntax ID         */ 0);
   if(dsccode)
   {
      printf("NWDSAddFilterToken returned: %04X\n", dsccode);
      goto Out;
   }

   dsccode = NWDSAddFilterToken(
            /* Cursor (exp tree) */ cur,
            /* TOKEN             */ FTOK_AVAL,
            /* Name or Value     */ "User",
            /* Syntax ID         */ SYN_CLASS_NAME);
   if(dsccode)
   {
      printf("NWDSAddFilterToken returned: %04X\n", dsccode);
      goto Out;
   }

   dsccode = NWDSAddFilterToken(
            /* Cursor (exp tree) */ cur,
            /* TOKEN             */ FTOK_AND,
            /* Name or Value     */ NULL,
            /* Syntax ID         */ 0);
   if(dsccode)
   {
      printf("NWDSAddFilterToken returned: %04X\n", dsccode);
      goto Out;
   }

   /* Filter on Objects that have been modified. */

   val = NULL;
   syntaxID = (NWSYNTAX_ID)0;  /* ignored this time */

   dsccode = NWDSAddFilterToken(
            /* Cursor (exp tree) */ cur,
            /* TOKEN             */ FTOK_MODTIME,
            /* Name or Value     */ val,
            /* Syntax ID         */ syntaxID);
   if(dsccode)
   {
      printf("NWDSAddFilterToken returned: %04X\n", dsccode);
      goto Out;
   }

   /* Put the time filter to search on */

   val = &timeFilter;
   syntaxID = (NWSYNTAX_ID)SYN_TIMESTAMP;

   dsccode = NWDSAddFilterToken(
            /* Cursor (exp tree) */ cur,
            /* TOKEN             */ FTOK_AVAL,
            /* Name or Value     */ val,
            /* Syntax ID         */ syntaxID);
   if(dsccode)
   {
      printf("NWDSAddFilterToken returned: %04X\n", dsccode);
      goto Out;
   }

   dsccode = NWDSAddFilterToken(
            /* Cursor (exp tree) */ cur,
            /* TOKEN             */ FTOK_END,
            /* Name or Value     */ NULL,
            /* Syntax ID         */ 0);
   if(dsccode)
   {
      printf("NWDSAddFilterToken returned: %04X\n", dsccode);
      goto Out;
   }

   /* The expression is complete.  Put the filter expression 
      into the filter buffer.  The freeVal function pointer
      would free the attribute values, but we allocated the
      values on the stack, so pass NULL.  */

   dsccode = NWDSPutFilter(           
            /* Context Handle */ dContext,
            /* Input Buffer   */ searchFilter,
            /* Cursor Pointer */ cur,
            /* Free val. func */ NULL);
   if(dsccode)
   {
      printf("NWDSPutFilter returned: %04X\n", dsccode);
      goto Out;
   }
   else
      cur = NULL; /* so we know later to free or not */

   /* Allocate a buffer to receive the results. */
   dsccode = NWDSAllocBuf(
            /* Buffer Size */ DEFAULT_MESSAGE_LEN,
            /* Buff. Point.*/ &retBuf);
   if(dsccode)
   {
      printf("NWDSAllocBuf returned: %04X\n", dsccode);
      goto Out;
   }

   /* Ready to search.  The base object is "[Root]", but it could also 
      have been an OU to start the search further down the tree.  */

   do
   {
      dsccode = NWDSExtSyncSearch(
               /* Context handle */ dContext,
               /* Base Obj Name  */ "[Root]",
               /* Scope          */ DS_SEARCH_SUBTREE,
               /* Search Alias?  */ searchAliases,
               /* Search Filter  */ searchFilter,
               /* Obj. Mod. T.S. */ &timeFilter,
               /* Info Type      */ infoType,
               /* All Attrib's ? */ allAttrs,
               /* Attrib. names  */ attrNames,
               /* Iter. Handle   */ &iterHandle,
               /* cnt Obj's 2 sch*/ (nint32)NULL,
               /* cnt obj's schd */ &cntObjectsSearched,
               /* Object returned*/ retBuf);

      if (dsccode)
      {
         printf("NWDSExtSyncSearch returned: %04X\n", dsccode);
         goto Out;
      }

      iterCnt++;
      ActOnData(retBuf);

   } while (iterHandle != (nint32)-1);

Out:
   if (retBuf)
      NWDSFreeBuf(retBuf);
   if (cur)
      NWDSFreeFilter(cur, NULL);
   if (searchFilter)
      NWDSFreeBuf(searchFilter);
   if (attrNames)
      NWDSFreeBuf(attrNames);
}

/****************************************************************************
** Function: int ActOnData(pBuf_T pBuffer)
**
** The buffer returned from NWDSSearch() is passed to this function. This
** buffer contains all of the object/attribute information that met the
** criteria of the search. This function reads this information from the
** buffer.  */

int ActOnData(pBuf_T  buffer)
{
   NWDSCCODE       dsccode;
   nuint32         attrValCount, attrCount, objectCount, syntaxID;
   nstr8           objectName[MAX_DN_CHARS + 1],
                   prevObjectName[MAX_DN_CHARS + 1];
   nstr8           attrName[MAX_SCHEMA_NAME_CHARS + 1],
                   attrVal[MAX_DN_CHARS + 1];
   Object_Info_T   objectInfo;
   int             err=1;
   nuint32         i, j, k;

   dsccode = NWDSGetObjectCount(
            /* Req. Context   */ dContext,
            /* P. to read buf.*/ buffer,
            /* Num. of OBJs   */ &objectCount);
   if(dsccode)
   {
      printf("\nNWDSGetObjectCount returned : %04X\n", dsccode);
      return(err);
   }
   
   for (i = 0; i < objectCount; i++)
   {
      dsccode = NWDSGetObjectName(
               /* Req. Context     */ dContext,
               /* P. to result buf */ buffer,
               /* object name      */ objectName,
               /* Attribute count  */ &attrCount,
               /* object Info      */ &objectInfo);
      if(dsccode)
      {
         printf("\nNWDSGetObjectName returned: %04X\n", dsccode);
         return(err);
      }

      /*  Check if this is a new object.  An object's data may have
         been split in the case of multiple iterations of 
         NWDSExtSyncSearch.  */

      if(strcmp(objectName, prevObjectName))
      {
         /* The object name could be saved or used to determine if this
            record exists in the external database. For simplicity, we
            just print it to the screen. */

         strcpy(prevObjectName, objectName);
         printf("\nObject Name: %s", objectName);

         PutDBData("fdn", objectName);

         lastTimeStamp.wholeSeconds=max(lastTimeStamp.wholeSeconds,
                             (nuint32)objectInfo.modificationTime);
      }

      /* Get all the attributes */

      for (j = 0; j < attrCount; j++)
      {
         /* Retrieve the attribute name and a count of its values */

         dsccode = NWDSGetAttrName(
                   /* Context        */ dContext,
                   /* attrib. buf    */ buffer,
                   /* attrib name    */ attrName,
                   /* attr. val. cnt */ &attrValCount,
                   /* Syntax ID      */ &syntaxID);
         if (dsccode < 0)
         {
            printf("\nNWDSGetAttrName returned: %04X\n", dsccode);
            return(err);
         }

         printf("\n\tAttribute Name : %s", attrName);
       printf("\n\t\tAttribute Value(s):\n");
         printf("\t\t------------------");

         /* Get all the attribute values. At this point the attribute name
            could be associated with the field of a record in an external
            data base. Once the objectName + its updated attribute values
            have been identified, the new record can be written to the
            external database. The attribute names and values are displayed
            on the screen and written to a file. The accompanying 
            READDB.EXE utility can be used to read the records from the 
            file. */
         
       for (k = 0; k < attrValCount; k++)
         {
            dsccode = NWDSGetAttrVal(
                      /* Context  */ dContext,
                      /* read buf */ buffer,
                      /* syntax id*/ syntaxID,
                      /* attr. val*/ attrVal);
            if (dsccode < 0)
            {
               printf("\nNWDSGetAttrVal returned: %04X\n", dsccode);
               return(err);
            }

            /*   Save only the first value to file if attrValCount > 1 */
            if (k == 0)
               PutDBData(attrName, attrVal);

            printf("\n\t\tVal: %s", attrVal);

         } /* end of attribute value loop */
      } /* end of attributes loop */

      /* Write the returned information to a file. If this were a "real"
         database, some checking would need to be done at this point to
         see if existing information should be modified or if this record
         should be appended to the file. Since this file is recreated each
         time this program is run, the record is simply appended.   */

      fwrite(&namesDB, sizeof(namesDB), 1, dbStream);
      memset(&namesDB, 0, sizeof(namesDB));

   } /* end of objects loop */

return(err);
}

/****************************************************************************
** Function: void PutDBData(pnstr8 attrName, pnstr8 attrVal)
**
** This function takes the attribute value and stores it in a dbRecord
** structure in preparation for saving this record to a file.
*/

void PutDBData(pnstr8 attrName, pnstr8 attrVal)
{
   int   i;
   int   cnt;
   
   if (!strncmp(attrName, "fdn", 3))
      cnt = 4;
   else
      for (i=0; i < 4; i++)
      {
         if (!strncmp(attrName, attrList[i], 2))
            cnt = i;
      }

   switch(cnt)
   {
     case 0:
     {
         memcpy(namesDB.FullName, attrVal, strlen(attrVal));
         break;
     }
      case 1:
     {
         memcpy(namesDB.CName, attrVal, strlen(attrVal));
       break;
     }
      case 2:
     {
         memcpy(namesDB.SurName, attrVal, strlen(attrVal));
         break;
     }
      case 3:
     {
         memcpy(namesDB.Telephone, attrVal, strlen(attrVal));
         break;
     }
      case 4:
     {
         memcpy(namesDB.fdn, attrVal, strlen(attrVal));
         break;
     }
   }
} 
