Platform SDK: Network Management

Determining the Validating Server on Windows 95/98

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

Determining the Windows NT/Windows 2000 domain server that validates a user's logon password from Windows 95/98 is an involved task. On Windows NT/Windows 2000, 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 specifying the validating server.

On Windows 95/98, there is no 32-bit function that will return the same information. You must use the 16-bit network management 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 NetServerEnum 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; it will be associated with 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 for the sample 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 globally 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 characters 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
//  functions can be cast to an appropriate value.

TIMER  *lpTime;
USER11  far *Users;
WORK10  far *Wksta;
SERVER0 far *Servers;
WORK01  far *Wksta01;

char * lpszTime;

// Declare values for use with the network management functions.

unsigned short svrEntries;
unsigned short svrRead;
unsigned short usrEntries;
unsigned short wkstaEntries;

// Set up a temporary variable to use as a loop control variable.

unsigned short i;

char Message[256];

// Define a global variable to hold the validating server 
//  and the time the user last logged on.

SVR_USR validate = { NULL, 0 };

// Global variable for the return value of the function; 
//  used in error checking.

DWORD netRet;

// Bogus WinMain so you can step through the example 
//  in a 16-bit 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 calling 
   //  the NetWkstaGetInfo function at level 10. 
   //  This will return the domain in which the user logged on.

   netRet = NetWkstaGetInfo( NULL,  // This workstation
                         LEVEL_10,  // Information requested
               (char far *)WrkSta,  // Buffer
                     SMALL_BUFFER, 
                    &wkstaEntries); // Expecting only 1 entry
                             
   if( netRet != NERR_Success )
   {
      // A network error occurred. 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);
   }

   // Now we must retrieve the backup domain controllers 
   //  and the primary domain controller to check 
   //  when the last user 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, end program.

      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. Now, query for the
   //  user information, then store it temporarily for comparison
   //  to the backup domain controller's data.

   netRet = NetUserGetInfo( (char far *)pdcName,  // Execute on the PDC
                            Wksta[0].wki10_username, // User's name
                            LEVEL_11,
                            (char far *)UserData, // Put structs here
                            SMALL_BUFFER,
                            &usrEntries); // expecting only one entry
   if( netRet != NERR_Success )
   {
       // No user account on the PDC. End the 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 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 BDCs.

   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 BDCs. This 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 BDCs . 
   // if svrEntries > 1
   //    Loop through the list checking the last logon against the
   //     current logon 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 the
         //  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 )
         { 
            // 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 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);
}