|
|
//
// MODULE: APGTSCTX.CPP
//
// PURPOSE: Implementation file for Thread Context
// Fully implements class APGTSContext, which provides the full context for a "pool" thread
// to perform a task
// Also includes helper class CCommands.
//
// PROJECT: Generic Troubleshooter DLL for Microsoft AnswerPoint
//
// COMPANY: Saltmine Creative, Inc. (206)-284-7511 [email protected]
//
// AUTHOR: Roman Mach, Joe Mabel
//
// ORIGINAL DATE: 8-2-96
//
// NOTES:
// 1. Several things in this file are marked as $ENGLISH. That means we've hard-coded
// English-language returns. This may yet be revisited, but as of 10/29/98 discussion
// between Ron Prior of Microsoft and Joe Mabel of Saltmine, we couldn't come up with a
// better solution to this. Notes are in the specification for the fall 1998 work on
// the Online Troubleshooter.
// 2. some of the methods of APGTSContext are implemented in file STATUSPAGES.CPP
//
// Version Date By Comments
//--------------------------------------------------------------------
// V0.1 - RM Original
// V3.0 7-22-98 JM Major revision, deprecate IDH, totally new approach to logging.
//
#pragma warning(disable:4786)
#include "stdafx.h"
#include <time.h>
#include "event.h"
#include "apgts.h"
#include "apgtscls.h"
#include "apgtscfg.h"
#include "apgtsmfc.h"
#include "CounterMgr.h"
#include "CharConv.h"
#include "SafeTime.h"
#include "RegistryPasswords.h"
#ifdef LOCAL_TROUBLESHOOTER
#include "HTMLFragLocal.h"
#endif
#include "Sniff.h"
// HTTP header variable name where cookie information is stored.
#define kHTTP_COOKIE "HTTP_COOKIE"
//
// CCommands ------------------------------------------------------
// The next several CCommands functions are analogous to MFC CArray
//
int APGTSContext::CCommands::GetSize( ) const { return m_arrPair.size(); }
void APGTSContext::CCommands::RemoveAll( ) { m_arrPair.clear(); }
bool APGTSContext::CCommands::GetAt( int nIndex, NID &nid, int &value ) const { if (nIndex<0 || nIndex>=m_arrPair.size()) return false; nid = m_arrPair[nIndex].nid; value = m_arrPair[nIndex].value; return true; }
int APGTSContext::CCommands::Add( NID nid, int value ) { NID_VALUE_PAIR pair;
pair.nid = nid; pair.value = value;
try { m_arrPair.push_back(pair); } catch (exception& x) { CString str; // Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), _T(""), EV_GTS_STL_EXCEPTION ); return( -1 ); }
return m_arrPair.size(); }
// a funky manipulation to deal with the following issue from old (pre V3.0) troubleshooters:
// Pre V3.0 Logging sequence and the service node's behavior were both based on some assumptions
// about the sequence of name/value pairs in the command list. Basically, the
// assumption was that the "table" would be on top of the form and the "questions"
// below it. This would result in ProblemAsk in first position (after any "template=
// <template-name> and type=<troubleshooter-name>, but that's weeded out before we ever
// hit the command list). If the HTI file has put "the table" at the bottom, that assumption
// is invalidated, so we have to manipulate the array.
// Because we could get old GET-method queries, we still have to deal with this as a backward
// compatibility issue.
void APGTSContext::CCommands::RotateProblemPageToFront() { int dwPairs = m_arrPair.size();
// Rotate till ProblemAsk is in position 0. (no known scenario where it starts out
// anywhere past position 1)
try { for (int i= 0; i<dwPairs; i++) { NID_VALUE_PAIR pair = m_arrPair.front(); // note: first element, not i-th element
if (pair.nid == nidProblemPage) break;
m_arrPair.erase(m_arrPair.begin()); m_arrPair.push_back(pair); } } catch (exception& x) { CString str; // Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), _T(""), EV_GTS_STL_EXCEPTION ); } } //
// CCommandsAddManager --------------------------------------------------
//
void APGTSContext::CCommandsAddManager::Add(NID nid, int value, bool sniffed) { if (sniffed) { int nCommands = m_Commands.GetSize(); for (int i = nCommands - 1; i >= 0; i--) // higher possibility, that matching
{ // node will be in the end of array
NID nid_curr; int value_curr; m_Commands.GetAt(i, nid_curr, value_curr); if (nid_curr == nid) { if (value_curr != value) { // If we're here, it means, that user has changed value
// of sniffed node in history table, therefore it is
// no longer treated as sniffed.
return; } else { m_Sniffed.Add(nid, value); return; } } } // sniffed node does not have matches in m_Commands
ASSERT(false); } else { m_Commands.Add(nid, value); } } //
// CAdditionalInfo ------------------------------------------------------
// The next several CAdditionalInfo functions are analogous to MFC CArray
//
int APGTSContext::CAdditionalInfo::GetSize( ) const { return m_arrPair.size(); }
void APGTSContext::CAdditionalInfo::RemoveAll( ) { m_arrPair.clear(); }
bool APGTSContext::CAdditionalInfo::GetAt( int nIndex, CString& name, CString& value ) const { if (nIndex<0 || nIndex>=m_arrPair.size()) return false; name = m_arrPair[nIndex].name; value = m_arrPair[nIndex].value; return true; }
int APGTSContext::CAdditionalInfo::Add( const CString& name, const CString& value ) { NAME_VALUE_PAIR pair;
pair.name = name; pair.value = value;
try { m_arrPair.push_back(pair); } catch (exception& x) { CString str; // Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), _T(""), EV_GTS_STL_EXCEPTION ); return( -1 ); }
return m_arrPair.size(); }
//-----------------
// INPUT *pECB - Describes the user request that has come in. This is our abstraction of
// Win32 EXTENSION_CONTROL_BLOCK, which is ISAPI's packaging of CGI data.
// INPUT *pConf - access to registry info & contents of all loaded troubleshooters
// INPUT *pLog - access to logging
// INPUT *pStat - statistical info, including pStat->dwRollover which is a unique number
// for this request, unique within the time the DLL has been loaded
APGTSContext::APGTSContext( CAbstractECB *pECB, CDBLoadConfiguration *pConf, CHTMLLog *pLog, GTS_STATISTIC *pStat, CSniffConnector* pSniffConnector ) : m_pECB(pECB), m_dwErr(0), m_strHeader(_T("Content-Type: text/html\r\n")), m_pConf(pConf), m_strVRoot(m_pConf->GetVrootPath()), m_pszQuery(NULL), m_pLog(pLog), m_bPostType(true), m_dwBytes(0), m_pStat(pStat), m_bPreload(false), m_bNewCookie(false), m_pcountUnknownTopics (&(g_ApgtsCounters.m_UnknownTopics)), m_pcountAllAccessesFinish (&(g_ApgtsCounters.m_AllAccessesFinish)), m_pcountStatusAccesses (&(g_ApgtsCounters.m_StatusAccesses)), m_pcountOperatorActions (&(g_ApgtsCounters.m_OperatorActions)), m_TopicName(_T("")), m_infer(pSniffConnector), m_CommandsAddManager(m_Commands, m_Sniffed) // You can compile with the SHOWPROGRESS option to get a report on the progress of this page.
#ifdef SHOWPROGRESS
, timeCreateContext(0), timeStartInfer(0), timeEndInfer(0), timeEndRender(0) #endif // SHOWPROGRESS
{ #ifdef SHOWPROGRESS
time(&timeCreateContext); #endif // SHOWPROGRESS
// obtain local host IP address
APGTS_nmspace::GetServerVariable(m_pECB, "SERVER_NAME", m_strLocalIPAddress); // HTTP response code. This or 302 Object Moved.
_tcscpy(m_resptype, _T("200 OK")); // initially assume we will respond without trouble
// supports GET, POST
if (!strcmp(m_pECB->GetMethod(), "GET")) { m_bPostType = false; m_dwBytes = strlen(m_pECB->GetQueryString()); } else m_dwBytes = m_pECB->GetBytesAvailable();
_tcscpy(m_ipstr,_T(""));
DWORD bufsize = MAXBUF - 1; if (! m_pECB->GetServerVariable("REMOTE_ADDR", m_ipstr, &bufsize)) _stprintf(m_ipstr,_T("IP?")); try { m_pszQuery = new TCHAR[m_dwBytes + 1];
//[BC-03022001] - added check for NULL ptr to satisfy MS code analysis tool.
if(!m_pszQuery) throw bad_alloc(); } catch (bad_alloc&) { CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T(""), _T(""), EV_GTS_CANT_ALLOC ); m_dwErr = EV_GTS_ERROR_NO_CHAR; return; }
if (m_bPostType) memcpy(m_pszQuery, m_pECB->GetData(), m_dwBytes); else memcpy(m_pszQuery, m_pECB->GetQueryString(), m_dwBytes);
m_pszQuery[m_dwBytes] = _T('\0'); }
//
// Even though this is a destructor, it does a lot of work:
// - sends the HTTP (HTML or cookie)to the user over the net
// - writes to the log.
APGTSContext::~APGTSContext() { DWORD dwLen; TCHAR *ptr;
// RESPONSE_HEADER
m_strHeader += _T("\r\n");
dwLen = m_strHeader.GetLength(); ptr = m_strHeader.GetBuffer(0);
m_pECB->ServerSupportFunction( HSE_REQ_SEND_RESPONSE_HEADER, m_resptype, &dwLen, (LPDWORD) ptr );
m_strHeader.ReleaseBuffer();
// HTML content follows
{ DWORD dwLen= 0;
if (! m_dwErr) dwLen = m_strText.GetLength(); if (!dwLen) { // $ENGLISH (see note at head of file)
SetError(_T("<P>Errors Occurred in This Context")); dwLen = m_strText.GetLength(); } #ifdef SHOWPROGRESS
CString strProgress; CSafeTime safetimeCreateContext(timeCreateContext); CSafeTime safetimeStartInfer(timeStartInfer); CSafeTime safetimeEndInfer(timeEndInfer); CSafeTime safetimeEndRender(timeEndRender); strProgress = _T("\nRequested "); strProgress += safetimeCreateContext.StrLocalTime(); strProgress += _T("\n<BR>Start Infer "); strProgress += safetimeStartInfer.StrLocalTime(); strProgress += _T("\n<BR>End Infer "); strProgress += safetimeEndInfer.StrLocalTime(); strProgress += _T("\n<BR>End Render "); strProgress += safetimeEndRender.StrLocalTime(); strProgress += _T("\n<BR>");
int i = m_strText.Find(_T("<BODY")); i = m_strText.Find(_T('>'), i); // end of BODY tag
if (i>=0) { m_strText= m_strText.Left(i+1) + strProgress + m_strText.Mid(i+1); } dwLen += strProgress.GetLength(); #endif // SHOWPROGRESS
// (LPCTSTR) cast gives us the underlying text bytes.
// >>> $UNICODE Actually, this would screw up under Unicode compile, because for HTML,
// this must be SBCS. Should really be a conversion to LPCSTR, which is non-trivial
// in a Unicode compile. JM 1/7/99
m_pECB->WriteClient((LPCTSTR)m_strText, &dwLen); }
// connection complete
m_logstr.AddCurrentNode(m_infer.NIDSelected());
if (m_dwErr) m_logstr.AddError(m_dwErr, 0); // finish up log
{ if (m_pLog) { CString strLog (m_logstr.GetStr());
m_dwErr = m_pLog->NewLog(strLog); if (m_dwErr) { CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T(""), _T(""), m_dwErr ); } } }
if (m_pszQuery) delete [] m_pszQuery; }
// Fully process a normal user request
// Should be called within the user context created by ImpersonateLoggedOnUser
void APGTSContext::ProcessQuery() { CheckAndLogCookie();
if (m_dwErr) { CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T("Remote IP Address:"), m_ipstr, m_dwErr ); } else { DoContent(); // You can compile with the SHOWPROGRESS option to get a report on the progress of this page.
#ifdef SHOWPROGRESS
time (&timeEndRender); #endif // SHOWPROGRESS
}
// Log the completion of all queries, good and bad.
m_pcountAllAccessesFinish->Increment(); }
//
//
void APGTSContext::DoContent() { TCHAR pszCmd[MAXBUF], pszValue[MAXBUF]; if (m_bPostType) { // validate incoming POST request
if (strcmp(m_pECB->GetContentType(), CONT_TYPE_STR) != 0) { // Output the content type to the event log.
CString strContentType; if (strlen( m_pECB->GetContentType() )) strContentType= m_pECB->GetContentType(); else strContentType= _T("not specified");
m_strText += _T("<P>Bad Data Received\n"); CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), m_ipstr, strContentType, EV_GTS_USER_BAD_DATA ); return; } }
if (RUNNING_ONLINE_TS()) { // Cookies are only used in the Online TS.
if (_tcsstr( m_pszQuery, C_COOKIETAG ) != NULL) { // V3.2 - Parse out cookies passed in either as hidden fields or as part of the URL.
if (m_Qry.GetFirst(m_pszQuery, pszCmd, pszValue)) { CString strCookieFreeQuery; bool bFoundAtLeastOneCookie= false; do { // This is supposed to be a case sensitive setting as per the specification.
if (!_tcsncmp( pszCmd, C_COOKIETAG, _tcslen( C_COOKIETAG ))) { // Found a cookie, add it to the map.
CString strCookieAttr= pszCmd + _tcslen( C_COOKIETAG ); APGTS_nmspace::CookieDecodeURL( strCookieAttr ); CString strCookieValue= pszValue; APGTS_nmspace::CookieDecodeURL( strCookieValue );
// Check the cookie name for compliance.
bool bCookieIsCompliant= true; for (int nPos= 0; nPos < strCookieAttr.GetLength(); nPos++) { TCHAR tcTmp= strCookieAttr.GetAt( nPos ); if ((!_istalnum( tcTmp )) && (tcTmp != _T('_'))) { bCookieIsCompliant= false; break; } } if (bCookieIsCompliant) { // Check the cookie setting for compliance.
if (strCookieValue.Find( _T("<") ) != -1) { bCookieIsCompliant= false; } else if (strCookieValue.Find( _T(">") ) != -1) { bCookieIsCompliant= false; } #if ( 0 )
// >>> I don't think that this check is necessary. RAB-20000408.
else { for (int nPos= 0; nPos < strCookieValue.GetLength(); nPos++) { TCHAR tcTmp= strCookieValue.GetAt( nPos ); if ((tcTmp == _T('<')) || (tcTmp == _T('>'))) { bCookieIsCompliant= false; break; } } } #endif
}
if (bCookieIsCompliant) { try { m_mapCookiesPairs[ strCookieAttr ]= strCookieValue; } catch (exception& x) { CString str; // Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), _T(""), EV_GTS_STL_EXCEPTION ); } } bFoundAtLeastOneCookie= true; } else { // Not a cookie, add it to the cookie free query.
if (strCookieFreeQuery.GetLength()) strCookieFreeQuery+= C_AMPERSAND; strCookieFreeQuery+= pszCmd; strCookieFreeQuery+= C_EQUALSIGN; strCookieFreeQuery+= pszValue; } } while (m_Qry.GetNext( pszCmd, pszValue )) ;
if (bFoundAtLeastOneCookie) { // Replace the original query string with a cookie free query.
memcpy( m_pszQuery, strCookieFreeQuery, strCookieFreeQuery.GetLength() ); m_pszQuery[ strCookieFreeQuery.GetLength() ] = _T('\0'); } } } }
// >>> The following code is commented by me as it is raw, and it will not work since
// topic pointer in m_infer is not set yet. Now we are taking SNIFFED_ nodes down
// to the level of APGTSContext::NextCommand, and parsing it there by
// APGTSContext::StripSniffedNodePrefix, and adding to sniffed array using
// functionality of APGTSContext::CCommandsAddManager class.
// Oleg. 10.29.99
/*
// In v3.2, sniffing is only in the Local TS. There's nothing inherent about that,
// but as long as it's so, might as well optimize for it.
if (RUNNING_LOCAL_TS()) { ClearSniffedList(); if (_tcsstr( m_pszQuery, C_SNIFFTAG ) != NULL) { // V3.2 - Parse out sniffed nodes passed in as hidden fields
if (m_Qry.GetFirst(m_pszQuery, pszCmd, pszValue)) { CString strSniffFreeQuery; bool bFoundAtLeastOneSniff= false; do { // This is supposed to be a case sensitive setting as per the specification.
if (!_tcsncmp( pszCmd, C_SNIFFTAG, _tcslen( C_SNIFFTAG ))) { // Found a sniffed node, add it to the list of sniffed nodes.
CString strSniffedNode= pszCmd + _tcslen( C_SNIFFTAG ); // >>> I believe that despite its name, CookieDecodeURL is
// exactly what we want - JM 10/11/99
APGTS_nmspace::CookieDecodeURL( strSniffedNode ); CString strSniffedState= pszValue; APGTS_nmspace::CookieDecodeURL( strSniffedState );
NID nid= NIDFromSymbolicName(strSniffedNode); int ist = _ttoi(strSniffedState);
if (ist != -1) PlaceNodeInSniffedList(nid, ist);
bFoundAtLeastOneSniff= true; } else { // Not a Sniffed node, add it to the sniff-free query.
if (strSniffFreeQuery.GetLength()) strSniffFreeQuery+= C_AMPERSAND; strSniffFreeQuery+= pszCmd; strSniffFreeQuery+= C_EQUALSIGN; strSniffFreeQuery+= pszValue; } } while (m_Qry.GetNext( pszCmd, pszValue )) ;
if (bFoundAtLeastOneSniff) { // Replace the original query string with a cookie free query.
memcpy( m_pszQuery, strSniffFreeQuery, strSniffFreeQuery.GetLength() ); m_pszQuery[ strSniffFreeQuery.GetLength() ] = _T('\0'); } } } } */ eOpAction OpAction = IdentifyOperatorAction(m_pECB); if (OpAction != eNoOpAction) { if (m_bPostType == true) { // Note: Hard-coded text that should be replaced.
m_strText += _T("<P>Post method not permitted for operator actions\n"); } else { // Increment the number of operator action requests.
m_pcountOperatorActions->Increment();
CString strArg; OpAction = ParseOperatorAction(m_pECB, strArg); if (OpAction != eNoOpAction) ExecuteOperatorAction(m_pECB, OpAction, strArg); } } else if (m_Qry.GetFirst(m_pszQuery, pszCmd, pszValue)) { DWORD dwStat = ProcessCommands(pszCmd, pszValue);
if (dwStat != 0) { if (dwStat == EV_GTS_INF_FIRSTACC || dwStat == EV_GTS_INF_FURTHER_GLOBALACC || dwStat == EV_GTS_INF_THREAD_OVERVIEWACC || dwStat == EV_GTS_INF_TOPIC_STATUSACC) { // Don't want to show contents of query, because it would put the actual
// password in the file.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T(""), _T(""), dwStat ); } else { m_dwErr = dwStat;
if (m_dwBytes > 78) { // It's longer than we want to stick in the event log.
// Cut it off with an ellipsis at byte 75, then null terminate it.
m_pszQuery[75] = _T('.'); m_pszQuery[76] = _T('.'); m_pszQuery[77] = _T('.'); m_pszQuery[78] = _T('\0'); }
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), m_pszQuery, _T(""), dwStat ); } } } else { m_strText += _T("<P>No Input Parameters Specified\n"); CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), m_ipstr, _T(""), EV_GTS_USER_NO_STRING );
} }
//
// Read a cookie (or write one, if there isn't one already)
void APGTSContext::CheckAndLogCookie() { // Suppressed this in Local TS, becuase it isn't using any cookies.
if (RUNNING_LOCAL_TS()) return; CString str; // scratch only
char szCookieNameValue[256]; // never Unicode, because cookies always ASCII
char *pszValue= NULL; // never Unicode, because cookies always ASCII
DWORD dwCookieLen = 255; if ( m_pECB->GetServerVariable( kHTTP_COOKIE, szCookieNameValue, &dwCookieLen)) { // Got a Cookie. Parse it
pszValue = GetCookieValue("GTS-COOKIE", szCookieNameValue); }
if( !pszValue ) { // Build a funky string for the cookie value. We want uniqueness for logging purposes.
// Make a local copy of remote IP address, then massage its dots into letters, each
// dependent on 4 bits of dwTempRO
// While not strictly unique, there are 12 bits worth of "uniqueness" here. However,
// every time the DLL is restarted, we go back to zero. Also, all servers start at zero.
// Later we form the cookie value by appending time to this, so it should be "pretty unique"
DWORD dwTempRO = m_pStat->dwRollover; // this value is unique to this user request
TCHAR *pch; TCHAR szTemp[50]; _tcscpy(szTemp, m_ipstr); while ((pch = _tcschr(szTemp, _T('.'))) != NULL) { *pch = (TCHAR)(dwTempRO & 0x0F) + _T('A'); dwTempRO >>= 4; }
// Create a cookie
time_t timeNow; // current time
time_t timeExpire; // when we set the cookie to expire
time(&timeNow); timeExpire = timeNow + (m_pConf->GetCookieLife() * 60 /* secs in a minute */);
// char, not TCHAR: cookie is always ASCII.
char szExpire[30];
{ CSafeTime safetimeExpire (timeExpire); asctimeCookie(safetimeExpire.GMTime(), szExpire); }
// char, not TCHAR: cookie is always ASCII.
char szNewCookie[256]; char szHeader[256];
sprintf(szNewCookie, "%s%ld, ", szTemp, timeNow); sprintf(szHeader, "Set-Cookie: GTS-COOKIE=%s; expires=%s; \r\n", szNewCookie, szExpire);
CCharConversion::ConvertACharToString(szHeader, str); m_strHeader += str;
pszValue = szNewCookie; m_bNewCookie = true; }
m_logstr.AddCookie(CCharConversion::ConvertACharToString(pszValue, str)); }
//
// This takes the string returned by getting the cookie environment
// variable and a specific cookie name and returns a the value
// of that cookie (if it exists). There could potentially be
// more than one cookie in the cookie string
//
// Cookies contain one or more semicolon-separated name/value pair:
// name1=value1;name2=value2; (etc.)
//
// INPUT *pszName name we're seeking
// INPUT *pszCookie the whole cookie string
// OUTPUT *pszCookie this string has been written to & should not be relied on
// RETURN value corresponding to *pszName (physically points into *pszCookie string)
// Returns NULL if not found
char *APGTSContext::GetCookieValue(char *pszName, char *pszCookie) { char *sptr, *eptr;
sptr = pszCookie; while (sptr != NULL) { if ((eptr = strstr(sptr,"=")) == NULL) return(NULL);
// replace the '=' with NULL
*eptr = _T('\0'); if (!strncmp(sptr,pszName,strlen(pszName)) ){ // get the value
sptr = eptr + 1; if ((eptr = strstr(sptr,";")) != NULL){ *eptr = _T('\0'); return(sptr); } else { // this is the last variable
return(sptr); } } if ((eptr = strstr(sptr,";")) != NULL) sptr = eptr +1; else sptr = NULL; } return(NULL); }
// INPUT gmt
// INPUT szOut must point to a buffer of at least 29 characters to hold 28 character
// text plus terminating null.
// OUTPUT szOut: a pointer to a string containing a text version of date/time info.
// Typical form would be "Sun, 3-Jan-1998 12:03:08 GMT" There is no choice about
// this form. It should always be exactly 28 characters.
// Regardless of whether the program is compiled for Unicode, this must always be ASCII:
// HTTP cookies are ASCII.
void APGTSContext::asctimeCookie(const struct tm &gmt, char * szOut) { char temp[20];
switch (gmt.tm_wday) { case 0: strcpy(szOut, "Sun, "); break; case 1: strcpy(szOut, "Mon, "); break; case 2: strcpy(szOut, "Tue, "); break; case 3: strcpy(szOut, "Wed, "); break; case 4: strcpy(szOut, "Thu, "); break; case 5: strcpy(szOut, "Fri, "); break; case 6: strcpy(szOut, "Sat, "); break; default: return; }
sprintf(temp, "%02d-", gmt.tm_mday); strcat(szOut, temp);
switch (gmt.tm_mon) { case 0: strcat(szOut, "Jan-"); break; case 1: strcat(szOut, "Feb-"); break; case 2: strcat(szOut, "Mar-"); break; case 3: strcat(szOut, "Apr-"); break; case 4: strcat(szOut, "May-"); break; case 5: strcat(szOut, "Jun-"); break; case 6: strcat(szOut, "Jul-"); break; case 7: strcat(szOut, "Aug-"); break; case 8: strcat(szOut, "Sep-"); break; case 9: strcat(szOut, "Oct-"); break; case 10: strcat(szOut, "Nov-"); break; case 11: strcat(szOut, "Dec-"); break; default: return; }
sprintf(temp, "%04d ", gmt.tm_year +1900); strcat(szOut, temp);
sprintf(temp, "%d:%02d:%02d GMT", gmt.tm_hour, gmt.tm_min, gmt.tm_sec); strcat(szOut, temp); }
//
// Assumes m_Qry.GetFirst has already been called.
// INPUT pszCmd & pszValue are the outputs of m_Qry.GetFirst.
DWORD APGTSContext::ProcessCommands(LPTSTR pszCmd, LPTSTR pszValue) { bool bTryStatus = false; // true = try to parse as an operator status request.
CString str; // strictly scratch
DWORD dwStat = 0;
// Check first if this is a HTI independent of DSC request.
if (!_tcsicmp( pszCmd, C_TEMPLATE)) { CString strBaseName, strHTItemplate; bool bValid; // Force the HTI file to be in the resource directory and have a HTI extension.
strBaseName= CAbstractFileReader::GetJustNameWithoutExtension( pszValue );
// Check for the case where the filename passed in was just a name.
// This is a workaround for what is a questionable implementation of
// GetJustNameWithoutExtension() i.e. returning an empty string when no
// forward slashes or backslashes or dots are detected. RAB-981215.
if ((strBaseName.IsEmpty()) && (_tcslen( pszValue ))) { // Set the base name from the passed in string.
strBaseName= pszValue; strBaseName.TrimLeft(); strBaseName.TrimRight(); }
if (!strBaseName.IsEmpty()) { strHTItemplate= m_pConf->GetFullResource(); strHTItemplate+= strBaseName; strHTItemplate+= _T(".hti"); }
// Check if HTI file already exists in the map of alternate HTI templates.
if (m_pConf->RetTemplateInCatalogStatus( strHTItemplate, bValid )) { // Template has been loaded, check if it is valid.
if (!bValid) strHTItemplate= _T(""); } else { CP_TEMPLATE cpTemplate; // Add the HTI file to the list of active alternate templates and then attempt to
// load the template.
m_pConf->AddTemplate( strHTItemplate ); m_pConf->GetTemplate( strHTItemplate, cpTemplate, m_bNewCookie);
// If the load failed then set the alternate name to blank so that the default
// template is used instead.
if (cpTemplate.IsNull()) strHTItemplate= _T(""); }
// If we have a valid HTI file, set the alternate HTI template.
if (!strHTItemplate.IsEmpty()) SetAltHTIname( strHTItemplate );
// Attempt to acquire the next step of name-value pairs.
m_Qry.GetNext( pszCmd, pszValue ); }
if (!_tcsicmp(pszCmd, C_TOPIC_AND_PROBLEM)) { // Code in this area uses ++ and -- on TCHAR* pointers, rather than using _tcsinc()
// and _tcsdec. This is OK because we never use non-ASCII in the query string.
// value attached to first command is commma-separated topic & problem
TCHAR * pchComma= _tcsstr(pszValue, _T(",")); if (pchComma) { // commma found
*pchComma = 0; // replace it with a null
TCHAR * pchProblem = pchComma; ++pchProblem; // to first character past the comma
// strip any blanks or other junk after the comma
while (*pchProblem > _T('\0') && *pchProblem <= _T(' ')) ++pchProblem;
--pchComma; // make pchComma point to last character before the comma
// strip any blanks or other junk before the comma
while (pchComma > pszValue && *pchComma > _T('\0') && *pchComma<= _T(' ')) *(pchComma--) = 0;
// Now push the problem back onto the query string to be found by a later GetNext()
CString strProbPair(_T("ProblemAsk=")); strProbPair += pchProblem;
m_Qry.Push(strProbPair); } // else treat this as just a topic
_tcscpy(pszCmd, C_TOPIC); }
// first command should be troubleshooter type (symbolic name)
// C_PRELOAD here means we've already done some "sniffing"
// All of these commands take type symbolic belief-network name as their value
// C_TYPE & C_PRELOAD use (deprecated) IDHs
// C_TOPIC uses NIDs
if (!_tcsicmp(pszCmd, C_TYPE) || !_tcsicmp(pszCmd, C_PRELOAD) || !_tcsicmp(pszCmd, C_TOPIC) ) { bool bUsesIDH = _tcsicmp(pszCmd, C_TOPIC)? true:false; // True if NOT "topic".
// The (deprecated) others use IDH.
CString strTopicName = pszValue; strTopicName.MakeLower();
m_TopicName= pszValue; // let outer world know what topic we are working on
// We use a reference-counting smart pointer to hold onto the CTopic. As long as
// cpTopic remains in scope, the relevant CTopic is guaranteed to remain in
// existence.
CP_TOPIC cpTopic; m_pConf->GetTopic(strTopicName, cpTopic, m_bNewCookie); CTopic * pTopic = cpTopic.DumbPointer(); if (pTopic) { m_logstr.AddTopic(strTopicName); // You can compile with the SHOWPROGRESS option to get a report on the progress of this page.
#ifdef SHOWPROGRESS
time (&timeStartInfer); #endif // SHOWPROGRESS
dwStat = DoInference(pszCmd, pszValue, pTopic, bUsesIDH); #ifdef SHOWPROGRESS
time (&timeEndInfer); #endif // SHOWPROGRESS
} else { dwStat = EV_GTS_ERROR_INF_BADTYPECMD;
// $ENGLISH (see note at head of file)
str = _T("<P>Unexpected troubleshooter topic:"); str += strTopicName; SetError(str);
m_logstr.AddTopic(_T("*UNKNOWN*")); m_pcountUnknownTopics->Increment(); } } //
//
// Now we are going to deal with status pages.
// But those pages require knowing of the IP address of the local machine.
// If m_strLocalIPAddress.GetLength() == 0, we were not able to identify
// the IP address and have to display an error message.
else if (0 == m_strLocalIPAddress.GetLength()) { dwStat = EV_GTS_ERROR_IP_GET;
// $ENGLISH (see note at head of file)
SetError(_T("<P>Status request must explicitly give IP address of the server.")); } #ifndef LOCAL_TROUBLESHOOTER
else if (!_tcsicmp(pszCmd, C_FIRST)) { DisplayFirstPage(false); dwStat = EV_GTS_INF_FIRSTACC; m_pcountStatusAccesses->Increment(); } #endif
// You can compile with the NOPWD option to suppress all password checking.
// This is intended mainly for creating test versions with this feature suppressed.
#ifdef NOPWD
else bTryStatus = true; #else
else if (!_tcsicmp(pszCmd, C_PWD)) { CString strPwd; CCharConversion::ConvertACharToString( pszValue, strPwd );
CRegistryPasswords pwd; if (pwd.KeyValidate( _T("StatusAccess"), strPwd) ) { time_t timeNow; time(&timeNow);
// Generate a temporary password
m_strTempPwd = CCharConversion::ConvertACharToString(m_ipstr, str); str.Format(_T("%d"), timeNow); m_strTempPwd += str;
m_pConf->GetRecentPasswords().Add(m_strTempPwd);
// Attempt to acquire the next step of name-value pairs.
m_Qry.GetNext( pszCmd, pszValue ); bTryStatus = true; } else if (m_pConf->GetRecentPasswords().Validate(strPwd) ) { m_strTempPwd = strPwd;
// Attempt to acquire the next step of name-value pairs.
m_Qry.GetNext( pszCmd, pszValue ); bTryStatus = true; } } #endif // ifndef NOPWD
else { dwStat = EV_GTS_ERROR_INF_BADCMD;
// $ENGLISH (see note at head of file)
str = _T("<P>Unexpected command: "); str += pszCmd; SetError(str); }
#ifndef LOCAL_TROUBLESHOOTER
if (bTryStatus) { if (!_tcsicmp(pszCmd, C_FIRST)) { DisplayFirstPage(true); dwStat = EV_GTS_INF_FIRSTACC; m_pcountStatusAccesses->Increment(); } else if (!_tcsicmp(pszCmd, C_FURTHER_GLOBAL)) { DisplayFurtherGlobalStatusPage(); dwStat = EV_GTS_INF_FURTHER_GLOBALACC; m_pcountStatusAccesses->Increment(); } else if (!_tcsicmp(pszCmd, C_THREAD_OVERVIEW)) { DisplayThreadStatusOverviewPage(); dwStat = EV_GTS_INF_THREAD_OVERVIEWACC; m_pcountStatusAccesses->Increment(); } else if (!_tcsicmp(pszCmd, C_TOPIC_STATUS)) { DisplayTopicStatusPage(pszValue); dwStat = EV_GTS_INF_TOPIC_STATUSACC; m_pcountStatusAccesses->Increment(); } else { dwStat = EV_GTS_ERROR_INF_BADCMD;
// $ENGLISH (see note at head of file)
str = _T("<P>Unexpected command: "); str += pszCmd; SetError(str); } } #endif
return (dwStat); }
BOOL APGTSContext::StrIsDigit(LPCTSTR pSz) { BOOL bRet = TRUE; while (*pSz) { if (!_istdigit(*pSz)) { bRet = FALSE; break; } pSz = _tcsinc(pSz); } return bRet; }
// INPUT szNodeName - symbolic name of node.
// RETURNS symbolic node number.
// On unrecognized symbolic name, returns nidNil
NID APGTSContext::NIDFromSymbolicName(LPCTSTR szNodeName) { // first handle all the special cases
if (0 == _tcsicmp(szNodeName, NODE_PROBLEM_ASK)) return nidProblemPage;
if (0 == _tcsicmp(szNodeName, NODE_SERVICE)) return nidService;
if (0 == _tcsicmp(szNodeName, NODE_FAIL)) return nidFailNode;
if (0 == _tcsicmp(szNodeName, NODE_FAILALLCAUSESNORMAL)) return nidSniffedAllCausesNormalNode;
if (0 == _tcsicmp(szNodeName, NODE_IMPOSSIBLE)) return nidImpossibleNode;
if (0 == _tcsicmp(szNodeName, NODE_BYE)) return nidByeNode;
// normal symbolic name
NID nid = m_infer.INode(szNodeName); if (nid == -1) return nidNil; else return nid; }
// Validate and convert a list of nodes and their associated states.
//
// INPUT pszCmd & pszValue are the outputs of m_Qry.GetNext.
// INPUT index into the HTTP query parameters. Parameters may follow any of the following
// PATTERNS.
// INPUT dwCount is the number shown at left in each of these patterns. Remember that this
// function never sees dwCount=1; that's been used to set m_bPreload:
// INPUT bUsesIDH - Interpret numerics as IDHs (a deprecated feature) rather than NIDs
// DEPRECATED BUT SUPPORTED PATTERNS when bUsesIDH == true
// "Preload"
// PROBABLY NOT EFFECTIVE BACKWARD COMPATIBLITY
// because if they've added any nodes, # of nodes in network will have changed)
// 1 - preload=<troubleshooter symbolic name>
// 2 - <# of nodes in network + 1000>=<problem node number + 1000>
// 3+ - <node symbolic name>=<node state value>
// Old "type" (we never generate these, but we have them here for backward compatibility.
// PROBABLY NOT EFFECTIVE BACKWARD COMPATIBLITY
// because if they've added any nodes, # of nodes in network will have changed)
// 1 - type=<troubleshooter symbolic name>
// 2 - <# of nodes in network + 1000>=<problem node number + 1000>
// 3+ - <node # + 1000>=<node state value>
// Newer "type", should be fully backward compatible.
// 1 - type=<troubleshooter symbolic name>
// 2 - ProblemAsk=<problem node symbolic name>
// 3+ - <node symbolic name>=<node state value>
// It is presumably OK for us to allow a slight superset of the known formats, which yields:
// 1 - preload=<troubleshooter symbolic name> OR
// type=<troubleshooter symbolic name>
// Determines m_bPreload before this fn is called
// 2 - <# of nodes in network + 1000>=<problem node number + 1000> OR
// ProblemAsk=<problem node symbolic name>
// We can distinguish between these by whether pszCmd is numeric
// 3+ - <node # + 1000>=<node state value> OR
// <node symbolic name>=<node state value>
// We can distinguish between these by whether pszCmd is numeric
// The only assumption in this overloading is that a symbolic name will never be entirely
// numeric.
// SUPPORTED PATTERN when bUsesIDH == false
// 1 - topic=<troubleshooter symbolic name>
// 2 - ProblemAsk=<problem node symbolic name>
// 3+ - <node symbolic name>=<node state value>
//
// LIMITATION ON STATE NUMBERS
// As of 11/97, <node state value> must always be one of:
// 0 - Fixed/Unfixed: Haven't solved problem
// Info: First option
// 1 - Info: Second option
// 101 - Go to "Bye" Page (User succeeded - applies to Fixed/Unfixed or Support Nodes only)
// 102 - Unknown (user doesn't know the correct answer here - applies to Fixed/Unfixed and
// Info nodes only)
// 103 - "Anything Else I Can Try"
// Since inputs of state values should always come from forms we generated, we don't
// systematically limit state numbers in the code here.
// V3.0 allows other numeric states: 0-99 should all be legal.
//
// RETURN 0 on success, otherwise an error code
DWORD APGTSContext::NextCommand(LPTSTR pszCmd, LPTSTR pszValue, bool bUsesIDH) { NID nid; int value = 0; // if pszValue is numeric, a NID or state.
// otherwise, pszValue is the symbolic name of a node,
// and this is its NID
bool sniffed = false;
if (StrIsDigit(pszCmd)) { // only should arise for bUsesIDH
// it's an IDH (typically node # + 1000), but can be <# of nodes in network> + 1000,
// interpreted as "ProblemAsk"
// The pages we generate never give us these values, but we recognize them.
IDH idh = _ttoi(pszCmd); nid = m_infer.NIDFromIDH(idh); } else { // The command is a symbolic name.
sniffed = StripSniffedNodePrefix(pszCmd); nid= NIDFromSymbolicName(pszCmd); }
if (StrIsDigit(pszValue)) { if (bUsesIDH) { int valueIDH = _ttoi(pszValue); if (nid == nidProblemPage) // problem node number + 1000
value = m_infer.NIDFromIDH(valueIDH); else // state value
value = valueIDH; } else { // value is a state number.
value = _ttoi(pszValue); } } else if (nid == nidProblemPage) { // Symbolic name of problem node
value = NIDFromSymbolicName(pszValue); } else return EV_GTS_ERROR_INF_BADPARAM;
m_CommandsAddManager.Add(nid, value, sniffed);
return 0; }
DWORD APGTSContext::NextAdditionalInfo(LPTSTR pszCmd, LPTSTR pszValue) { if (RUNNING_LOCAL_TS()) { if ( 0 == _tcscmp(pszCmd, C_ALLOW_AUTOMATIC_SNIFFING_NAME) && (0 == _tcsicmp(pszValue, C_ALLOW_AUTOMATIC_SNIFFING_CHECKED) || 0 == _tcsicmp(pszValue, C_ALLOW_AUTOMATIC_SNIFFING_UNCHECKED))) { m_AdditionalInfo.Add(pszCmd, pszValue); return 0; } if (0 == _tcscmp(pszCmd, C_LAST_SNIFFED_MANUALLY)) { if (0 == _tcscmp(pszValue, SZ_ST_SNIFFED_MANUALLY_TRUE)) m_infer.SetLastSniffedManually(true); return 0; } } return EV_GTS_ERROR_INF_BADPARAM; }
// Name - value pairs that we can ignore
DWORD APGTSContext::NextIgnore(LPTSTR pszCmd, LPTSTR pszValue) { if (RUNNING_LOCAL_TS()) { // Value "-1" can come from a field, used for manual sniffing,
// when other then "Sniff" button is pressed
CString strValue(pszValue); CString strSniffFailure;
strValue.TrimLeft(); strValue.TrimRight(); strSniffFailure.Format(_T("%d"), SNIFF_FAILURE_RESULT); if (strValue == strSniffFailure) { // Name in this case should be a valid node name
NID nid = nidNil;
StripSniffedNodePrefix(pszCmd); nid = NIDFromSymbolicName(pszCmd); if (nid != nidNil) return 0; } } return EV_GTS_ERROR_INF_BADPARAM; }
VOID APGTSContext::ClearCommandList() { m_Commands.RemoveAll(); }
VOID APGTSContext::ClearSniffedList() { m_Sniffed.RemoveAll(); }
VOID APGTSContext::ClearAdditionalInfoList() { m_AdditionalInfo.RemoveAll(); } /*
// Return false on failure; shouldn't ever arise.
bool APGTSContext::PlaceNodeInCommandList(NID nid, IST ist) { return (m_Commands.Add(nid, ist) > 0); } */ /*
// Return false on failure; shouldn't ever arise.
bool APGTSContext::PlaceNodeInSniffedList(NID nid, IST ist) { return (m_Sniffed.Add(nid, ist) > 0); } */ /*
// Return false on failure; shouldn't ever arise.
bool APGTSContext::PlaceInAdditionalInfoList(const CString& name, const CString& value) { return (m_AdditionalInfo.Add(name, value) > 0); } */ // INPUT: node short name, possibly prefixed by SNIFFED_
// OUTPUT: node short name
// RETURN: true is prefix was stripped
bool APGTSContext::StripSniffedNodePrefix(LPTSTR szName) { if (0 == _tcsnicmp(szName, C_SNIFFTAG, _tcslen(C_SNIFFTAG))) { // use "memmove" since we are operating with overlapped regions!
memmove(szName, szName + _tcslen(C_SNIFFTAG), _tcslen(szName + _tcslen(C_SNIFFTAG)) + 1); return true; } return false; }
VOID APGTSContext::SetNodesPerCommandList() { int nCommands = m_Commands.GetSize(); for (int i= 0; i<nCommands; i++) { NID nid; int value; // typically a state (IST), except if nid==nidProblemPage, where value is a NID
m_Commands.GetAt( i, nid, value ); m_infer.SetNodeState(nid, value); } }
VOID APGTSContext::SetNodesPerSniffedList() { int nSniffed = m_Sniffed.GetSize(); for (int i= 0; i<nSniffed; i++) { NID nid; int ist; m_Sniffed.GetAt( i, nid, ist ); m_infer.AddToSniffed(nid, ist); } }
VOID APGTSContext::ProcessAdditionalInfoList() { int nCount = m_AdditionalInfo.GetSize(); for (int i= 0; i < nCount; i++) { if (RUNNING_LOCAL_TS()) { CString name; CString value; m_AdditionalInfo.GetAt( i, name, value ); if (name == C_ALLOW_AUTOMATIC_SNIFFING_NAME) { value.MakeLower(); if (m_infer.GetSniff()) { // set AllowAutomaticSniffing flag
if (value == C_ALLOW_AUTOMATIC_SNIFFING_CHECKED) m_infer.GetSniff()->SetAllowAutomaticSniffingPolicy(true); if (value == C_ALLOW_AUTOMATIC_SNIFFING_UNCHECKED) m_infer.GetSniff()->SetAllowAutomaticSniffingPolicy(false); } } } else { // process additional info in Online TS here
} } }
VOID APGTSContext::ReadPolicyInfo() { if (RUNNING_LOCAL_TS()) { if (m_infer.GetSniff()) { // set AllowManualSniffing flag
DWORD dwManualSniffing = 0; m_pConf->GetRegistryMonitor().GetNumericInfo(CAPGTSRegConnector::eSniffManual, dwManualSniffing); m_infer.GetSniff()->SetAllowManualSniffingPolicy(dwManualSniffing ? true : false); // >>> $TODO$ I do not like setting Policy Editor values explicitely,
// as it done here. Probably we will have to implement
// CSniffPolicyInfo (abstract) class, designed for passing
// those values to sniffer (CSniff).
// Oleg. 11.05.99
} } }
VOID APGTSContext::LogNodesPerCommandList() { int nCommands = m_Commands.GetSize(); for (int i= 0; i<nCommands; i++) { NID nid; int value; // typically a state (IST), except if nid==nidProblemPage, where value is a NID
m_Commands.GetAt( i, nid, value ); if (nid == nidProblemPage) m_logstr.AddNode(value, 1); else m_logstr.AddNode(nid, value); } }
// builds and returns the Start Over link
// only relevant for Online TS
// >>> JM 10/8/99: I believe it would be redundant to URL-encode
CString APGTSContext::GetStartOverLink() { CString str; #ifndef LOCAL_TROUBLESHOOTER
bool bHasQuestionMark = false;
// ISAPI DLL's URL
str = m_strVRoot;
// CK_ name value pairs
if (!m_mapCookiesPairs.empty()) { // V3.2 - Output any CK_name-value pairs as hidden fields.
for (CCookiePairs::const_iterator it = m_mapCookiesPairs.begin(); it != m_mapCookiesPairs.end(); ++it) { if (bHasQuestionMark) str += _T("&"); else { str += _T("?"); bHasQuestionMark = true; } CString strAttr= it->first; CString strValue= it->second; APGTS_nmspace::CookieEncodeURL( strAttr ); APGTS_nmspace::CookieEncodeURL( strValue ); str += C_COOKIETAG + strAttr; str += _T("="); str += strValue; } }
// template
const CString strAltHTIname= GetAltHTIname(); if (!strAltHTIname.IsEmpty()) { if (bHasQuestionMark) str += _T("&"); else { str += _T("?"); bHasQuestionMark = true; } str += C_TEMPLATE; str += _T("="); str += strAltHTIname; }
// topic
if (!m_TopicName.IsEmpty()) { if (bHasQuestionMark) str += _T("&"); else { str += _T("?"); bHasQuestionMark = true; } str += C_TOPIC; str += _T("="); str += m_TopicName; } #endif
return str; }
// Assumes m_Qry.GetFirst has already been called.
// INPUT pszCmd & pszValue are the outputs of m_Qry.GetFirst.
// These same buffers are used for subsequent calls to m_Qry.GetNext.
// INPUT *pTopic - represents contents of appropriate DSC, HTI, BES
// INPUT bUsesIDH - Interpret numerics as IDHs (a deprecated feature) rather than NIDs
// Return 0 on success, EV_GTS_ERROR_INF_BADPARAM on failure.
DWORD APGTSContext::DoInference(LPTSTR pszCmd, LPTSTR pszValue, CTopic * pTopic, bool bUsesIDH) { DWORD dwStat = 0; CString strTopic = pszValue; CString strHTTPcookies; if (!_tcsicmp(pszCmd, C_PRELOAD)) m_bPreload = true;
m_infer.SetTopic(pTopic);
ClearCommandList(); ClearSniffedList(); ClearAdditionalInfoList();
while (m_Qry.GetNext(pszCmd, pszValue)) { if ((dwStat = NextCommand(pszCmd, pszValue, bUsesIDH)) != 0) if ((dwStat = NextAdditionalInfo(pszCmd, pszValue)) != 0) if ((dwStat = NextIgnore(pszCmd, pszValue)) != 0) break; }
if (!dwStat) { m_Commands.RotateProblemPageToFront(); LogNodesPerCommandList(); SetNodesPerCommandList(); SetNodesPerSniffedList(); ProcessAdditionalInfoList(); ReadPolicyInfo();
// Append to m_strText: contents of an HTML page based on HTI template.
// History & next recommendation
// Build the $StartForm string.
CString strStartForm; CString strTmpLine; const CString strAltHTIname= GetAltHTIname();
if (RUNNING_LOCAL_TS()) strStartForm = _T("<FORM NAME=\"ButtonForm\">\n"); else strStartForm.Format( _T("<FORM METHOD=POST ACTION=\"%s\">\n"), m_pConf->GetVrootPath() );
if (RUNNING_ONLINE_TS()) { // This processes name-value pairs, parallel to those which would come
// from a cookie to determine look and feel, but which in this case actually
// were originally sent in as CK_ pairs in Get or Post.
// These values are only used in the Online TS.
try { if (!m_mapCookiesPairs.empty()) { // V3.2 - Output any CK_name-value pairs as hidden fields.
for (CCookiePairs::const_iterator it = m_mapCookiesPairs.begin(); it != m_mapCookiesPairs.end(); ++it) { CString strAttr= it->first; CString strValue= it->second; APGTS_nmspace::CookieEncodeURL( strAttr ); APGTS_nmspace::CookieEncodeURL( strValue );
strTmpLine.Format( _T("<INPUT TYPE=HIDDEN NAME=\"%s%s\" VALUE=\"%s\">\n"), C_COOKIETAG, strAttr, strValue ); strStartForm+= strTmpLine; } } // This processes name-value pairs, which actually come
// from a cookie to determine look and feel.
// These values are only used in the Online TS.
// V3.2 - Extract all look-and-feel cookie name-value pairs from the HTTP headers.
char szCookieNameValue[ 1024 ]; // never Unicode, because cookies always ASCII
DWORD dwCookieLen= 1023; if ( m_pECB->GetServerVariable( kHTTP_COOKIE, szCookieNameValue, &dwCookieLen )) strHTTPcookies= szCookieNameValue; else { // Determine if the buffer was too small.
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { // never Unicode, because cookies always ASCII.
char *pszCookieNameValue= new char[ dwCookieLen + 1 ]; if ( m_pECB->GetServerVariable( kHTTP_COOKIE, pszCookieNameValue, &dwCookieLen )) strHTTPcookies= pszCookieNameValue; delete [] pszCookieNameValue; } else { // Note memory failure in event log.
CString strLastError; strLastError.Format( _T("%d"), ::GetLastError() ); CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), strLastError, _T(""), EV_GTS_ERROR_EXTRACTING_HTTP_COOKIES ); } } } catch (bad_alloc&) { // Note memory failure in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T(""), _T(""), EV_GTS_CANT_ALLOC ); } catch (exception& x) { CString str; // Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), _T(""), EV_GTS_STL_EXCEPTION ); } }
if (!strAltHTIname.IsEmpty()) { // Add the alternate HTI template name to the $StartForm string.
strTmpLine.Format( _T("<INPUT TYPE=HIDDEN NAME=\"template\" VALUE=\"%s\">\n"), CAbstractFileReader::GetJustName( strAltHTIname ) ); strStartForm+= strTmpLine; } strTmpLine.Format( _T("<INPUT TYPE=HIDDEN NAME=\"topic\" VALUE=\"%s\">\n"), strTopic ); strStartForm+= strTmpLine;
// Determine whether an alternate HTI template should be used.
bool bAlternatePageGenerated= false; if (!strAltHTIname.IsEmpty()) { // Attempt to extract a pointer to the requested HTI template and if successful
// then create a page from it.
CP_TEMPLATE cpTemplate;
m_pConf->GetTemplate( strAltHTIname, cpTemplate, m_bNewCookie ); CAPGTSHTIReader *pHTI= cpTemplate.DumbPointer(); if (pHTI) { CString strResourcePath; m_pConf->GetRegistryMonitor().GetStringInfo(CAPGTSRegConnector::eResourcePath, strResourcePath); #ifdef LOCAL_TROUBLESHOOTER
CHTMLFragmentsLocal frag( strResourcePath, pHTI->HasHistoryTable() ); #else
CHTMLFragmentsTS frag( strResourcePath, pHTI->HasHistoryTable() ); #endif
// Add the $StartForm string to the HTML fragments.
frag.SetStartForm(strStartForm);
frag.SetStartOverLink(GetStartOverLink()); // JSM V3.2 get list of Net prop names needed by HTI;
// pass them to frag. CInfer will fill in
// the net prop values, using these names, in FillInHTMLFragments()
{ vector<CString> arr_props; pHTI->ExtractNetProps(arr_props); for(vector<CString>::iterator i = arr_props.begin(); i < arr_props.end(); i++) frag.AddNetPropName(*i); }
m_infer.IdentifyPresumptiveCause(); m_infer.FillInHTMLFragments(frag);
pHTI->CreatePage( frag, m_strText, m_mapCookiesPairs, strHTTPcookies ); bAlternatePageGenerated= true; } } if (!bAlternatePageGenerated) { // The page was not generated from an alternate HTI template, generate it now.
// >>> $MAINT an awful lot of common code with the above. Can't we
// set up a private method?
CString strResourcePath; m_pConf->GetRegistryMonitor().GetStringInfo(CAPGTSRegConnector::eResourcePath, strResourcePath); #ifdef LOCAL_TROUBLESHOOTER
CHTMLFragmentsLocal frag( strResourcePath, pTopic->HasHistoryTable() ); #else
CHTMLFragmentsTS frag( strResourcePath, pTopic->HasHistoryTable() ); #endif
// Add the $StartForm string to the HTML fragments.
frag.SetStartForm(strStartForm);
frag.SetStartOverLink(GetStartOverLink());
// JSM V3.2 get list of Net prop names needed by HTI;
// pass them to frag. CInfer will fill in
// the net prop values, using these names, in FillInHTMLFragments()
{ vector<CString> arr_props; pTopic->ExtractNetProps(arr_props); for(vector<CString>::iterator i = arr_props.begin(); i < arr_props.end(); i++) frag.AddNetPropName(*i); } m_infer.IdentifyPresumptiveCause(); m_infer.FillInHTMLFragments(frag);
pTopic->CreatePage( frag, m_strText, m_mapCookiesPairs, strHTTPcookies ); }
if (m_infer.AppendBESRedirection(m_strHeader)) // We have no more recommendations, but there is a BES file present, so we
// have to redirect the user
_tcscpy(m_resptype, _T("302 Object Moved")); } else { SetError(_T("")); }
return dwStat; }
CString APGTSContext::RetCurrentTopic() const { return( m_TopicName ); }
// Operator actions which must be performed by the main thread are caught in
// APGTSExtension::IsEmergencyRequest
// All other operator actions can be identified by this routine.
// INPUT *pECB: our abstraction from EXTENSION_CONTROL_BLOCK, which is ISAPI's way of
// packaging CGI data. pECB should never be null.
APGTSContext::eOpAction APGTSContext::IdentifyOperatorAction(CAbstractECB *pECB) { if (strcmp(pECB->GetMethod(), "GET")) return eNoOpAction; if (strncmp(pECB->GetQueryString(), SZ_EMERGENCY_DEF, strlen(SZ_OP_ACTION))) return eNoOpAction; if ( ! strncmp(pECB->GetQueryString() + strlen(SZ_OP_ACTION), SZ_RELOAD_TOPIC, strlen(SZ_RELOAD_TOPIC))) return eReloadTopic; if ( ! strncmp(pECB->GetQueryString() + strlen(SZ_OP_ACTION), SZ_KILL_THREAD, strlen(SZ_KILL_THREAD))) return eKillThread; if ( ! strncmp(pECB->GetQueryString() + strlen(SZ_OP_ACTION), SZ_RELOAD_ALL_TOPICS, strlen(SZ_RELOAD_ALL_TOPICS))) return eReloadAllTopics; if ( ! strncmp(pECB->GetQueryString() + strlen(SZ_OP_ACTION), SZ_SET_REG, strlen(SZ_SET_REG))) return eSetReg;
return eNoOpAction; }
// Identify a request to perform an operator action that does not have to be performed
// on the main thread.
// Should be called only after we have determined this is an operator action: sends an
// error msg to end user if it's not.
// Based loosely on APGTSExtension::ParseEmergencyRequest
// INPUT *pECB: our abstraction from EXTENSION_CONTROL_BLOCK, which is ISAPI's way of
// packaging CGI data. pECB should never be null.
// OUTPUT strArg - any argument for this operation
// Returns identified operator action.
APGTSContext::eOpAction APGTSContext::ParseOperatorAction( CAbstractECB *pECB, CString & strArg) { TCHAR *pszProblem= NULL; CHAR * ptr = pECB->GetQueryString();
CHAR * ptrArg = strstr(pECB->GetQueryString(), "&"); if(ptrArg) { // Turn the ampersand into a terminator and point past it.
// Everything past it is an argument.
*(ptrArg++) = '\0'; CCharConversion::ConvertACharToString(ptrArg, strArg) ; } else strArg = _T("");
// In a sense this test is redundant (should know this before this fn is called) but
// this seemed like a safer way to code JM 11/2/98
eOpAction ret = IdentifyOperatorAction(pECB);
if ( ret == eNoOpAction) pszProblem= _T("Wrong Format"); else { switch(ret) { case eReloadTopic: ptr += strlen(SZ_OP_ACTION) + strlen(SZ_RELOAD_TOPIC); break; case eKillThread: ptr += strlen(SZ_OP_ACTION) + strlen(SZ_KILL_THREAD); break; case eReloadAllTopics: ptr += strlen(SZ_OP_ACTION) + strlen(SZ_RELOAD_ALL_TOPICS); break; case eSetReg: ptr += strlen(SZ_OP_ACTION) + strlen(SZ_SET_REG); break; default: CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T(""), _T(""), EV_GTS_ERROR_INVALIDOPERATORACTION ); } // You can compile with the NOPWD option to suppress all password checking.
// This is intended mainly for creating test versions with this feature suppressed.
#ifndef NOPWD
CRegistryPasswords pwd; CString str; if (! pwd.KeyValidate( _T("ActionAccess"), CCharConversion::ConvertACharToString(ptr, str) ) ) { pszProblem= _T("Bad password"); ret = eNoOpAction; } #endif // ifndef NOPWD
}
if (pszProblem) { CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), pszProblem, _T(""), EV_GTS_CANT_PROC_OP_ACTION );
m_dwErr = EV_GTS_CANT_PROC_OP_ACTION; }
return ret; }
// Execute a request to do one of:
// - Reload one topic
// - Kill (and restart) one pool thread
// - Reload all monitored files.
// INPUT *pECB: our abstraction from EXTENSION_CONTROL_BLOCK, which is ISAPI's way of
// packaging CGI data. pECB should never be null.
// INPUT action - chooses among the three possible actions
// INPUT strArg - provides any necessary arguments for that action
// RETURNS HSE_STATUS_SUCCESS, HSE_STATUS_ERROR
void APGTSContext::ExecuteOperatorAction( CAbstractECB *pECB, eOpAction action, const CString & strArg) { m_strText += _T("<HTML><HEAD><TITLE>AP GTS Command</TITLE></HEAD>"); m_strText += _T("<BODY BGCOLOR=#FFFFFF>");
switch (action) { case eReloadTopic: { bool bAlreadyInCatalog; m_strText += _T("<H1>Reload Topic "); m_strText += strArg; m_strText += _T("</H1>"); m_pConf->GetTopicShop().BuildTopic(strArg, &bAlreadyInCatalog); if (!bAlreadyInCatalog) { m_strText += strArg; m_strText += _T(" is not a known topic. Either it is not in the current LST file") _T(" or the Online Troubleshooter is waiting to see the resource directory") _T(" "settle" before loading the LST file."); } break; } case eKillThread: m_strText += _T("<H1>Kill Thread"); m_strText += strArg; m_strText += _T("</H1>"); if (m_pConf->GetThreadPool().ReinitializeThread(_ttoi(strArg))) m_strText += _T("Thread killed. System will attempt to spin a new thread."); else m_strText += _T("No such thread"); break; case eReloadAllTopics: m_strText += _T("<H1>Reload All Topics</H1>"); m_pConf->GetTopicShop().RebuildAll(); break; case eSetReg: { CHttpQuery query; TCHAR szCmd[MAXBUF]; TCHAR szVal[MAXBUF]; CString strCmd, strVal; query.GetFirst(strArg, szCmd, szVal); CCharConversion::ConvertACharToString(szCmd, strCmd); CCharConversion::ConvertACharToString(szVal, strVal); m_strText += _T("<H1>Set registry value"); m_strText += strCmd; m_strText += _T(" = "); m_strText += strVal; m_strText += _T("</H1>");
CAPGTSRegConnector RegConnect( _T("") ); bool bChanged ; bool bExists = RegConnect.SetOneValue(szCmd, szVal, bChanged );
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), strCmd, strVal, bExists ? EV_GTS_SET_REG_VALUE : EV_GTS_CANT_SET_REG_VALUE);
if (bChanged) m_strText += _T("Successful."); else if (bExists) { m_strText += strCmd; m_strText += _T(" already had value "); m_strText += strVal; m_strText += _T("."); } else { m_strText += strCmd; m_strText += _T(" Unknown."); }
break; } default: m_strText += _T("<H1>Unknown operation</H1>"); break; } m_strText += strArg;
m_strText += _T("</BODY></HTML>"); }
// override any partially written page with an error page.
void APGTSContext::SetError(LPCTSTR szMessage) { _tcscpy(m_resptype, _T("400 Bad Request"));
CString str(_T("<H3>Possible invalid data received</H3>\n")); str += szMessage;
m_pConf->CreateErrorPage(str, m_strText);
}
void APGTSContext::SetAltHTIname( const CString& strHTIname ) { m_strAltHTIname= strHTIname; }
CString APGTSContext::GetAltHTIname() const { return( m_strAltHTIname ); }
|