/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

   registry.c

Abstract:

   Registry reading routines for License Server.  Can Scan the registry
   for all License Service entries, or for a specific service.

Author:

   Arthur Hanson (arth) 07-Dec-1994

Revision History:

   Jeff Parham (jeffparh) 05-Dec-1995
      o  Removed unnecessary RegConnect() to local server.
      o  Added secure service list.  This list tracks the products that
         require "secure" license certificates for all licenses; i.e., the
         products that do not accept the 3.51 Honesty method of "enter the
         number of license you purchased."
      o  Added routine to update the concurrent limit value in the registry
         to accurately reflect the connection limit of secure products.

--*/

#include <stdlib.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <rpc.h>
#include <rpcndr.h>
#include <dsgetdc.h>


#include "llsapi.h"
#include "debug.h"
#include "llssrv.h"
#include "registry.h"
#include "ntlsapi.h"
#include "mapping.h"
#include "msvctbl.h"
#include "svctbl.h"
#include "purchase.h"
#include "perseat.h"
#include "server.h"
#include "llsutil.h"

// #define API_TRACE 1

#define NUM_MAPPING_ENTRIES 2

const LPTSTR NameMappingTable2[] = {
   TEXT("Microsoft SQL Server"),
   TEXT("Microsoft SNA Server")
}; // NameMappingTable2


ULONG NumFilePrintEntries = 0;
LPTSTR *FilePrintTable = NULL;

#define KEY_NAME_SIZE 512

HANDLE LLSRegistryEvent;


ULONG LocalServiceListSize = 0;
PLOCAL_SERVICE_RECORD *LocalServiceList = NULL;

RTL_RESOURCE LocalServiceListLock;

static ULONG          SecureServiceListSize    = 0;
static LPTSTR *       SecureServiceList        = NULL;
static ULONG          SecureServiceBufferSize  = 0;       // in bytes!
static TCHAR *        SecureServiceBuffer      = NULL;


/////////////////////////////////////////////////////////////////////////
VOID
ConfigInfoRegistryInit(
   DWORD *  pReplicationType,
   DWORD *  pReplicationTime,
   DWORD *  pLogLevel,
   BOOL *   pPerServerCapacityWarning
   )
{
   HKEY           hKey2 = NULL;
   DWORD          dwType, dwSize;
   static BOOL    ReportedError = FALSE;
   static const TCHAR   RegKeyText[] = TEXT("System\\CurrentControlSet\\Services\\LicenseService\\Parameters");
   LONG           Status;
   BOOL           ret = FALSE;
   DWORD          ReplicationType, ReplicationTime;
   DWORD          LogLevel;
   DWORD          DisableCapacityWarning;

   ReplicationType = ReplicationTime = LogLevel = 0;

   //
   // Create registry key-name we are looking for
   //

   if ((Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText, 0, KEY_READ, &hKey2)) == ERROR_SUCCESS)
   {
      dwSize = sizeof(ReplicationType);
      Status = RegQueryValueEx(hKey2, TEXT("ReplicationType"), NULL, &dwType, (LPBYTE) &ReplicationType, &dwSize);

      if (Status == ERROR_SUCCESS)
      {
         dwSize = sizeof(ReplicationTime);
         Status = RegQueryValueEx(hKey2, TEXT("ReplicationTime"), NULL, &dwType, (LPBYTE) &ReplicationTime, &dwSize);

         if (Status == ERROR_SUCCESS)
         {
            *pReplicationType = ReplicationType;
            *pReplicationTime = ReplicationTime;
         }
         else
         {
            if (!ReportedError)
            {
               ReportedError = TRUE;
#ifdef DEBUG
               dprintf(TEXT("LLS: (WARNING) No registry parm for ReplicationTime\n"));
#endif
            }
         }

      }
      else
      {
         if (!ReportedError)
         {
            ReportedError = TRUE;
#ifdef DEBUG
            dprintf(TEXT("LLS: (WARNING) No registry parm for ReplicationType\n"));
#endif
         }
      }

      // LogLevel (REG_DWORD): determines how much info is dumped to the EventLog.
      //   Higher values imply more logging.  Default: 0.
      dwSize = sizeof( LogLevel );
      Status = RegQueryValueEx( hKey2, TEXT("LogLevel"), NULL, &dwType, (LPBYTE) &LogLevel, &dwSize);
      if ( ERROR_SUCCESS == Status )
         *pLogLevel = LogLevel;
      else
         *pLogLevel = 0;

      //
      // Read the per server capacity warning value. A warning when the per
      // server license usage nears 90-95% of the total number of licenses.
      // A non-zero registry value disables the per server capacity warning
      // mechanism.
      //
      // It is not likely this value wll be present. Default to warn.
      //

      dwSize = sizeof( DisableCapacityWarning );

      Status = RegQueryValueEx( hKey2,
                                TEXT("DisableCapacityWarning"),
                                NULL,
                                &dwType,
                                (LPBYTE)&DisableCapacityWarning,
                                &dwSize);

      if ( ERROR_SUCCESS == Status && DisableCapacityWarning ) {
         *pPerServerCapacityWarning = FALSE;
      }
      else {
         *pPerServerCapacityWarning = TRUE;
      }

      // ProductData (REG_BINARY): an encrypted buffer of concatenated service names
      //    that determine which services need to have secure certificates
      //    for license entry
      Status = RegQueryValueEx( hKey2, TEXT("ProductData"), NULL, &dwType, NULL, &dwSize );
      if ( ERROR_SUCCESS == Status )
      {
         TCHAR *     NewSecureServiceBuffer     = NULL;
         LPTSTR *    NewSecureServiceList       = NULL;
         ULONG       NewSecureServiceListSize   = 0;
         ULONG       NewSecureServiceBufferSize;

         NewSecureServiceBufferSize = dwSize;
         NewSecureServiceBuffer = LocalAlloc( LMEM_FIXED, NewSecureServiceBufferSize );

         if ( NULL != NewSecureServiceBuffer )
         {
            Status = RegQueryValueEx( hKey2, TEXT("ProductData"), NULL, &dwType, (LPBYTE) NewSecureServiceBuffer, &dwSize);

            if ( ERROR_SUCCESS == Status )
            {
               Status = DeBlock( NewSecureServiceBuffer, dwSize );

               if (    ( STATUS_SUCCESS == Status )
                    && (    ( NULL == SecureServiceBuffer )
                         || ( memcmp( NewSecureServiceBuffer, SecureServiceBuffer, dwSize ) ) ) )
               {
                  // process changes in secure product list
                  DWORD    i;
                  DWORD    ProductNdx;

                  NewSecureServiceListSize = 0;

                  // count number of product names contained in the buffer
                  for ( i=0; ( i < dwSize ) && ( NewSecureServiceBuffer[i] != TEXT( '\0' ) ); i++ )
                  {
                     // skip to beginning of next product name
                     for ( ; ( i < dwSize ) && ( NewSecureServiceBuffer[i] != TEXT( '\0' ) ); i++ );
                     i++;

                     if ( i * sizeof( TCHAR) < dwSize )
                     {
                        // properly null-terminated product name
                        NewSecureServiceListSize++;
                     }
                  }

                  if ( 0 != NewSecureServiceListSize )
                  {
                     NewSecureServiceList = LocalAlloc( LMEM_FIXED, sizeof( LPTSTR ) * NewSecureServiceListSize );

                     if ( NULL != NewSecureServiceList )
                     {
                        for ( i = ProductNdx = 0; ProductNdx < NewSecureServiceListSize; ProductNdx++ )
                        {
                           NewSecureServiceList[ ProductNdx ] = &NewSecureServiceBuffer[i];

                           // skip to beginning of next product name
                           for ( ; NewSecureServiceBuffer[i] != TEXT( '\0' ); i++ );
                           i++;
                        }

                        // new secure product list read successfully; use it
                        if ( NULL != SecureServiceBuffer )
                        {
                           LocalFree( SecureServiceBuffer );
                        }
                        if ( NULL != SecureServiceList )
                        {
                           LocalFree( SecureServiceList );
                        }

                        SecureServiceBuffer     = NewSecureServiceBuffer;
                        SecureServiceList       = NewSecureServiceList;
                        SecureServiceListSize   = NewSecureServiceListSize;
                        SecureServiceBufferSize = NewSecureServiceBufferSize;
                     }
                  }
               }
            }
         }

         // free buffers if we aren't using them anymore
         if (    ( NULL              != NewSecureServiceList )
              && ( SecureServiceList != NewSecureServiceList ) )
         {
            LocalFree( NewSecureServiceList );
         }

         if (    ( NULL                != NewSecureServiceBuffer )
              && ( SecureServiceBuffer != NewSecureServiceBuffer ) )
         {
            LocalFree( NewSecureServiceBuffer );
         }
      }

      RegCloseKey(hKey2);
   }

} // ConfigInfoRegistryInit


/////////////////////////////////////////////////////////////////////////
NTSTATUS
FilePrintTableInit(
   )

/*++

Routine Description:

   Builds up the FilePrint mapping table by enumerating the keys in the
   registry init'd by the various install programs.

Arguments:


Return Value:

   None.

--*/

{
   HKEY hKey2;
   static const TCHAR RegKeyText[] = TEXT("System\\CurrentControlSet\\Services\\LicenseService\\FilePrint");
   static TCHAR KeyText[KEY_NAME_SIZE], ClassText[KEY_NAME_SIZE];
   NTSTATUS Status;
   DWORD index = 0;
   DWORD KeySize, ClassSize, NumKeys, NumValue, MaxKey, MaxClass, MaxValue, MaxValueData, MaxSD;
   FILETIME LastWrite;
   LPTSTR *pFilePrintTableTmp;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: FilePrintTableInit\n"));
#endif
   //
   // Create registry key-name we are looking for
   //

   if ((Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText, 0, KEY_READ, &hKey2)) == ERROR_SUCCESS) {
      //
      // Find out how many sub-keys there are to intialize our table size.
      // The table can still grow dynamically, this just makes having to
      // realloc it a rare occurance.
      //
      ClassSize = KEY_NAME_SIZE;
      Status = RegQueryInfoKey(hKey2, ClassText, &ClassSize, NULL,
                               &NumKeys, &MaxKey, &MaxClass, &NumValue,
                               &MaxValue, &MaxValueData, &MaxSD, &LastWrite);

      if (Status == ERROR_SUCCESS) {
         FilePrintTable = (LPTSTR *) LocalAlloc(LPTR, sizeof(LPTSTR) * NumKeys);

         while ((Status == ERROR_SUCCESS) && (FilePrintTable != NULL)) {
             //
             // Double check in-case we need to expand the table.
             //
             if (index > NumKeys) {
                pFilePrintTableTmp = (LPTSTR *) LocalReAlloc(FilePrintTable, sizeof(LPTSTR) * (NumKeys+1), LHND);

                if (pFilePrintTableTmp == NULL)
                {
                    Status = ERROR_NOT_ENOUGH_MEMORY;
                    break;
                } else
                {
                    NumKeys++;
                    FilePrintTable = pFilePrintTableTmp;
                }
             }

             //
             // Now read in the key name and add it to the table
             //
             KeySize = KEY_NAME_SIZE;
             Status = RegEnumKeyEx(hKey2, index, KeyText, &KeySize, NULL, NULL, NULL, &LastWrite);
             if (Status == ERROR_SUCCESS) {
                 //
                 // Allocate space in our table and copy the key
                 //
                 FilePrintTable[index] = (LPTSTR) LocalAlloc(LPTR, (KeySize + 1) * sizeof(TCHAR));
                 
                 if (FilePrintTable[index] != NULL) {
                     lstrcpy(FilePrintTable[index], KeyText);
                     index++;
                 } else
                     Status = ERROR_NOT_ENOUGH_MEMORY;

             }
         }
      }
#ifdef DEBUG
        else {
           dprintf(TEXT("LLS FilePrintTable Error: 0x%lx\n"), Status);
        }
#endif

      RegCloseKey( hKey2 );
   }

   if (FilePrintTable != NULL)
      NumFilePrintEntries = index;
   else
      NumFilePrintEntries = 0;

   return Status;

} // FilePrintTableInit


/////////////////////////////////////////////////////////////////////////
NTSTATUS
RegistryMonitor (
    IN PVOID ThreadParameter
    )

/*++

Routine Description:

   Watches for any changes in the Licensing Keys, and if any updates our
   internal information.

Arguments:

    ThreadParameter - Indicates how many active threads there currently
        are.

Return Value:

   None.

--*/

{
   LONG Status = 0;
   HKEY hKey1 = NULL;
   HKEY hKey2 = NULL;
   NTSTATUS NtStatus = STATUS_SUCCESS;
   static const TCHAR RegKeyText1[] = TEXT("System\\CurrentControlSet\\Services\\LicenseService");
   static const TCHAR RegKeyText2[] = TEXT("System\\CurrentControlSet\\Services\\LicenseInfo");
   HANDLE Events[2];
   DWORD dwWhichEvent = 0;      // Keeps track of which event was last triggered

   //
   // Open registry key-name we are looking for
   //

   if ((Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText1, 0, KEY_ALL_ACCESS, &hKey1)) != ERROR_SUCCESS) {
#if DBG
      dprintf(TEXT("LLS RegistryMonitor - RegOpenKeyEx failed: 0x%lX\n"), Status);
#endif
      return (NTSTATUS) Status;
   }

   if ((Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText2, 0, KEY_ALL_ACCESS, &hKey2)) != ERROR_SUCCESS) {
#if DBG
      dprintf(TEXT("LLS RegistryMonitor - RegOpenKeyEx 2 failed: 0x%lX\n"), Status);
#endif
      RegCloseKey(hKey1);

      return (NTSTATUS) Status;
   }

   if ((Status = NtCreateEvent(Events,EVENT_QUERY_STATE | EVENT_MODIFY_STATE | SYNCHRONIZE,NULL,SynchronizationEvent,FALSE)) != ERROR_SUCCESS)
   {
#if DBG
      dprintf(TEXT("LLS RegistryMonitor - RegOpenKeyEx 2 failed: 0x%lX\n"), Status);
#endif
      RegCloseKey(hKey1);
      RegCloseKey(hKey2);

      return (NTSTATUS) Status;
   }

   Events[1] = LLSRegistryEvent;

   //
   // Loop forever
   //
   for ( ; ; ) {

      if ((dwWhichEvent == 0) || (dwWhichEvent == 2))
      {
          Status = RegNotifyChangeKeyValue(hKey1, TRUE, REG_NOTIFY_CHANGE_LAST_SET, LLSRegistryEvent, TRUE);
          if (Status != ERROR_SUCCESS) {
#if DBG
              dprintf(TEXT("LLS RegNotifyChangeKeyValue Failed: %lu\n"), Status);
#endif
          }
      }

      if ((dwWhichEvent == 0) || (dwWhichEvent == 1))
      {
          Status = RegNotifyChangeKeyValue(hKey2, TRUE, REG_NOTIFY_CHANGE_LAST_SET, Events[0], TRUE);
          if (Status != ERROR_SUCCESS) {
#if DBG
              dprintf(TEXT("LLS RegNotifyChangeKeyValue 2 Failed: %lu\n"), Status);
#endif
          }
      }

      NtStatus = NtWaitForMultipleObjects( 2, Events, WaitAny, TRUE, NULL );

      switch (NtStatus)
      {
          case 0:
              dwWhichEvent = 1;
              break;
          case 1:
              dwWhichEvent = 2;
              break;
          default:
              dwWhichEvent = 0;
              break;
      }

#if DELAY_INITIALIZATION
      EnsureInitialized();
#endif

      //
      // Re-synch the lists
      //
      LocalServiceListUpdate();
      LocalServerServiceListUpdate();
      ServiceListResynch();
      ConfigInfoRegistryUpdate();
      LocalServiceListConcurrentLimitSet();

      if (dwWhichEvent == 0)
      {
#if DBG
         dprintf(TEXT("LLS Registry Event Notification Failed: %lu\n"), NtStatus);
#endif
         //
         // If we failed - sleep for 2 minutes before looping
         //
         Sleep(120000L);
      }
   }

   return NtStatus;

} // RegistryMonitor


/////////////////////////////////////////////////////////////////////////
VOID
RegistryInit( )

/*++

Routine Description:

   Looks in registry for given service and sets values accordingly.

Arguments:

Return Value:

   None.

--*/

{
   NTSTATUS       Status;
   BOOL           ret = FALSE;
   DWORD          Mode, ConcurrentLimit;

   Mode = 0;
   ConcurrentLimit = 0;

   //
   // Create a key to tell us about any changes in the registry
   //
   Status = NtCreateEvent(
                &LLSRegistryEvent,
                EVENT_QUERY_STATE | EVENT_MODIFY_STATE | SYNCHRONIZE,
                NULL,
                SynchronizationEvent,
                FALSE
                );

   ASSERT(NT_SUCCESS(Status));

} // RegistryInit


/////////////////////////////////////////////////////////////////////////
VOID
RegistryStartMonitor( )

/*++

Routine Description:

   Looks in registry for given service and sets values accordingly.

Arguments:

Return Value:

   None.

--*/

{
   HANDLE Thread;
   DWORD Ignore;

   //
   // Now dispatch a thread to watch for any registry changes
   //
   Thread = CreateThread(
                 NULL,
                 0L,
                 (LPTHREAD_START_ROUTINE) RegistryMonitor,
                 0L,
                 0L,
                 &Ignore
                 );

   if (Thread != NULL)
       CloseHandle(Thread);

} // RegistryStartMonitor


/////////////////////////////////////////////////////////////////////////
VOID
RegistryInitValues(
   LPTSTR ServiceName,
   BOOL *PerSeatLicensing,
   ULONG *SessionLimit
   )

/*++

Routine Description:

   Looks in registry for given service and sets values accordingly.

Arguments:

   Service Name -

   PerSeatLicensing -

   SessionLimit -

Return Value:

   None.

--*/

{
   HKEY hKey2 = NULL;
   DWORD dwType, dwSize;
   static TCHAR RegKeyText[512];
   LONG Status;
   DWORD Mode, ConcurrentLimit;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: RegistryInitValues\n"));
#endif

#ifdef SPECIAL_USER_LIMIT
   *PerSeatLicensing = FALSE;
   *SessionLimit     = SPECIAL_USER_LIMIT;
#else // #ifdef SPECIAL_USER_LIMIT
   Mode = 0;
   ConcurrentLimit = 0;

   //
   // Create registry key-name we are looking for
   //
   lstrcpy(RegKeyText, TEXT("System\\CurrentControlSet\\Services\\LicenseInfo\\"));
   lstrcat(RegKeyText, ServiceName);

   if ((Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText, 0, KEY_READ, &hKey2)) == ERROR_SUCCESS) {
      //
      // First Get Mode
      //
      dwSize = sizeof(Mode);
      Status = RegQueryValueEx(hKey2, TEXT("Mode"), NULL, &dwType, (LPBYTE) &Mode, &dwSize);

#if DBG
      if ((TraceFlags & TRACE_REGISTRY) && (Status == ERROR_SUCCESS))
         dprintf(TEXT("Found Reg-Key for [%s] Mode: %ld\n"), ServiceName, Mode);
#endif
      //
      // Now Concurrent Limit
      //
      dwSize = sizeof(ConcurrentLimit);
      Status = RegQueryValueEx(hKey2, TEXT("ConcurrentLimit"), NULL, &dwType, (LPBYTE) &ConcurrentLimit, &dwSize);

#if DBG
      if ((TraceFlags & TRACE_REGISTRY) && (Status == ERROR_SUCCESS))
         dprintf(TEXT("Found Reg-Key for [%s] ConcurrentLimit: %ld\n"), ServiceName, ConcurrentLimit);
#endif
      RegCloseKey(hKey2);

   }


   if (Mode == 0) {
      *PerSeatLicensing = TRUE;
      *SessionLimit = 0;
   } else {
      *PerSeatLicensing = FALSE;
      *SessionLimit = ConcurrentLimit;
   }
#endif // #else // #ifdef SPECIAL_USER_LIMIT
} // RegistryInitValues


/////////////////////////////////////////////////////////////////////////
VOID
RegistryDisplayNameGet(
   LPTSTR ServiceName,
   LPTSTR DefaultName,
   LPTSTR *pDisplayName
   )

/*++

Routine Description:


Arguments:

   Service Name -

Return Value:

   None.

--*/

{
   HKEY                    hKey2 = NULL;
   DWORD                   dwType, dwSize;
   static TCHAR            RegKeyText[512];
   static TCHAR            DisplayName[512];
   LONG                    Status;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: RegistryDisplayNameGet\n"));
#endif

   lstrcpy(DisplayName, DefaultName);

   //
   // Create registry key-name we are looking for
   //
   lstrcpy(RegKeyText, TEXT("System\\CurrentControlSet\\Services\\LicenseInfo\\"));
   lstrcat(RegKeyText, ServiceName);

   if ((Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText, 0, KEY_READ, &hKey2)) == ERROR_SUCCESS) {
      dwSize = sizeof(DisplayName);
      Status = RegQueryValueEx(hKey2, TEXT("DisplayName"), NULL, &dwType, (LPBYTE) DisplayName, &dwSize);

#  if DBG
      if ((TraceFlags & TRACE_REGISTRY) && (Status == ERROR_SUCCESS))
         dprintf(TEXT("Found Reg-Key for [%s] DisplayName: %s\n"), ServiceName, DisplayName);
#  endif
      RegCloseKey(hKey2);

   }

   *pDisplayName = LocalAlloc(LPTR, (lstrlen(DisplayName) + 1) * sizeof(TCHAR));
   if (*pDisplayName != NULL)
      lstrcpy(*pDisplayName, DisplayName);

} // RegistryDisplayNameGet


/////////////////////////////////////////////////////////////////////////
VOID
RegistryFamilyDisplayNameGet(
   LPTSTR ServiceName,
   LPTSTR DefaultName,
   LPTSTR *pDisplayName
   )

/*++

Routine Description:


Arguments:

   Service Name -

Return Value:

   None.

--*/

{
   HKEY hKey2 = NULL;
   DWORD dwType, dwSize;
   static TCHAR RegKeyText[512];
   static TCHAR DisplayName[MAX_PATH + 1];
   LONG Status;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: RegistryFamilyDisplayNameGet\n"));
#endif

   lstrcpy(DisplayName, DefaultName);

   //
   // Create registry key-name we are looking for
   //
   lstrcpy(RegKeyText, TEXT("System\\CurrentControlSet\\Services\\LicenseInfo\\"));
   lstrcat(RegKeyText, ServiceName);

   if ((Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText, 0, KEY_READ, &hKey2)) == ERROR_SUCCESS) {
      dwSize = sizeof(DisplayName);
      Status = RegQueryValueEx(hKey2, TEXT("FamilyDisplayName"), NULL, &dwType, (LPBYTE) DisplayName, &dwSize);

#  if DBG
      if ((TraceFlags & TRACE_REGISTRY) && (Status == ERROR_SUCCESS))
         dprintf(TEXT("Found Reg-Key for [%s] FamilyDisplayName: %s\n"), ServiceName, DisplayName);
#  endif
      RegCloseKey(hKey2);

   }

   *pDisplayName = LocalAlloc(LPTR, (lstrlen(DisplayName) + 1) * sizeof(TCHAR));
   if (*pDisplayName != NULL)
      lstrcpy(*pDisplayName, DisplayName);
} // RegistryFamilyDisplayNameGet


/////////////////////////////////////////////////////////////////////////
LPTSTR
ServiceFindInTable(
   LPTSTR ServiceName,
   const LPTSTR Table[],
   ULONG TableSize,
   ULONG *TableIndex
   )

/*++

Routine Description:

   Does search of table to find matching service name.

Arguments:

   Service Name -

   Table -

   TableSize -

   TableIndex -

Return Value:

   Pointer to found service or NULL if not found.

--*/

{
   ULONG i = 0;
   BOOL Found = FALSE;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: ServiceFindInTable\n"));
#endif
   while ((i < TableSize) && (!Found)) {
      Found = !lstrcmpi(ServiceName, Table[i]);
      i++;
   }

   if (Found) {
      i--;
      *TableIndex = i;
      return Table[i];
   } else
      return NULL;

} // ServiceFindInTable


/////////////////////////////////////////////////////////////////////////
VOID
RegistryInitService(
   LPTSTR ServiceName,
   BOOL *PerSeatLicensing,
   ULONG *SessionLimit
   )

/*++

Routine Description:

   Gets init values for a given service from the registry.  If not found
   then just returns default values.

Arguments:

   ServiceName -

   PerSeatLicensing -

   SessionLimit -

Return Value:

--*/

{
   //
   // These are the default values
   //
   ULONG TableEntry;
   LPTSTR SvcName = NULL;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: RegistryInitService\n"));
#endif
   *PerSeatLicensing = FALSE;
   *SessionLimit = 0;

   //
   // Check if it is a file/print service - if so don't worry about rest
   // of registry entries.
   //
   if (ServiceFindInTable(ServiceName, FilePrintTable, NumFilePrintEntries, &TableEntry)) {
      return;
   }

   //
   // Not FilePrint - see if we need to map the name.
   //
   SvcName = ServiceFindInTable(ServiceName, NameMappingTable2, NUM_MAPPING_ENTRIES, &TableEntry);

   // if it wasn't found, use original ServiceName
   if (SvcName == NULL)
      SvcName = ServiceName;

    RegistryInitValues(SvcName, PerSeatLicensing, SessionLimit);

#if DBG
      if (TraceFlags & TRACE_REGISTRY)
         if (*PerSeatLicensing)
            dprintf(TEXT("LLS - Registry Init: PerSeat: Y Svc: %s\n"), SvcName);
         else
            dprintf(TEXT("LLS - Registry Init: PerSeat: N Svc: %s\n"), SvcName);
#endif

} // RegistryInitService



/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////



/////////////////////////////////////////////////////////////////////////
NTSTATUS
LocalServiceListInit()

/*++

Routine Description:

Arguments:

   None.

Return Value:

   None.

--*/

{
   NTSTATUS status = STATUS_SUCCESS;

   try
   {
       RtlInitializeResource(&LocalServiceListLock);
   } except(EXCEPTION_EXECUTE_HANDLER ) {
       status = GetExceptionCode();
   }

   if (!NT_SUCCESS(status))
       return status;

   //
   // Now scan the registry and add all the services
   //
   LocalServiceListUpdate();

   return STATUS_SUCCESS;
} // LocalServiceListInit


/////////////////////////////////////////////////////////////////////////
int __cdecl LocalServiceListCompare(const void *arg1, const void *arg2) {
   PLOCAL_SERVICE_RECORD Svc1, Svc2;

   Svc1 = (PLOCAL_SERVICE_RECORD) *((PLOCAL_SERVICE_RECORD *) arg1);
   Svc2 = (PLOCAL_SERVICE_RECORD) *((PLOCAL_SERVICE_RECORD *) arg2);

   return lstrcmpi( Svc1->Name, Svc2->Name );

} // LocalServiceListCompare


/////////////////////////////////////////////////////////////////////////
PLOCAL_SERVICE_RECORD
LocalServiceListFind(
   LPTSTR Name
   )

/*++

Routine Description:

   Internal routine to actually do binary search on LocalServiceList, this
   does not do any locking as we expect the wrapper routine to do this.
   The search is a simple binary search.

Arguments:

   ServiceName -

Return Value:

   Pointer to found server table entry or NULL if not found.

--*/

{
   LONG begin = 0;
   LONG end = (LONG) LocalServiceListSize - 1;
   LONG cur;
   int match;
   PLOCAL_SERVICE_RECORD Service;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: LocalServiceListFind\n"));
#endif

   if ((LocalServiceListSize == 0) || (Name == NULL))
      return NULL;

   while (end >= begin) {
      // go halfway in-between
      cur = (begin + end) / 2;
      Service = LocalServiceList[cur];

      // compare the two result into match
      match = lstrcmpi(Name, Service->Name);

      if (match < 0)
         // move new begin
         end = cur - 1;
      else
         begin = cur + 1;

      if (match == 0)
         return Service;
   }

   return NULL;

} // LocalServiceListFind


/////////////////////////////////////////////////////////////////////////
PLOCAL_SERVICE_RECORD
LocalServiceListAdd(
   LPTSTR Name,
   LPTSTR DisplayName,
   LPTSTR FamilyDisplayName,
   DWORD ConcurrentLimit,
   DWORD FlipAllow,
   DWORD Mode,
   DWORD HighMark
   )

/*++

Routine Description:


Arguments:

   ServiceName -

Return Value:

   Pointer to added service table entry, or NULL if failed.

--*/

{
   LPTSTR NewName;
   PLOCAL_SERVICE_RECORD Service;
   PLOCAL_SERVICE_RECORD *pLocalServiceListTmp;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: LocalServiceListAdd\n"));
#endif

   if ((Name == NULL) || (*Name == TEXT('\0'))) {
#if DBG
      dprintf(TEXT("Error LLS: LocalServiceListAdd Bad Parms\n"));
#endif
      ASSERT(FALSE);
      return NULL;
   }

   //
   // Try to find the name
   //
   Service = LocalServiceListFind(Name);
   if (Service != NULL) {
      Service->ConcurrentLimit = ConcurrentLimit;
      Service->FlipAllow = FlipAllow;
      Service->Mode = Mode;
      return Service;
   }

   //
   // No record - so create a new one
   //
   if (LocalServiceList == NULL) {
      pLocalServiceListTmp = (PLOCAL_SERVICE_RECORD *) LocalAlloc(LPTR, sizeof(PLOCAL_SERVICE_RECORD));
   } else {
      pLocalServiceListTmp = (PLOCAL_SERVICE_RECORD *) LocalReAlloc(LocalServiceList, sizeof(PLOCAL_SERVICE_RECORD) * (LocalServiceListSize + 1), LHND);
   }

   //
   // Make sure we could allocate server table
   //
   if (pLocalServiceListTmp == NULL) {
      return NULL;
   } else {
       LocalServiceList = pLocalServiceListTmp;
   }

   //
   // Allocate space for Record.
   //
   Service = (PLOCAL_SERVICE_RECORD) LocalAlloc(LPTR, sizeof(LOCAL_SERVICE_RECORD));
   if (Service == NULL) {
      ASSERT(FALSE);
      return NULL;
   }

   LocalServiceList[LocalServiceListSize] = Service;

   //
   // Name
   //
   NewName = (LPTSTR) LocalAlloc(LPTR, (lstrlen(Name) + 1) * sizeof(TCHAR));
   if (NewName == NULL) {
      ASSERT(FALSE);
      LocalFree(Service);
      return NULL;
   }

   // now copy it over...
   Service->Name = NewName;
   lstrcpy(NewName, Name);

   //
   // DisplayName
   //
   NewName = (LPTSTR) LocalAlloc(LPTR, (lstrlen(DisplayName) + 1) * sizeof(TCHAR));
   if (NewName == NULL) {
      ASSERT(FALSE);
      LocalFree(Service->Name);
      LocalFree(Service);
      return NULL;
   }

   // now copy it over...
   Service->DisplayName = NewName;
   lstrcpy(NewName, DisplayName);

   //
   // FamilyDisplayName
   //
   NewName = (LPTSTR) LocalAlloc(LPTR, (lstrlen(FamilyDisplayName) + 1) * sizeof(TCHAR));
   if (NewName == NULL) {
      ASSERT(FALSE);
      LocalFree(Service->Name);
      LocalFree(Service->DisplayName);
      LocalFree(Service);
      return NULL;
   }

   // now copy it over...
   Service->FamilyDisplayName = NewName;
   lstrcpy(NewName, FamilyDisplayName);

   //
   // Initialize other stuff
   //
   Service->ConcurrentLimit = ConcurrentLimit;
   Service->FlipAllow = FlipAllow;
   Service->Mode = Mode;
   Service->HighMark = HighMark;

   LocalServiceListSize++;

   // Have added the entry - now need to sort it in order of the service names
   qsort((void *) LocalServiceList, (size_t) LocalServiceListSize, sizeof(PLOCAL_SERVICE_RECORD), LocalServiceListCompare);

   return Service;

} // LocalServiceListAdd


/////////////////////////////////////////////////////////////////////////
VOID
LocalServiceListUpdate( )

/*++

Routine Description:

   Looks in registry for given service and sets values accordingly.

Arguments:

Return Value:

   None.

--*/

{
   HKEY hKey2 = NULL;
   HKEY hKey3 = NULL;
   static TCHAR KeyName[MAX_PATH + 1];
   static TCHAR DisplayName[MAX_PATH + 1];
   static TCHAR FamilyDisplayName[MAX_PATH + 1];
   LONG EnumStatus;
   NTSTATUS Status;
   DWORD iSubKey = 0;
   DWORD dwType, dwSize;
   DWORD ConcurrentLimit, FlipAllow, Mode, HighMark;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: LocalServiceListUpdate\n"));
#endif

   RtlAcquireResourceExclusive(&LocalServiceListLock, TRUE);

   EnumStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("System\\CurrentControlSet\\Services\\LicenseInfo"), 0, KEY_READ, &hKey2);

   while (EnumStatus == ERROR_SUCCESS) {
      EnumStatus = RegEnumKey(hKey2, iSubKey, KeyName, MAX_PATH + 1);
      iSubKey++;

      if (EnumStatus == ERROR_SUCCESS) {
         if ((Status = RegOpenKeyEx(hKey2, KeyName, 0, KEY_READ, &hKey3)) == ERROR_SUCCESS) {
            dwSize = sizeof(DisplayName);
            Status = RegQueryValueEx(hKey3, TEXT("DisplayName"), NULL, &dwType, (LPBYTE) DisplayName, &dwSize);

            dwSize = sizeof(FamilyDisplayName);
            if (Status == ERROR_SUCCESS) {
               Status = RegQueryValueEx(hKey3, TEXT("FamilyDisplayName"), NULL, &dwType, (LPBYTE) FamilyDisplayName, &dwSize);

               if (Status != ERROR_SUCCESS) {
                  lstrcpy(FamilyDisplayName, DisplayName);
                  Status = ERROR_SUCCESS;
               }
            }

            dwSize = sizeof(Mode);
            if (Status == ERROR_SUCCESS)
               Status = RegQueryValueEx(hKey3, TEXT("Mode"), NULL, &dwType, (LPBYTE) &Mode, &dwSize);

            dwSize = sizeof(FlipAllow);
            if (Status == ERROR_SUCCESS)
               Status = RegQueryValueEx(hKey3, TEXT("FlipAllow"), NULL, &dwType, (LPBYTE) &FlipAllow, &dwSize);

            dwSize = sizeof(ConcurrentLimit);
            if (Status == ERROR_SUCCESS)
               if (Mode == 0)
                  ConcurrentLimit = 0;
               else
                  Status = RegQueryValueEx(hKey3, TEXT("ConcurrentLimit"), NULL, &dwType, (LPBYTE) &ConcurrentLimit, &dwSize);

            dwSize = sizeof(HighMark);
            if (Status == ERROR_SUCCESS) {
               Status = RegQueryValueEx(hKey3, TEXT("LocalKey"), NULL, &dwType, (LPBYTE) &HighMark, &dwSize);
               if (Status != ERROR_SUCCESS) {
                  Status = ERROR_SUCCESS;
                  HighMark = 0;
               }
            }

            //
            // If we read in everything then add to our table
            //
            if (Status == ERROR_SUCCESS)
               LocalServiceListAdd(KeyName, DisplayName, FamilyDisplayName, ConcurrentLimit, FlipAllow, Mode, HighMark);

            RegCloseKey(hKey3);
         }
      }
   }

   RegCloseKey(hKey2);

   RtlReleaseResource(&LocalServiceListLock);
} // LocalServiceListUpdate


/////////////////////////////////////////////////////////////////////////
VOID
LocalServiceListHighMarkSet( )

/*++

Routine Description:

Arguments:

Return Value:

   None.

--*/

{
   HKEY hKey2 = NULL;
   static TCHAR RegKeyText[512];
   LONG Status;
   ULONG i, j;
   PSERVICE_RECORD Service;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: LocalServiceListHighMarkSet\n"));
#endif

   RtlAcquireResourceExclusive(&LocalServiceListLock, TRUE);

   for (i = 0; i < LocalServiceListSize; i++) {
      RtlAcquireResourceShared(&ServiceListLock, TRUE);
      j = 0;
      Service = NULL;

      while ( (j < ServiceListSize) && (Service == NULL) ) {
         if (!lstrcmpi(LocalServiceList[i]->DisplayName, ServiceList[j]->DisplayName) )
            Service = ServiceList[j];

         j++;
      }

      RtlReleaseResource(&ServiceListLock);

      if (Service != NULL) {
         //
         // Create registry key-name we are looking for
         //
         lstrcpy(RegKeyText, TEXT("System\\CurrentControlSet\\Services\\LicenseInfo\\"));
         lstrcat(RegKeyText, LocalServiceList[i]->Name);

         Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText, 0, KEY_WRITE, &hKey2);

         if (Status == ERROR_SUCCESS)
         {
            Status = RegSetValueEx(hKey2, TEXT("LocalKey"), 0, REG_DWORD, (LPBYTE) &Service->HighMark, sizeof(Service->HighMark));
            RegCloseKey( hKey2 );
         }
      }
   }

   RtlReleaseResource(&LocalServiceListLock);
} // LocalServiceListHighMarkSet


///////////////////////////////////////////////////////////////////////////////
VOID
LocalServiceListConcurrentLimitSet( )

/*++

Routine Description:

   Write concurrent limit to the registry for all secure services.

   Modified from LocalServiceListHighMarkSet() implementation.

Arguments:

   None.

Return Value:

   None.

--*/

{
   HKEY hKey2 = NULL;
   TCHAR RegKeyText[512];
   LONG Status;
   ULONG i, j;

#if DBG
   if (TraceFlags & TRACE_FUNCTION_TRACE)
      dprintf(TEXT("LLS TRACE: LocalServiceListConcurrentLimitSet\n"));
#endif

   RtlAcquireResourceExclusive(&LocalServiceListLock, TRUE);

   for (i = 0; i < LocalServiceListSize; i++)
   {
      //
      // Create registry key-name we are looking for
      //
      lstrcpy(RegKeyText, TEXT("System\\CurrentControlSet\\Services\\LicenseInfo\\"));
      lstrcat(RegKeyText, LocalServiceList[i]->Name);

      Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText, 0, KEY_READ, &hKey2);

      if (Status == ERROR_SUCCESS)
      {
         DWORD    dwConcurrentLimit;
         DWORD    cbConcurrentLimit = sizeof( dwConcurrentLimit );
         DWORD    dwType;

         // don't write unless we have to (to avoid triggering the registry monitor thread)
         Status = RegQueryValueEx(hKey2, TEXT("ConcurrentLimit"), NULL, &dwType, (LPBYTE) &dwConcurrentLimit, &cbConcurrentLimit );

         if ( ServiceIsSecure( LocalServiceList[i]->DisplayName ) )
         {
            LocalServiceList[i]->ConcurrentLimit = LocalServiceList[i]->Mode ? ProductLicensesGet( LocalServiceList[i]->DisplayName, TRUE ) : 0;

            // secure product
            if (    ( ERROR_SUCCESS != Status )
                 || ( REG_DWORD != dwType )
                 || ( dwConcurrentLimit != LocalServiceList[i]->ConcurrentLimit ) )
            {
               RegCloseKey( hKey2 );
               Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKeyText, 0, KEY_WRITE, &hKey2);

               ASSERT( ERROR_SUCCESS == Status );
               if ( ERROR_SUCCESS == Status )
               {
                  Status = RegSetValueEx(hKey2, TEXT("ConcurrentLimit"), 0, REG_DWORD, (LPBYTE) &LocalServiceList[i]->ConcurrentLimit, sizeof( LocalServiceList[i]->ConcurrentLimit ) );
               }
            }
         }

         RegCloseKey( hKey2 );
      }
   }

   RtlReleaseResource(&LocalServiceListLock);
} // LocalServiceListConcurrentLimitSet


///////////////////////////////////////////////////////////////////////////////
BOOL ServiceIsSecure( LPTSTR ServiceName )

/*++

Routine Description:

   Determine whether a given service disallows 3.51 Honesty-style
   license purchases.

Arguments:

   ServiceName (LPTSTR)
      Service to check.

Return Value:

   TRUE if service requires secure certificate,
   FALSE if it accepts 3.51 Honesty-style license purchases.

--*/

{
   BOOL  IsSecure = FALSE;

   if ( NULL != SecureServiceList )
   {
      DWORD    i;

      RtlEnterCriticalSection( &ConfigInfoLock );

      for ( i=0; i < SecureServiceListSize; i++ )
      {
         if ( !lstrcmpi( SecureServiceList[i], ServiceName ) )
         {
            IsSecure = TRUE;
            break;
         }
      }

      RtlLeaveCriticalSection( &ConfigInfoLock );
   }

   return IsSecure;
}


///////////////////////////////////////////////////////////////////////////////
NTSTATUS ServiceSecuritySet( LPTSTR ServiceName )

/*++

Routine Description:

   Add a given service to the secure service list.

Arguments:

   ServiceName (LPTSTR)
      Service to add.

Return Value:

   STATUS_SUCCESS or Win error or NTSTATUS error code.

--*/

{
   NTSTATUS    nt;
   DWORD       i;
   BOOL        bChangedValue = FALSE;

   RtlEnterCriticalSection( &ConfigInfoLock );

   for ( i=0; i < SecureServiceListSize; i++ )
   {
      if ( !lstrcmpi( SecureServiceList[i], ServiceName ) )
      {
         // product already registered as secure
         break;
      }
   }

   if ( i < SecureServiceListSize )
   {
      // product already registered as secure
      nt = STATUS_SUCCESS;
   }
   else
   {
      TCHAR *     NewSecureServiceBuffer;
      ULONG       NewSecureServiceBufferSize;

      NewSecureServiceBufferSize = ( SecureServiceBufferSize ? SecureServiceBufferSize : sizeof( TCHAR ) ) + sizeof( TCHAR ) * ( 1 + lstrlen( ServiceName ) );
      NewSecureServiceBuffer     = LocalAlloc( LPTR, NewSecureServiceBufferSize );

      if ( NULL == NewSecureServiceBuffer )
      {
         nt = STATUS_NO_MEMORY;
         ASSERT( FALSE );
      }
      else
      {
         if ( NULL != SecureServiceBuffer )
         {
            // copy over current secure service strings
            memcpy( NewSecureServiceBuffer, SecureServiceBuffer, SecureServiceBufferSize - sizeof( TCHAR ) );

            // add new secure service (don't forget last string is followed by 2 nulls)
            memcpy( (LPBYTE) NewSecureServiceBuffer + SecureServiceBufferSize - sizeof( TCHAR ), ServiceName, NewSecureServiceBufferSize - SecureServiceBufferSize - sizeof( TCHAR ) );
         }
         else
         {
            // add new secure service (don't forget last string is followed by 2 nulls)
            memcpy( NewSecureServiceBuffer, ServiceName, NewSecureServiceBufferSize - sizeof( TCHAR ) );
         }

         ASSERT( 0 == *( (LPBYTE) NewSecureServiceBuffer + NewSecureServiceBufferSize - 2 * sizeof( TCHAR ) ) );
         ASSERT( 0 == *( (LPBYTE) NewSecureServiceBuffer + NewSecureServiceBufferSize -     sizeof( TCHAR ) ) );

         // encrypt buffer
         nt = EBlock( NewSecureServiceBuffer, NewSecureServiceBufferSize );
         ASSERT( STATUS_SUCCESS == nt );

         if ( STATUS_SUCCESS == nt )
         {
            HKEY     hKeyParameters;

            // save new list to registry
            nt = RegOpenKeyEx( HKEY_LOCAL_MACHINE, TEXT("System\\CurrentControlSet\\Services\\LicenseService\\Parameters"), 0, KEY_WRITE, &hKeyParameters );
            ASSERT( STATUS_SUCCESS == nt );

            if ( STATUS_SUCCESS == nt )
            {
               nt = RegSetValueEx( hKeyParameters, TEXT( "ProductData" ), 0, REG_BINARY, (LPBYTE) NewSecureServiceBuffer, NewSecureServiceBufferSize );
               ASSERT( STATUS_SUCCESS == nt );

               if ( STATUS_SUCCESS == nt )
               {
                  bChangedValue = TRUE;
               }
            }

            RegCloseKey( hKeyParameters );
         }

         LocalFree( NewSecureServiceBuffer );
      }
   }

   RtlLeaveCriticalSection( &ConfigInfoLock );

   if ( ( STATUS_SUCCESS == nt ) && bChangedValue )
   {
      // key updated, now update internal copy
      ConfigInfoRegistryUpdate();
   }

   return nt;
}


///////////////////////////////////////////////////////////////////////////////
NTSTATUS ProductSecurityPack( LPDWORD pcchProductSecurityStrings, WCHAR ** ppchProductSecurityStrings )

/*++

Routine Description:

   Pack the secure service list into a contiguous buffer for transmission.

   NOTE: If the routine succeeds, the caller must later MIDL_user_free() the
   buffer at *ppchProductSecurityStrings.

Arguments:

   pcchProductSecurityStrings (LPDWORD)
      On return, holds the size (in characters) of the buffer pointed to
      by *ppchProductSecurityStrings.
   ppchProductSecurityStrings (WCHAR **)
      On return, holds the address of the buffer allocated to hold the names
      of the secure products.

Return Value:

   STATUS_SUCCESS or STATUS_NO_MEMORY.

--*/

{
   NTSTATUS    nt;

   RtlEnterCriticalSection( &ConfigInfoLock );

   *ppchProductSecurityStrings = MIDL_user_allocate( SecureServiceBufferSize );

   if ( NULL == *ppchProductSecurityStrings )
   {
      nt = STATUS_NO_MEMORY;
      ASSERT( FALSE );
   }
   else
   {
      memcpy( *ppchProductSecurityStrings, SecureServiceBuffer, SecureServiceBufferSize );
      *pcchProductSecurityStrings = SecureServiceBufferSize / sizeof( TCHAR );

      nt = STATUS_SUCCESS;
   }

   RtlLeaveCriticalSection( &ConfigInfoLock );

   return nt;
}


///////////////////////////////////////////////////////////////////////////////
NTSTATUS ProductSecurityUnpack( DWORD cchProductSecurityStrings, WCHAR * pchProductSecurityStrings )

/*++

Routine Description:

   Unpack a secure service list packed by ProductSecurityPack().  The products
   contained in the pack are added to the current secure product list.

Arguments:

   cchProductSecurityStrings (DWORD)
      The size (in characters) of the buffer pointed to by pchProductSecurityStrings.
   pchProductSecurityStrings (WCHAR *)
      The address of the buffer allocated to hold the names of the secure products.

Return Value:

   STATUS_SUCCESS.

--*/

{
   DWORD    i;

   for ( i=0;
            ( i < cchProductSecurityStrings )
         && ( TEXT('\0') != pchProductSecurityStrings[i] );
         i += 1 + lstrlen( &pchProductSecurityStrings[i] ) )
   {
      ServiceSecuritySet( &pchProductSecurityStrings[i] );
   }

   return STATUS_SUCCESS;
}

#if DBG
///////////////////////////////////////////////////////////////////////////////
void ProductSecurityListDebugDump()

/*++

Routine Description:

   Dump contents of product security list to debug console.

Arguments:

   None.

Return Value:

   None.

--*/

{
   if ( NULL == SecureServiceList )
   {
      dprintf( TEXT( "No secure products.\n" ) );
   }
   else
   {
      DWORD    i;

      RtlEnterCriticalSection( &ConfigInfoLock );

      for ( i=0; i < SecureServiceListSize; i++ )
      {
         dprintf( TEXT( "(%3ld) %s\n" ), (long)i, SecureServiceList[i] );
      }

      RtlLeaveCriticalSection( &ConfigInfoLock );
   }
}
#endif