/*++ Copyright (c) 1994-2001 Microsoft Corporation Module Name : iissite.cpp Abstract: IIS Site Object Author: Ronald Meijer (ronaldm) Sergei Antonov (sergeia) Project: Internet Services Manager Revision History: 10/28/2000 sergeia Split from iisobj.cpp --*/ #include "stdafx.h" #include "common.h" #include "inetprop.h" #include "InetMgrApp.h" #include "iisobj.h" #include "machsht.h" #include "errors.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif #define new DEBUG_NEW // // CIISSite implementation // // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // // Site Result View definition // /* static */ int CIISSite::_rgnLabels[COL_TOTAL] = { IDS_RESULT_SERVICE_DESCRIPTION, IDS_RESULT_SERVICE_STATE, IDS_RESULT_SERVICE_DOMAIN_NAME, IDS_RESULT_SERVICE_IP_ADDRESS, IDS_RESULT_SERVICE_TCP_PORT, IDS_RESULT_STATUS, }; /* static */ int CIISSite::_rgnWidths[COL_TOTAL] = { 180, 70, 120, 105, 40, 200, }; /* static */ CComBSTR CIISSite::_bstrStarted; /* static */ CComBSTR CIISSite::_bstrStopped; /* static */ CComBSTR CIISSite::_bstrPaused; /* static */ CComBSTR CIISSite::_bstrUnknown; /* static */ CComBSTR CIISSite::_bstrPending; /* static */ CComBSTR CIISSite::_bstrAllUnassigned; /* static */ BOOL CIISSite::_fStaticsLoaded = FALSE; /* static */ void CIISSite::InitializeHeaders(LPHEADERCTRL lpHeader) /*++ Routine Description: Initialize the result headers Arguments: LPHEADERCTRL lpHeader : Header control Return Value: None --*/ { CIISObject::BuildResultView(lpHeader, COL_TOTAL, _rgnLabels, _rgnWidths); // CIISDirectory::InitializeHeaders(lpHeader); if (!_fStaticsLoaded) { _fStaticsLoaded = _bstrStarted.LoadString(IDS_STARTED) && _bstrStopped.LoadString(IDS_STOPPED) && _bstrPaused.LoadString(IDS_PAUSED) && _bstrUnknown.LoadString(IDS_UNKNOWN) && _bstrPending.LoadString(IDS_PENDING) && _bstrAllUnassigned.LoadString(IDS_IP_ALL_UNASSIGNED); } } /* virtual */ void CIISSite::InitializeChildHeaders( IN LPHEADERCTRL lpHeader ) /*++ Routine Description: Build result view for immediate descendant type Arguments: LPHEADERCTRL lpHeader : Header control Return Value: None --*/ { CIISDirectory::InitializeHeaders(lpHeader); } CIISSite::CIISSite( IN CIISMachine * pOwner, IN CIISService * pService, IN LPCTSTR szNodeName ) /*++ Routine Description: Constructor. Determine if the given service is administrable, and resolve the details Arguments: CIISMachine * pOwner : Owner machine object CIISService * pService : Service type LPCTSTR szNodeName : Node name (numeric) Return Value: N/A Notes: This constructor does not immediately resolve the display name of the site. It will only resolve its display information when asked --*/ : CIISMBNode(pOwner, szNodeName), m_pService(pService), m_fResolved(FALSE), m_strDisplayName(), // // Data members -- plonk in some defaults // m_dwState(MD_SERVER_STATE_INVALID), m_fDeletable(FALSE), m_fWolfPackEnabled(FALSE), m_fFrontPageWeb(FALSE), m_sPort(80), m_dwID(::_ttol(szNodeName)), m_dwIPAddress(0L), m_dwWin32Error(ERROR_SUCCESS), m_bstrHostHeaderName(), m_bstrComment() { ASSERT_PTR(m_pService); } CIISSite::CIISSite( IN CIISMachine * pOwner, IN CIISService * pService, IN LPCTSTR szNodeName, IN DWORD dwState, IN BOOL fDeletable, IN BOOL fClusterEnabled, IN USHORT sPort, IN DWORD dwID, IN DWORD dwIPAddress, IN DWORD dwWin32Error, IN LPOLESTR szHostHeaderName, IN LPOLESTR szComment ) /*++ Routine Description: Construct with full information Arguments: CIISMachine * pOwner : Owner machine object CIISService * pService : Service type LPCTSTR szNodeName : Node name (numeric) plus datamembers Return Value: N/A --*/ : CIISMBNode(pOwner, szNodeName), m_pService(pService), m_fResolved(TRUE), m_strDisplayName(), // // Data Members // m_dwState(dwState), m_fDeletable(fDeletable), m_fWolfPackEnabled(fClusterEnabled), m_sPort(sPort), m_dwID(dwID), m_dwIPAddress(dwIPAddress), m_dwWin32Error(dwWin32Error), m_bstrHostHeaderName(szHostHeaderName), m_bstrComment(szComment) { ASSERT_PTR(m_pService); } CIISSite::~CIISSite() /*++ Routine Description: Destructor Arguments: N/A Return Value: N/A --*/ { } /* virtual */ HRESULT CIISSite::RefreshData() /*++ Routine Description: Refresh relevant configuration data required for display. Arguments: None Return Value: HRESULT --*/ { CError err; CWaitCursor wait; CComBSTR bstrPath; CMetaKey * pKey = NULL; do { ASSERT_PTR(_lpConsoleNameSpace); err = BuildMetaPath(bstrPath); BREAK_ON_ERR_FAILURE(err); // We need instance key here CString path_inst; CMetabasePath::GetInstancePath(bstrPath, path_inst); BOOL fContinue = TRUE; while (fContinue) { fContinue = FALSE; if (NULL == (pKey = new CMetaKey(QueryInterface(), path_inst))) { TRACEEOLID("RefreshData: OOM"); err = ERROR_NOT_ENOUGH_MEMORY; break; } err = pKey->QueryResult(); if (IsLostInterface(err)) { SAFE_DELETE(pKey); fContinue = OnLostInterface(err); } } BREAK_ON_ERR_FAILURE(err); CInstanceProps inst(pKey, _T(""), m_dwID); err = inst.LoadData(); BREAK_ON_ERR_FAILURE(err); m_dwState = inst.m_dwState; m_fDeletable = !inst.m_fNotDeletable; // // Don't be confused -- cluster enabled refers // to wolfpack and has nothing to do with app server // m_fWolfPackEnabled = inst.IsClusterEnabled(); m_sPort = (SHORT)inst.m_nTCPPort; m_dwID = inst.QueryInstance(); m_dwIPAddress = inst.m_iaIpAddress; m_dwWin32Error = inst.m_dwWin32Error; m_bstrHostHeaderName = inst.m_strDomainName; m_bstrComment = inst.m_strComment; m_strDisplayName.Empty(); // Check if it is Frontpage controlled site pKey->QueryValue(MD_FRONTPAGE_WEB, m_fFrontPageWeb); CChildNodeProps child(pKey, SZ_MBN_ROOT); err = child.LoadData(); BREAK_ON_ERR_FAILURE(err); m_strRedirectPath = child.GetRedirectedPath(); } while(FALSE); SAFE_DELETE(pKey); if (m_dwWin32Error == ERROR_SUCCESS) { m_dwWin32Error = err.Win32Error(); } return err; } /* virtual */ int CIISSite::QueryImage() const /*++ Routine Description: Return bitmap index for the site Arguments: None Return Value: Bitmap index --*/ { ASSERT_PTR(m_pService); if (!m_fResolved) { TRACEEOLID("Resolving name for site #" << QueryNodeName()); if (m_hScopeItem == NULL) { // // BUGBUG: // // This is probably related to MMC bug #324519 // where we're asked for the display info immediately // after adding the item to the console view. This // appears to fail only on refresh because the scope // item handle is missing, and we can't build a metabase // path yet. // TRACEEOLID("BUGBUG: Prematurely asked for display information"); //ASSERT(FALSE); return iError; } CIISSite * that = (CIISSite *)this; CError err = that->RefreshData(); that->m_fResolved = err.Succeeded(); } return !m_dwWin32Error && m_pService ? m_pService->QuerySiteImage() : iError; } /* virtual */ LPOLESTR CIISSite::QueryDisplayName() /*++ Routine Description: Return primary display name of this site. Arguments: None Return Value: The display name --*/ { AFX_MANAGE_STATE(::AfxGetStaticModuleState()); if (!m_fResolved) { TRACEEOLID("Resolving name for site #" << QueryNodeName()); if (m_hScopeItem == NULL) { // // BUGBUG: // // This is probably related to MMC bug #324519 // where we're asked for the display info immediately // after adding the item to the console view. This // appears to fail only on refresh because the scope // item handle is missing, and we can't build a metabase // path yet. // TRACEEOLID("BUGBUG: Prematurely asked for display information"); //ASSERT(FALSE); return OLESTR(""); } CError err = RefreshData(); m_fResolved = err.Succeeded(); } if (m_strDisplayName.IsEmpty()) { CIPAddress ia(m_dwIPAddress); CInstanceProps::GetDisplayText( m_strDisplayName, m_bstrComment, m_bstrHostHeaderName, ia, m_sPort, m_dwID ); } CString buf = m_strDisplayName; if (m_dwState == MD_SERVER_STATE_STOPPED) { buf.Format(IDS_STOPPED_SITE_FMT, m_strDisplayName); } else if (m_dwState == MD_SERVER_STATE_PAUSED) { buf.Format(IDS_PAUSED_SITE_FMT, m_strDisplayName); } m_bstrDisplayNameStatus = buf; // return (LPTSTR)(LPCTSTR)m_strDisplayName; return m_bstrDisplayNameStatus; } /* virtual */ LPOLESTR CIISSite::GetResultPaneColInfo(int nCol) /*++ Routine Description: Return result pane string for the given column number Arguments: int nCol : Column number Return Value: String --*/ { ASSERT(_fStaticsLoaded); TCHAR sz[255]; switch(nCol) { case COL_DESCRIPTION: return QueryDisplayName(); case COL_STATE: switch(m_dwState) { case MD_SERVER_STATE_STARTED: return _bstrStarted; case MD_SERVER_STATE_PAUSED: return _bstrPaused; case MD_SERVER_STATE_STOPPED: return _bstrStopped; case MD_SERVER_STATE_STARTING: case MD_SERVER_STATE_PAUSING: case MD_SERVER_STATE_CONTINUING: case MD_SERVER_STATE_STOPPING: return _bstrPending; } return OLESTR(""); case COL_DOMAIN_NAME: return m_bstrHostHeaderName; case COL_IP_ADDRESS: { CIPAddress ia(m_dwIPAddress); if (ia.IsZeroValue()) { _bstrResult = _bstrAllUnassigned; } else { _bstrResult = ia; } } return _bstrResult; case COL_TCP_PORT: _bstrResult = ::_itot(m_sPort, sz, 10); return _bstrResult; case COL_STATUS: { AFX_MANAGE_STATE(::AfxGetStaticModuleState()); CError err(m_dwWin32Error); if (err.Succeeded()) { return OLESTR(""); } _bstrResult = err; } return _bstrResult; } ASSERT_MSG("Bad column number"); return OLESTR(""); } /* virtual */ int CIISSite::CompareResultPaneItem(CIISObject * pObject, int nCol) /*++ Routine Description: Compare two CIISObjects on sort item criteria Arguments: CIISObject * pObject : Object to compare against int nCol : Column number to sort on Return Value: 0 if the two objects are identical <0 if this object is less than pObject >0 if this object is greater than pObject --*/ { ASSERT_READ_PTR(pObject); if (nCol == 0) { return CompareScopeItem(pObject); } // // First criteria is object type // int n1 = QuerySortWeight(); int n2 = pObject->QuerySortWeight(); if (n1 != n2) { return n1 - n2; } // // Both are CIISSite objects // CIISSite * pSite = (CIISSite *)pObject; switch(nCol) { // // Special case columns // case COL_IP_ADDRESS: { CIPAddress ia1(m_dwIPAddress); CIPAddress ia2(pSite->QueryIPAddress()); return ia1.CompareItem(ia2); } case COL_TCP_PORT: n1 = QueryPort(); n2 = pSite->QueryPort(); return n1 - n2; case COL_STATUS: { DWORD dw1 = QueryWin32Error(); DWORD dw2 = pSite->QueryWin32Error(); return dw1 - dw2; } case COL_DESCRIPTION: case COL_STATE: case COL_DOMAIN_NAME: default: // // Lexical sort // return ::lstrcmpi( GetResultPaneColInfo(nCol), pObject->GetResultPaneColInfo(nCol) ); } } /* virtual */ HRESULT CIISSite::BuildURL(CComBSTR & bstrURL) const /*++ Routine Description: Recursively build up the URL from the current node and its parents. For a site node, add the machine name. Arguments: CComBSTR & bstrURL : Returns URL Return Value: HRESULT --*/ { HRESULT hr = S_OK; // // Prepend parent portion (protocol in this case) // CIISMBNode * pNode = GetParentNode(); if (pNode) { hr = pNode->BuildURL(bstrURL); } if (SUCCEEDED(hr)) { CString strOwner; /////////////////////////////////////////////////////////////////////////// // // Try to build an URL. Use in order of priority: // // Domain name:port/root // ip address:port/root // computer name:port/root // if (m_bstrHostHeaderName.Length()) { strOwner = m_bstrHostHeaderName; } else if (m_dwIPAddress != 0L) { CIPAddress ia(m_dwIPAddress); ia.QueryIPAddress(strOwner); } else { if (IsLocal()) { // // Security reasons restrict this to "localhost" oftentimes // strOwner = _bstrLocalHost; } else { LPOLESTR lpOwner = QueryMachineName(); strOwner = PURE_COMPUTER_NAME(lpOwner); } } TCHAR szPort[6]; // 65536 max. _itot(m_sPort, szPort, 10); strOwner += _T(":"); strOwner += szPort; bstrURL.Append(strOwner); } return hr; } /*virtual*/ HRESULT CIISSite::AddMenuItems( LPCONTEXTMENUCALLBACK piCallback, long * pInsertionAllowed, DATA_OBJECT_TYPES type ) { ASSERT_READ_PTR(piCallback); // // Add base menu items // HRESULT hr = CIISObject::AddMenuItems( piCallback, pInsertionAllowed, type ); if (SUCCEEDED(hr)) { ASSERT(pInsertionAllowed != NULL); if ((*pInsertionAllowed & CCM_INSERTIONALLOWED_NEW) != 0) { AddMenuSeparator(piCallback); if (IsFtpSite()) { if (GetOwner()->CanAddInstance() && !GetOwner()->Has10ConnectionsLimit()) { AddMenuItemByCommand(piCallback, IDM_NEW_FTP_SITE); } AddMenuItemByCommand(piCallback, IDM_NEW_FTP_VDIR); } else if (IsWebSite()) { if (GetOwner()->CanAddInstance() && !GetOwner()->Has10ConnectionsLimit()) { AddMenuItemByCommand(piCallback, IDM_NEW_WEB_SITE); } AddMenuItemByCommand(piCallback, IDM_NEW_WEB_VDIR); } } if (!m_fFrontPageWeb && (*pInsertionAllowed & CCM_INSERTIONALLOWED_TASK) != 0) { AddMenuSeparator(piCallback); AddMenuItemByCommand(piCallback, IDM_TASK_SECURITY_WIZARD); } } return hr; } HRESULT CIISSite::InsertNewInstance(DWORD inst) { return m_pService->InsertNewInstance(inst); } HRESULT CIISSite::InsertNewAlias(CString alias) { CError err; // Now we should insert and select this new site CIISDirectory * pAlias = new CIISDirectory(m_pOwner, m_pService, alias); if (pAlias != NULL) { // If item is not expanded we will get error and no effect if (!IsExpanded()) { SelectScopeItem(); IConsoleNameSpace2 * pConsole = (IConsoleNameSpace2 *)GetConsoleNameSpace(); pConsole->Expand(QueryScopeItem()); } err = pAlias->AddToScopePaneSorted(QueryScopeItem(), FALSE); if (err.Succeeded()) { VERIFY(SUCCEEDED(pAlias->SelectScopeItem())); } } else { err = ERROR_NOT_ENOUGH_MEMORY; } return err; } /* virtual */ HRESULT CIISSite::Command( long lCommandID, CSnapInObjectRootBase * pObj, DATA_OBJECT_TYPES type ) /*++ Routine Description: Handle command from context menu. Arguments: long lCommandID : Command ID CSnapInObjectRootBase * pObj : Base object DATA_OBJECT_TYPES type : Data object type Return Value: HRESULT --*/ { AFX_MANAGE_STATE(::AfxGetStaticModuleState()); HRESULT hr = S_OK; DWORD dwCommand = 0; DWORD inst; CString alias; switch (lCommandID) { case IDM_STOP: dwCommand = MD_SERVER_COMMAND_STOP; break; case IDM_START: dwCommand = m_dwState == MD_SERVER_STATE_PAUSED ? MD_SERVER_COMMAND_CONTINUE : MD_SERVER_COMMAND_START; break; case IDM_PAUSE: dwCommand = m_dwState == MD_SERVER_STATE_PAUSED ? MD_SERVER_COMMAND_CONTINUE : MD_SERVER_COMMAND_PAUSE; break; case IDM_NEW_FTP_SITE: if (SUCCEEDED(hr = AddFTPSite(pObj, type, &inst))) { hr = InsertNewInstance(inst); } break; case IDM_NEW_FTP_VDIR: if (SUCCEEDED(hr = CIISMBNode::AddFTPVDir(pObj, type, alias))) { hr = InsertNewAlias(alias); } break; case IDM_NEW_WEB_SITE: if (SUCCEEDED(hr = CIISMBNode::AddWebSite(pObj, type, &inst))) { hr = InsertNewInstance(inst); } break; case IDM_NEW_WEB_VDIR: if (SUCCEEDED(hr = CIISMBNode::AddWebVDir(pObj, type, alias))) { hr = InsertNewAlias(alias); } break; // // Pass on to base class // default: hr = CIISMBNode::Command(lCommandID, pObj, type); } if (dwCommand) { hr = ChangeState(dwCommand); } return hr; } /* virtual */ HRESULT CIISSite::CreatePropertyPages( IN LPPROPERTYSHEETCALLBACK lpProvider, IN LONG_PTR handle, IN IUnknown * pUnk, IN DATA_OBJECT_TYPES type ) /*++ Routine Description: Create the property pages for the given object Arguments: LPPROPERTYSHEETCALLBACK lpProvider : Provider LONG_PTR handle : Handle. IUnknown * pUnk, DATA_OBJECT_TYPES type Return Value: HRESULT --*/ { AFX_MANAGE_STATE(::AfxGetStaticModuleState()); CComBSTR bstrPath; // // CODEWORK: What to do with m_err? This might be // a bad machine object in the first place. Aborting // when the machine object has an error code isn't // such a bad solution here. // /* if (m_err.Failed()) { m_err.MessageBox(); return m_err; } */ CError err(BuildMetaPath(bstrPath)); if (err.Succeeded()) { err = ShowPropertiesDlg( lpProvider, QueryAuthInfo(), bstrPath, GetMainWindow(), (LPARAM)this, handle ); } err.MessageBoxOnFailure(); return err; } HRESULT CIISSite::ChangeState(DWORD dwCommand) /*++ Routine Description: Change the state of this instance (started/stopped/paused) Arguments: DWORD dwCommand : MD_SERVER_COMMAND_START, etc. Return Value: HRESULT --*/ { CError err; CComBSTR bstrPath; AFX_MANAGE_STATE(::AfxGetStaticModuleState()); do { CWaitCursor wait; err = BuildMetaPath(bstrPath); // We need instance key here CString path_inst; CMetabasePath::GetInstancePath(bstrPath, path_inst); BREAK_ON_ERR_FAILURE(err) CInstanceProps ip(QueryAuthInfo(), path_inst); err = ip.LoadData(); BREAK_ON_ERR_FAILURE(err) err = ip.ChangeState(dwCommand); BREAK_ON_ERR_FAILURE(err) err = RefreshData(); if (err.Succeeded()) { err = RefreshDisplay(); } } while(FALSE); err.MessageBoxOnFailure(); return err; } /* virtual */ HRESULT CIISSite::EnumerateScopePane(HSCOPEITEM hParent) /*++ Routine Description: Enumerate scope child items. Arguments: HSCOPEITEM hParent : Parent console handle Return Value: HRESULT --*/ { CError err = EnumerateVDirs(hParent, m_pService); if (err.Succeeded() && !IsFtpSite() && m_strRedirectPath.IsEmpty()) { if (m_dwWin32Error == ERROR_SUCCESS) { err = EnumerateWebDirs(hParent, m_pService); } } if (err.Failed()) { m_dwWin32Error = err.Win32Error(); RefreshDisplay(); } return err; } /*virtual*/ HRESULT CIISSite::EnumerateResultPane(BOOL fExp, IHeaderCtrl * pHdr, IResultData * pResData) { CError err = CIISObject::EnumerateResultPane(fExp, pHdr, pResData); if ( err.Succeeded() && QueryWin32Error() == ERROR_SUCCESS && !IsFtpSite() && m_strRedirectPath.IsEmpty() ) { err = CIISMBNode::EnumerateResultPane_(fExp, pHdr, pResData, m_pService); if (err.Failed()) { m_dwWin32Error = err.Win32Error(); } } return err; } /* virtual */ HRESULT CIISSite::BuildMetaPath(CComBSTR & bstrPath) const /*++ Routine Description: Recursively build up the metabase path from the current node and its parents Arguments: CComBSTR & bstrPath : Returns metabase path Return Value: HRESULT Notes: This will return the home directory path, e.g. "lm/w3svc/2/root", not the path of the instance. --*/ { // // Build instance path // HRESULT hr = CIISMBNode::BuildMetaPath(bstrPath); if (SUCCEEDED(hr)) { // // Add root directory path // bstrPath.Append(_cszSeparator); bstrPath.Append(g_cszRoot); } return hr; } // CODEWORK: make it work from CIISMBNode::DeleteNode HRESULT CIISSite::DeleteNode(IResultData * pResult) { CError err; if (!NoYesMessageBox(IDS_CONFIRM_DELETE)) return err; do { CComBSTR path; CMetaInterface * pInterface = QueryInterface(); ASSERT(pInterface != NULL); err = CIISMBNode::BuildMetaPath(path); if (err.Failed()) break; CMetaKey mk(pInterface, METADATA_MASTER_ROOT_HANDLE, METADATA_PERMISSION_WRITE); if (!mk.Succeeded()) break; err = mk.DeleteKey(path); if (err.Failed()) break; err = RemoveScopeItem(); } while (FALSE); if (err.Failed()) { DisplayError(err); } return err; } // // We are not supporting empty comments on sites. Even if it is OK for // metabase, it will bring more problems in UI. Empty name will be displayed // as [Site #N] in UI, and when user will try to rename it again, it could be // stored in metabase in this format. // HRESULT CIISSite::RenameItem(LPOLESTR new_name) { CComBSTR path; CError err; if (new_name != NULL && lstrlen(new_name) > 0) { err = BuildMetaPath(path); if (err.Succeeded()) { // We need instance key here CString path_inst; CMetabasePath::GetInstancePath(path, path_inst); CMetaKey mk(QueryInterface(), path_inst, METADATA_PERMISSION_WRITE); err = mk.QueryResult(); if (err.Succeeded()) { err = mk.SetValue(MD_SERVER_COMMENT, CString(new_name)); if (err.Succeeded()) { m_strDisplayName = new_name; } } } } return err; }