Windows2003-3790/inetcore/outlookexpress/inetcomm/imnxport/ixpnntp.cpp
2020-09-30 16:53:55 +02:00

3149 lines
96 KiB
C++

// --------------------------------------------------------------------------------
// Ixpnntp.cpp
// Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
//
// Eric Andrews
// Steve Serdy
// --------------------------------------------------------------------------------
#include "pch.hxx"
#include "dllmain.h"
#include <stdio.h>
#include "ixpnntp.h"
#include "asynconn.h"
#include "ixputil.h"
#include "strconst.h"
#include "resource.h"
#include <shlwapi.h>
#include "demand.h"
// --------------------------------------------------------------------------------
// Some handle macros for simple pointer casting
// --------------------------------------------------------------------------------
#define NNTPTHISIXP ((INNTPTransport *)(CIxpBase *) this)
#define NUM_HEADERS 100
CNNTPTransport::CNNTPTransport(void) : CIxpBase(IXP_NNTP)
{
ZeroMemory(&m_rMessage, sizeof(m_rMessage));
ZeroMemory(&m_sicinfo, sizeof(SSPICONTEXT));
DllAddRef();
m_substate = NS_RESP;
}
CNNTPTransport::~CNNTPTransport(void)
{
SafeRelease(m_rMessage.pstmMsg);
DllRelease();
}
// --------------------------------------------------------------------------------
// CNNTPTransport::QueryInterface
// --------------------------------------------------------------------------------
HRESULT CNNTPTransport::QueryInterface(REFIID riid, LPVOID *ppv)
{
// Locals
HRESULT hr=S_OK;
// Bad param
if (ppv == NULL)
{
hr = TrapError(E_INVALIDARG);
goto exit;
}
// Init
*ppv=NULL;
// IID_IUnknown
if (IID_IUnknown == riid)
*ppv = ((IUnknown *)(INNTPTransport *)this);
// IID_IInternetTransport
else if (IID_IInternetTransport == riid)
*ppv = ((IInternetTransport *)(CIxpBase *)this);
// IID_INNTPTransport
else if (IID_INNTPTransport == riid)
*ppv = (INNTPTransport *)this;
// IID_INNTPTransport2
else if (IID_INNTPTransport2 == riid)
*ppv = (INNTPTransport2 *)this;
// If not null, addref it and return
if (NULL != *ppv)
{
((LPUNKNOWN)*ppv)->AddRef();
goto exit;
}
// No Interface
hr = TrapError(E_NOINTERFACE);
exit:
// Done
return hr;
}
// --------------------------------------------------------------------------------
// CNNTPTransport::AddRef
// --------------------------------------------------------------------------------
ULONG CNNTPTransport::AddRef(void)
{
return ++m_cRef;
}
// --------------------------------------------------------------------------------
// CNNTPTransport::Release
// --------------------------------------------------------------------------------
ULONG CNNTPTransport::Release(void)
{
if (0 != --m_cRef)
return m_cRef;
delete this;
return 0;
}
//
// FUNCTION: CNNTPTransport::OnNotify()
//
// PURPOSE: This function get's called whenever the CAsyncConn class
// sends or receives data.
//
// PARAMETERS:
// asOld - State of the connection before this event
// asNew - State of the connection after this event
// ae - Identifies the event that has occured
//
void CNNTPTransport::OnNotify(ASYNCSTATE asOld, ASYNCSTATE asNew, ASYNCEVENT ae)
{
// Enter Critical Section
EnterCriticalSection(&m_cs);
// Handle Event
switch(ae)
{
case AE_RECV:
OnSocketReceive();
break;
case AE_SENDDONE:
if (m_substate == NS_SEND_ENDPOST)
{
HrSendCommand((LPSTR) NNTP_ENDPOST, NULL, FALSE);
m_substate = NS_ENDPOST_RESP;
}
break;
case AE_LOOKUPDONE:
if (AS_DISCONNECTED == asNew)
{
DispatchResponse(IXP_E_CANT_FIND_HOST, TRUE);
OnDisconnected();
}
else
OnStatus(IXP_CONNECTING);
break;
// --------------------------------------------------------------------------------
case AE_CONNECTDONE:
if (AS_DISCONNECTED == asNew)
{
DispatchResponse(IXP_E_FAILED_TO_CONNECT, TRUE);
OnDisconnected();
}
else if (AS_HANDSHAKING == asNew)
{
OnStatus(IXP_SECURING);
}
else
OnConnected();
break;
// --------------------------------------------------------------------------------
case AE_CLOSE:
if (AS_RECONNECTING != asNew && IXP_AUTHRETRY != m_status)
{
if (IXP_DISCONNECTING != m_status && IXP_DISCONNECTED != m_status)
{
if (AS_HANDSHAKING == asOld)
DispatchResponse(IXP_E_SECURE_CONNECT_FAILED, TRUE);
else
DispatchResponse(IXP_E_CONNECTION_DROPPED, TRUE);
}
OnDisconnected();
}
break;
default:
CIxpBase::OnNotify(asOld, asNew, ae);
break;
}
// Leave Critical Section
LeaveCriticalSection(&m_cs);
}
//
// FUNCTION: CNNTPTransport::InitNew()
//
// PURPOSE: The client calls this to specify a callback interface and a log
// file path if desired.
//
// PARAMETERS:
// pszLogFilePath - Path to the file to write logging info to.
// pCallback - Pointer to the callback interface to send results etc
// to.
//
// RETURN VALUE:
// HRESULT
//
HRESULT CNNTPTransport::InitNew(LPSTR pszLogFilePath, INNTPCallback *pCallback)
{
// Let the base class handle the rest
return (CIxpBase::OnInitNew("NNTP", pszLogFilePath, FILE_SHARE_READ | FILE_SHARE_WRITE,
(ITransportCallback *) pCallback));
}
// --------------------------------------------------------------------------------
// CNNTPTransport::HandsOffCallback
// --------------------------------------------------------------------------------
STDMETHODIMP CNNTPTransport::HandsOffCallback(void)
{
return CIxpBase::HandsOffCallback();
}
// --------------------------------------------------------------------------------
// CNNTPTransport::GetStatus
// --------------------------------------------------------------------------------
STDMETHODIMP CNNTPTransport::GetStatus(IXPSTATUS *pCurrentStatus)
{
return CIxpBase::GetStatus(pCurrentStatus);
}
// --------------------------------------------------------------------------------
// CNNTPTransport::InetServerFromAccount
// --------------------------------------------------------------------------------
STDMETHODIMP CNNTPTransport::InetServerFromAccount(IImnAccount *pAccount, LPINETSERVER pInetServer)
{
return CIxpBase::InetServerFromAccount(pAccount, pInetServer);
}
// --------------------------------------------------------------------------------
// CNNTPTransport::Connect
// --------------------------------------------------------------------------------
HRESULT CNNTPTransport::Connect(LPINETSERVER pInetServer, boolean fAuthenticate,
boolean fCommandLogging)
{
// Does user want us to always prompt for his password? Prompt him here to avoid
// inactivity timeouts while the prompt is up, unless a password was supplied
if (ISFLAGSET(pInetServer->dwFlags, ISF_ALWAYSPROMPTFORPASSWORD) &&
'\0' == pInetServer->szPassword[0])
{
HRESULT hr;
if (NULL != m_pCallback)
hr = m_pCallback->OnLogonPrompt(pInetServer, NNTPTHISIXP);
if (NULL == m_pCallback || S_OK != hr)
return IXP_E_USER_CANCEL;
}
return CIxpBase::Connect(pInetServer, fAuthenticate, fCommandLogging);
}
// --------------------------------------------------------------------------------
// CNNTPTransport::DropConnection
// --------------------------------------------------------------------------------
HRESULT CNNTPTransport::DropConnection(void)
{
return CIxpBase::DropConnection();
}
// --------------------------------------------------------------------------------
// CNNTPTransport::Disconnect
// --------------------------------------------------------------------------------
HRESULT CNNTPTransport::Disconnect(void)
{
HRESULT hr = S_OK;
if (SUCCEEDED(hr = CIxpBase::Disconnect()))
{
m_state = NS_DISCONNECTED;
m_pSocket->Close();
}
return (hr);
}
// --------------------------------------------------------------------------------
// CNNTPTransport::IsState
// --------------------------------------------------------------------------------
HRESULT CNNTPTransport::IsState(IXPISSTATE isstate)
{
return CIxpBase::IsState(isstate);
}
// --------------------------------------------------------------------------------
// CNNTPTransport::GetServerInfo
// --------------------------------------------------------------------------------
HRESULT CNNTPTransport::GetServerInfo(LPINETSERVER pInetServer)
{
return CIxpBase::GetServerInfo(pInetServer);
}
// --------------------------------------------------------------------------------
// CNNTPTransport::GetIXPType
// --------------------------------------------------------------------------------
IXPTYPE CNNTPTransport::GetIXPType(void)
{
return IXP_NNTP;
}
// --------------------------------------------------------------------------------
// CNNTPTransport::ResetBase
// --------------------------------------------------------------------------------
void CNNTPTransport::ResetBase(void)
{
EnterCriticalSection(&m_cs);
if (m_substate != NS_RECONNECTING)
{
m_state = NS_DISCONNECTED;
m_substate = NS_RESP;
m_fSupportsXRef = FALSE;
m_rgHeaders = 0;
m_pMemInfo = 0;
if (m_sicinfo.pCallback)
SSPIFreeContext(&m_sicinfo);
ZeroMemory(&m_sicinfo, sizeof(m_sicinfo));
m_cSecPkg = -1; // number of sec pkgs to try, -1 if not inited
m_iSecPkg = -1; // current sec pkg being tried
m_iAuthType = AUTHINFO_NONE;
ZeroMemory(m_rgszSecPkg, sizeof(m_rgszSecPkg)); // array of pointers into m_szSecPkgs
m_szSecPkgs = NULL; // string returned by "AUTHINFO TRANSACT TWINKIE"
m_fRetryPkg = FALSE;
m_pAuthInfo = NULL;
m_fNoXover = FALSE;
}
LeaveCriticalSection(&m_cs);
}
// ------------------------------------------------------------------------------------
// CNNTPTransport::DoQuit
// ------------------------------------------------------------------------------------
void CNNTPTransport::DoQuit(void)
{
CommandQUIT();
}
// --------------------------------------------------------------------------------
// CNNTPTransport::OnConnected
// --------------------------------------------------------------------------------
void CNNTPTransport::OnConnected(void)
{
m_state = NS_CONNECT;
m_substate = NS_CONNECT_RESP;
CIxpBase::OnConnected();
}
// --------------------------------------------------------------------------------
// CNNTPTransport::OnDisconnect
// --------------------------------------------------------------------------------
void CNNTPTransport::OnDisconnected(void)
{
ResetBase();
CIxpBase::OnDisconnected();
}
// --------------------------------------------------------------------------------
// CNNTPTransport::OnEnterBusy
// --------------------------------------------------------------------------------
void CNNTPTransport::OnEnterBusy(void)
{
IxpAssert(m_state == NS_IDLE);
}
// --------------------------------------------------------------------------------
// CNNTPTransport::OnLeaveBusy
// --------------------------------------------------------------------------------
void CNNTPTransport::OnLeaveBusy(void)
{
m_state = NS_IDLE;
}
//
// FUNCTION: CNNTPTransport::OnSocketReceive()
//
// PURPOSE: This function reads the data off the socket and parses that
// information based on the current state of the transport.
//
void CNNTPTransport::OnSocketReceive(void)
{
HRESULT hr;
UINT uiResp;
EnterCriticalSection(&m_cs);
// Handle the current NNTP State
switch (m_state)
{
case NS_CONNECT:
{
HandleConnectResponse();
break;
}
case NS_AUTHINFO:
{
HandleConnectResponse();
break;
}
case NS_NEWGROUPS:
{
// If we're waiting for the original response line then get it and
// parse it here
if (NS_RESP == m_substate)
{
if (FAILED(hr = HrGetResponse()))
goto exit;
// If the command failed, inform the user and exit
if (IXP_NNTP_NEWNEWSGROUPS_FOLLOWS != m_uiResponse)
{
hr = IXP_E_NNTP_NEWGROUPS_FAILED;
DispatchResponse(hr, TRUE);
break;
}
// Advance the substate to data receive
m_substate = NS_DATA;
}
// Process the returned data
ProcessListData();
break;
}
case NS_LIST:
{
// If we're waiting for the original response line then get it and
// parse it here.
if (NS_RESP == m_substate)
{
if (FAILED(hr = HrGetResponse()))
goto exit;
// If the command failed, inform the user and exit
if (IXP_NNTP_LIST_DATA_FOLLOWS != m_uiResponse)
{
hr = IXP_E_NNTP_LIST_FAILED;
DispatchResponse(hr, TRUE);
break;
}
// Advance the substate to data recieve
m_substate = NS_DATA;
}
// Start processing the data retrieved from the command
ProcessListData();
break;
}
case NS_LISTGROUP:
{
if (NS_RESP == m_substate)
{
if (FAILED(hr = HrGetResponse()))
goto exit;
if (IXP_NNTP_GROUP_SELECTED != m_uiResponse)
{
hr = IXP_E_NNTP_LISTGROUP_FAILED;
DispatchResponse(hr, TRUE);
break;
}
m_substate = NS_DATA;
}
ProcessListGroupData();
break;
}
case NS_GROUP:
if (FAILED(hr = HrGetResponse()))
goto exit;
ProcessGroupResponse();
break;
case NS_ARTICLE:
{
if (NS_RESP == m_substate)
{
if (FAILED(hr = HrGetResponse()))
goto exit;
if (IXP_NNTP_ARTICLE_FOLLOWS != m_uiResponse)
{
hr = IXP_E_NNTP_ARTICLE_FAILED;
DispatchResponse(hr, TRUE);
break;
}
m_substate = NS_DATA;
}
ProcessArticleData();
break;
}
case NS_HEAD:
{
if (NS_RESP == m_substate)
{
if (FAILED(hr = HrGetResponse()))
goto exit;
if (IXP_NNTP_HEAD_FOLLOWS != m_uiResponse)
{
hr = IXP_E_NNTP_HEAD_FAILED;
DispatchResponse(hr, TRUE);
break;
}
m_substate = NS_DATA;
}
ProcessArticleData();
break;
}
case NS_BODY:
{
if (NS_RESP == m_substate)
{
if (FAILED(hr = HrGetResponse()))
goto exit;
if (IXP_NNTP_BODY_FOLLOWS != m_uiResponse)
{
hr = IXP_E_NNTP_BODY_FAILED;
DispatchResponse(hr, TRUE);
break;
}
m_substate = NS_DATA;
}
ProcessArticleData();
break;
}
case NS_POST:
if (NS_RESP == m_substate)
{
// Get the response to our post command
if (FAILED(hr = HrGetResponse()))
goto exit;
// If the response wasn't 340, then we can't post and might
// as well bail.
if (IXP_NNTP_SEND_ARTICLE_TO_POST != m_uiResponse)
{
hr = IXP_E_NNTP_POST_FAILED;
// Make sure we free up the stream
SafeRelease(m_rMessage.pstmMsg);
DispatchResponse(hr, TRUE);
break;
}
// Send the message
if (SUCCEEDED(hr = HrPostMessage()))
{
HrSendCommand((LPSTR) NNTP_ENDPOST, 0, FALSE);
m_substate = NS_ENDPOST_RESP;
}
else if (IXP_E_WOULD_BLOCK == hr)
{
// We will send the crlf . crlf when we get the AE_SENDDONE
// notification. This is handled in OnNotify().
m_substate = NS_SEND_ENDPOST;
}
else
{
// Some unhandled error occured.
hr = IXP_E_NNTP_POST_FAILED;
DispatchResponse(hr, TRUE);
}
}
else if (NS_ENDPOST_RESP == m_substate)
{
// Here's the response from the server regarding the post. Send
// it off to the user.
hr = HrGetResponse();
if (IXP_NNTP_ARTICLE_POSTED_OK != m_uiResponse)
hr = IXP_E_NNTP_POST_FAILED;
DispatchResponse(hr, TRUE);
}
break;
case NS_IDLE:
break;
case NS_DISCONNECTED:
break;
case NS_QUIT:
if (FAILED(hr = HrGetResponse()))
goto exit;
DispatchResponse(S_OK, TRUE);
// Make sure the socket closes if the server doesn't drop the
// connection itself.
m_pSocket->Close();
break;
case NS_LAST:
if (FAILED(hr = HrGetResponse()))
goto exit;
ProcessNextResponse();
break;
case NS_NEXT:
if (FAILED(hr = HrGetResponse()))
goto exit;
ProcessNextResponse();
break;
case NS_STAT:
if (FAILED(hr = HrGetResponse()))
goto exit;
ProcessNextResponse();
break;
case NS_MODE:
// Very little to do with this response other than return it to
// the caller.
if (FAILED(hr = HrGetResponse()))
goto exit;
DispatchResponse(S_OK);
break;
case NS_DATE:
if (FAILED(hr = HrGetResponse()))
goto exit;
ProcessDateResponse();
break;
case NS_HEADERS:
if (NS_RESP == m_substate)
{
// Get the response string from the socket
if (FAILED(hr = HrGetResponse()))
goto exit;
// There's a couple of things that can happen here. First, if
// the response is IXP_NNTP_OVERVIEW_FOLLOWS, then everything is
// great and we can party on. If the response is > 500, then
// XOVER isn't supported on this server and we have to try using
// XHDR to retrieve the headers.
if (m_uiResponse >= 500 && m_gethdr == GETHDR_XOVER)
{
hr = BuildHeadersFromXhdr(TRUE);
if (FAILED(hr))
DispatchResponse(hr, TRUE);
break;
}
else if (2 != (m_uiResponse / 100))
{
hr = IXP_E_NNTP_HEADERS_FAILED;
DispatchResponse(hr, TRUE);
break;
}
m_substate = NS_DATA;
}
// Parse the returned data strings
if (m_gethdr == GETHDR_XOVER)
ProcessXoverData();
else
BuildHeadersFromXhdr(FALSE);
break;
case NS_XHDR:
if (NS_RESP == m_substate)
{
if (FAILED(hr = HrGetResponse()))
goto exit;
if (2 != (m_uiResponse / 100))
{
hr = IXP_E_NNTP_XHDR_FAILED;
DispatchResponse(hr, TRUE);
break;
}
m_substate = NS_DATA;
}
ProcessXhdrData();
break;
default:
IxpAssert(FALSE);
break;
}
exit:
LeaveCriticalSection(&m_cs);
}
HRESULT CNNTPTransport::HandleConnectResponse(void)
{
HRESULT hr = E_FAIL;
IxpAssert(m_substate >= NS_CONNECT_RESP);
switch (m_substate)
{
// Parse the banner and make sure we got a valid response. If so,
// then issue a "MODE READER" command to inform the server that we
// are a client and not another server.
case NS_CONNECT_RESP:
if (SUCCEEDED(hr = HrGetResponse()))
{
// Make sure we got a valid response from the server
if (IXP_NNTP_POST_ALLOWED != m_uiResponse &&
IXP_NNTP_POST_NOTALLOWED != m_uiResponse)
{
// If we didn't get a valid response, disconnect and inform
// the client that the connect failed.
Disconnect();
m_state = NS_DISCONNECTED;
DispatchResponse(IXP_E_NNTP_RESPONSE_ERROR, TRUE);
}
else
{
// Stash the response code so if we finally connect we can
// return whether or not posting is allowed
m_hrPostingAllowed =
(m_uiResponse == IXP_NNTP_POST_ALLOWED) ? S_OK : S_FALSE;
// Move to the next state where we issue the "MODE READER"
hr = HrSendCommand((LPSTR) NNTP_MODE_READER_CRLF, NULL, FALSE);
if (SUCCEEDED(hr))
{
m_state = NS_CONNECT;
m_substate = NS_MODE_READER_RESP;
}
}
}
break;
// Read the response from the "MODE READER" command off the socket. If
// the user wants us to handle authentication, then start that.
// Otherwise, we're done and can consider this a terminal state.
case NS_MODE_READER_RESP:
if (SUCCEEDED(hr = HrGetResponse()))
{
if (m_fConnectAuth)
// This is TRUE if the user specified in the Connect() call
// that we should automatically logon for them.
StartLogon();
else
{
// Otherwise consider ourselves ready to accept commands
DispatchResponse(m_hrPostingAllowed, TRUE);
}
}
break;
// We issued an empty AUTHINFO GENERIC command to get a list of security
// packages supported by the server. We parse that list and continue with
// sicily authentication.
case NS_GENERIC_TEST:
if (SUCCEEDED(hr = HrGetResponse()))
{
if (m_uiResponse == IXP_NNTP_AUTH_OK)
{
m_substate = NS_GENERIC_PKG_DATA;
hr = ProcessGenericTestResponse();
}
else
{
// could be an old MSN server, so try AUTHINFO TRANSACT TWINKIE
hr = HrSendCommand((LPSTR) NNTP_TRANSACTTEST_CRLF, NULL, FALSE);
m_substate = NS_TRANSACT_TEST;
}
}
break;
// We issued an empty AUTHINFO GENERIC command to get a list of security
// packages supported by the server. We parse that list and continue with
// sicily authentication.
case NS_GENERIC_PKG_DATA:
hr = ProcessGenericTestResponse();
break;
// We issued a invalid AUTHINFO TRANSACT command to get a list of security
// packages supported by the server. We parse that list and continue with
// sicily authentication.
case NS_TRANSACT_TEST:
if (SUCCEEDED(hr = HrGetResponse()))
{
ProcessTransactTestResponse();
}
break;
// We issued a AUTHINFO {TRANSACT|GENERIC} <package name> to the server. Parse this
// response to see if the server understands the package. If so, then send
// the negotiation information, otherwise try a different security package.
case NS_TRANSACT_PACKAGE:
if (SUCCEEDED(hr = HrGetResponse()))
{
if (m_uiResponse == IXP_NNTP_PASSWORD_REQUIRED)
{
Assert(m_iAuthType != AUTHINFO_NONE);
if (m_iAuthType == AUTHINFO_GENERIC)
HrSendCommand((LPSTR) NNTP_GENERICCMD, m_sicmsg.szBuffer, FALSE);
else
HrSendCommand((LPSTR) NNTP_TRANSACTCMD, m_sicmsg.szBuffer, FALSE);
m_substate = NS_TRANSACT_NEGO;
}
else
{
TryNextSecPkg();
}
}
break;
// We received a response to our negotiation command. If the response
// is 381, then generate a response to the server's challange. Otherwise,
// we disconnect and try to reconnect with the next security package.
case NS_TRANSACT_NEGO:
if (SUCCEEDED(hr = HrGetResponse()))
{
if (m_uiResponse == IXP_NNTP_PASSWORD_REQUIRED)
{
SSPIBUFFER Challenge, Response;
// Skip over the "381 "
SSPISetBuffer(m_pszResponse + 4, SSPI_STRING, 0, &Challenge);
// Generate a response from the server's challange
if (SUCCEEDED(hr = SSPIResponseFromChallenge(&m_sicinfo, &Challenge, &Response)))
{
Assert(m_iAuthType != AUTHINFO_NONE);
if (m_iAuthType == AUTHINFO_GENERIC)
HrSendCommand((LPSTR) NNTP_GENERICCMD, Response.szBuffer, FALSE);
else
HrSendCommand((LPSTR) NNTP_TRANSACTCMD, Response.szBuffer, FALSE);
// if a continue is required, stay in this state
if (!Response.fContinue)
m_substate = NS_TRANSACT_RESP;
break;
}
else
{
Disconnect();
DispatchResponse(IXP_E_SICILY_LOGON_FAILED, TRUE);
break;
}
}
else
m_fRetryPkg = TRUE;
// We need to reset the connection if we get here
m_substate = NS_RECONNECTING;
OnStatus(IXP_AUTHRETRY);
m_pSocket->Close();
m_pSocket->Connect();
}
break;
// This is the final response to our sicily negotiation. This should
// be either that we succeeded or didn't. If we succeeded, then we've
// reached a terminal state and can inform the user that we're ready
// to accept commands. Otherwise, we reconnect and try the next
// security package.
case NS_TRANSACT_RESP:
if (SUCCEEDED(hr = HrGetResponse()))
{
if (IXP_NNTP_AUTH_OK == m_uiResponse)
{
OnStatus(IXP_AUTHORIZED);
DispatchResponse(m_hrPostingAllowed, TRUE);
}
else
{
// We need to reset the connection
OnStatus(IXP_AUTHRETRY);
m_substate = NS_RECONNECTING;
m_fRetryPkg = TRUE;
m_pSocket->Close();
m_pSocket->Connect();
}
}
break;
// We issued an AUTHINFO USER <username> and this is the server's
// response. We're expecting either that a password is required or
// that we've succeesfully authenticated.
case NS_AUTHINFO_USER_RESP:
if (SUCCEEDED(hr = HrGetResponse()))
{
// If the server requires a password to go along with the username
// then send it now.
if (IXP_NNTP_PASSWORD_REQUIRED == m_uiResponse)
{
LPSTR pszPassword;
// Choose the password based on if we were called from
// Connect() or CommandAUTHINFO().
if (m_state == NS_AUTHINFO)
pszPassword = m_pAuthInfo->pszPass;
else
pszPassword = m_rServer.szPassword;
HrSendCommand((LPSTR) NNTP_AUTHINFOPASS, pszPassword, FALSE);
m_substate = NS_AUTHINFO_PASS_RESP;
}
// Otherwise, consider ourselves connected and in a state to accept
// commands
else
{
OnStatus(IXP_AUTHORIZED);
DispatchResponse(m_hrPostingAllowed, TRUE);
}
}
break;
// We sent a AUTHINFO PASS <password> command to complement the AUTHINFO
// USER command. This response will tell us whether we're authenticated
// or not. Either way this is a terminal state.
case NS_AUTHINFO_PASS_RESP:
if (SUCCEEDED(hr = HrGetResponse()))
{
// If the password was accepted, consider ourselves connected
// and in a state to accept commands.
if (IXP_NNTP_AUTH_OK >= m_uiResponse)
{
OnStatus(IXP_AUTHORIZED);
DispatchResponse(m_hrPostingAllowed, TRUE);
}
// If the password was rejected, then use the callback to prompt
// the user for new credentials.
else
{
// Need to disconnect and reconnect to make sure we're in a
// known, stable state with the server.
m_substate = NS_RECONNECTING;
if (FAILED(LogonRetry(IXP_E_NNTP_INVALID_USERPASS)))
{
DispatchResponse(IXP_E_USER_CANCEL, TRUE);
}
}
}
break;
// We sent a AUTHINFO SIMPLE command to the server to see if the command
// is supported. If the server returns 350, then we should send the
// username and password
case NS_AUTHINFO_SIMPLE_RESP:
if (SUCCEEDED(hr = HrGetResponse()))
{
// If we got a response to continue, then send the user name and
// password
if (IXP_NNTP_CONTINUE_AUTHORIZATION == m_uiResponse)
{
IxpAssert(m_pAuthInfo);
if (SUCCEEDED(hr = HrSendCommand(m_pAuthInfo->pszUser,
m_pAuthInfo->pszPass, FALSE)))
m_substate = NS_AUTHINFO_SIMPLE_USERPASS_RESP;
else
DispatchResponse(hr, TRUE);
}
else
{
// Otherwise fail and inform the user.
DispatchResponse(hr, TRUE);
}
}
break;
// This is the final response from the AUTHINFO SIMPLE command. All
// we need to do is inform the user.
case NS_AUTHINFO_SIMPLE_USERPASS_RESP:
if (SUCCEEDED(hr = HrGetResponse()))
{
DispatchResponse(hr, TRUE);
}
break;
}
return (hr);
}
//
// FUNCTION: CNNTPTransport::DispatchResponse()
//
// PURPOSE: Takes the server response string, packages it up into a
// NNTPRESPONSE structure, and returns it to the callback
// interface.
//
// PARAMETERS:
// hrResult - The result code to send to the callback.
// fDone - True if the command has completed.
// pResponse - If the command is returning data, then this should
// be filled in with the data to be returned.
//
void CNNTPTransport::DispatchResponse(HRESULT hrResult, BOOL fDone,
LPNNTPRESPONSE pResponse)
{
// Locals
NNTPRESPONSE rResponse;
// If a response was passed in, use it...
if (pResponse)
CopyMemory(&rResponse, pResponse, sizeof(NNTPRESPONSE));
else
ZeroMemory(&rResponse, sizeof(NNTPRESPONSE));
rResponse.fDone = fDone;
// Set up the return structure
rResponse.state = m_state;
rResponse.rIxpResult.hrResult = hrResult;
rResponse.rIxpResult.pszResponse = PszDupA(m_pszResponse);
rResponse.rIxpResult.uiServerError = m_uiResponse;
rResponse.rIxpResult.hrServerError = m_hrResponse;
rResponse.rIxpResult.dwSocketError = m_pSocket->GetLastError();
rResponse.rIxpResult.pszProblem = NULL;
rResponse.pTransport = this;
// If Done...
if (fDone)
{
// No current command
m_state = NS_IDLE;
m_substate = NS_RESP;
// Reset Last Response
SafeMemFree(m_pszResponse);
m_hrResponse = S_OK;
m_uiResponse = 0;
// If we have user/pass info cached, free it
if (m_pAuthInfo)
{
SafeMemFree(m_pAuthInfo->pszUser);
SafeMemFree(m_pAuthInfo->pszPass);
SafeMemFree(m_pAuthInfo);
}
// Leave Busy State
LeaveBusy();
}
// Give the Response to the client
if (m_pCallback)
((INNTPCallback *) m_pCallback)->OnResponse(&rResponse);
SafeMemFree(rResponse.rIxpResult.pszResponse);
}
//
// FUNCTION: CNNTPTransport::HrGetResponse()
//
// PURPOSE: Reads the server response string off the socket and stores
// the response info in local members.
//
// RETURN VALUE:
// HRESULT
//
HRESULT CNNTPTransport::HrGetResponse(void)
{
HRESULT hr = S_OK;
int cbLine;
// Clear response
if (m_pszResponse != NULL)
SafeMemFree(m_pszResponse);
// Read the line from the socket
hr = m_pSocket->ReadLine(&m_pszResponse, &cbLine);
// Handle incomplete lines
if (hr == IXP_E_INCOMPLETE)
goto exit;
// Socket error
if (FAILED(hr))
{
hr = TrapError(IXP_E_SOCKET_READ_ERROR);
goto exit;
}
// Strip the trailing CRLF
StripCRLF(m_pszResponse, (ULONG *) &cbLine);
// Log it
if (m_pLogFile)
m_pLogFile->WriteLog(LOGFILE_RX, m_pszResponse);
// Get the response code from the beginning of the string
m_uiResponse = StrToInt(m_pszResponse);
// Tell the client about the server response
if (m_pCallback)
m_pCallback->OnCommand(CMD_RESP, m_pszResponse, hr, NNTPTHISIXP);
exit:
return (hr);
}
//
// FUNCTION: CNNTPTransport::StartLogon()
//
// PURPOSE: Starts the login process with the server based on information
// provided by the user in Connect().
//
void CNNTPTransport::StartLogon(void)
{
HRESULT hr;
// If not using sicily or it's not installed, try simple USER/PASS authentication
if (m_rServer.fTrySicily)
{
// If sicily is installed
if (FIsSicilyInstalled())
{
// Status
OnStatus(IXP_AUTHORIZING);
// If we haven't enumerated the security packages yet, do so
if (m_cSecPkg == -1)
{
hr = HrSendCommand((LPSTR) NNTP_GENERICTEST_CRLF, NULL, FALSE);
m_substate = NS_GENERIC_TEST;
}
else
{
// We've reconnected, try the next security package
TryNextSecPkg();
}
}
else
{
Disconnect();
DispatchResponse(IXP_E_LOAD_SICILY_FAILED, TRUE);
}
}
else
{
hr = MaybeTryAuthinfo();
if (FAILED(hr))
{
OnError(hr);
DropConnection();
DispatchResponse(hr, TRUE);
}
}
}
HRESULT CNNTPTransport::LogonRetry(HRESULT hrLogon)
{
HRESULT hr = S_OK;
// Let the user know that the logon failed
OnError(hrLogon);
// Update the transport status
OnStatus(IXP_AUTHRETRY);
// Enter Auth Retry State
m_pSocket->Close();
// Turn off the watchdog timer
m_pSocket->StopWatchDog();
// Ask the user to provide credetials
if (NULL == m_pCallback || S_FALSE == m_pCallback->OnLogonPrompt(&m_rServer, NNTPTHISIXP))
{
OnDisconnected();
return (E_FAIL);
}
// Finding Host Progress
OnStatus(IXP_FINDINGHOST);
// Connect to server
hr = m_pSocket->Connect();
if (FAILED(hr))
{
OnError(TrapError(IXP_E_SOCKET_CONNECT_ERROR));
OnDisconnected();
return hr;
}
// Start WatchDog
m_pSocket->StartWatchDog();
return (S_OK);
}
/////////////////////////////////////////////////////////////////////////////
//
// CNNTPTransport::ProcessGenericTestResponse
//
// processes AUTHINFO GENERIC response
//
HRESULT CNNTPTransport::ProcessGenericTestResponse()
{
HRESULT hr;
LPSTR pszLines = NULL;
int iRead, iLines;
m_cSecPkg = 0;
if (SUCCEEDED(hr = m_pSocket->ReadLines(&pszLines, &iRead, &iLines)))
{
LPSTR pszT = pszLines, pszPkg;
while (*pszT && m_cSecPkg < MAX_SEC_PKGS)
{
// check for end of list condition
if ((pszT[0] == '.') && ((pszT[1] == '\r' && pszT[2] == '\n') || (pszT[1] == '\n')))
break;
pszPkg = pszT;
// look for an LF or CRLF to end the line
while (*pszT && !(pszT[0] == '\n' || (pszT[0] == '\r' && pszT[1] == '\n')))
pszT++;
// strip the LF or CRLF
while (*pszT == '\r' || *pszT == '\n')
*pszT++ = 0;
m_rgszSecPkg[m_cSecPkg++] = PszDupA(pszPkg);
}
// we've reached the end of the list, otherwise there is more data expected
if (pszT[0] == '.')
{
Assert(pszT[1] == '\r' && pszT[2] == '\n');
m_iAuthType = AUTHINFO_GENERIC;
hr = TryNextSecPkg();
}
MemFree(pszLines);
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////
//
// CNNTPTransport::ProcessTransactTestResponse
//
// processes AUTHINFO TRANSACT TWINKIE response
//
HRESULT CNNTPTransport::ProcessTransactTestResponse()
{
HRESULT hr = NOERROR;
m_cSecPkg = 0;
if (m_uiResponse == IXP_NNTP_PROTOCOLS_SUPPORTED)
{
LPSTR pszT;
pszT = m_szSecPkgs = PszDupA(m_pszResponse + 3); // skip over 485
while (*pszT && IsSpace(pszT))
pszT++;
while (*pszT && m_cSecPkg < MAX_SEC_PKGS)
{
m_rgszSecPkg[m_cSecPkg++] = pszT;
while (*pszT && !IsSpace(pszT))
pszT++;
while (*pszT && IsSpace(pszT))
*pszT++ = 0;
}
m_iAuthType = AUTHINFO_TRANSACT;
return TryNextSecPkg();
}
else
{
Disconnect();
DispatchResponse(IXP_E_SICILY_LOGON_FAILED, TRUE);
return NOERROR;
}
}
/////////////////////////////////////////////////////////////////////////////
//
// CNNTPTransport::TryNextSecPkg
//
// tries the next security package, or reverts to basic if necessary
//
HRESULT CNNTPTransport::TryNextSecPkg()
{
HRESULT hr;
Assert(m_cSecPkg != -1);
Assert(m_iAuthType != AUTHINFO_NONE);
TryNext:
if (!m_fRetryPkg)
m_iSecPkg++;
if (m_iSecPkg < m_cSecPkg)
{
Assert(m_cSecPkg);
SSPIFreeContext(&m_sicinfo);
if (!lstrcmpi(m_rgszSecPkg[m_iSecPkg], NNTP_BASIC))
return MaybeTryAuthinfo();
// In case the Sicily function brings up UI, we need to turn off the
// watchdog so we don't time out waiting for the user
m_pSocket->StopWatchDog();
if (SUCCEEDED(hr = SSPILogon(&m_sicinfo, m_fRetryPkg, SSPI_BASE64, m_rgszSecPkg[m_iSecPkg], &m_rServer, m_pCallback)))
{
if (m_fRetryPkg)
{
m_fRetryPkg = FALSE;
}
if (SUCCEEDED(hr = SSPIGetNegotiate(&m_sicinfo, &m_sicmsg)))
{
DOUTL(2, "Trying to connect using %s security...", m_rgszSecPkg[m_iSecPkg]);
if (m_iAuthType == AUTHINFO_GENERIC)
hr = HrSendCommand((LPSTR) NNTP_GENERICCMD, m_rgszSecPkg[m_iSecPkg], FALSE);
else
hr = HrSendCommand((LPSTR) NNTP_TRANSACTCMD, m_rgszSecPkg[m_iSecPkg], FALSE);
// Restart the watchdog timer now that we've issued our next command to the server.
m_pSocket->StartWatchDog();
m_substate = NS_TRANSACT_PACKAGE;
}
else
{
hr = IXP_E_SICILY_LOGON_FAILED;
goto TryNext;
}
}
else
{
m_fRetryPkg = FALSE;
goto TryNext;
}
}
else
{
OnError(IXP_E_SICILY_LOGON_FAILED);
DropConnection();
DispatchResponse(IXP_E_SICILY_LOGON_FAILED, TRUE);
hr = NOERROR;
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////
//
// CNNTPTransport::MaybeTryAuthinfo
//
// tries basic authinfo if necessary, else moves to connected state
//
HRESULT CNNTPTransport::MaybeTryAuthinfo()
{
HRESULT hr;
if (*m_rServer.szUserName)
{
OnStatus(IXP_AUTHORIZING);
hr = HrSendCommand((LPSTR) NNTP_AUTHINFOUSER, m_rServer.szUserName, FALSE);
m_substate = NS_AUTHINFO_USER_RESP;
}
else
{
// Logon not needed for this news server (or so we'll have to assume)
OnStatus(IXP_AUTHORIZED);
DispatchResponse(S_OK, TRUE);
hr = NOERROR;
}
return hr;
}
#define Whitespace(_ch) (((_ch) == ' ') || ((_ch) == '\t') || ((_ch) == '\n'))
BOOL ScanNum(LPSTR *ppsz, BOOL fEnd, DWORD *pdw)
{
DWORD n = 0;
LPSTR psz;
Assert(ppsz != NULL);
Assert(pdw != NULL);
psz = *ppsz;
if (*psz == 0 || Whitespace(*psz))
return(FALSE);
while (*psz != 0 && !Whitespace(*psz))
{
if (*psz >= '0' && *psz <= '9')
{
n *= 10;
n += *psz - '0';
psz++;
}
else
{
return(FALSE);
}
}
if (Whitespace(*psz))
{
if (fEnd)
return(FALSE);
while (*psz != 0 && Whitespace(*psz))
psz++;
}
else
{
Assert(*psz == 0);
if (!fEnd)
return(FALSE);
}
*ppsz = psz;
*pdw = n;
return(TRUE);
}
BOOL ScanWord(LPCSTR psz, LPSTR pszDest)
{
Assert(psz != NULL);
Assert(pszDest != NULL);
if (*psz == 0 || Whitespace(*psz))
return(FALSE);
while (*psz != 0 && !Whitespace(*psz))
{
*pszDest = *psz;
psz++;
pszDest++;
}
*pszDest = 0;
return(TRUE);
}
/////////////////////////////////////////////////////////////////////////////
//
// CNNTPTransport::ProcessGroupResponse
//
// processes the GROUP response
//
HRESULT CNNTPTransport::ProcessGroupResponse(void)
{
NNTPGROUP rGroup;
DWORD dwResp;
NNTPRESPONSE rResp;
LPSTR psz;
LPSTR pszGroup = 0;
ZeroMemory(&rGroup, sizeof(NNTPGROUP));
ZeroMemory(&rResp, sizeof(NNTPRESPONSE));
if (m_uiResponse == IXP_NNTP_GROUP_SELECTED)
{
rResp.fMustRelease = TRUE;
pszGroup = PszDupA(m_pszResponse);
psz = m_pszResponse;
if (!ScanNum(&psz, FALSE, &dwResp) ||
!ScanNum(&psz, FALSE, &rGroup.dwCount) ||
!ScanNum(&psz, FALSE, &rGroup.dwFirst) ||
!ScanNum(&psz, FALSE, &rGroup.dwLast) ||
!ScanWord(psz, pszGroup))
{
m_hrResponse = IXP_E_NNTP_RESPONSE_ERROR;
}
else
{
if (pszGroup)
{
rGroup.pszGroup = PszDupA(pszGroup);
}
rResp.rGroup = rGroup;
}
}
else if (m_uiResponse == IXP_NNTP_NO_SUCH_NEWSGROUP)
m_hrResponse = IXP_E_NNTP_GROUP_NOTFOUND;
else
m_hrResponse = IXP_E_NNTP_GROUP_FAILED;
DispatchResponse(m_hrResponse, TRUE, &rResp);
SafeMemFree(pszGroup);
return (m_hrResponse);
}
HRESULT CNNTPTransport::ProcessNextResponse(void)
{
LPSTR psz;
NNTPNEXT rNext;
DWORD dwResp;
NNTPRESPONSE rResp;
ZeroMemory(&rNext, sizeof(NNTPNEXT));
ZeroMemory(&rResp, sizeof(NNTPRESPONSE));
// If success was returned, then parse the response
if (m_uiResponse == IXP_NNTP_ARTICLE_RETRIEVED)
{
rResp.fMustRelease = TRUE;
// Allocate a buffer for the message id
rNext.pszMessageId = PszAllocA(lstrlen(m_pszResponse));
if (NULL != rNext.pszMessageId)
{
psz = m_pszResponse;
if (!ScanNum(&psz, FALSE, &dwResp) ||
!ScanNum(&psz, FALSE, &rNext.dwArticleNum) ||
!ScanWord(psz, rNext.pszMessageId))
{
m_hrResponse = IXP_E_NNTP_RESPONSE_ERROR;
}
else
{
m_hrResponse = S_OK;
// Since this is just a union, a little sleeze and we wouldn't
// actually need to to this...
if (m_state == NS_NEXT)
rResp.rNext = rNext;
else if (m_state == NS_LAST)
rResp.rLast = rNext;
else
rResp.rStat = rNext;
}
}
else
{
m_hrResponse = TrapError(E_OUTOFMEMORY);
}
}
else if ((m_state == NS_NEXT && m_uiResponse == IXP_NNTP_NO_NEXT_ARTICLE) ||
(m_state == NS_LAST && m_uiResponse == IXP_NNTP_NO_PREV_ARTICLE) ||
(m_state == NS_STAT && m_uiResponse == IXP_NNTP_NO_SUCH_ARTICLE_NUM))
{
m_hrResponse = IXP_E_NNTP_NEXT_FAILED;
}
else
m_hrResponse = S_OK;
DispatchResponse(m_hrResponse, TRUE, &rResp);
return (m_hrResponse);
}
HRESULT CNNTPTransport::ProcessListData(void)
{
HRESULT hr = S_OK;
LPSTR pszLines = NULL;
DWORD dwRead, dwLines;
NNTPLIST rList;
LPSTR pszT;
NNTPRESPONSE rResponse;
ZeroMemory(&rList, sizeof(NNTPLIST));
ZeroMemory(&rResponse, sizeof(NNTPRESPONSE));
// Get the remaining data off the socket
if (SUCCEEDED(hr = m_pSocket->ReadLines(&pszLines, (int *)&dwRead, (int *)&dwLines)))
{
// Allocate and array to hold the lines
if (!MemAlloc((LPVOID*) &rList.rgszLines, dwLines * sizeof(LPSTR)))
{
OnError(E_OUTOFMEMORY);
hr = E_OUTOFMEMORY;
goto error;
}
ZeroMemory(rList.rgszLines, sizeof(LPSTR) * dwLines);
// Parse the buffer returned from the protocol. We need to find the
// end of the list.
pszT = pszLines;
while (*pszT)
{
// Check for the end of list condition
if ((pszT[0] == '.') && ((pszT[1] == '\r' && pszT[2] == '\n') || (pszT[1] == '\n')))
break;
// Save the line
rList.rgszLines[rList.cLines++] = pszT;
// Find the LF or CRLF at the end of the line
while (*pszT && !(pszT[0] == '\n' || (pszT[0] == '\r' && pszT[1] == '\n')))
pszT++;
// Strip off the LF or CRLF and add a terminator
while (*pszT == '\r' || *pszT == '\n')
*pszT++ = 0;
}
// If we parsed more lines off of the buffer than was returned to us,
// then either we have a parsing bug, or the socket class has a counting
// bug.
IxpAssert(rList.cLines <= dwLines);
// We've readed the end of the list, otherwise there is more data expected
if (pszT[0] == '.')
{
// Double check that this dot is followed by a CRLF
IxpAssert(pszT[1] == '\r' && pszT[2] == '\n');
rResponse.fDone = TRUE;
}
rResponse.rList = rList;
rResponse.fMustRelease = TRUE;
DispatchResponse(S_OK, rResponse.fDone, &rResponse);
}
return (hr);
error:
SafeMemFree(pszLines);
return (hr);
}
HRESULT CNNTPTransport::ProcessListGroupData(void)
{
HRESULT hr = S_OK;
LPSTR pszLines = NULL;
LPSTR pszBeginLine = NULL;
DWORD dwRead, dwLines;
NNTPLISTGROUP rListGroup;
LPSTR pszT;
NNTPRESPONSE rResp;
ZeroMemory(&rListGroup, sizeof(NNTPLIST));
ZeroMemory(&rResp, sizeof(NNTPRESPONSE));
// Get the remaining data off the socket
if (SUCCEEDED(hr = m_pSocket->ReadLines(&pszLines, (int *)&dwRead, (int *)&dwLines)))
{
// Allocate and array to hold the lines
if (!MemAlloc((LPVOID*) &rListGroup.rgArticles, dwLines * sizeof(DWORD)))
{
hr = E_OUTOFMEMORY;
OnError(E_OUTOFMEMORY);
goto error;
}
// Parse the buffer returned from the protocol. We need to find the
// end of the list.
pszT = pszLines;
rListGroup.cArticles = 0;
while (*pszT)
{
// Check for the end of list condition
if ((pszT[0] == '.') && ((pszT[1] == '\r' && pszT[2] == '\n') || (pszT[1] == '\n')))
break;
// Save the beginning of the line
pszBeginLine = pszT;
// Find the LF or CRLF at the end of the line
while (*pszT && !(pszT[0] == '\n' || (pszT[0] == '\r' && pszT[1] == '\n')))
pszT++;
// Strip off the LF or CRLF and add a terminator
while (*pszT == '\r' || *pszT == '\n')
*pszT++ = 0;
// Convert the line to a number and add it to the array
rListGroup.rgArticles[rListGroup.cArticles] = StrToInt(pszBeginLine);
if (rListGroup.rgArticles[rListGroup.cArticles])
rListGroup.cArticles++;
}
// If we parsed more lines off of the buffer than was returned to us,
// then either we have a parsing bug, or the socket class has a counting
// bug.
IxpAssert(rListGroup.cArticles <= dwLines);
// We've readed the end of the list, otherwise there is more data expected
if (pszT[0] == '.')
{
// Double check that this dot is followed by a CRLF
IxpAssert(pszT[1] == '\r' && pszT[2] == '\n');
rResp.fDone = TRUE;
}
rResp.rListGroup = rListGroup;
rResp.fMustRelease = TRUE;
DispatchResponse(S_OK, rResp.fDone, &rResp);
}
error:
SafeMemFree(pszLines);
return (hr);
}
BOOL CharsToNum(LPCSTR psz, int cch, WORD *pw)
{
int i;
WORD w = 0;
Assert(psz != NULL);
Assert(pw != NULL);
for (i = 0; i < cch; i++)
{
if (*psz >= '0' && *psz <= '9')
{
w *= 10;
w += *psz - '0';
psz++;
}
else
{
return(FALSE);
}
}
*pw = w;
return(TRUE);
}
HRESULT CNNTPTransport::ProcessDateResponse(void)
{
HRESULT hr = S_OK;
SYSTEMTIME st;
NNTPRESPONSE rResp;
DWORD dwResp;
LPSTR psz;
ZeroMemory(&rResp, sizeof(NNTPRESPONSE));
// This information is returned in the format YYYYMMDDhhmmss
if (m_uiResponse == IXP_NNTP_DATE_RESPONSE)
{
ZeroMemory(&st, sizeof(SYSTEMTIME));
psz = StrChr(m_pszResponse, ' ');
if (psz == NULL ||
!CharsToNum(++psz, 4, &st.wYear) ||
!CharsToNum(&psz[4], 2, &st.wMonth) ||
!CharsToNum(&psz[6], 2, &st.wDay) ||
!CharsToNum(&psz[8], 2, &st.wHour) ||
!CharsToNum(&psz[10], 2, &st.wMinute) ||
!CharsToNum(&psz[12], 2, &st.wSecond))
{
m_hrResponse = IXP_E_NNTP_RESPONSE_ERROR;
}
else
{
rResp.rDate = st;
m_hrResponse = S_OK;
}
}
else
m_hrResponse = IXP_E_NNTP_DATE_FAILED;
DispatchResponse(m_hrResponse, TRUE, &rResp);
return (hr);
}
HRESULT CNNTPTransport::ProcessArticleData(void)
{
HRESULT hr;
DWORD dwRead, dwLines;
LPSTR psz;
DWORD cbSubtract;
NNTPARTICLE rArticle;
NNTPRESPONSE rResp;
ZeroMemory(&rArticle, sizeof(NNTPARTICLE));
ZeroMemory(&rResp, sizeof(NNTPRESPONSE));
// Bug #25073 - Get the article number from the response string
DWORD dwT;
psz = m_pszResponse;
ScanNum(&psz, FALSE, &dwT);
ScanNum(&psz, TRUE, &rArticle.dwArticleNum);
// Read the waiting data off the socket
hr = m_pSocket->ReadLines(&rArticle.pszLines, (int*) &dwRead, (int*) &dwLines);
if (hr == IXP_E_INCOMPLETE)
return (hr);
// Check forfailure
if (FAILED(hr))
{
DispatchResponse(hr);
return (hr);
}
// See if this is the end of the response
if (FEndRetrRecvBodyNews(rArticle.pszLines, dwRead, &cbSubtract))
{
// Remove the trailing dot from the buffer
dwRead -= cbSubtract;
rArticle.pszLines[dwRead] = 0;
rResp.fDone = TRUE;
}
// Unstuff the dots
UnStuffDotsFromLines(rArticle.pszLines, (int *)&dwRead);
rArticle.pszLines[dwRead] ='\0';
// Fill out the response
rResp.rArticle = rArticle;
rResp.rArticle.cbLines = dwRead;
rResp.rArticle.cLines = dwLines;
rResp.fMustRelease = TRUE;
DispatchResponse(hr, rResp.fDone, &rResp);
return hr;
}
HRESULT CNNTPTransport::ProcessXoverData(void)
{
HRESULT hr;
LPSTR pszLines = NULL;
LPSTR pszNextLine = NULL;
LPSTR pszField = NULL;
LPSTR pszNextField = NULL;
int iRead, iLines;
NNTPHEADERRESP rHdrResp;
NNTPRESPONSE rResp;
NNTPHEADER *rgHdr;
PMEMORYINFO pMemInfo = 0;
ZeroMemory(&rHdrResp, sizeof(NNTPHEADERRESP));
ZeroMemory(&rResp, sizeof(NNTPRESPONSE));
// Read the data that is waiting on the socket
if (SUCCEEDED(hr = m_pSocket->ReadLines(&pszLines, &iRead, &iLines)))
{
// Allocate the MEMORYINFO struct we use to stash the pointers
if (!MemAlloc((LPVOID*) &pMemInfo, sizeof(MEMORYINFO)))
{
OnError(E_OUTOFMEMORY);
hr = E_OUTOFMEMORY;
goto error;
}
pMemInfo->cPointers = 1;
pMemInfo->rgPointers[0] = pszLines;
rHdrResp.dwReserved = (DWORD_PTR) pMemInfo;
// Allocate the array of headers
Assert(iLines);
if (!MemAlloc((LPVOID*) &(rHdrResp.rgHeaders), iLines * sizeof(NNTPHEADER)))
{
OnError(E_OUTOFMEMORY);
hr = E_OUTOFMEMORY;
goto error;
}
rgHdr = rHdrResp.rgHeaders;
// Loop until we either run out of lines or we find a line that begins
// with "."
pszNextLine = pszLines;
while (*pszNextLine && *pszNextLine != '.')
{
pszField = pszNextLine;
// Look ahead to find the beginning of the next line
while (*pszNextLine)
{
if (*pszNextLine == '\n')
{
// NULL out a CR followed by a LF
if (pszNextLine > pszField && *(pszNextLine - 1) == '\r')
*(pszNextLine - 1) = 0;
// NULL out and skip over the LF
*pszNextLine++ = 0;
break;
}
pszNextLine++;
}
// At this point, pszField points to the beginning of this XOVER
// line, and pszNextLine points to the next.
// Parse the article number field
if (pszNextField = GetNextField(pszField))
{
rgHdr[rHdrResp.cHeaders].dwArticleNum = StrToInt(pszField);
pszField = pszNextField;
}
else
goto badrecord;
// Parse the Subject: field
if (pszNextField = GetNextField(pszField))
{
rgHdr[rHdrResp.cHeaders].pszSubject = pszField;
pszField = pszNextField;
}
else
goto badrecord;
// Parse the From: field
if (pszNextField = GetNextField(pszField))
{
rgHdr[rHdrResp.cHeaders].pszFrom = pszField;
pszField = pszNextField;
}
else
goto badrecord;
// Parse the Date: field
if (pszNextField = GetNextField(pszField))
{
rgHdr[rHdrResp.cHeaders].pszDate = pszField;
pszField = pszNextField;
}
else
goto badrecord;
// Parse the Message-ID field
if (pszNextField = GetNextField(pszField))
{
rgHdr[rHdrResp.cHeaders].pszMessageId = pszField;
pszField = pszNextField;
}
else
goto badrecord;
rgHdr[rHdrResp.cHeaders].pszReferences = pszField;
pszField = GetNextField(pszField);
// Parse the bytes field (we can live without this)
if (pszField)
{
rgHdr[rHdrResp.cHeaders].dwBytes = StrToInt(pszField);
pszField = GetNextField(pszField);
}
else
{
rgHdr[rHdrResp.cHeaders].dwBytes = 0;
}
// Parse the article size in lines (we can live without this also)
if (pszField)
{
rgHdr[rHdrResp.cHeaders].dwLines = StrToInt(pszField);
pszField = GetNextField(pszField);
}
else
{
rgHdr[rHdrResp.cHeaders].dwLines = 0;
}
// NOTE: The XRef: field in the XOver record is an optional field
// that a server may or may not support. Also, if the message is
// not crossposted, then the XRef: field won't be present either.
// Therefore just cause we don't find any XRef: fields doesn't mean
// it isn't supported.
// Look for aditional fields that might contain XRef
rgHdr[rHdrResp.cHeaders].pszXref = 0;
while (pszField)
{
if (!StrCmpNI(pszField, c_szXrefColon, 5))
{
// We found at least one case where the xref: was supplied.
// We now know for sure that this server supports the xref:
// field in it's XOver records.
m_fSupportsXRef = TRUE;
rgHdr[rHdrResp.cHeaders].pszXref = pszField + 6;
break;
}
pszField = GetNextField(pszField);
}
rHdrResp.cHeaders++;
// If we've found a bad record, then we just skip right over it
// and move on to the next.
badrecord:
;
}
// We've reached the end of the list, otherwise there is more data
// expected.
rResp.fDone = (*pszNextLine == '.');
rHdrResp.fSupportsXRef = m_fSupportsXRef;
// Return what we've retrieved
rResp.fMustRelease = TRUE;
rResp.rHeaders = rHdrResp;
DispatchResponse(hr, rResp.fDone, &rResp);
return (S_OK);
}
return (hr);
error:
// Free anything we've allocated
SafeMemFree(rHdrResp.rgHeaders);
SafeMemFree(pMemInfo);
SafeMemFree(pszLines);
DispatchResponse(hr, TRUE);
return (hr);
}
// Data comes in the form "<article number> <header>"
HRESULT CNNTPTransport::ProcessXhdrData(void)
{
HRESULT hr;
LPSTR pszLines = NULL;
LPSTR pszNextLine = NULL;
LPSTR pszField = NULL;
LPSTR pszNextField = NULL;
int iRead, iLines;
NNTPXHDRRESP rXhdr;
NNTPRESPONSE rResp;
NNTPXHDR *rgHdr = 0;
PMEMORYINFO pMemInfo = 0;
ZeroMemory(&rXhdr, sizeof(NNTPXHDRRESP));
ZeroMemory(&rResp, sizeof(NNTPRESPONSE));
// Read the data that is waiting on the socket
if (SUCCEEDED(hr = m_pSocket->ReadLines(&pszLines, &iRead, &iLines)))
{
// Allocate the MEMORYINFO struct we use to stash the pointers
if (!MemAlloc((LPVOID*) &pMemInfo, sizeof(MEMORYINFO)))
{
OnError(E_OUTOFMEMORY);
hr = E_OUTOFMEMORY;
goto error;
}
pMemInfo->cPointers = 1;
pMemInfo->rgPointers[0] = pszLines;
rXhdr.dwReserved = (DWORD_PTR) pMemInfo;
// Allocate the array of XHDRs
Assert(iLines);
if (!MemAlloc((LPVOID*) &(rXhdr.rgHeaders), iLines * sizeof(NNTPXHDR)))
{
// This is _very_ broken. We leave a whole bunch of data on the
// socket. What should we do?
OnError(E_OUTOFMEMORY);
hr = E_OUTOFMEMORY;
goto error;
}
rgHdr = rXhdr.rgHeaders;
// Loop until we either run out of lines or we find a line that begins
// with "."
pszNextLine = pszLines;
while (*pszNextLine && *pszNextLine != '.')
{
pszField = pszNextLine;
// Scan ahead and find the end of the line
while (*pszNextLine)
{
if (*pszNextLine == '\n')
{
// NULL out a CR followed by a LF
if (pszNextLine > pszField && *(pszNextLine - 1) == '\r')
*(pszNextLine - 1) = 0;
// NULL out and skip over the LF
*pszNextLine++ = 0;
break;
}
pszNextLine++;
}
// Parse the article number
rgHdr[rXhdr.cHeaders].dwArticleNum = StrToInt(pszField);
// Find the seperating space
rgHdr[rXhdr.cHeaders].pszHeader = 0;
while (*pszField && *pszField != ' ')
pszField++;
// Make the beginning of the header point to the first character
// after the space.
if (*(pszField + 1))
rgHdr[rXhdr.cHeaders].pszHeader = (pszField + 1);
if (rgHdr[rXhdr.cHeaders].dwArticleNum && rgHdr[rXhdr.cHeaders].pszHeader)
rXhdr.cHeaders++;
}
// We've reached the end of the list, otherwise there is more data
// expected.
rResp.fDone = (*pszNextLine == '.');
// Return what we've retrieved
rResp.rXhdr = rXhdr;
rResp.fMustRelease = TRUE;
DispatchResponse(hr, rResp.fDone, &rResp);
return (S_OK);
}
error:
SafeMemFree(rgHdr);
SafeMemFree(pMemInfo);
SafeMemFree(pszLines);
return (hr);
}
LPSTR CNNTPTransport::GetNextField(LPSTR pszField)
{
while (*pszField && *pszField != '\t')
pszField++;
if (*pszField == '\t')
{
*pszField++ = 0;
return pszField;
}
return NULL;
}
HRESULT CNNTPTransport::CommandAUTHINFO(LPNNTPAUTHINFO pAuthInfo)
{
HRESULT hr;
if (!pAuthInfo)
return (E_INVALIDARG);
// Make a copy of this struct so we can use the info during the callback
// if necessary
if (pAuthInfo->authtype == AUTHTYPE_USERPASS ||
pAuthInfo->authtype == AUTHTYPE_SIMPLE)
{
if (!MemAlloc((LPVOID*) &m_pAuthInfo, sizeof(NNTPAUTHINFO)))
{
OnError(E_OUTOFMEMORY);
return (E_OUTOFMEMORY);
}
ZeroMemory(m_pAuthInfo, sizeof(NNTPAUTHINFO));
m_pAuthInfo->pszUser = PszDupA(pAuthInfo->pszUser);
m_pAuthInfo->pszPass = PszDupA(pAuthInfo->pszUser);
}
EnterCriticalSection(&m_cs);
// Issue the command as appropriate
switch (pAuthInfo->authtype)
{
case AUTHTYPE_USERPASS:
hr = HrSendCommand((LPSTR) NNTP_AUTHINFOUSER, pAuthInfo->pszUser);
if (SUCCEEDED(hr))
m_substate = NS_AUTHINFO_USER_RESP;
break;
case AUTHTYPE_SIMPLE:
hr = HrSendCommand((LPSTR) NNTP_AUTHINFOSIMPLE_CRLF, NULL);
if (SUCCEEDED(hr))
m_substate = NS_AUTHINFO_SIMPLE_RESP;
break;
case AUTHTYPE_SASL:
// If we haven't enumerated the security packages yet, do so
if (m_cSecPkg == -1)
{
hr = HrSendCommand((LPSTR) NNTP_GENERICTEST_CRLF, NULL, FALSE);
if (SUCCEEDED(hr))
m_substate = NS_GENERIC_TEST;
}
else
{
// We've reconnected, try the next security package
TryNextSecPkg();
}
break;
}
if (SUCCEEDED(hr))
m_state = NS_AUTHINFO;
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandGROUP(LPSTR pszGroup)
{
HRESULT hr;
if (!pszGroup)
return (E_INVALIDARG);
EnterCriticalSection(&m_cs);
hr = HrSendCommand((LPSTR) NNTP_GROUP, pszGroup);
if (SUCCEEDED(hr))
m_state = NS_GROUP;
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandLAST(void)
{
HRESULT hr;
EnterCriticalSection(&m_cs);
hr = HrSendCommand((LPSTR) NNTP_LAST_CRLF, NULL);
if (SUCCEEDED(hr))
m_state = NS_LAST;
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandNEXT(void)
{
HRESULT hr;
EnterCriticalSection(&m_cs);
hr = HrSendCommand((LPSTR) NNTP_NEXT_CRLF, NULL);
if (SUCCEEDED(hr))
m_state = NS_NEXT;
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandSTAT(LPARTICLEID pArticleId)
{
HRESULT hr;
char szTemp[20];
EnterCriticalSection(&m_cs);
// Check to see if the optional article number/id was provided
if (pArticleId)
{
// If we were given a message id, then use that
if (pArticleId->idType == AID_MSGID)
hr = HrSendCommand((LPSTR) NNTP_STAT, pArticleId->pszMessageId);
else
{
// Convert the article number to a string and send the command
wnsprintf(szTemp, ARRAYSIZE(szTemp), "%d", pArticleId->dwArticleNum);
hr = HrSendCommand((LPSTR) NNTP_STAT, szTemp);
}
}
else
{
// No number or id, so just send the command
hr = HrSendCommand((LPSTR) NNTP_STAT, (LPSTR) c_szCRLF);
}
if (SUCCEEDED(hr))
{
m_state = NS_STAT;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandARTICLE(LPARTICLEID pArticleId)
{
HRESULT hr;
char szTemp[20];
EnterCriticalSection(&m_cs);
// Check to see if the optional article number/id was provided
if (pArticleId)
{
// Send the command appropriate to what type of article id was given
if (pArticleId->idType == AID_MSGID)
hr = HrSendCommand((LPSTR) NNTP_ARTICLE, pArticleId->pszMessageId);
else
{
// convert the article number to a string and send the command
wnsprintf(szTemp, ARRAYSIZE(szTemp), "%d", pArticleId->dwArticleNum);
hr = HrSendCommand((LPSTR) NNTP_ARTICLE, szTemp);
}
}
else
{
hr = HrSendCommand((LPSTR) NNTP_ARTICLE, (LPSTR) c_szCRLF);
}
if (SUCCEEDED(hr))
{
m_state = NS_ARTICLE;
m_substate = NS_RESP;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandHEAD(LPARTICLEID pArticleId)
{
HRESULT hr;
char szTemp[20];
EnterCriticalSection(&m_cs);
// Check to see if the optional article number/id was provided
if (pArticleId)
{
// Send the command appropriate to what type of article id was given
if (pArticleId->idType == AID_MSGID)
hr = HrSendCommand((LPSTR) NNTP_HEAD, pArticleId->pszMessageId);
else
{
// convert the article number to a string and send the command
wnsprintf(szTemp, ARRAYSIZE(szTemp), "%d", pArticleId->dwArticleNum);
hr = HrSendCommand((LPSTR) NNTP_HEAD, szTemp);
}
}
else
{
hr = HrSendCommand((LPSTR) NNTP_HEAD, (LPSTR) c_szCRLF);
}
if (SUCCEEDED(hr))
{
m_state = NS_HEAD;
m_substate = NS_RESP;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandBODY(LPARTICLEID pArticleId)
{
HRESULT hr;
char szTemp[20];
EnterCriticalSection(&m_cs);
// Check to see if the optional article number/id was provided
if (pArticleId)
{
// Send the command appropriate to what type of article id was given
if (pArticleId->idType == AID_MSGID)
hr = HrSendCommand((LPSTR) NNTP_BODY, pArticleId->pszMessageId);
else
{
// convert the article number to a string and send the command
wnsprintf(szTemp, ARRAYSIZE(szTemp), "%d", pArticleId->dwArticleNum);
hr = HrSendCommand((LPSTR) NNTP_BODY, szTemp);
}
}
else
{
hr = HrSendCommand((LPSTR) NNTP_BODY, (LPSTR) c_szCRLF);
}
if (SUCCEEDED(hr))
{
m_state = NS_BODY;
m_substate = NS_RESP;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandPOST(LPNNTPMESSAGE pMessage)
{
HRESULT hr;
if (!pMessage || (pMessage && !pMessage->pstmMsg))
return (E_INVALIDARG);
EnterCriticalSection(&m_cs);
// Make a copy of the message struct so we have it when we get
// an response from the server that it's OK to post
#pragma prefast(suppress:11, "noise")
m_rMessage.cbSize = pMessage->cbSize;
SafeRelease(m_rMessage.pstmMsg);
#pragma prefast(suppress:11, "noise")
m_rMessage.pstmMsg = pMessage->pstmMsg;
m_rMessage.pstmMsg->AddRef();
hr = HrSendCommand((LPSTR) NNTP_POST_CRLF, NULL);
if (SUCCEEDED(hr))
{
m_state = NS_POST;
m_substate = NS_RESP;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandLIST(LPSTR pszArgs)
{
HRESULT hr;
EnterCriticalSection(&m_cs);
if (pszArgs)
hr = HrSendCommand((LPSTR) NNTP_LIST, pszArgs);
else
hr = HrSendCommand((LPSTR) NNTP_LIST, (LPSTR) c_szCRLF);
if (SUCCEEDED(hr))
{
m_state = NS_LIST;
m_substate = NS_RESP;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandLISTGROUP(LPSTR pszGroup)
{
HRESULT hr;
EnterCriticalSection(&m_cs);
if (pszGroup)
hr = HrSendCommand((LPSTR) NNTP_LISTGROUP, pszGroup);
else
hr = HrSendCommand((LPSTR) NNTP_LISTGROUP, (LPSTR) c_szCRLF);
if (SUCCEEDED(hr))
{
m_state = NS_LISTGROUP;
m_substate = NS_RESP;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandNEWGROUPS(SYSTEMTIME *pstLast, LPSTR pszDist)
{
HRESULT hr = S_OK;
LPSTR pszCmd = NULL;
DWORD cchCmd = 18;
// Make sure a SYSTEMTIME struct is provided
if (!pstLast)
return (E_INVALIDARG);
// Allocate enough room for the command string "NEWGROUPS YYMMDD HHMMSS <pszDist>"
if (pszDist)
cchCmd += lstrlen(pszDist);
if (!MemAlloc((LPVOID*) &pszCmd, cchCmd))
{
OnError(E_OUTOFMEMORY);
return (E_OUTOFMEMORY);
}
// Put the command arguments together
wnsprintf(pszCmd, cchCmd, "%02d%02d%02d %02d%02d%02d ", pstLast->wYear - (100 * (pstLast->wYear / 100)),
pstLast->wMonth, pstLast->wDay, pstLast->wHour, pstLast->wMinute,
pstLast->wSecond);
if (pszDist)
StrCatBuff(pszCmd, pszDist, cchCmd);
// Send the command
EnterCriticalSection(&m_cs);
hr = HrSendCommand((LPSTR) NNTP_NEWGROUPS, pszCmd);
if (SUCCEEDED(hr))
{
m_state = NS_NEWGROUPS;
m_substate = NS_RESP;
}
LeaveCriticalSection(&m_cs);
SafeMemFree(pszCmd);
return (hr);
}
HRESULT CNNTPTransport::CommandDATE(void)
{
HRESULT hr;
EnterCriticalSection(&m_cs);
hr = HrSendCommand((LPSTR) NNTP_DATE_CRLF, NULL);
if (SUCCEEDED(hr))
m_state = NS_DATE;
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandMODE(LPSTR pszMode)
{
HRESULT hr;
// Make sure the caller provided a mode command to send
if (!pszMode || (pszMode && !*pszMode))
return (E_INVALIDARG);
EnterCriticalSection(&m_cs);
hr = HrSendCommand((LPSTR) NNTP_MODE, pszMode);
if (SUCCEEDED(hr))
m_state = NS_MODE;
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::CommandXHDR(LPSTR pszHeader, LPRANGE pRange, LPSTR pszMessageId)
{
HRESULT hr = S_OK;
LPSTR pszArgs = 0;
DWORD cc = 0;
// You can't specify BOTH a range and a message id
if (pRange && pszMessageId)
return (E_INVALIDARG);
if (!pszHeader)
return (E_INVALIDARG);
// Make sure the range information is valid
if (pRange)
{
if ((pRange->idType != RT_SINGLE && pRange->idType != RT_RANGE) ||
pRange->dwFirst == 0 ||
(pRange->idType == RT_RANGE && pRange->dwLast < pRange->dwFirst))
return (E_INVALIDARG);
}
// Allocate a string for the arguments
cc = 32 + lstrlen(pszHeader) + (pszMessageId ? lstrlen(pszMessageId) : 0);
if (!MemAlloc((LPVOID*) &pszArgs, cc))
{
OnError(E_OUTOFMEMORY);
return (E_OUTOFMEMORY);
}
EnterCriticalSection(&m_cs);
// Handle the message-id case first
if (pszMessageId)
{
wnsprintf(pszArgs, cc, "%s %s", pszHeader, pszMessageId);
hr = HrSendCommand((LPSTR) NNTP_XHDR, pszArgs);
}
else if (pRange)
{
// Range case
if (pRange->idType == RT_SINGLE)
{
wnsprintf(pszArgs, cc, "%s %ld", pszHeader, pRange->dwFirst);
hr = HrSendCommand((LPSTR) NNTP_XHDR, pszArgs);
}
else if (pRange->idType == RT_RANGE)
{
wnsprintf(pszArgs, cc, "%s %ld-%ld", pszHeader, pRange->dwFirst, pRange->dwLast);
hr = HrSendCommand((LPSTR) NNTP_XHDR, pszArgs);
}
}
else
{
// Current article case
hr = HrSendCommand((LPSTR) NNTP_XHDR, pszHeader);
}
// If we succeeded to send the command to the server, then update our state
// to receive the response from the XHDR command
if (SUCCEEDED(hr))
{
m_state = NS_XHDR;
m_substate = NS_RESP;
}
LeaveCriticalSection(&m_cs);
SafeMemFree(pszArgs);
return (hr);
}
HRESULT CNNTPTransport::CommandQUIT(void)
{
HRESULT hr = IXP_E_NOT_CONNECTED;
EnterCriticalSection(&m_cs);
// Make sure we're actually connected to the server
if (m_state != NS_DISCONNECTED && m_state != NS_CONNECT || (m_state == NS_CONNECT && m_substate != NS_RECONNECTING))
{
// Send the QUIT command to the server
hr = HrSendCommand((LPSTR) NNTP_QUIT_CRLF, NULL);
if (SUCCEEDED(hr))
m_state = NS_QUIT;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::GetHeaders(LPRANGE pRange)
{
HRESULT hr;
char szRange[32];
// Make sure the range information is valid
if (!pRange)
return (E_INVALIDARG);
if ((pRange->idType != RT_SINGLE && pRange->idType != RT_RANGE) ||
pRange->dwFirst == 0 ||
(pRange->idType == RT_RANGE && pRange->dwLast < pRange->dwFirst))
return (E_INVALIDARG);
if (pRange->idType == RT_SINGLE)
pRange->dwLast = pRange->dwFirst;
// In case XOVER isn't supported on this server, we'll store this range so
// we can try XHDR instead.
m_rRange = *pRange;
// Check to see if we know that XOVER will fail
if (m_fNoXover)
{
return (BuildHeadersFromXhdr(TRUE));
}
EnterCriticalSection(&m_cs);
// If dwLast == 0, then the person is requesting a single record, otherwise
// the person is requesting a range. Build the commands appropriately.
if (RT_RANGE == pRange->idType)
wnsprintf(szRange, ARRAYSIZE(szRange), "%s %lu-%lu\r\n", NNTP_XOVER, pRange->dwFirst, pRange->dwLast);
else
wnsprintf(szRange, ARRAYSIZE(szRange), "%s %lu\r\n", NNTP_XOVER, pRange->dwFirst);
hr = HrSendCommand(szRange, NULL);
if (SUCCEEDED(hr))
{
m_state = NS_HEADERS;
m_substate = NS_RESP;
m_gethdr = GETHDR_XOVER;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::ReleaseResponse(LPNNTPRESPONSE pResp)
{
HRESULT hr = S_OK;
DWORD i;
// First double check to see if this even needs to be released
if (FALSE == pResp->fMustRelease)
return (S_FALSE);
switch (pResp->state)
{
case NS_GROUP:
SafeMemFree(pResp->rGroup.pszGroup);
break;
case NS_LAST:
SafeMemFree(pResp->rLast.pszMessageId);
break;
case NS_NEXT:
SafeMemFree(pResp->rNext.pszMessageId);
break;
case NS_STAT:
SafeMemFree(pResp->rStat.pszMessageId);
break;
case NS_ARTICLE:
SafeMemFree(pResp->rArticle.pszMessageId);
SafeMemFree(pResp->rArticle.pszLines);
break;
case NS_HEAD:
SafeMemFree(pResp->rHead.pszMessageId);
SafeMemFree(pResp->rHead.pszLines);
break;
case NS_BODY:
SafeMemFree(pResp->rBody.pszMessageId);
SafeMemFree(pResp->rBody.pszLines);
break;
case NS_NEWGROUPS:
// Since the response here is just one buffer, then we can just
// free the first line and all the others will be freed as well.
if (pResp->rNewgroups.rgszLines)
{
SafeMemFree(pResp->rNewgroups.rgszLines[0]);
MemFree(pResp->rNewgroups.rgszLines);
}
break;
case NS_LIST:
// Since the response here is just one buffer, then we can just
// free the first line and all the others will be freed as well.
if (pResp->rList.rgszLines)
{
MemFree(pResp->rList.rgszLines[0]);
MemFree(pResp->rList.rgszLines);
}
break;
case NS_LISTGROUP:
SafeMemFree(pResp->rListGroup.rgArticles);
break;
case NS_HEADERS:
{
// This frees the memory that contains all of the
PMEMORYINFO pMemInfo = (PMEMORYINFO) pResp->rHeaders.dwReserved;
for (UINT i = 0; i < pMemInfo->cPointers; i++)
SafeMemFree(pMemInfo->rgPointers[i]);
SafeMemFree(pMemInfo);
// This frees the array that pointed to the parsed xhdr responses
SafeMemFree(pResp->rHeaders.rgHeaders);
break;
}
case NS_XHDR:
{
// This frees the memory that contains all of the
PMEMORYINFO pMemInfo = (PMEMORYINFO) pResp->rXhdr.dwReserved;
SafeMemFree(pMemInfo->rgPointers[0]);
SafeMemFree(pMemInfo);
// This frees the array that pointed to the parsed xhdr responses
SafeMemFree(pResp->rXhdr.rgHeaders);
break;
}
default:
// If we get here that means one of two things:
// (1) the user messed with pResp->fMustRelease flag and is an idiot
// (2) Somewhere in the transport we set fMustRelease when we didn't
// actually return data that needs to be freed. This is bad and
// should be tracked.
IxpAssert(FALSE);
}
return (hr);
}
HRESULT CNNTPTransport::BuildHeadersFromXhdr(BOOL fFirst)
{
HRESULT hr = S_OK;
DWORD cHeaders;
BOOL fDone = FALSE;
if (fFirst)
{
// Set the header retrieval type
m_gethdr = GETHDR_XHDR;
m_fNoXover = TRUE;
m_cHeaders = 0;
// Get the first range of headers to retrieve
m_rRangeCur.dwFirst = m_rRange.dwFirst;
m_rRangeCur.dwLast = min(m_rRange.dwLast, m_rRangeCur.dwFirst + NUM_HEADERS);
cHeaders = m_rRangeCur.dwLast - m_rRangeCur.dwFirst + 1;
// Allocate an array for the headers
Assert(m_rgHeaders == 0);
if (!MemAlloc((LPVOID*) &m_rgHeaders, cHeaders * sizeof(NNTPHEADER)))
{
SafeMemFree(m_pMemInfo);
OnError(E_OUTOFMEMORY);
DispatchResponse(E_OUTOFMEMORY);
return (E_OUTOFMEMORY);
}
ZeroMemory(m_rgHeaders, cHeaders * sizeof(NNTPHEADER));
// Set the state correctly
m_hdrtype = HDR_SUBJECT;
// Issue first request
hr = SendNextXhdrCommand();
}
else
{
Assert(m_substate == NS_DATA);
// Parse the data and add it to our array
hr = ProcessNextXhdrResponse(&fDone);
// fDone will be TRUE when we've received all the data from the
// preceeding request.
if (fDone)
{
// If there are still headers left to retrieve, then advance the
// header type state and issue the next command.
if (m_hdrtype < HDR_XREF)
{
m_hdrtype++;
// issue command
hr = SendNextXhdrCommand();
}
else
{
// All done with this batch. Send the response to the caller.
NNTPRESPONSE rResp;
ZeroMemory(&rResp, sizeof(NNTPRESPONSE));
rResp.rHeaders.cHeaders = m_cHeaders;
rResp.rHeaders.rgHeaders = m_rgHeaders;
rResp.rHeaders.fSupportsXRef = TRUE;
rResp.rHeaders.dwReserved = (DWORD_PTR) m_pMemInfo;
rResp.fMustRelease = TRUE;
// It's the caller's responsibility to free this now
m_rgHeaders = NULL;
m_cHeaders = 0;
m_pMemInfo = 0;
// If these are equal, then we've retrieved all of the headers
// that were requested
if (m_rRange.dwLast == m_rRangeCur.dwLast)
{
rResp.fDone = TRUE;
DispatchResponse(S_OK, TRUE, &rResp);
}
else
{
rResp.fDone = FALSE;
DispatchResponse(S_OK, FALSE, &rResp);
// There are headers we haven't retrieved yet. Go ahead
// and issue the next group of xhdrs.
m_rRange.dwFirst = m_rRangeCur.dwLast + 1;
Assert(m_rRange.dwFirst <= m_rRange.dwLast);
BuildHeadersFromXhdr(TRUE);
}
}
}
}
return (hr);
}
HRESULT CNNTPTransport::SendNextXhdrCommand(void)
{
char szTemp[256];
HRESULT hr;
LPCSTR c_rgHdr[HDR_MAX] = { NNTP_HDR_SUBJECT,
NNTP_HDR_FROM,
NNTP_HDR_DATE,
NNTP_HDR_MESSAGEID,
NNTP_HDR_REFERENCES,
NNTP_HDR_LINES,
NNTP_HDR_XREF };
// Build the command string to send to the server
wnsprintf(szTemp, ARRAYSIZE(szTemp), "%s %s %ld-%ld\r\n", NNTP_XHDR, c_rgHdr[m_hdrtype],
m_rRangeCur.dwFirst, m_rRangeCur.dwLast);
EnterCriticalSection(&m_cs);
// Send the command to the server
hr = HrSendCommand(szTemp, NULL, FALSE);
if (SUCCEEDED(hr))
{
m_state = NS_HEADERS;
m_substate = NS_RESP;
m_iHeader = 0;
}
LeaveCriticalSection(&m_cs);
return (hr);
}
HRESULT CNNTPTransport::ProcessNextXhdrResponse(BOOL* pfDone)
{
HRESULT hr;
LPSTR pszLines = NULL;
LPSTR pszNextLine = NULL;
LPSTR pszField = NULL;
LPSTR pszNextField = NULL;
int iRead, iLines;
DWORD dwTemp;
// Read the data that is waiting on the socket
if (SUCCEEDED(hr = m_pSocket->ReadLines(&pszLines, &iRead, &iLines)))
{
// Realloc our array of pointers to free and add this pszLines to the end
if (m_pMemInfo)
{
if (MemRealloc((LPVOID*) &m_pMemInfo, sizeof(MEMORYINFO)
+ (((m_pMemInfo ? m_pMemInfo->cPointers : 0) + 1) * sizeof(LPVOID))))
{
m_pMemInfo->rgPointers[m_pMemInfo->cPointers] = (LPVOID) pszLines;
m_pMemInfo->cPointers++;
}
}
else
{
if (MemAlloc((LPVOID*) &m_pMemInfo, sizeof(MEMORYINFO)))
{
m_pMemInfo->rgPointers[0] = pszLines;
m_pMemInfo->cPointers = 1;
}
}
// Loop until we either run out of lines or we find a line that begins
// with "."
pszNextLine = pszLines;
while (*pszNextLine && *pszNextLine != '.')
{
pszField = pszNextLine;
// Scan ahead and find the end of the line
while (*pszNextLine)
{
if (*pszNextLine == '\n')
{
// NULL out a CR followed by a LF
if (pszNextLine > pszField && *(pszNextLine - 1) == '\r')
*(pszNextLine - 1) = 0;
// NULL out and skip over the LF
*pszNextLine++ = 0;
break;
}
pszNextLine++;
}
// Parse the article number
if (m_hdrtype == HDR_SUBJECT)
{
m_rgHeaders[m_iHeader].dwArticleNum = StrToInt(pszField);
m_cHeaders++;
}
else
{
// Make sure this field matches the header that's next in the array
if (m_rgHeaders[m_iHeader].dwArticleNum != (DWORD) StrToInt(pszField))
{
dwTemp = m_iHeader;
// If the number is less, then we can loop until we find it
while (m_iHeader < (m_rRangeCur.dwLast - m_rRangeCur.dwFirst) &&
m_rgHeaders[m_iHeader].dwArticleNum < (DWORD) StrToInt(pszField))
{
m_iHeader++;
}
// We never found a matching header, so we should consider this record
// bogus.
if (m_iHeader >= (m_rRangeCur.dwLast - m_rRangeCur.dwFirst + 1))
{
IxpAssert(0);
m_iHeader = dwTemp;
goto BadRecord;
}
}
}
// Find the seperating space
while (*pszField && *pszField != ' ')
pszField++;
// Advance past the space
if (*pszField)
pszField++;
// Parse the actual data field into our header array. Make
// the beginning of the header point to the first character
// after the space.
switch (m_hdrtype)
{
case HDR_SUBJECT:
m_rgHeaders[m_iHeader].pszSubject = pszField;
break;
case HDR_FROM:
m_rgHeaders[m_iHeader].pszFrom = pszField;
break;
case HDR_DATE:
m_rgHeaders[m_iHeader].pszDate = pszField;
break;
case HDR_MSGID:
m_rgHeaders[m_iHeader].pszMessageId = pszField;
break;
case HDR_REFERENCES:
m_rgHeaders[m_iHeader].pszReferences = pszField;
break;
case HDR_LINES:
m_rgHeaders[m_iHeader].dwLines = StrToInt(pszField);
break;
case HDR_XREF:
m_rgHeaders[m_iHeader].pszXref = pszField;
break;
default:
// How the heck do we get here?
IxpAssert(0);
}
m_iHeader++;
BadRecord:
;
}
// We've reached the end of the list, otherwise there is more data
// expected.
*pfDone = (*pszNextLine == '.');
return (S_OK);
}
return (hr);
}
HRESULT CNNTPTransport::HrPostMessage(void)
{
HRESULT hr;
int cbSent = 0;
EnterCriticalSection(&m_cs);
hr = m_pSocket->SendStream(m_rMessage.pstmMsg, &cbSent, TRUE);
SafeRelease(m_rMessage.pstmMsg);
LeaveCriticalSection(&m_cs);
return (hr);
}
//***************************************************************************
// Function: SetWindow
//
// Purpose:
// This function creates the current window handle for async winsock process.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
STDMETHODIMP CNNTPTransport::SetWindow(void)
{
HRESULT hr;
Assert(NULL != m_pSocket);
if(m_pSocket)
hr= m_pSocket->SetWindow();
else
hr= E_UNEXPECTED;
return hr;
}
//***************************************************************************
// Function: ResetWindow
//
// Purpose:
// This function closes the current window handle for async winsock process.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
STDMETHODIMP CNNTPTransport::ResetWindow(void)
{
HRESULT hr;
Assert(NULL != m_pSocket);
if(m_pSocket)
hr= m_pSocket->ResetWindow();
else
hr= E_UNEXPECTED;
return hr;
}