//****************************************************************************
//
//                     Terminal Server CDmodem
//
//
// Copyright 1996, Citrix Systems Inc.
// Copyright (C) 1994-95 Microsft Corporation. All rights reserved.
//
//  Filename: init.c
//
//  Revision History
//
//  Nov 1998       updated by Qunbiao Guo for terminal server
//  Mar  28 1992   Gurdeep Singh Pall  Created for RASMAN
//
//  Description: This file contains init code for cdmodem
//
//****************************************************************************


#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>

#include <windows.h>
#include <tapi.h>
#include <rasndis.h>
#include <wanioctl.h>
#include <rasman.h>
#include <raserror.h>
#include <eventlog.h>

#include <media.h>
#include <device.h>
#include <rasmxs.h>
#include <isdn.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include "rastapi.h"

#ifdef CITRIX
#include <winstaw.h>
#include <icadd.h>
#include <icaapi.h>
#include "cdmodem.h"
#endif // CITRIX

#ifdef CITRIX
#ifdef DBG
#define DBGPRINT(_arg) DbgPrint _arg
#else
#define DBGPRINT(_arg) { }
#endif
#endif // CITRIX

#pragma warning (error:4312)

HLINEAPP RasLine = 0 ;
HINSTANCE   RasInstance = 0 ;
TapiLineInfo   *RasTapiLineInfo ;
DWORD    TotalLines = 0 ;
DWORD    TotalPorts ;
TapiPortControlBlock *RasPorts ;
TapiPortControlBlock *RasPortsEnd ;
//DWORD     NegotiatedApiVersion ;
//DWORD     NegotiatedExtVersion ;
HANDLE      RasTapiMutex ;
BOOL     Initialized = FALSE ;
DWORD       TapiThreadId    ;
HANDLE      TapiThreadHandle;
DWORD       LoaderThreadId;
DWORD     ValidPorts = 0;
DWORD     NumberOfRings = 1 ;

HANDLE      ghAsyMac = INVALID_HANDLE_VALUE ;

//DWORD  EnumerateTapiPorts () ;
BOOL  ReadUsageInfoFromRegistry() ;
TapiLineInfo *FindLineByHandle (HLINE) ;
TapiPortControlBlock *FindPortByRequestId (DWORD) ;
TapiPortControlBlock *FindPortByAddressId (TapiLineInfo *, DWORD) ;
TapiPortControlBlock *FindPortByAddress   (CHAR *) ;
DWORD InitiatePortDisconnection (TapiPortControlBlock *hIOPort) ;
TapiPortControlBlock *FindPortByAddressAndName (CHAR *address, CHAR *name) ;

//* InitTapi()
//
//
//*
BOOL
InitRasTapi (HANDLE hInst, DWORD ul_reason_being_called, LPVOID lpReserved)
{
    STARTUPINFO        startupinfo ;
    DWORD dwErr;
    static BOOLEAN DllInitialized = FALSE ;


    DBGPRINT(( "CDMODEM: InitRasTapi: hInst 0x%x, reason 0x%x\n",
        hInst, ul_reason_being_called ));

   switch (ul_reason_being_called) {

   case DLL_PROCESS_ATTACH:

   if (RasPorts != 0)
       return 1 ;

   RasInstance = hInst ;

   ReadUsageInfoFromRegistry();

   if ((RasTapiMutex = CreateMutex (NULL, FALSE, NULL)) == NULL)
       return 0 ;

   DllInitialized = TRUE ;

   break ;

    case DLL_PROCESS_DETACH:

   //
   // If DLL did not successfully initialize for this process dont try to clean up
   //
   if (!DllInitialized)
       break ;

        lineShutdown (RasLine) ;
        PostThreadMessage (TapiThreadId, WM_QUIT, 0, 0) ;
       break ;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
#ifndef CITRIX
        //
        // If the thread that has created the TAPI
        // message queue thread via EnumerateTapiPorts
        // is exiting, also clean up the TAPI message
        // queue thread at this time.
        //
        // NOTE: We cannot do an explicit WaitForSingleObject()
        // on the TAPI message queue thread because we are
        // in a DLL init proc.  This would cause a deadlock,
        // since the exiting thread has to execute this DLL
        // init proc before exiting.  The TAPI message queue
        // thread actually takes care of the final DLL unload.
        // See EnumerateTapiPorts below.
        //
        if (GetCurrentThreadId() == LoaderThreadId)
           PostThreadMessage (TapiThreadId, WM_QUIT, 0, 0) ;

#endif // CITRIX
   break;

    }

    return 1 ;
}



//* EnumerateTapiPorts()
//
//  Function: First we call line initialize and construct a TLI for each line
//       Then for each line we enumerate addresses and go through each address
//       If the address is configured to be used with RAS we fill in the
//       approp. info into the TPCB for the address (now port).
//
//  Return:   GetLastError(), SUCCESS
//
//*
DWORD
EnumerateTapiPorts (HANDLE event)
{
    WORD     i, k ;
    TapiLineInfo    *nextline ;
    DWORD       lines = 0 ;
    BYTE     buffer[800] ;
    LINEADDRESSCAPS *lineaddrcaps ;
    LINEDEVCAPS       *linedevcaps ;
    CHAR     address[40] ;
    CHAR     devicetype[MAX_DEVICETYPE_NAME] = {0};
    DWORD       devicetypelength;
    CHAR     devicename[MAX_DEVICE_NAME] = {0};
    DWORD       devicenamelength;
    CHAR     szregkey[128];
    LINEEXTENSIONID extensionid ;
    DWORD       totaladdresses ;
#ifndef CITRIX
    TapiPortControlBlock *nextport ;
#else // CITRIX
    DWORD       addresses ;
    TapiPortControlBlock *nextport = NULL, *port ;
#endif // CITRIX
    MSG msg ;
    HINSTANCE hInst;
    TapiPortControlBlock *pports ;
    LINEINITIALIZEEXPARAMS param ;
    DWORD       version = HIGH_VERSION ;

    memset (&param, 0, sizeof (LINEINITIALIZEEXPARAMS)) ;
    param.dwOptions   = LINEINITIALIZEEXOPTION_USEHIDDENWINDOW ;
    param.dwTotalSize = sizeof(param) ;

#ifdef CITRIX
    DBGPRINT(( "CDMODEM: EnumerateTapiPorts: Entry\n" ));
#endif // CITRIX

    if (lineInitializeEx (&RasLine,
                          RasInstance,
                          (LINECALLBACK) RasTapiCallback,
                          REMOTEACCESS_APP,
                          &lines,
                          &version,
                          &param) )
       goto error ;

   if (lines == 0) {
      goto error;
   }

    nextline = RasTapiLineInfo = LocalAlloc (LPTR, sizeof (TapiLineInfo) * lines) ;

    if (nextline == NULL)
       goto error ;

    TotalLines = lines ;
#ifdef CITRIX
    totaladdresses = 0;
#endif // CITRIX

    for (i=0; i<lines; i++) {  // for all lines get the addresses -> ports

      if (lineNegotiateAPIVersion(RasLine,
                                  i,
                                  LOW_VERSION,
                                  HIGH_VERSION,
                                  &nextline->NegotiatedApiVersion,
                                  &extensionid) ) {
#ifdef CITRIX
                   totaladdresses++;
#endif // CITRIX
         nextline->TLI_LineState = PS_UNINITIALIZED ;
         nextline++ ;
         continue ;
      }

      if (lineNegotiateExtVersion(RasLine,
                                  i,
                                  nextline->NegotiatedApiVersion,
                                  LOW_EXT_VERSION,
                                  HIGH_EXT_VERSION,
                                  &nextline->NegotiatedExtVersion)) {

         nextline->NegotiatedExtVersion = 0;
      }

      memset (buffer, 0, sizeof(buffer)) ;

      linedevcaps = (LINEDEVCAPS *)buffer ;
      linedevcaps->dwTotalSize = sizeof (buffer) ;

      // Get a count of all addresses across all lines
      //
      if (lineGetDevCaps (RasLine,
                          i,
                          nextline->NegotiatedApiVersion,
                          nextline->NegotiatedExtVersion,
                          linedevcaps)) {
#ifdef CITRIX
                        totaladdresses += linedevcaps->dwNumAddresses;
#endif // CITRIX
         nextline->TLI_LineState = PS_UNINITIALIZED ;
         nextline++ ;
         continue ;
      }

#ifdef CITRIX
                totaladdresses++;
#endif // CITRIX
      nextline->TLI_LineId = i ; // fill TLI struct. id.
      nextline->TLI_LineState = PS_CLOSED ;

      nextline++ ;
    }
#ifdef CITRIX
    DBGPRINT(( "CDMODEM: ETP: totaladdresses %d\n", totaladdresses));
#endif // CITRIX

    // Now that we know the number of lines and number of addresses per line
    // we now fillin the TPCB structure per address
    //
    for (i=0, nextline = RasTapiLineInfo; i<TotalLines ; i++, nextline++) {

      BOOL fModem = FALSE ;

      if (RasTapiLineInfo[i].TLI_LineState == PS_UNINITIALIZED)
         continue ;

      memset (buffer, 0, sizeof(buffer)) ;

      linedevcaps = (LINEDEVCAPS *)buffer ;
      linedevcaps->dwTotalSize = sizeof(buffer) ;

      // Get a count of all addresses across all lines
      //
      if (lineGetDevCaps (RasLine,
                          i,
                          nextline->NegotiatedApiVersion,
                          nextline->NegotiatedExtVersion,
                          linedevcaps))

          goto error ;

      // Figure out if this is a unimodem device or not
      //
      if (nextline->NegotiatedApiVersion == HIGH_VERSION)  {

          // first convert all nulls in the device class string to non nulls.
          //
          DWORD  j ;
          char *temp ;

         for (j=0, temp = (CHAR *)linedevcaps+linedevcaps->dwDeviceClassesOffset;
              j<linedevcaps->dwDeviceClassesSize;
              j++, temp++)

            if (*temp == '\0')
                 *temp = ' ' ;

#ifdef CITRIX
         DBGPRINT(( "CDMODEM: HIGH VERSION provider: %s, line name: %s \n",
                    (char*) linedevcaps+linedevcaps->dwProviderInfoOffset,
                    (CHAR *)linedevcaps+linedevcaps->dwLineNameOffset));
#endif

          // select only those devices that have comm/datamodem as a device class
          //
          if ( (strstr((CHAR *)linedevcaps+linedevcaps->dwDeviceClassesOffset, "comm/datamodem") != NULL) ||
               (strstr((CHAR *)linedevcaps+linedevcaps->dwProviderInfoOffset, "Modem") != NULL) ) {

         DWORD stringlen = (linedevcaps->dwLineNameSize > MAX_DEVICE_NAME - 1 ? MAX_DEVICE_NAME - 1 : linedevcaps->dwLineNameSize) ;

         strcpy (devicetype, DEVICETYPE_UNIMODEM) ;
         strncpy (devicename, (CHAR *)linedevcaps+linedevcaps->dwLineNameOffset, stringlen) ;
         devicename[stringlen] = '\0' ;

         lstrcpynA(szregkey, (CHAR *)linedevcaps+linedevcaps->dwDevSpecificOffset+(2*sizeof(DWORD)), linedevcaps->dwDevSpecificSize);
         szregkey[linedevcaps->dwDevSpecificSize] = '\0';

         fModem = TRUE ;
          }

      } else {

          // Provider info is of the following format
          //   <media name>\0<device name>\0
          //   where - media name is - ISDN, SWITCH56, FRAMERELAY, etc.
          //         device name is  Digiboard PCIMAC, Cirel, Intel, etc.
          //
          // Since this format is used only by NDISWAN miniports this may not be present if the TSP
          // is a non unimodem and a non NDISWAN miniport. The following code (carefully) tries to
          // parse the
          //
          DWORD copylen ;
          DWORD providerinfosize = linedevcaps->dwProviderInfoSize + 2;
          BYTE* providerinfo = LocalAlloc (LPTR, providerinfosize) ;
          if (providerinfo == NULL)
             goto error ;
          memcpy (providerinfo, (CHAR *)linedevcaps+linedevcaps->dwProviderInfoOffset, linedevcaps->dwProviderInfoSize) ;
          providerinfo[providerinfosize-1] = '\0' ;
          providerinfo[providerinfosize-2] = '\0' ;



#ifdef CITRIX
         DBGPRINT(( "CDMODEM: None High Version, version: %d \n",nextline->NegotiatedApiVersion));
#endif

          if (strlen (providerinfo) > MAX_DEVICETYPE_NAME) {

         //
         //  If this name is longer than specified for a devicetype name then this is not a
         //  wan miniport device. In this case copy the provider info into the devicename
         //
         copylen = (strlen(providerinfo) > MAX_DEVICE_NAME ? MAX_DEVICE_NAME : strlen(providerinfo)) ;

         strcpy (devicetype, "XXXX") ; // put in a dummy device type - this will get skipped.
         strncpy (devicename, providerinfo, copylen) ;

          } else {

         //
         // treat this case as the the properly formatted name
         //
         strcpy (devicetype, providerinfo) ;
         copylen = (strlen(providerinfo+strlen(providerinfo)+1) > MAX_DEVICE_NAME ? MAX_DEVICE_NAME : strlen(providerinfo+strlen(providerinfo)+1)) ;
         strncpy (devicename, providerinfo+strlen(providerinfo)+1, copylen) ;
         devicename[copylen] = '\0' ;
          }
      }

#ifndef CITRIX
      totaladdresses = linedevcaps->dwNumAddresses ;

      for (k=0; k < totaladdresses; k++) {
#else // CITRIX
                for (k=0; k < linedevcaps->dwNumAddresses; k++) {
#endif // CITRIX

          if (!fModem) {

            memset (buffer, 0, sizeof(buffer)) ;

            lineaddrcaps = (LINEADDRESSCAPS*) buffer ;
            lineaddrcaps->dwTotalSize = sizeof (buffer) ;

            if (lineGetAddressCaps (RasLine, i, k, nextline->NegotiatedApiVersion, nextline->NegotiatedExtVersion, lineaddrcaps)) {
                DBGPRINT(( "CDMODEM: ETP: linegetaddresecaps error\n" ));
                continue;
            }


            memcpy (address, (CHAR *)lineaddrcaps + lineaddrcaps->dwAddressOffset, sizeof (address) -1 ) ;

#ifndef CITRIX
         if ((nextport = FindPortByAddress(address)) == NULL)
             continue ; // this address not configured for remoteaccess
#endif // CITRIX

          } else {

         GetAssociatedPortName (szregkey, address) ;

#ifndef CITRIX
              if ((nextport = FindPortByAddressAndName(address, devicename)) == NULL)
             continue ; // this address not configured for remoteaccess
#endif // CITRIX

          }


#ifndef CITRIX
          // nextport is the TPCB for this address

          nextport->TPCB_Line  = &RasTapiLineInfo[i] ;
          nextport->TPCB_State = PS_CLOSED ;
          nextport->TPCB_Endpoint = 0xffffffff ;
          nextport->TPCB_AddressId = k ;

          // Copy over the devicetype and devicename
          strcpy (nextport->TPCB_DeviceType, devicetype) ;
#else // CITRIX
          // Under RAS, the TPCB was allocated and partially
          // set up in the function ReadUsageInfoFromRegistry,
          // since WinFrame doesn't have this info convienently
          // sectioned off in the Registry it is built here
          // from the info garnered from TAPI itself.  This
          // potentially could add lines to the table that
          // WinFrame has no interest in, but it will do no
          // harm, since the lines are only acted upon when
          // an upper-level WinFrame API is called for a true
          // WinFrame-configured line.  (Bruce Fortune, Citrix)
          //
          if (nextport == NULL) {
         int i;

         // Allocate the linear list of TPCBs based on the number
         // of addresses.  This may be wasteful, and a better
         // solution would be to cull the info from WinFrame's
         // registry info.  (Bruce Fortune, Citrix)
         //
         TotalPorts = totaladdresses;
         port = nextport = RasPorts = LocalAlloc (LPTR,
             sizeof (TapiPortControlBlock) * TotalPorts) ;
         if (!port) {
             goto error;
         }
         RasPortsEnd = RasPorts + TotalPorts ;
         while (port < RasPortsEnd) {
             DBGPRINT(( "CDMODEM: ETP: TPCB init loop\n" ));
             port->TPCB_State = PS_UNINITIALIZED;
             port++;
         }
         port = nextport++;
          } else {

         // Look for this address in the table, just in case
         // of duplicates.  If the lookup fails, use the
         // next TPCB in the linear list.
         //
         if ((port = FindPortByAddress(address)) == NULL) {
             port = nextport++; // use next available entry
             if (port >= RasPortsEnd)  { 
            DbgPrint("CDMODEM: ETP: Insufficient TPCB.\n");
            DbgBreakPoint();
             }
         }
          }

          port->TPCB_Signature = CONTROLBLOCKSIGNATURE ;
          port->TPCB_Line  = &RasTapiLineInfo[i] ;
          port->TPCB_State = PS_CLOSED ;
          port->TPCB_Endpoint = 0xffffffff ;
          port->TPCB_AddressId = k ;
          port->TPCB_Usage = CALL_IN_OUT;

          strcpy (port->TPCB_DeviceType, devicetype) ;
          strncpy (port->TPCB_Name, devicename, sizeof(port->TPCB_Name)-1 ) ;
          port->TPCB_Name[sizeof(port->TPCB_Name)-1] = 0;
          strncpy (port->TPCB_Address, address, sizeof(port->TPCB_Address)-1) ;
                    port->TPCB_Address[sizeof(port->TPCB_Address)-1] = 0;
#endif // CITRIX


          // For unimodem devices we need to fix up names
          //
          if (fModem) {

             // Device Name is of the form "COM1: Hayes"
             //

             DBGPRINT(("CDMODEM: address %s, devicename %s \n", address, devicename));
             strcpy (port->TPCB_DeviceName, address) ;
             strcpy (port->TPCB_DeviceName, ":") ;
             strcpy (port->TPCB_DeviceName, devicename) ;


            strncpy (port->TPCB_Name, address, sizeof(port->TPCB_Name)-1) ;
                     port->TPCB_Name[sizeof(port->TPCB_Name)-1] = 0;


          } else {

             if (devicename[0] != '\0')
                strcpy (port->TPCB_DeviceName, devicename) ; // default


          }
          DBGPRINT(( "CDMODEM: ETP: p 0x%x, DN \"%s\", a \"%s\"\n",
                port, port->TPCB_DeviceName, port->TPCB_Name ));
      }

    }

#ifndef CITRIX
    //
    // Calculate the number of valid ports
    //
    for (pports = RasPorts, i=0; i < TotalPorts; i++, pports++) {
        if (pports->TPCB_State == PS_UNINITIALIZED)
            continue ;
        ValidPorts++;
    }
#else // CITRIX
    //
    // Okay, if this DLL got loaded, then at least one TAPI port
    // is being used by WinFrame.  Since this is all we know,
    // ValidPorts will be used here just to indicate that
    // there is *at least* one port worth doing this for...
    //
    ValidPorts = 1;
#endif // CITRIX

    //
    // Increase the reference count on our DLL
    // so it won't get unloaded out from under us.
    //

    hInst = LoadLibrary("cdmodem.dll");


    // Notify the api that the initialization is done
    //
    SetEvent (event) ;

    //
    // If there are ports, then we hang around until
    // we are shut down.  Otherwise, we exit immediately.
    //
    if (ValidPorts) {
        while (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg) ;
        }

    DBGPRINT(( "CDMODEM: ETP: WM_QUIT Message received, shutting down\n" ));
    }

    lineShutdown (RasLine) ;

    //
    // The following call atomically unloads our
    // DLL and terminates this thread.
    //
    FreeLibraryAndExitThread(hInst, SUCCESS);

error:

   if (RasLine) {
      lineShutdown (RasLine) ;
   }

    RasLine = 0 ;

    SetEvent (event) ;

    return ((DWORD)-1) ;
}



//* ReadUsageInfoFromRegistry()
//
//
//
//
//
//*
BOOL
ReadUsageInfoFromRegistry()
{
    DWORD   size;
    DWORD   type;
    HKEY    hkey;

    //
    // Read the number of rings key from the registry
    //
    if (RegOpenKey (HKEY_LOCAL_MACHINE, TEXT("System\\CurrentControlSet\\Services\\Rasman\\Parameters"), &hkey))
        return FALSE;

    size = sizeof(DWORD) ;
    if (RegQueryValueEx (hkey, TEXT("NumberOfRings"), NULL, &type, (LPBYTE)&NumberOfRings, &size))
        NumberOfRings = 1 ;

    if ((NumberOfRings < 1) || (NumberOfRings > 20))
        NumberOfRings = 1 ;

    RegCloseKey (hkey) ;

    return TRUE;
}
                  
//* RasTapiCallback()
//
//  Function: Callback entrypoint for TAPI.
//
//  Returns:  Nothing.
//*
VOID FAR PASCAL
RasTapiCallback (HANDLE context, DWORD msg, DWORD instance, DWORD param1, DWORD param2, DWORD param3)
{
    LINECALLINFO    *linecallinfo ;
    BYTE     buffer [1000] ;
    HCALL       hcall ;
    HLINE       linehandle ;
    TapiLineInfo    *line ;
    TapiPortControlBlock *port ;
    DWORD       i ;
    DWORD       retcode ;

    // **** Exclusion Begin ****
    GetMutex (RasTapiMutex, INFINITE) ;


   DBGPRINT(( "RasTapiCallback: Ctx 0x%x, Msg: %d, Inst 0x%x, Param1: %d\n",
                                context, msg, instance, param1 ));

   switch (msg) {

   case LINE_CALLSTATE:

       hcall = (HCALL) (ULONG_PTR) context ;
       line = ULongToPtr(instance);

       // If line is closed dont bother
       //
       if (line->TLI_LineState == PS_CLOSED)
      break ;

       memset (buffer, 0, sizeof(buffer)) ;
       linecallinfo = (LINECALLINFO *) buffer ;
       linecallinfo->dwTotalSize = sizeof(buffer) ;

       // If line get call info fails return.
       //
       if (lineGetCallInfo (hcall, linecallinfo) > 0x80000000)
      break ;

       // Locate the ras port for this call
       //
       if ((port = FindPortByAddressId (line, linecallinfo->dwAddressID)) == NULL) {

      // Did not find a ras port for the call. Ignore it.
      //
      break ;
       }

       DBGPRINT(("RasTapiCallBack: LINE_CALLSTATE check param1 \n"));
       // A new call is coming in
       //
       if (param1 == LINECALLSTATE_OFFERING) {

        if ((line->TLI_LineState == PS_LISTENING) && (port->TPCB_State == PS_LISTENING)) {

          port->TPCB_CallHandle = hcall ;

          //
          // for unimodem devices wait for the specified number of rings
          //
          if (_stricmp (port->TPCB_DeviceType, DEVICETYPE_UNIMODEM) == 0) {

         //
         // call has already been answered by somebody else and is being offered to me
         //
         if (linecallinfo->dwCallStates == LINECALLSTATE_CONNECTED) {

             port->TPCB_ListenState = LS_COMPLETE ;

             //
             // Complete event so that rasman calls DeviceWork to proceed the listen state machine.
             //
             SetEvent (port->TPCB_ReqNotificationHandle) ;

         } else {

             port->TPCB_ListenState = LS_RINGING ;
             port->TPCB_NumberOfRings = NumberOfRings ;

         }

          } else {

         //
         // For other devices make transition to next listening state
         //
         port->TPCB_ListenState = LS_ACCEPT ;

         //
         // Complete event so that rasman calls DeviceWork to proceed the listen state machine.
         //
         SetEvent (port->TPCB_ReqNotificationHandle) ;

          }

      } else {

          //
          // We were not expecting the call. Make transition to next listening state
          //
          port->TPCB_ListenState = LS_ERROR ;
          port->TPCB_CallHandle = hcall ;

          //
          // not interested in call, drop it.
          //
          InitiatePortDisconnection (port) ;

      }

      break ;
       }


       // Call connected.
       //
       if (param1 == LINECALLSTATE_CONNECTED ) {

          DBGPRINT((" RasTapiCallBack: LINECALLSTATE_CONNECTED  \n"));

          if (port->TPCB_State == PS_CONNECTING) {

          //
          // We were requesting the call. Complete event so that rasman calls DeviceWork() to complete the
          // connection process.
          //
          SetEvent (port->TPCB_ReqNotificationHandle) ;

         } else {

          port->TPCB_CallHandle = hcall ;

          // This is a call we are asnwering. Now we can indicate to rasman that the call has come in.
          //
          // Setting listen state to LS_COMPLETE may be redundant but handles the case where the answer
          // completes *after* the connection is indicated
          //
          port->TPCB_ListenState = LS_COMPLETE ;

          //
          // Complete event so that rasman knows of incoming call and calls devicework.
          //
          SetEvent (port->TPCB_ReqNotificationHandle) ;
         }

       }

       //
       // Failure of sorts.
       //
       if ((param1 == LINECALLSTATE_BUSY) || (param1 == LINECALLSTATE_SPECIALINFO)) {

      // If we were connecting, notify rasman to call devicework so that the connection attempt can
      // be gracefully failed.
      //
         if (port->TPCB_State == PS_CONNECTING)
            SetEvent (port->TPCB_ReqNotificationHandle) ;

      //  Can this be received for the answering case?
      //
       }

       //
       // Disconnection happened
       //
       if (param1 == LINECALLSTATE_DISCONNECTED) {

      // If we were connecting, notify rasman to call devicework so that the connection attempt can
      // be gracefully failed.
      //
         if (port->TPCB_State == PS_CONNECTING) {

          if (param2 == LINEDISCONNECTMODE_BUSY)
         port->TPCB_AsyncErrorCode = ERROR_LINE_BUSY ;
          else if (param2 == LINEDISCONNECTMODE_NOANSWER)
         port->TPCB_AsyncErrorCode = ERROR_NO_ANSWER ;
          else if (param2 == LINEDISCONNECTMODE_NODIALTONE)
         port->TPCB_AsyncErrorCode = ERROR_NO_DIALTONE ;
                    else if (param2 == LINEDISCONNECTMODE_CANCELLED)
                        port->TPCB_AsyncErrorCode = ERROR_USER_DISCONNECTION;

          SetEvent (port->TPCB_ReqNotificationHandle) ;

      } else if (port->TPCB_State != PS_CLOSED) {

          // If we were connected and got a disconnect notification then this could be hardware failure or
          // a remote disconnection. Determine this and save the reason away.
          //
          if (port->TPCB_State == PS_CONNECTED) {
         LINECALLSTATUS *pcallstatus ;
         BYTE buffer[200] ;

         memset (buffer, 0, sizeof(buffer)) ;
         pcallstatus = (LINECALLSTATUS *) buffer ;
         pcallstatus->dwTotalSize = sizeof (buffer) ;
         lineGetCallStatus (port->TPCB_CallHandle, pcallstatus) ;
         if (pcallstatus->dwCallState == LINECALLSTATE_DISCONNECTED)
             port->TPCB_DisconnectReason = SS_LINKDROPPED ;
         else
             port->TPCB_DisconnectReason = SS_HARDWAREFAILURE ;

          } else
         port->TPCB_DisconnectReason = 0 ;

          //
          // This means that we got a disconnect indication in one of the other states (listening, connected, etc.)
          // We initiate our disconnect state machine.
          //
          if (InitiatePortDisconnection (port) != PENDING) {

         // Disconnection succeeded or failed. Both are end states for the disconnect state machine so notify
         // rasman that a disconnection has happened.
         //
         // DbgPrint("SignalDisc: 1\n") ;
         SetEvent (port->TPCB_DiscNotificationHandle) ;
          }
      }
       }

       //
       // A busy call state - our attempt to dialout failed
       //
     if (param1 == LINECALLSTATE_BUSY) {

      if (port->TPCB_State == PS_CONNECTING) {
      port->TPCB_AsyncErrorCode = ERROR_LINE_BUSY ;
      SetEvent (port->TPCB_ReqNotificationHandle) ;
      }
       }


       // Idle indication is useful to complete the disconnect state machine.
       //
       if (param1 == LINECALLSTATE_IDLE) {

      // DbgPrint ("I, State = %d\n", port->TPCB_State) ;

         if ((port->TPCB_State == PS_DISCONNECTING) && (port->TPCB_RequestId == INFINITE)) {

            // IDLE notification came after LineDrop Succeeded so safe to
            // deallocate the call
            //
            port->TPCB_State = PS_OPEN ;
            lineDeallocateCall (port->TPCB_CallHandle) ;
            // DbgPrint ("D + Signal Disc\n") ;
            port->TPCB_CallHandle = (HCALL) 0xffffffff ;
            line->IdleReceived = FALSE;
            SetEvent (port->TPCB_DiscNotificationHandle) ;
         } else {

            // We have not yet disconnected so do not deallocate call
            // yet.  This will be done when the disconnect completes.
            //
            line->IdleReceived = TRUE;
          // DbgPrint ("IdleNoAction\n") ;
         }
       }

   break ;


    case LINE_REPLY:

        // This message is sent to indicate completion of an asynchronous API.
        //

       DbgPrint ("LineReply: ReqId:%d", param1) ;


       // Find for which port the async request succeeded. This is done by searching for pending
       // request id that is also provided in this message.
       //
       if ((port = FindPortByRequestId (param1)) == NULL) {
            DbgPrint ("\n") ;

       break ;

       } else

       DbgPrint (" State = %d\n", port->TPCB_State) ;


        // Set request id to invalid.
        //
       port->TPCB_RequestId = INFINITE ;

       if (port->TPCB_State == PS_DISCONNECTING) {

          // lineDrop completed. Note that we ignore the return code in param2. This is
          // because we cant do anything else.
            //
         if (port->TPCB_Line->IdleReceived) {

            // We received IDLE notification before/during disconnect
            // so deallocate this call
            //
            port->TPCB_Line->IdleReceived = FALSE;
            port->TPCB_State = PS_OPEN ;
            lineDeallocateCall (port->TPCB_CallHandle) ;
            // DbgPrint ("D + SignalDisc\n") ;
            port->TPCB_CallHandle = (HCALL) 0xffffffff ;
                SetEvent (port->TPCB_DiscNotificationHandle) ;

         } else {

                // wait for idle message before signalling disconnect
                //
                ; // DbgPrint ("DropCompNoAction\n") ;
            }

         break ;
       }

        // Some other api completed
        //
       if (param2 == SUCCESS) {

         // Success means take no action - unless we are listening
         // in which case it means move to the next state - we simply
         // set the event that will result in a call to DeviceWork() to
         // make the actual call for the next state
         //
         if (port->TPCB_State != PS_LISTENING)
            break ;

         // Proceed to the next listening sub-state
         //
         if (port->TPCB_ListenState == LS_ACCEPT) {

            port->TPCB_ListenState =  LS_ANSWER ;
            SetEvent (port->TPCB_ReqNotificationHandle) ;

         } else if (port->TPCB_ListenState == LS_ANSWER) {

            port->TPCB_ListenState = LS_COMPLETE ;
         }


       } else {

         // For connecting and listening ports this means the attempt failed
         // because of some error
         //
         if (port->TPCB_State == PS_CONNECTING) {

            port->TPCB_AsyncErrorCode = ERROR_PORT_OR_DEVICE ;
            SetEvent (port->TPCB_ReqNotificationHandle) ;

         } else if (port->TPCB_State == PS_LISTENING) {

             // Because ACCEPT may not be supported by the device - treat error as success
                //
            if (port->TPCB_ListenState == LS_ACCEPT)
               port->TPCB_ListenState =  LS_ANSWER ;
            else
               port->TPCB_ListenState =  LS_ERROR ;

            SetEvent (port->TPCB_ReqNotificationHandle) ;
         }

         // Some other API failed, we dont know and we dont care. Ignore.
         //
         else if (port->TPCB_State != PS_CLOSED) {

                ; // DbgPrint ("RASTAPI: LINE_REPLY failed. Ignored.\n") ;
            }
       }

      break ;

    case LINE_CLOSE:

        // Typically sent when things go really wrong.
        //

        DbgPrint ("RASTAPI: received LINE_CLOSE message\n") ;

        // Find which line is indication came for.
        //
        line = ULongToPtr(instance) ;

        // if line not found or if it is closed just return
        //
        if ((line == NULL) || (line->TLI_LineState == PS_CLOSED))
            break ;

        // For every port that is on the line - open the line again and signal
        // hw failure
        //
        for (port = RasPorts, i = 0; i < TotalPorts; i++, port++) {

            // Skip ports that arent initialized
            //
           if (port->TPCB_State == PS_UNINITIALIZED)
               continue ;

            if (port->TPCB_Line == line) {

                if (retcode = lineOpen (RasLine,
                               port->TPCB_Line->TLI_LineId,
                               &port->TPCB_Line->TLI_LineHandle,
                               port->TPCB_Line->NegotiatedApiVersion,
                               port->TPCB_Line->NegotiatedExtVersion,
                               (ULONG) (ULONG_PTR) port->TPCB_Line,
                               LINECALLPRIVILEGE_OWNER,
                               port->TPCB_MediaMode,
                               NULL));      //
      // Set monitoring of rings
      //
      lineSetStatusMessages (port->TPCB_Line->TLI_LineHandle, LINEDEVSTATE_RINGING, 0) ;

                // If the port is listening - then we do not need to do anything since the listen is
                // posted implicitly,
                //
                if (port->TPCB_State != PS_LISTENING) {
                    //
                    // These settings should cause connections and connect attempts to
                    // fail.
                    //
                    port->TPCB_AsyncErrorCode = ERROR_FROM_DEVICE ;
                   port->TPCB_CallHandle =  (HCALL) 0xffffffff ;
                    port->TPCB_ListenState = LS_ERROR ;

                    // Signal hardware failure to rasman.
                    //
                    // DbgPrint ("SignalDisc: 3\n") ;
                    SetEvent (port->TPCB_DiscNotificationHandle) ;
                }
            }
        }
        break ;

    case LINE_LINEDEVSTATE:

   //
   // we are only interested in ringing message
   //
   if (param1 != LINEDEVSTATE_RINGING)
       break ;

   // Find which line is indication came for.
        //
        line = ULongToPtr(instance) ;

        // if line not found or if it is closed just return
        //
        if ((line == NULL) || (line->TLI_LineState == PS_CLOSED))
            break ;

   // get the port from the line
        //
        for (port = RasPorts, i = 0; i < TotalPorts; i++, port++) {

            // Skip ports that arent initialized
            //
       if (port->TPCB_State == PS_UNINITIALIZED)
      continue ;

       if ((port->TPCB_Line == line) && (port->TPCB_State == PS_LISTENING) && (port->TPCB_ListenState == LS_RINGING)) {

      //
      // count down the rings
      //
      port->TPCB_NumberOfRings -= 1 ;
      // DbgPrint ("Ring count = %d\n", port->TPCB_NumberOfRings) ;

      //
      // if the ring count has gone down to zero this means that we should pick up the call.
      //
      if (port->TPCB_NumberOfRings == 0) {

          port->TPCB_ListenState = LS_ACCEPT ;

          //
          // Complete event so that rasman calls DeviceWork to proceed the listen state machine.
          //
          SetEvent (port->TPCB_ReqNotificationHandle) ;
      }

      break ;
            }
   }

   break ;

    case LINE_ADDRESSSTATE:
    case LINE_CALLINFO:
    case LINE_CREATE:
    case LINE_DEVSPECIFIC:
    case LINE_DEVSPECIFICFEATURE:
    case LINE_GATHERDIGITS:
    case LINE_GENERATE:

    case LINE_MONITORDIGITS:
    case LINE_MONITORMEDIA:
    case LINE_MONITORTONE:
    case LINE_REQUEST:
   default:
        // All unhandled unsolicited messages.
        //
      ;
    }

    // **** Exclusion End ****
   FreeMutex (RasTapiMutex) ;
}



//* FindPortByAddressId()
//
//
//
//
//*
TapiPortControlBlock *
FindPortByAddressId (TapiLineInfo *line, DWORD addid)
{
    DWORD   i ;
    TapiPortControlBlock *port ;

    for (i=0, port=RasPorts; i<TotalPorts; i++, port++) {

   if ((port->TPCB_AddressId == addid) && (port->TPCB_Line == line))
       return port ;

    }

    return NULL ;
}


//* FindPortByAddress()
//
//
//
//
//*
TapiPortControlBlock *
FindPortByAddress (CHAR *address)
{
    DWORD   i ;
    TapiPortControlBlock *port ;

    for (i=0, port=RasPorts; i<TotalPorts; i++, port++) {

   if (_stricmp (port->TPCB_Address, address) == 0)
       return port ;

    }

    return NULL ;
}


//* FindPortByAddress()
//
//
//
//
//*
TapiPortControlBlock *
FindPortByAddressAndName (CHAR *address, CHAR *name)
{
    DWORD   i ;
    TapiPortControlBlock *port ;

    for (i=0, port=RasPorts; i<TotalPorts; i++, port++) {

   if ((_stricmp (port->TPCB_Address, address) == 0) &&
       (_strnicmp (port->TPCB_Name, name, MAX_PORT_NAME-1) == 0))
       return port ;

    }

    return NULL ;
}


//* FindPortByRequestId()
//
//
//
//
//*
TapiPortControlBlock *
FindPortByRequestId (DWORD reqid)
{
    DWORD   i ;
    TapiPortControlBlock *port ;

    for (i=0, port=RasPorts; i<TotalPorts; i++, port++) {

   if (port->TPCB_RequestId == reqid)
       return port ;

    }

    return NULL ;
}



//* FindLineByHandle()
//
//
//
//
//*
TapiLineInfo *
FindLineByHandle (HLINE linehandle)
{
    DWORD i ;
    TapiLineInfo *line ;

    for (i=0, line=RasTapiLineInfo; i<TotalLines; i++, line++) {

   if (line->TLI_LineHandle == linehandle)
       return line ;

    }

    return NULL ;
}



#define VALNAME_ATTACHEDTO "AttachedTo"

BOOL
GetAssociatedPortName(
   char  * szKeyName,
   CHAR * szPortName)
/*
 * Given the registry key name 'szRegistryKeyName' corresponding to the modem entry, fills in
 * 'wszAddress' with the associated port like COM1, ..
 *
 */
{
   HKEY   hKeyModem;
   DWORD  dwType;
   DWORD  cbValueBuf;
#if 0
   char   buf[256];
#endif

#if 0
   wsprintfA(buf,"RegistryKey -> %s \n", szKeyName);
   OutputDebugStringA(buf);
#endif

   if ( RegOpenKeyExA(HKEY_LOCAL_MACHINE,
                     szKeyName,
                     0,
                     KEY_READ,
                     &hKeyModem ) )
   {
       return( FALSE );
   }

   cbValueBuf = 40 ;

   if ( RegQueryValueExA(hKeyModem,
                        VALNAME_ATTACHEDTO,
                        NULL,
                        &dwType,
         (LPBYTE)szPortName,
                        &cbValueBuf ))
   {
      return ( FALSE );
   }

   RegCloseKey( hKeyModem );

   return ( TRUE );
}