|
|
// --------------------------------------------------------------------------------
// 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; }
|