Determining the Validating Server on Windows 95

Windows 95 and Windows 98

The code sample in this topic demonstrates how to determine the validating server on Windows 95, using the Net functions.

Determining the Windows NT Domain server that validate a user's logon password from a Windows 95 or Windows 98 workstation is an involved task. On Windows NT, the 32-bit NetWkstaUserGetInfo function determines the validating server. The function uses level 1 to return a WKSTA_USER_INFO_1 structure. The wkui1_logon_server member will contain a pointer to a Unicode string containing the validating server.

On Windows 95 and Windows 98, there is no 32-bit function that will return the same information. You must use the 16-bit Net functions to retrieve the same information. The functions are exported from NETAPI.DLL. The link libraries are included with the 16-bit version of Microsoft® Visual C++ ( version 1.5x ).

Use the following basic steps to determine the validating server:

  1. Determine the user's logon domain using NetWkstaGetInfo.
  2. Find the Primary Domain Controller (PDC) using NetGetDCName.
  3. Get the user information from the PDC for comparison to the Backup Domain Controller (BDC) data using NetUserGetInfo.
  4. Get a list of BDCs using the NetServerEnum2 function.
  5. Loop through the list of BDCs, using NetUserGetInfo to retrieve the specific user information, comparing each last logon time, searching for the greatest value.

The largest last logon value will be the latest logon time, this will be the last server to validate the user's logon password.

The following short 16-bit program illustrates how to determine the validating server for a Windows 95 user.

You must use the LAN.H and the NETAPI.LIB files distributed with the SDK to work. The user must be certain that the directories for the .H and .LIB files are in the search path for the project.

Global Variables:

*/
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define INCL_NET 
#include <lan.h>
//
// Constant definitions
//
#define LEVEL_01 1
#define LEVEL_11 11
#define LEVEL_10 10
#define LEVEL_00 0

#define SMALL_BUFFER 1024
#define MEDIUM_BUFFER 4*1024
#define SERVER_NAME  50
//
// Buffer allocation.  Just doing it global for simplicity
//
LPSTR bdcNames[SMALL_BUFFER];
LPSTR UserData[MEDIUM_BUFFER];
LPSTR WrkSta[SMALL_BUFFER];
LPSTR pdcName[SERVER_NAME];
LPSTR servername[SERVER_NAME]; 
//
// Create a structure to hold the current server and the logon time
// So the values are together.
//
typedef struct svr_usr {
        LPSTR server;
        long logon_time;
        } SVR_USR;
//
// Create typedefs for the larger structure names to shorten 
// the number of chars to type.
//
typedef struct user_info_11  USER11;
typedef struct wksta_info_10 WORK10;
typedef struct wksta_info_1 WORK01;
typedef struct server_info_100 SERVER100;
typedef struct tm TIMER;
//
// Declare pointers so the buffers returned from the system
// APIs can be caste to the appropriate value.
//
TIMER  *lpTime;
USER11  far *Users;
WORK10  far *Wksta;
SERVER0 far *Servers;
WORK01  far *Wksta01;

char * lpszTime;

//
// Declare some short values for use with the LAN Man APIs
//
unsigned short svrEntries;
unsigned short svrRead;
unsigned short usrEntries;
unsigned short wkstaEntries;
//
// Setup a temp var to use as a loop control var
//
unsigned short i;

char Message[256];
//
// define a global var to hold the validating server and the time
// the user last logged in.
//
SVR_USR validate = { NULL, 0 };
//
// Global var for the return value of the LAN Man API used in
// error checking.
//
DWORD netRet;
//
// Bogus WinMain so you can step through the example in VC 1.52
// debugger.
//
int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
HANDLE hInstance;                /* current instance         */
HANDLE hPrevInstance;            /* previous instance        */
LPSTR lpCmdLine;                 /* command line             */
int nCmdShow;                    /* show-window type (open/icon) */
{
   //
   // First, Get the workstation information using NetWkstaGetInfo
   // at level 10.  This will return the Domain the user logged into
   //
   netRet = NetWkstaGetInfo( NULL,     // This workstation
                             LEVEL_10, // Level of Info requested.
                             (char far *)WrkSta,   // Buffer
                             SMALL_BUFFER,
                             &wkstaEntries); // Expecting only 1 entrie
                             
   if( netRet != NERR_Success )
   {
      //
      // A network error occured.  Print it to the stdout
      //
      sprintf(Message,"ERROR: NetWkstaGetInfo API Failed. Error Code: %d\n", netRet);
      MessageBox( NULL, Message, "NetWkstaGetInfo Error", MB_OK);
      return(0);
   }
   //
   // Success, we have workstation information.
   // Now we must retrieve the backup domain controllers and the
   // primary domain controller to check to see when the user last
   // logon was validated. 
   // The argument list is set as follows:
   //  Execute on the local machine ( NULL )
   //  Pass the domain name of interest (Wksta[0].wki10_logon_domain)
   //  Pass a buffer to receive the domain controller name
   //  Pass the size of the DC name buffer
   // 
   Wksta = (WORK10 *)WrkSta;
   netRet = NetGetDCName( NULL,  
                          Wksta[0].wki10_logon_domain,
                          (char far *)pdcName,
                          SERVER_NAME);
   if(netRet != NERR_Success )
   {
      //
      // Could not locate a PDC.  Something is wrong, terminate Prog.
      //
      sprintf(Message,"ERROR: NetGetDCName API Failed. Error Code: %d\n", netRet);
      MessageBox( NULL, Message, "NetGetDCName Error", MB_OK);
      return(0);
   }
   //
   // We have the primary domain controller. Lets query it for the
   // users information, then store it temprarily for comparison
   // to the BDC's (Backup Domain Controller) data.
   //
   //
   netRet = NetUserGetInfo( (char far *)pdcName,  // Execute on the PDC
                            Wksta[0].wki10_username, // User's name
                            LEVEL_11,
                            (char far *)UserData, // Put strucs here
                            SMALL_BUFFER,
                            &usrEntries); // expecting only one entry
   if( netRet != NERR_Success )
   {
       // 
       // No user account on the PDC.  Something does not jive.
       // terminate program.
       //
      sprintf(Message,"ERROR: NetUserGetInfo API Failed on PDC. Error Code: %d\n", netRet);
      MessageBox( NULL, Message, "NetUserGetInfo Error", MB_OK);
      return(0);
   }
   //
   // Set the structure so that the PDC is the starting validating server
   //
   validate.server =  (char far *)pdcName;
   Users = (USER11 * )UserData;
   validate.logon_time = Users[0].usri11_last_logon;
   //
   // Now retrieve all of the BDC's
   //
   netRet = NetServerEnum2(  NULL,
                             LEVEL_00,
                             (char far *)bdcNames,
                             SMALL_BUFFER,
                             &svrRead,
                             &svrEntries,
                             SV_TYPE_DOMAIN_BAKCTRL,
                             Wksta[0].wki10_logon_domain);
                             
   if( netRet != NERR_Success )
   {
      //
      // OOPS, no BDC's.  That could be an error
      //
      sprintf(Message,"ERROR: NetServerEnum2 API Failed. Error Code: %d", netRet);
      MessageBox( NULL, Message, "NetServerEnum2", MB_OK);
      return( 0 );
   }
   //
   // Great, we have a list of backup domain controllers 
   // if svrEntries > 1.
   //
   // Loop through the list checking to the last logon time against the
   // one currently stored in validate.logon_time
   //
   Servers =( SERVER0 * )bdcNames;
   for( i = 0; i < svrEntries; i++ )
   {
       //
       // Must add the \\ to the names returned from NetServerEnum()
       //
       _fstrcpy( (LPSTR)servername,"\\\\");
       _fstrcat( (LPSTR)servername,Servers[i].sv0_name);
       netRet = NetUserGetInfo( (LPSTR)servername,  // Execute on a BDC
                               Wksta[0].wki10_username, // User's name
                               LEVEL_11,
                               (char far *)UserData,
                               SMALL_BUFFER,
                               &usrEntries); // expecting only one entry
       Users = (USER11 *)UserData;
       if( netRet == NERR_Success )
       { 
          //
          // Great, we found a user entry on this BDC.  Compare his
          // last logon time to the stored time.  Replace the stored time
          // if the time is greater.
          // Replace the servername so the time and the server match.
          //
          if( Users[0].usri11_last_logon > validate.logon_time)
          {
             validate.server = (char far *)&Servers[i];
             validate.logon_time = Users[0].usri11_last_logon;
          } 
          else if( Users[0].usri11_last_logon == validate.logon_time )
          { 
             //
             // Just to let you know that this could indicate a problem
             //
             sprintf(Message,"Values are the same. %ls %ls", Servers[i].sv0_name, validate.server);
             MessageBox( NULL, Message, "HMMMM...........",MB_OK);
          }
       }
    } 
    //
    // Convert the time in seconds to the a time structure for display and
    // build the output string.
    //                        
    lpTime = gmtime( &validate.logon_time);
    lpszTime = asctime( lpTime );
    _fstrcpy((LPSTR)Message, (LPSTR)"Username: ");
    _fstrcat((LPSTR)Message, (LPSTR)Wksta[0].wki10_username);
    _fstrcat((LPSTR)Message, (LPSTR)"\nLast Logon: ");
    _fstrcat((LPSTR)Message, (LPSTR)lpszTime);
    _fstrcat((LPSTR)Message, (LPSTR)"Logon Server: ");
    _fstrcat((LPSTR)Message, (LPSTR)validate.server);
    //
    // Display the information.
    //
    MessageBox(NULL,Message,"Logon Validation Information", MB_OK);
    return(0);
}