text: //+-----------------------------------------------------------------------
  //
  // Microsoft Windows
  //
  // Copyright (c) Microsoft Corporation
  //
  // File:        kpkdc.h
  //
  // Contents:    routines for communicating with the kdc
  //
  // History:     10-Jul-2001     t-ryanj         Created
  //
  //------------------------------------------------------------------------
  #include <nt.h>
  #include <ntrtl.h>
  #include <nturtl.h>
  #include <windows.h>
  #ifndef SECURITY_WIN32
  #define SECURITY_WIN32
  #endif
  #include <kerbcomm.h> 
  #include <dsgetdc.h>
  #include <lm.h> /* for NetApiBufferFree */
  #include "kpkdc.h"
  #include "kpcontext.h"
  #include "kpcore.h"
  
  #define KDC_SERVICE_NAME "kerberos"
  #define KDC_FALLBACK_PORT 88 
  
  SHORT KpDefaultKdcPort;
  BOOL KpWSAStarted = FALSE;
  
  ULONG
  KpGetPduValue(
      PKPCONTEXT pContext
      );
  
  VOID
  KpInitDefaultKdcPort(
      VOID
      );
  
  //+-------------------------------------------------------------------------
  //
  //  Function:   KpInitWinsock
  //
  //  Synopsis:   Starts winsock
  //
  //  Effects:
  //
  //  Arguments:  
  //
  //  Requires:
  //
  //  Returns:    Success value.  If FALSE, GetLastError() for details.
  //
  //  Notes:
  //
  //
  //--------------------------------------------------------------------------
  BOOL
  KpInitWinsock(
      VOID 
      )
  {
      WORD wWinsockVersionRequested = MAKEWORD( 2,2 );
      WSADATA wsaData;
      DWORD WSAStartError;
  
      DsysAssert(!KpWSAStarted);
      WSAStartError = WSAStartup( wWinsockVersionRequested, &wsaData );
      if( WSAStartError == SOCKET_ERROR )
      {
  	DebugLog( DEB_ERROR, "%s(%d): Error starting winsock: 0x%x.\n", __FILE__, __LINE__, WSAGetLastError() );
          SetLastError(WSAGetLastError());
  	goto Cleanup;
      }
  
      KpWSAStarted = TRUE;
  
      KpInitDefaultKdcPort();
  
  Cleanup:
      return KpWSAStarted;
  }    
  
  //+-------------------------------------------------------------------------
  //
  //  Function:   KpCleanupWinsock
  //
  //  Synopsis:   Cleans up winsock.
  //
  //  Effects:
  //
  //  Arguments:  
  //
  //  Requires:
  //
  //  Returns:    
  //
  //  Notes:
  //
  //
  //--------------------------------------------------------------------------
  VOID
  KpCleanupWinsock(
      VOID
      )
  {
      if( KpWSAStarted )
      {
  	WSACleanup();
          KpWSAStarted = FALSE;
      }
  }
  
  //+-------------------------------------------------------------------------
  //
  //  Function:   KpInitDefaultKdcPort
  //
  //  Synopsis:   Finds the appropriate port for talking to kdcs. 
  //
  //  Effects:
  //
  //  Arguments:  
  //
  //  Requires:
  //
  //  Returns:    
  //
  //  Notes:
  //
  //
  //--------------------------------------------------------------------------
  VOID
  KpInitDefaultKdcPort(
      VOID
      )
  {
      PSERVENT krb5;
  
      /* TODO: Read from registry. */
  
      //
      // Ask winsock what port kerberos works on.  This should be defined in 
      // %systemroot%\system32\drivers\etc\services 
      // Note that winsock manages the servent struct, we don't need to free it.
      //
  
      if( krb5 = getservbyname( KDC_SERVICE_NAME, NULL ) )
      {
          KpDefaultKdcPort = krb5->s_port;
      }
      else
      {
          DebugLog( DEB_WARN, "%s(%d): Could not determine kerberos port; falling back to port %d.\n", __FILE__, __LINE__, KDC_FALLBACK_PORT );
          KpDefaultKdcPort = htons( KDC_FALLBACK_PORT );
      }
  }
  
  //+-------------------------------------------------------------------------
  //
  //  Function:   KpKdcWrite
  //
  //  Synopsis:   Writes the response to the KDC.
  //
  //  Effects:
  //
  //  Arguments:  pContext - the context
  //
  //  Requires:
  //
  //  Returns:
  //
  //  Notes:      
  //
  //
  //--------------------------------------------------------------------------
  VOID
  KpKdcWrite(
      PKPCONTEXT pContext
      )
  {
      PKERB_KDC_REQUEST pKdcReq = NULL;
      KERBERR KerbErr;
      ULONG PduValue = 0;  /* compiler stupid, insists on =0 */
      PDOMAIN_CONTROLLER_INFOA pKdcInfo = NULL;
      DWORD DsError;
      sockaddr_in KdcAddr;
      int connecterr;
      BOOL WriteSuccess;
  
      DebugLog( DEB_TRACE, "%s(%d): %d bytes read from http.\n", __FILE__, __LINE__, pContext->pECB->cbTotalBytes );
  
      //
      // First we need to find the PduValue for the request so we can unpack.
      //
  
      PduValue = KpGetPduValue(pContext);
  
      if( !PduValue )
          goto Error;
  
      //
      // Unpack the request.
      //
  
      KerbErr = KerbUnpackData( pContext->databuf + sizeof(DWORD),
  			      pContext->buflen - sizeof(DWORD),
  			      PduValue,
  			      (PVOID*)&pKdcReq );
  
      if( !KERB_SUCCESS(KerbErr) )
      {
  	DebugLog( DEB_ERROR, "%s(%d): Kerberos Error: 0x%x\n", __FILE__, __LINE__, KerbErr );
  	goto Error;
      }
  
      //
      // Now we need to look for a KDC for the realm specified in the request.
      //
  
      DebugLog( DEB_TRACE, "%s(%d): Realm found to be %s.\n", __FILE__, __LINE__, pKdcReq->request_body.realm );
  
      DsError = DsGetDcNameA( NULL, /* Computer Name */
  			    pKdcReq->request_body.realm,
  			    NULL, /* Domain GUID */
  			    NULL, /* Site Name */
  			    DS_IP_REQUIRED | DS_KDC_REQUIRED,
  			    &pKdcInfo );
  
      if( DsError != NO_ERROR )
      {
  	DebugLog( DEB_ERROR, "%s(%d): Error from DsGetDcName: 0x%x\n", __FILE__, __LINE__, DsError );
  	goto Error;
      }
  
      DebugLog( DEB_TRACE, "%s(%d): DC found.  %s\n", __FILE__, __LINE__, pKdcInfo->DomainControllerAddress );
  
      //
      // Construct the SOCKADDR_IN to connect to the kdc.
      //
  
      KdcAddr.sin_family = AF_INET;
      KdcAddr.sin_port = KpDefaultKdcPort;
      KdcAddr.sin_addr.s_addr = inet_addr(pKdcInfo->DomainControllerAddress+2 /* past the \\ */ );
  
      //
      // Create the socket.
      //
  
      pContext->KdcSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
  
      if( !pContext->KdcSock )
      {
  	DebugLog( DEB_ERROR, "%s(%d): Error creating socket: 0x%x\n", __FILE__, __LINE__, WSAGetLastError() );
  	goto Error;
      }
  
      //
      // Connect the socket.
      //
  
      connecterr = connect( pContext->KdcSock, (sockaddr*)&KdcAddr, sizeof(sockaddr_in) );
  
      if( connecterr )
      {
  	DebugLog( DEB_ERROR, "%s(%d): Error connecting to Kdc: 0x%x\n", __FILE__, __LINE__, WSAGetLastError() );
  	goto Error;
      }
  
      //
      // Associate the socket with the iocp.
      //
  
  /*    if( KpGlobalIocp != CreateIoCompletionPort( (HANDLE)pContext->KdcSock,
  						KpGlobalIocp,
  						KPCK_CHECK_CONTEXT,
  						0 ) ) */
      if( !BindIoCompletionCallback( (HANDLE)pContext->KdcSock,
                                     KpIoCompletionRoutine,
                                     0 ) )
      {
  	DebugLog( DEB_ERROR, "%s(%d): Error associating Iocp with Kdc socket: 0x%x.\n", __FILE__, __LINE__, GetLastError() );
  	goto Error;
      }
  
      //
      // Write the request asynchronously.
      //
  
      ZeroMemory( &(pContext->ol), sizeof(OVERLAPPED) );
      pContext->dwStatus = KP_KDC_WRITE;
  
      WriteSuccess = WriteFile( (HANDLE)pContext->KdcSock,
  			      pContext->databuf,
  			      pContext->buflen,
  			      NULL, /* bytes written - not used in async */
  			      &(pContext->ol) );
  
      if( !WriteSuccess )
      {
  	if( GetLastError() != ERROR_IO_PENDING )
  	{
  	    DebugLog( DEB_ERROR, "%s(%d): Error issuing socket write: 0x%x\n", __FILE__, __LINE__, GetLastError() );
  	    goto Error;
  	}
      }
  
  
  Cleanup:
      if( pKdcInfo )
  	NetApiBufferFree( pKdcInfo );
      if( pKdcReq )
  	KerbFreeData( PduValue, pKdcReq );
      return;
  
  Error:
      KpReleaseContext(pContext);
      goto Cleanup;
  }
  
  //+-------------------------------------------------------------------------
  //
  //  Function:   KpKdcRead
  //
  //  Synopsis:   Reads the response from the KDC.
  //
  //  Effects:
  //
  //  Arguments:  pContext
  //
  //  Requires:
  //
  //  Returns:
  //
  //  Notes:      Just a stub now.
  //
  //
  //--------------------------------------------------------------------------
  VOID
  KpKdcRead(
      PKPCONTEXT pContext 
      )
  {
      BOOL ReadSuccess;
  
      //
      // Read as much data as possible from the socket asynchronously.
      //
  
      ZeroMemory( &(pContext->ol), sizeof(OVERLAPPED) );
  
      pContext->dwStatus = KP_KDC_READ;
  
      DebugLog( DEB_PEDANTIC, "%s(%d): Reading up to %d bytes from Kdc socket.\n", __FILE__, __LINE__, pContext->buflen - pContext->bytesReceived );
      
      ReadSuccess = ReadFile( (HANDLE)pContext->KdcSock,
  			    pContext->databuf + pContext->bytesReceived,
  			    pContext->buflen - pContext->bytesReceived,
  			    NULL, /* bytes read - not used in async */
  			    &(pContext->ol) );
  
      if( !ReadSuccess )
      {
  	if( GetLastError() != ERROR_IO_PENDING )
  	{
  	    DebugLog( DEB_ERROR, "%s(%d): Error issuing read: 0x%x\n", __FILE__, __LINE__, GetLastError() );
  	    goto Error;
  	}
      }
  
  Cleanup:
      return;
  
  Error:
      KpReleaseContext(pContext);
      goto Cleanup;
  }
  
  //+-------------------------------------------------------------------------
  //
  //  Function:   KpKdcReadDone
  //
  //  Synopsis:   Checks to see if enough data has been read.
  //
  //  Effects:    
  //
  //  Arguments:  pContext
  //
  //  Requires:   
  //
  //  Returns:    TRUE of reading is complete.
  //
  //  Notes:      
  //
  //--------------------------------------------------------------------------
  BOOL
  KpKdcReadDone(
      PKPCONTEXT pContext
      )
  {
      InterlockedExchangeAdd( (LONG*)&(pContext->bytesReceived), (LONG)pContext->ol.InternalHigh );
      return pContext->bytesReceived == pContext->buflen;
  }
  
  //+-------------------------------------------------------------------------
  //
  //  Function:   KpCalcLength
  //
  //  Synopsis:   Calculates the length of the incoming request.
  //
  //  Effects:    
  //
  //  Arguments:  pContext
  //
  //  Requires:   
  //
  //  Returns:    Success value.
  //
  //  Notes:      
  //
  //--------------------------------------------------------------------------
  BOOL
  KpCalcLength(
      PKPCONTEXT pContext 
      )
  {
      BOOL fRet = TRUE;
      DWORD cbContent;
      LPBYTE newbuf = NULL;
  
      //
      // Spec says that all requests that we recieve should be in TCP form,
      // meaning they are preceeded by at least 4 bytes in network byte order
      // indicating the length of the message.  So if we don't have at least
      // four bytes of data, we're toast.
      //
  
      if( pContext->ol.InternalHigh < sizeof(DWORD) )
      {
  	DebugLog( DEB_TRACE, "%s(%d): Less than sizeof(DWORD) bytes received.  CalcLength failed.\n", __FILE__, __LINE__ );
  	goto Error;
      }
  
      //
      // Convert the four bytes to host byte order.
      //
  
      cbContent = ntohl(*(u_long*)pContext->databuf);
  
      //
      // If the sign bit is set, that means that another four bytes are
      // needed for the length of the message.  That's too long, so
      // we'll just punt.
      //
  
      if( cbContent & 0x80000000 ) /* high bit set */
      {
  	DebugLog( DEB_TRACE, "%s(%d): Length won't fit in DWORD.\n", __FILE__, __LINE__ );
  	goto Error;
      }
  
      //
      // So we're expecting as a total length, the length of the length
      // plus the length of the content.
      //
  
      pContext->bytesExpected = sizeof(DWORD) + cbContent;
  
      //
      // Realloc our buffer to be large enough to hold the whole message.
      //
  
      pContext->buflen = sizeof(DWORD) + cbContent;
      newbuf = (LPBYTE)KpReAlloc( pContext->databuf, pContext->buflen );
      if( !newbuf )
      {
  	DebugLog( DEB_TRACE, "%s(%d): Realloc failed.  CalcLength failed.\n", __FILE__, __LINE__ );
  	goto Error;
      }
      pContext->databuf = newbuf;
  
      DebugLog( DEB_TRACE, "%s(%d): Looking for reply of length %d.\n", __FILE__, __LINE__, pContext->bytesExpected );
  
  Cleanup:
      return fRet;
  
  Error:
      fRet = FALSE;
      goto Cleanup;
  }
  
  //+-------------------------------------------------------------------------
  //
  //  Function:   KpGetPduValue
  //
  //  Synopsis:   Looks at the first byte of the request to determine the 
  //              message type, and returns the appropriate PduValue for
  //              unpacking.
  //
  //  Effects:    
  //
  //  Arguments:  pContext
  //
  //  Requires:   
  //
  //  Returns:    The appropriate PduValue, or 0 on failure.  Note that 0 
  //              is a valid PduValue, but since we're only concerned with
  //              AS-REQ and TGS-REQ, we can ignore it.
  //
  //  Notes:      
  //
  //--------------------------------------------------------------------------
  ULONG
  KpGetPduValue(
      PKPCONTEXT pContext
      )
  {
      ULONG PduValue = 0;
  
      //
      // If we don't have the first byte, we can't exactly parse it.
      //
  
      if( pContext->buflen < 1 + sizeof(DWORD) )
          goto Cleanup;
  
      //
      // The last five bits of the first byte signify the message type.
      //
  
      switch( pContext->databuf[sizeof(DWORD)] & 0x1f ) /* last 5 bits */
      {
      case 0xa: /* AS-REQUEST */
          DebugLog( DEB_TRACE, "%s(%d): Processing AS-REQUEST.\n", __FILE__, __LINE__ );
  	PduValue = KERB_AS_REQUEST_PDU;
  	break;
  		
      case 0xc: /* TGS-REQUEST */
          DebugLog( DEB_TRACE, "%s(%d): Processing TGS-REQUEST.\n", __FILE__, __LINE__ );
  	PduValue = KERB_TGS_REQUEST_PDU;
  	break;
  	
      default:
  	DebugLog( DEB_ERROR, "%s(%d): Not an AS or TGS request.\n", __FILE__, __LINE__ );
          goto Cleanup;
      }
  
  Cleanup:
      return PduValue;
  }
  