You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3148 lines
96 KiB
3148 lines
96 KiB
// --------------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|