/********************************************************************/
/**               Copyright(c) 1989 Microsoft Corporation.	   **/
/********************************************************************/

//***
//
// Filename:	port.c
//
// Description: This module contains the entry points for the AppleTalk
//		monitor that manipulate ports.
//
//		The following are the functions contained in this module.
//		All these functions are exported.
//
//    			OpenPort
//    			ClosePort
//    			EnumPortsW
//    			AddPortW
//    			ConfigurePortW
//    			DeletePortW
//
// History:
//
//      Aug 26,1992     frankb  	Initial version
//	June 11,1993.	NarenG		Bug fixes/clean up
//

#include <windows.h>
#include <winspool.h>
#include <winsplp.h>
#include <winsock.h>
#include <atalkwsh.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <lmcons.h>

#include <prtdefs.h>

#include "atalkmon.h"
#include "atmonmsg.h"
#include <bltrc.h>
#include "dialogs.h"

//**
//
// Call:	AddPort
//
// Returns:	TRUE	- Success
//		FALSE	- False
//
// Description:
// 		This routine is called when the user selects 'other...'
//		from the port list of the print manager.  It presents a browse
//		dialog to the user to allow the user to locate a LaserWriter
//		on the AppleTalk network.
//
BOOL 
AddPort(
    IN LPWSTR pName, 
    IN HWND   hwnd, 
    IN LPWSTR pMonitorName
){

    PATALKPORT	pNewPort;
    PATALKPORT	pWalker;
    DWORD	dwRetCode;

    DBGPRINT(("Entering AddPort\n")) ;

    //
    // Allocate an initialized port
    //

    if ( ( pNewPort = AllocAndInitializePort()) == NULL ) 
    {
	SetLastError( ERROR_NOT_ENOUGH_MEMORY );
	return( FALSE );
    }

    //
    // Set up the query socket. If this fails we assume that it is because
    // the stack is not started and we let the Add Port dialogs bring
    // up the error
    //

    if ( OpenAndBindAppleTalkSocket( &(pNewPort->sockQuery) ) != NO_ERROR ) 
        pNewPort->sockQuery = INVALID_SOCKET;

    if ( !AddPortDialog( hwnd, pNewPort ) )
    {
	//
	// If the dialog failed for some reason then we just return. The
	// dialog has taken care of displaying an error popup.
	//

	if ( pNewPort->sockQuery != INVALID_SOCKET )
    	    closesocket( pNewPort->sockQuery );

	FreeAppleTalkPort( pNewPort );

        DBGPRINT(("AddPortDialog returns not OK\n")) ;

	return( TRUE );
    }

    //
    // Clean up the query socket
    //

    closesocket( pNewPort->sockQuery );
    pNewPort->sockQuery = INVALID_SOCKET;

    WaitForSingleObject( hmutexPortList, INFINITE );


    do {

    	//
    	// walk the list and make sure we are not a duplicate
    	//

        dwRetCode = NO_ERROR;

    	for( pWalker = pPortList; pWalker != NULL; pWalker = pWalker->pNext )
    	{
	    if ( _wcsicmp( pWalker->pPortName, pNewPort->pPortName ) == 0 ) 
 	    {
	    	dwRetCode = ERROR_PRINTER_ALREADY_EXISTS;
		break;
	    }
	}

	if ( dwRetCode != NO_ERROR )
	    break;

    	// 
    	// add port to registry 
    	//

    	if ( ( dwRetCode = CreateRegistryPort( pNewPort )) != NO_ERROR ) 
	    break;

    	//
    	// Add port to our list 
    	//

    	pNewPort->pNext = pPortList;
    	pPortList       = pNewPort;

    } while ( FALSE );

    ReleaseMutex( hmutexPortList );

    if ( dwRetCode != NO_ERROR )
    {
 	SetLastError( dwRetCode );
    	FreeAppleTalkPort( pNewPort );
	return( FALSE );
    }

    SetEvent( hevConfigChange );

    return( TRUE );

}

//**
//
// Call:	DeletePort
//
// Returns:	TRUE	- Success
//		FALSE   - Failure
//
// Description:
//		This routine is called by the print manager to remove
//		a port from our configuration.  Need to verify that it can only
//		be called when the port is not active, or we need to resolve
//		the issue of deleting an active port.  DeletePort will release
//		the printer if it is captured.
BOOL 
DeletePort(
    IN LPWSTR pName, 
    IN HWND   hwnd, 
    IN LPWSTR pPortName
){

    PATALKPORT  pPrevious;
    PATALKPORT  pWalker;
    DWORD	dwRetCode = ERROR_UNKNOWN_PORT;

    DBGPRINT(("Entering DeletePort\n")) ;

    WaitForSingleObject( hmutexPortList, INFINITE );

    for ( pWalker = pPortList, pPrevious = pPortList; 
	  pWalker != NULL; 
	  pPrevious = pWalker,
          pWalker = pWalker->pNext ) 
    {

	if ( _wcsicmp( pPortName, pWalker->pPortName ) == 0 )
	{

	    if ( pWalker->fPortFlags & SFM_PORT_IN_USE )
	    {
	 	dwRetCode = ERROR_DEVICE_IN_USE;
		break;
	    }

    	    //
    	    // remove from registry
    	    //

    	    if (( dwRetCode = RegDeleteKey( hkeyPorts, pPortName )) 
							!= ERROR_SUCCESS ) 
		break;

	    // 
	    // Remove from active list 
	    //

	    if ( pWalker == pPortList )
		pPortList = pPortList->pNext;
	    else
		pPrevious->pNext = pWalker->pNext;
		
	    //
	    // Put it in the delete list
	    //

    	    WaitForSingleObject( hmutexDeleteList, INFINITE );

	    pWalker->pNext = pDeleteList;
	    pDeleteList    = pWalker;

    	    ReleaseMutex( hmutexDeleteList );

	    break;
	}
    }

    ReleaseMutex( hmutexPortList );

    if ( dwRetCode != NO_ERROR )
    {
    	SetLastError( dwRetCode );

        return( FALSE );
    }

    SetEvent( hevConfigChange );

    return( TRUE );
}

//**
//
// Call:	EnumPorts
//
// Returns:	TRUE 	- Success
//		FALSE	- Failure
//
// Description:
//		EnumPorts is called by the print manager to get
//		information about all configured ports for the monitor.
BOOL 
EnumPorts(
    IN 	LPWSTR   pName,
    IN  DWORD 	 dwLevel,
    IN  LPBYTE 	 pPorts,
    IN  DWORD 	 cbBuf,
    OUT LPDWORD  pcbNeeded,
    OUT PDWORD   pcReturned
){

    PATALKPORT      pWalker;
    LPWSTR          pNames;
    PPORT_INFO_1    pPortInfo = (PPORT_INFO_1)pPorts;

    *pcReturned = 0;
    *pcbNeeded  = 0;

    //
    // validate parameters
    //

    if ( dwLevel != 1 ) 
    {
	SetLastError( ERROR_INVALID_LEVEL );

	return( FALSE );
    }

    //
    // get size needed 
    //

    WaitForSingleObject( hmutexPortList, INFINITE );

    for ( pWalker = pPortList; pWalker != NULL; pWalker = pWalker->pNext ) 
    {
	*pcbNeeded += ((sizeof(WCHAR) * (wcslen(pWalker->pPortName) + 1)) 
		      + sizeof(PORT_INFO_1));
    }

    DBGPRINT(("buffer size needed=%d\n", *pcbNeeded)) ;

    //
    // if buffer too small, return error 
    //

    if ( ( *pcbNeeded > cbBuf ) || ( pPorts == NULL )) 
    {
	SetLastError( ERROR_INSUFFICIENT_BUFFER );

	DBGPRINT(("insufficient buffer\n"));

    	ReleaseMutex( hmutexPortList );

	return( FALSE );
    }

    //
    // fill the buffer 
    //

    DBGPRINT(("attempting to copy to buffer\n")) ;

    for ( pWalker = pPortList, pNames = (LPWSTR)(pPorts+cbBuf); 
	  pWalker != NULL; 
	  pWalker = pWalker->pNext ) 
    {

	DBGPRINT(("copying %ws\n", pWalker->pPortName)) ;

	pNames -= ( wcslen( pWalker->pPortName ) + 1 );

	wcscpy( (LPWSTR)pNames, pWalker->pPortName );

	(*pcReturned)++;

	pPortInfo->pName = pNames;

	pPortInfo++;

    }

    ReleaseMutex( hmutexPortList );

    return( TRUE );
}

//**
//
// Call:	OpenPort
//
// Returns:	TRUE 	- Success
//		FALSE   - Failure
//
// Description:  
//	This routine is called by the print manager to
//	get a handle for a port to be used in subsequent calls
//	to read and write data to the port.  It opens an AppleTalk
//	Address on the server for use in establishing connections
//	when a job is sent to print.  It looks like the NT Print
//	Spooler only calls OpenPort once.
//
//	NOTE: In order to allow for the AppleTalk stack to be turned off
//      while printing is not happening, OpenPort will not go to the
//      stack.  Instead, it will just validate the parameters and
//      return a handle.  The stack will be accessed on StartDocPort.
//
// 	OpenPort is called whenever a port becomes configured to
// 	be used by one or more NT Printers.  We use this fact to recognize
// 	when we need to start capturing the printer.  This routine sets
// 	the port state to open and then kicks off a config event to
// 	capture or release it.
//
BOOL 
OpenPort(
    IN LPWSTR 	pName, 
    IN PHANDLE  pHandle
){

    PATALKPORT      pWalker;

    DBGPRINT(("Entering OpenPort\n")) ;

    //
    // find the printer in our list
    //

    WaitForSingleObject( hmutexPortList, INFINITE );

    for ( pWalker = pPortList; pWalker != NULL; pWalker = pWalker->pNext ) 
    {
	if ( _wcsicmp( pWalker->pPortName, pName ) == 0 ) 
	{
	    pWalker->fPortFlags |= SFM_PORT_OPEN;
	    pWalker->fPortFlags &= ~SFM_PORT_CLOSE_PENDING;
	    break;
	}
    }

    ReleaseMutex( hmutexPortList );

    if ( pWalker == NULL ) 
    {
	SetLastError( ERROR_UNKNOWN_PORT );

	DBGPRINT(("ERROR: Could not find printer %ws\n", pName)) ;

	return( FALSE );
    }

    SetEvent( hevConfigChange );

    *pHandle = pWalker;

    return( TRUE );
}

//**
//
// Call:	ClosePort
//
// Returns:	TRUE	- Success
//		FALSE	- Failure
//
// Description:
// 		This routine is called to release the handle to
//		the open port.  It looks like the spooler only calls
//		ClosePort prior to deleting a port (maybe).  Otherwise,
//		ports are never closed by the spooler.
//
//		This routine simply cleans up the handle and returns.
//
// 		When the NT spooler recognizes that no printers are configured
// 		to use a port, it calls ClosePort().  We mark the port status as
// 		closed, and release the printer if it is captured.
//
BOOL 
ClosePort(
    IN HANDLE hPort
){

    PATALKPORT  pPort = (PATALKPORT)hPort;
    PATALKPORT  pWalker;
    DWORD	dwRetCode = ERROR_UNKNOWN_PORT;

    DBGPRINT(("Entering ClosePort\n"));

    if ( pPort == NULL ) 
    {
	SetLastError( ERROR_INVALID_HANDLE );

	DBGPRINT(("ERROR: ClosePort on closed handle\n")) ;

	return( FALSE );
    }

    //
    // find the printer in our list
    //

    WaitForSingleObject( hmutexPortList, INFINITE );

    for ( pWalker = pPortList; pWalker != NULL; pWalker = pWalker->pNext ) 
    {
	if ( _wcsicmp( pWalker->pPortName, pPort->pPortName ) == 0 ) 
	{
	    if ( pWalker->fPortFlags & SFM_PORT_IN_USE )
		dwRetCode = ERROR_BUSY;
	    else
	    {
	        pWalker->fPortFlags &= ~SFM_PORT_OPEN;
	        pWalker->fPortFlags |= SFM_PORT_CLOSE_PENDING;
    	        pWalker->fPortFlags &= ~SFM_PORT_CAPTURED;
		dwRetCode = NO_ERROR;
	    }

	    break;
	}
    }

    ReleaseMutex( hmutexPortList );

    if ( dwRetCode != NO_ERROR ) 
    {
	SetLastError( dwRetCode );

	return( FALSE );
    }

    SetEvent( hevConfigChange );

    return( TRUE );
}

//**
//
// Call:	ConfigurePort
//
// Returns:	TRUE	- Success
//		FALSE	- Failure
//
// Description:
//
BOOL 
ConfigurePort(
    IN LPWSTR  pName,
    IN HWND    hwnd,
    IN LPWSTR  pPortName
){

    DWORD       dwRetCode;
    BOOL    	fCapture;
    BOOL 	fIsSpooler;
    PATALKPORT  pWalker;

    DBGPRINT(("Entering ConfigurePort\n")) ;

    //
    // find the port structure
    //

    WaitForSingleObject( hmutexPortList, INFINITE );

    for ( pWalker = pPortList; pWalker != NULL; pWalker = pWalker->pNext )
    {
	if ( _wcsicmp( pPortName, pWalker->pPortName ) == 0 ) 
	{
    	    fCapture   = pWalker->fPortFlags & SFM_PORT_CAPTURED;
    	    fIsSpooler = pWalker->fPortFlags & SFM_PORT_IS_SPOOLER;
	    break;
	}
    }

    ReleaseMutex( hmutexPortList );

    if ( pWalker == NULL ) 
    {
	DBGPRINT(("ERROR: port not found\n")) ;
        SetLastError( ERROR_UNKNOWN_PORT );
	return( FALSE );
    }

    //
    // configure the port. If there was any error in the dialog, it would
    // have been displayed already.
    //

    if ( !ConfigPortDialog( hwnd, fIsSpooler, &fCapture ) )
	return( TRUE );

    WaitForSingleObject( hmutexPortList, INFINITE );

    do {

    	for ( pWalker = pPortList; pWalker != NULL; pWalker = pWalker->pNext )
    	{
	    if ( _wcsicmp( pPortName, pWalker->pPortName ) == 0 )
		break;
	}

        if ( pWalker == NULL ) 
    	{
	    dwRetCode = ERROR_UNKNOWN_PORT;
	    break;
        }

	if ( fCapture )
	    pWalker->fPortFlags |= SFM_PORT_CAPTURED;
	else
	    pWalker->fPortFlags &= ~SFM_PORT_CAPTURED;

	//
	// save changes to registry
	//
 	
	dwRetCode = SetRegistryInfo( pWalker );

    } while( FALSE );

    ReleaseMutex( hmutexPortList );

    if ( dwRetCode != NO_ERROR )
    {
	SetLastError( dwRetCode );
	return( FALSE );
    }

    SetEvent( hevConfigChange );

    return( TRUE );
}