// // 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 support@saltmine.com // // 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 #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= // and type=, 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= 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("

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
Start Infer "); strProgress += safetimeStartInfer.StrLocalTime(); strProgress += _T("\n
End Infer "); strProgress += safetimeEndInfer.StrLocalTime(); strProgress += _T("\n
End Render "); strProgress += safetimeEndRender.StrLocalTime(); strProgress += _T("\n
"); int 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("

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("

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("

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("

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("

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("

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("

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= // 2 - <# of nodes in network + 1000>= // 3+ - = // 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= // 2 - <# of nodes in network + 1000>= // 3+ - = // Newer "type", should be fully backward compatible. // 1 - type= // 2 - ProblemAsk= // 3+ - = // It is presumably OK for us to allow a slight superset of the known formats, which yields: // 1 - preload= OR // type= // Determines m_bPreload before this fn is called // 2 - <# of nodes in network + 1000>= OR // ProblemAsk= // We can distinguish between these by whether pszCmd is numeric // 3+ - = OR // = // 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= // 2 - ProblemAsk= // 3+ - = // // LIMITATION ON STATE NUMBERS // As of 11/97, 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; iSetAllowAutomaticSniffingPolicy(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>> 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("

\n"); else strStartForm.Format( _T("\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("\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("\n"), CAbstractFileReader::GetJustName( strAltHTIname ) ); strStartForm+= strTmpLine; } strTmpLine.Format( _T("\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 arr_props; pHTI->ExtractNetProps(arr_props); for(vector::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 arr_props; pTopic->ExtractNetProps(arr_props); for(vector::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("AP GTS Command"); m_strText += _T(""); switch (action) { case eReloadTopic: { bool bAlreadyInCatalog; m_strText += _T("

Reload Topic "); m_strText += strArg; m_strText += _T("

"); 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("

Kill Thread"); m_strText += strArg; m_strText += _T("

"); 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("

Reload All Topics

"); 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("

Set registry value"); m_strText += strCmd; m_strText += _T(" = "); m_strText += strVal; m_strText += _T("

"); 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("

Unknown operation

"); break; } m_strText += strArg; m_strText += _T(""); } // override any partially written page with an error page. void APGTSContext::SetError(LPCTSTR szMessage) { _tcscpy(m_resptype, _T("400 Bad Request")); CString str(_T("

Possible invalid data received

\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 ); }