WindowsXP-SP1/termsrv/cdmodem/src/modem.c
2020-09-30 16:53:49 +02:00

691 lines
20 KiB
C

/*************************************************************************
*
* modem.c
*
* Modem Routines
*
*
* Copyright 1996, Citrix Systems Inc.
*
* Author: Brad Pedersen (7/15/96)
*
* $Log: M:\nt\private\citrix\cd\cdmodem\src\VCS\modem.c $
*
* Rev 1.7 23 Feb 1998 21:14:56 kurtp
* fix CPR 8656, callback fails
*
* Rev 1.6 26 Jun 1997 15:25:32 wf20r
* move to WF40 tree
*
* Rev 1.5 20 Jun 1997 18:06:56 butchd
* update
*
* Rev 1.4 06 Feb 1997 17:39:12 kurtp
* update
*
* Rev 1.3 17 Dec 1996 09:50:26 brucef
* Do not close Connect Event if disconnect timeout.
*
* Rev 1.2 09 Dec 1996 13:57:42 brucef
* Return a true ERROR when STATUS_TIMEOUT occurs.
*
* Rev 1.1 28 Oct 1996 08:47:08 bradp
* update
*
* Rev 1.0 16 Oct 1996 11:16:16 brucef
* Initial revision.
*
* Rev 1.5 25 Sep 1996 13:23:42 bradp
* update
*
* Rev 1.4 12 Sep 1996 13:40:14 brucef
* update
*
* Rev 1.3 11 Sep 1996 17:50:42 brucef
* update
*
* Rev 1.2 05 Sep 1996 11:18:18 brucef
* update
*
* Rev 1.1 20 Aug 1996 10:08:02 bradp
* update
*
* Rev 1.0 15 Jul 1996 11:04:10 bradp
* Initial revision.
*
*
*************************************************************************/
/*
* Includes
*/
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntddkbd.h>
#include <ntddmou.h>
#include <windows.h>
#include <winbase.h>
#include <winerror.h>
#include <raserror.h>
#include <stdlib.h>
#include <tapi.h>
#include <winstaw.h>
#include <icadd.h>
#include <icaapi.h>
#include <rasman.h>
#include <rasmxs.h>
#include <media.h>
#include <cdmodem.h>
/*=============================================================================
== External Functions Defined
=============================================================================*/
NTSTATUS IcaMemoryAllocate( ULONG, PVOID * );
VOID IcaMemoryFree( PVOID );
NTSTATUS ModemAPIInit( PCDMODEM );
NTSTATUS ModemOpen( PCDMODEM );
NTSTATUS ModemClose( PCDMODEM );
NTSTATUS ModemInitialize( PCDMODEM );
NTSTATUS ModemHangup( PCDMODEM );
NTSTATUS ModemCallback( PCDMODEM, PICA_STACK_CALLBACK );
DWORD APIENTRY PortEnum(PCDMODEM, BYTE *pBuffer, WORD *pwSize, WORD *pwNumPorts);
DWORD APIENTRY PortSetInfo(HANDLE hIOPort, RASMAN_PORTINFO *pInfo);
DWORD APIENTRY PortOpen(char *pszPortName, HANDLE *phIOPort, HANDLE hNotify);
DWORD APIENTRY PortDisconnect(HANDLE hPort);
DWORD APIENTRY PortClose (HANDLE hIOPort);
DWORD APIENTRY PortDisconnect (HANDLE hPort);
DWORD APIENTRY DeviceListen(HANDLE hPort, char *pszDeviceType,
char *pszDeviceName,
HANDLE hNotifier);
DWORD APIENTRY DeviceConnect(HANDLE hPort, char *pszDeviceType,
char *pszDeviceName, HANDLE hNotifier);
DWORD APIENTRY DeviceWork(HANDLE hPort, HANDLE hNotifier);
VOID APIENTRY DeviceDone(HANDLE hPort);
DWORD APIENTRY PortGetIOHandle(HANDLE hPort, HANDLE *FileHandle);
/*=============================================================================
== Internal Functions Defined
=============================================================================*/
/*=============================================================================
== Global variables
=============================================================================*/
/****************************************************************************
*
* ModemAPIInit
*
* Initialize the Modem APIs and associated TAPI engine.
*
*
* ENTRY:
*
* EXIT:
* STATUS_SUCCESS - Success
* STATUS_OPEN_FAILED - Unable to open/init TAPI engine
*
****************************************************************************/
NTSTATUS
ModemAPIInit( PCDMODEM pCdModem )
{
WORD Size;
WORD NumPorts;
/*
* We want TAPI to be initialized, the provided method is to call
* PortEnum() to enumerate all the TAPI ports. The side effect of
* PortEnum() doing this is that it primes the entire TAPI engine first.
* It's this side effect that we are interested in; however, since
* at this point, we don't care about the data returned, a
* zero-byte buffer is supplied, which will cause BUFFER_TOO_SMALL
* to be returned if the TAPI engine got started to the point
* where it's usable. So, anything other than BUFFER_TOO_SMALL
* means that TAPI didn't start properly, or if it did and no
* TAPI ports were found (0 bytes of buffer was sufficient).
*/
Size = 0;
if ( PortEnum( pCdModem, NULL, &Size, &NumPorts ) != ERROR_BUFFER_TOO_SMALL ) {
/*
* Either there aren't any modems (size==0 resulted in SUCCESS), or
* another error was encountered. In either case, the modem
* APIs aren't usable.
*/
goto error;
}
return( STATUS_SUCCESS );
/*=============================================================================
== Error Returns
=============================================================================*/
error:
return( STATUS_OPEN_FAILED ); // - Better NTSTATUS code ???
}
/****************************************************************************
*
* ModemOpen
*
* Open TAPI device
*
*
* ENTRY:
* pCdModem (input)
* pointer modem connection driver data structure
* pEndpoint (input)
* pointer to a stack endpoint if reopening Modem Device.
*
* EXIT:
* STATUS_SUCCESS - Success
* other - Error return code
*
****************************************************************************/
NTSTATUS
ModemOpen( PCDMODEM pCdModem )
{
NTSTATUS Status;
LONG TAPIStatus;
DWORD NumDevs;
static BOOL firsttime = TRUE;
/*
* Initialize the Modem APIs upon the first time through.
* Note, that the underlying init routines have the necessary
* syncronization in case mutilple "first time" callers make
* it through the unsynchronized "firsttime" gate below.
*/
if ( firsttime ) {
if ( (Status = ModemAPIInit( pCdModem )) != STATUS_SUCCESS ) {
goto error;
}
firsttime = FALSE;
}
DBGPRINT(( "CDMODEM: ModemOpen, Entry - opening \"%S\"\n",
pCdModem->DeviceName
));
Status = STATUS_SUCCESS;
error:
DBGPRINT(( "CDMODEM: ModemOpen, 0x%x\n", Status ));
return( Status );
}
/****************************************************************************
*
* ModemClose
*
* Since closing the TAPI device would close all TAPI-based connections,
* we don't do anything here.
*
*
* ENTRY:
* pCdModem (input)
* pointer modem connection driver data structure
*
* EXIT:
* STATUS_SUCCESS - Success
* other - Error return code
*
****************************************************************************/
NTSTATUS
ModemClose( PCDMODEM pCdModem )
{
NTSTATUS Status;
Status = STATUS_SUCCESS;
DBGPRINT(( "CDMODEM: ModemClose, 0x%x\n", Status ));
return( Status );
}
/****************************************************************************
*
* ModemInitialize
*
* Initialize modem
*
*
* ENTRY:
* pCdModem (input)
* pointer modem connection driver data structure
*
* EXIT:
* STATUS_SUCCESS - Success
* other - Error return code
*
****************************************************************************/
NTSTATUS
ModemInitialize( PCDMODEM pCdModem )
{
#define MI_EVENT_DISCONNECT 0
#define MI_EVENT_INCOMING 1
#define MI_EVENT_MAX 2
HANDLE hEvents[MI_EVENT_MAX];
NTSTATUS Status;
DWORD Error;
CHAR DeviceNameA[ DEVICENAME_LENGTH + 1 ];
/*
* Initialize modem and wait for incoming call.
*/
DBGPRINT(( "CDMODEM: ModemInitialize, Entry\n" ));
hEvents[MI_EVENT_DISCONNECT] = CreateEvent( NULL, FALSE, FALSE, NULL );
if ( !hEvents[MI_EVENT_DISCONNECT] ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto baddiscevent;
}
pCdModem->hDiscEvent = hEvents[MI_EVENT_DISCONNECT];
/*
* TAPI does ANSI.
*/
wcstombs( DeviceNameA, pCdModem->DeviceName, sizeof(DeviceNameA) );
DBGPRINT(( "CDMODEM: ModemInitialize, opening \"%s\"\n", DeviceNameA ));
Error = PortOpen( DeviceNameA,
&pCdModem->hPort,
hEvents[MI_EVENT_DISCONNECT] );
if ( Error ) {
DBGPRINT(( "CDMODEM: ModemInitialize: PortOpen failed (%d)\n",
Error ));
Status = STATUS_OPEN_FAILED;
goto badportopen;
}
hEvents[MI_EVENT_INCOMING] = CreateEvent( NULL, FALSE, FALSE, NULL );
if ( !hEvents[MI_EVENT_INCOMING] ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto badicevent;
}
/*
* Listen for the phone to ring.
*/
Error = DeviceListen( pCdModem->hPort,
NULL,
NULL,
hEvents[MI_EVENT_INCOMING] );
if ( Error != PENDING ) {
DBGPRINT(( "CDMODEM: ModemInitialize: DeviceListen failed (%d)\n",
Error ));
Status = STATUS_OPEN_FAILED;
goto baddevicelisten;
}
/*
* This simple state machine drives the more complex state machine
* lifted from the RAS server, it advances the incoming call through
* to the answered state. If the INCOMING event is
* signaled, the call is advanced to the next state via DeviceWork().
* As long as future state transitions are required, DeviceWork() returns
* a status of PENDING, when all state transitions are complete,
* DeviceWork() returns SUCCESS; at which point the incoming call
* has been answered and the communication port is ready for
* data.
*/
nextstate:
Error = IcaCdWaitForMultipleObjects( pCdModem->hStack, MI_EVENT_MAX, hEvents, FALSE, INFINITE );
switch ( Error ) {
case WAIT_TIMEOUT:
DBGPRINT(( "CDMODEM: ModemInitialize: Timeout\n" ));
Status = STATUS_OPEN_FAILED;
goto timeout;
case MI_EVENT_DISCONNECT + WAIT_OBJECT_0:
DBGPRINT(( "CDMODEM: ModemInitialize: Disconnected\n" ));
Status = STATUS_OPEN_FAILED;
goto disconnect;
case MI_EVENT_INCOMING + WAIT_OBJECT_0:
DBGPRINT(( "CDMODEM: ModemInitialize: Incoming Call\n" ));
Error = DeviceWork( pCdModem->hPort, hEvents[MI_EVENT_INCOMING] );
if ( Error == SUCCESS ) {
DeviceDone( pCdModem->hPort );
break;
}
if ( Error != PENDING ) {
DBGPRINT(( "CDMODEM: ModemInitialize: DeviceWork failed (%d)\n",
Error ));
Status = STATUS_OPEN_FAILED;
goto baddevicework;
}
goto nextstate;
case 0xffffffff:
DBGPRINT(( "CDMODEM: ModemInitialize: Wait returned 0xffffffff\n" ));
Status = STATUS_OPEN_FAILED;
goto badwait;
default:
DBGPRINT(( "CDMODEM: ModemInitialize: Unknown return from Wait\n" ));
Status = STATUS_OPEN_FAILED;
goto badwait;
}
Error = PortGetIOHandle( pCdModem->hPort, &pCdModem->hCommDevice );
if ( Error ) {
DBGPRINT(( "CDMODEM: ModemInitialize: PortGetIOHandle failed (%d)\n",
Error ));
Status = STATUS_OPEN_FAILED;
goto badportgetiohandle;
}
CloseHandle( hEvents[MI_EVENT_INCOMING] );
Status = STATUS_SUCCESS;
DBGPRINT(( "CDMODEM: ModemInitialize, 0x%x\n", Status ));
return( Status );
/*=============================================================================
== Error Returns
=============================================================================*/
badportgetiohandle:
timeout:
disconnect:
badwait:
baddevicework:
baddevicelisten:
CloseHandle( hEvents[MI_EVENT_INCOMING] );
badicevent:
PortClose( pCdModem->hPort );
badportopen:
CloseHandle( pCdModem->hDiscEvent );
pCdModem->hDiscEvent = NULL;
baddiscevent:
DBGPRINT(( "CDMODEM: ModemInitialize, ERROR 0x%x\n", Status ));
return( Status );
}
/****************************************************************************
*
* ModemHangup
*
* Hangup modem and close TAPI device
*
*
* ENTRY:
* pCdModem (input)
* pointer modem connection driver data structure
*
* EXIT:
* STATUS_SUCCESS - Success
* other - Error return code
*
****************************************************************************/
NTSTATUS
ModemHangup( PCDMODEM pCdModem )
{
NTSTATUS Status;
DWORD Error;
DBGPRINT(( "CDMODEM: ModemHangup, entry\n" ));
/*
* Hangup and close modem
*/
Error = PortDisconnect( pCdModem->hPort );
if ( Error == PENDING ) {
ASSERT( pCdModem->hDiscEvent );
DBGPRINT(("CDMODEM: ModemHangup waiting for client to disconnect\n"));
Error = IcaCdWaitForSingleObject( pCdModem->hStack,
pCdModem->hDiscEvent,
DISCONN_TIMEOUT );
DBGPRINT(("CDMODEM: ModemHangup wait complete Error 0x%x\n", Error ));
switch ( Error ) {
case WAIT_OBJECT_0:
break;
case WAIT_TIMEOUT:
DBGPRINT(( "CDMODEM: ModemHangup: Timeout\n" ));
break;
case 0xffffffff:
DBGPRINT(( "CDMODEM: ModemHangup: Wait returned 0xffffffff\n" ));
break;
default:
DBGPRINT(( "CDMODEM: ModemHangup: Unknown return from Wait\n" ));
break;
}
}
if ( pCdModem->hPort ) {
PortClose( pCdModem->hPort );
}
pCdModem->hPort = NULL;
pCdModem->hCommDevice = NULL;
if ( pCdModem->hDiscEvent ) {
CloseHandle( pCdModem->hDiscEvent );
}
pCdModem->hDiscEvent = NULL;
Status = STATUS_SUCCESS;
DBGPRINT(( "CDMODEM: ModemHangup, 0x%x\n", Status ));
return( Status );
}
/****************************************************************************
*
* ModemCallback
*
* Dial modem for callback
*
*
* ENTRY:
* pCdModem (input)
* pointer modem connection driver data structure
* pCallback (input)
* pointer to callback data structure
*
* EXIT:
* STATUS_SUCCESS - Success
* other - Error return code
*
****************************************************************************/
NTSTATUS
ModemCallback( PCDMODEM pCdModem,
PICA_STACK_CALLBACK pCallback )
{
#define MC_EVENT_DISCONNECT 0
#define MC_EVENT_CONNECT 1
#define MC_EVENT_MAX 2
HANDLE hEvents[MC_EVENT_MAX];
NTSTATUS Status;
DWORD Error;
RASMAN_PORTINFO portinfo;
RAS_PARAMS *params;
CHAR PhoneNumberA[ CALLBACK_LENGTH + 1 ];
/*
* Disconnect Port, then make an outgoing call to
* the supplied number.
*/
ASSERT( pCdModem->hDiscEvent );
DBGPRINT(("CDMODEM: ModemCallback waiting for client to disconnect\n"));
/*
* Add extra delay (6X) because it takes time for local modem to detect
* carrier drop, when ModemHangup is called you don't need the extra
* time because we are dropping carrier locally.
*/
Error = IcaCdWaitForSingleObject( pCdModem->hStack, pCdModem->hDiscEvent,
6 * DISCONN_TIMEOUT );
DBGPRINT(("CDMODEM: ModemCallback wait complete Error 0x%x\n", Error ));
switch ( Error ) {
case WAIT_OBJECT_0:
break;
case WAIT_TIMEOUT:
DBGPRINT(( "CDMODEM: ModemCallback: Timeout\n" ));
Status = STATUS_OPEN_FAILED;
goto timeout1;
case 0xffffffff:
DBGPRINT(( "CDMODEM: ModemCallback: Wait returned 0xffffffff\n" ));
Status = STATUS_OPEN_FAILED;
goto badwait1;
default:
DBGPRINT(( "CDMODEM: ModemCallback: Unknown return from Wait\n" ));
Status = STATUS_OPEN_FAILED;
goto badwait1;
}
/*
* TAPI does ANSI.
*/
wcstombs( PhoneNumberA, pCallback->PhoneNumber, sizeof(PhoneNumberA) );
/*
* Setup the number to call.
*/
portinfo.PI_NumOfParams = 1;
params = &portinfo.PI_Params[0];
params->P_Type = String;
params->P_Attributes = 0;
params->P_Value.String.Length = strlen ( PhoneNumberA );
params->P_Value.String.Data = PhoneNumberA;
strcpy( params->P_Key, MXS_PHONENUMBER_KEY );
Error = PortSetInfo( pCdModem->hPort, &portinfo );
if ( Error != SUCCESS ) {
goto badportsetinfo;
}
hEvents[MC_EVENT_DISCONNECT] = pCdModem->hDiscEvent;
hEvents[MC_EVENT_CONNECT] = CreateEvent( NULL, FALSE, FALSE, NULL );
if ( !hEvents[MC_EVENT_CONNECT] ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto badconnectevent;
}
/*
* Make the call to the number given.
*/
Error = DeviceConnect( pCdModem->hPort, NULL, NULL,
hEvents[MC_EVENT_CONNECT] );
if ( Error != PENDING ) {
DBGPRINT(( "CDMODEM: ModemCallback: DeviceConnect failed (%d)\n",
Error ));
Status = STATUS_OPEN_FAILED;
goto baddeviceconnect;
}
/*
* This simple state machine drives the more complex state machine
* lifted from the RAS server, it advances the incoming call through
* to the call completed state. If the CONNECT event is
* signaled, the call is advanced to the next state via DeviceWork().
* As long as future state transitions are required, DeviceWork() returns
* a status of PENDING, when all state transitions are complete,
* DeviceWork() returns SUCCESS; at which point the outgoing call
* has been established and the communication port is ready for
* data.
*/
nextstate:
Error = IcaCdWaitForMultipleObjects( pCdModem->hStack, MC_EVENT_MAX, hEvents, FALSE,
CALLBACK_TIMEOUT );
switch ( Error ) {
case WAIT_TIMEOUT:
DBGPRINT(( "CDMODEM: ModemCallback: Timeout\n" ));
Status = STATUS_OPEN_FAILED;
goto timeout2;
case MC_EVENT_DISCONNECT + WAIT_OBJECT_0:
DBGPRINT(( "CDMODEM: ModemCallback: Disconnected\n" ));
Status = STATUS_OPEN_FAILED;
goto disconnect;
case MC_EVENT_CONNECT + WAIT_OBJECT_0:
DBGPRINT(( "CDMODEM: ModemCallback: Connecting\n" ));
Error = DeviceWork( pCdModem->hPort, hEvents[MC_EVENT_CONNECT] );
if ( Error == SUCCESS ) {
DeviceDone( pCdModem->hPort );
break; // call successful
}
if ( Error != PENDING ) {
DBGPRINT(( "CDMODEM: ModemCallback: DeviceWork failed (%d)\n",
Error ));
Status = STATUS_OPEN_FAILED;
goto baddevicework;
}
goto nextstate;
break;
case 0xffffffff:
DBGPRINT(( "CDMODEM: ModemCallback: Wait returned 0xffffffff\n" ));
Status = STATUS_OPEN_FAILED;
goto badwait2;
default:
DBGPRINT(( "CDMODEM: ModemCallback: Unknown return from Wait\n" ));
Status = STATUS_OPEN_FAILED;
goto badwait2;
}
Error = PortGetIOHandle( pCdModem->hPort, &pCdModem->hCommDevice );
if ( Error ) {
DBGPRINT(( "CDMODEM: ModemCallback: PortGetIOHandle failed (%d)\n",
Error ));
Status = STATUS_OPEN_FAILED;
goto badportgetiohandle;
}
CloseHandle( hEvents[MC_EVENT_CONNECT] );
Status = STATUS_SUCCESS;
DBGPRINT(( "CDMODEM: ModemCallback, %S han 0x%x , 0x%x\n",
pCallback->PhoneNumber, pCdModem->hCommDevice, Status ));
return( Status );
/*=============================================================================
== Error Returns
=============================================================================*/
badportgetiohandle:
badwait2:
timeout2:
disconnect:
baddevicework:
baddeviceconnect:
CloseHandle( hEvents[MC_EVENT_CONNECT] );
badconnectevent:
badportsetinfo:
badwait1:
timeout1:
return( Status );
}