/*++ Copyright (c) 1994-2001 Microsoft Corporation Module Name : usersess.cpp Abstract: FTP User Sessions Dialog Author: Ronald Meijer (ronaldm) Sergei Antonov (sergeia) Project: Internet Services Manager Revision History: --*/ // // Include Files // #include "stdafx.h" #include "resource.h" #include "common.h" #include "inetprop.h" #include "ftpsht.h" #include "fservic.h" #include "usersess.h" #include HRESULT ImpersonateUser(LPCTSTR pszUserName, LPCTSTR pszDomain, LPCTSTR pszPassword, HANDLE *pCurImpToken, HANDLE *pLoggedOnUserToken); HRESULT UnImpersonateUser(HANDLE hSavedImpToken, HANDLE hLoggedOnUser); #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif // // Registry key name for this dialog // const TCHAR g_szRegKey[] = _T("User Sessions"); // // User Sessions Listbox Column Definitions // static const ODL_COLUMN_DEF_EX BASED_CODE g_aColumns[] = { // ================================================================================================== // Weight Label Sort Helper Function // ================================================================================================== { 2, IDS_CONNECTED_USERS, (CObjectPlus::PCOBJPLUS_ORDER_FUNC)&CFtpUserInfo::OrderByName }, { 1, IDS_FROM, (CObjectPlus::PCOBJPLUS_ORDER_FUNC)&CFtpUserInfo::OrderByHostAddress }, { 1, IDS_TIME, (CObjectPlus::PCOBJPLUS_ORDER_FUNC)&CFtpUserInfo::OrderByTime }, }; #define NUM_COLUMNS (sizeof(g_aColumns) / sizeof(g_aColumns[0])) CFtpUserInfo::CFtpUserInfo(LPIIS_USER_INFO_1 lpUserInfo) /*++ Routine Description: Constructor Arguments: LPIIS_USER_INFO_1 lpUserInfo : User info structure Return Value: N/A --*/ : m_idUser(lpUserInfo->idUser), m_strUser(lpUserInfo->pszUser), m_fAnonymous(lpUserInfo->fAnonymous), // Network Byte Order // || // \/ m_iaHost(lpUserInfo->inetHost, TRUE), m_tConnect(lpUserInfo->tConnect) { } int CFtpUserInfo::OrderByName( const CObjectPlus * pobFtpUser ) const /*++ Routine Description: Sorting helper function to sort by user name. The CObjectPlus pointer really refers to another CFtpUserInfo object Arguments: LPIIS_USER_INFO_1 lpUserInfo : User info structure Return Value: Sort return code (-1, 0, +1) --*/ { const CFtpUserInfo * pob = (CFtpUserInfo *)pobFtpUser; ASSERT(pob != NULL); return ::lstrcmpi(QueryUserName(), pob->QueryUserName()); } int CFtpUserInfo::OrderByTime( const CObjectPlus * pobFtpUser ) const /*++ Routine Description: Sorting helper function to sort by user connect time. The CObjectPlus pointer really refers to another CFtpUserInfo object Arguments: LPIIS_USER_INFO_1 lpUserInfo : User info structure Return Value: Sort return code (-1, 0, +1) --*/ { const CFtpUserInfo * pob = (CFtpUserInfo *)pobFtpUser; ASSERT(pob != NULL); return QueryConnectTime() > pob->QueryConnectTime() ? +1 : QueryConnectTime() == pob->QueryConnectTime() ? 0 : -1; } int CFtpUserInfo::OrderByHostAddress( const CObjectPlus * pobFtpUser ) const /*++ Routine Description: Sorting helper function to sort by host address. The CObjectPlus pointer really refers to another CFtpUserInfo object Arguments: LPIIS_USER_INFO_1 lpUserInfo : User info structure Return Value: Sort return code (-1, 0, +1) --*/ { const CFtpUserInfo * pob = (CFtpUserInfo *)pobFtpUser; ASSERT(pob != NULL); return QueryHostAddress().CompareItem(pob->QueryHostAddress()); } IMPLEMENT_DYNAMIC(CFtpUsersListBox, CHeaderListBox); // // User listbox bitmaps // enum { BMP_USER = 0, BMP_ANONYMOUS, // // Don't move this one // BMP_TOTAL }; const int CFtpUsersListBox::nBitmaps = BMP_TOTAL; CFtpUsersListBox::CFtpUsersListBox() : m_strTimeSep(_T(":")), CHeaderListBox(HLS_DEFAULT, g_szRegKey) { // // Get intl time seperator // VERIFY(::GetLocaleInfo( ::GetUserDefaultLCID(), LOCALE_STIME, m_strTimeSep.GetBuffer(10), 10)); } void CFtpUsersListBox::DrawItemEx( IN CRMCListBoxDrawStruct & ds ) /*++ Routine Description: Draw item. This is called from the CRMCListBox base class Arguments: CRMCListBoxDrawStruct & ds : Drawing structure Return Value: None --*/ { CFtpUserInfo * pFTPUser = (CFtpUserInfo *)ds.m_ItemData; ASSERT(pFTPUser != NULL); // // Display a user bitmap // DrawBitmap(ds, 0, pFTPUser->QueryAnonymous() ? BMP_ANONYMOUS : BMP_USER); ColumnText(ds, 0, TRUE, pFTPUser->QueryUserName()); ColumnText(ds, 1, FALSE, pFTPUser->QueryHostAddress()); DWORD dwTime = pFTPUser->QueryConnectTime(); DWORD dwHours = dwTime / (60L * 60L); DWORD dwMinutes = (dwTime / 60L) % 60L; DWORD dwSeconds = dwTime % 60L; CString strTime; strTime.Format( _T("%d%s%02d%s%02d"), dwHours, (LPCTSTR)m_strTimeSep, dwMinutes, (LPCTSTR)m_strTimeSep, dwSeconds); ColumnText(ds, 2, FALSE, strTime); } /* virtual */ BOOL CFtpUsersListBox::Initialize() /*++ Routine Description: Initialize the listbox. Insert the columns as requested, and lay them out appropriately Arguments: None Return Value: TRUE for succesful initialisation, FALSE otherwise --*/ { if (!CHeaderListBox::Initialize()) { return FALSE; } // // Build all columns // HINSTANCE hInst = AfxGetResourceHandle(); for (int nCol = 0; nCol < NUM_COLUMNS; ++nCol) { InsertColumn( nCol, g_aColumns[nCol].cd.nWeight, g_aColumns[nCol].cd.nLabelID, hInst ); } // // Try to set the widths from the stored registry value, // otherwise distribute according to column weights specified // // if (!SetWidthsFromReg()) // { DistributeColumns(); // } return TRUE; } CUserSessionsDlg::CUserSessionsDlg( LPCTSTR lpstrServerName, DWORD dwInstance, LPCTSTR pAdminName, LPCTSTR pAdminPassword, CWnd * pParent, BOOL fLocal ) /*++ Routine Description: Constructor for FTP user sessions dialog Arguments: LPCTSTR lpstrServerName : Server name to connect to CWnd * pParent : Pointer to parent window Return Value: N/A --*/ : m_list_Users(), m_ListBoxRes(IDB_USERS, m_list_Users.nBitmaps), m_oblFtpUsers(), m_strServerName(lpstrServerName), m_strAdminName(pAdminName), m_strAdminPassword(pAdminPassword), m_nSortColumn(0), m_dwInstance(dwInstance), m_hImpToken(INVALID_HANDLE_VALUE), m_hLogToken(INVALID_HANDLE_VALUE), m_NetUseSessionCreated(FALSE), m_fLocal(fLocal), CDialog(CUserSessionsDlg::IDD, pParent) { //{{AFX_DATA_INIT(CUserSessionsDlg) //}}AFX_DATA_INIT m_list_Users.AttachResources(&m_ListBoxRes); VERIFY(m_strTotalConnected.LoadString(IDS_USERS_TOTAL)); } void CUserSessionsDlg::DoDataExchange(CDataExchange * pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CUserSessionsDlg) DDX_Control(pDX, IDC_STATIC_NUM_CONNECTED, m_static_Total); DDX_Control(pDX, IDC_BUTTON_DISCONNECT_ALL, m_button_DisconnectAll); DDX_Control(pDX, IDC_BUTTON_DISCONNECT, m_button_Disconnect); //}}AFX_DATA_MAP DDX_Control(pDX, IDC_LIST_USERS, m_list_Users); } // // Message Map // BEGIN_MESSAGE_MAP(CUserSessionsDlg, CDialog) //{{AFX_MSG_MAP(CUserSessionsDlg) ON_BN_CLICKED(IDC_BUTTON_DISCONNECT, OnButtonDisconnect) ON_BN_CLICKED(IDC_BUTTON_DISCONNECT_ALL, OnButtonDisconnectAll) ON_BN_CLICKED(IDC_BUTTON_REFRESH, OnButtonRefresh) ON_LBN_SELCHANGE(IDC_LIST_USERS, OnSelchangeListUsers) //}}AFX_MSG_MAP ON_NOTIFY_RANGE(HDN_ITEMCLICK, 0, 0xFFFF, OnHeaderItemClick) END_MESSAGE_MAP() DWORD CUserSessionsDlg::SortUsersList() /*++ Routine Description: Sort the list of ftp users on the current sorting key Arguments: None Return Value: ERROR return code --*/ { ASSERT(m_nSortColumn >= 0 && m_nSortColumn < NUM_COLUMNS); BeginWaitCursor(); DWORD err = m_oblFtpUsers.Sort( (CObjectPlus::PCOBJPLUS_ORDER_FUNC)g_aColumns[m_nSortColumn].pSortFn); EndWaitCursor(); return err; } HRESULT CUserSessionsDlg::ConnectToComputer() { CError err; DWORD rc; BOOL already_connected = FALSE; CString server, user, password; server = m_strServerName; // check if lpMachineName starts with \\ // if it doesn't then make sure it does, or it's null (for local machine) server = _T("\\\\"); server += PURE_COMPUTER_NAME((LPCTSTR) m_strServerName); user = m_strAdminName; password = m_strAdminPassword; // // As it turned out in some cases we cannot get access to file system // even if we are connected to metabase. We will add connection in this // case also. // if (!m_fLocal) { BOOL bEmptyPassword = FALSE; // Add use for this resource NETRESOURCE nr; nr.dwType = RESOURCETYPE_DISK; nr.lpLocalName = NULL; nr.lpRemoteName = (LPTSTR)(LPCTSTR)server; nr.lpProvider = NULL; // We need to close connections that we may have to this resource rc = WNetCancelConnection2((LPTSTR)(LPCTSTR)server, 0, TRUE); if (rc == ERROR_OPEN_FILES) { already_connected = TRUE; } // Empty strings below mean no password, which is wrong. NULLs mean // default user and default password -- this could work better for local case. LPCTSTR p1 = password, p2 = user; // In case when password is really was set empty, passing NULL will fail. if (password.IsEmpty() && !bEmptyPassword) { p1 = NULL; } if (user.IsEmpty()) { p2 = NULL; } if (!already_connected) { rc = WNetAddConnection2(&nr, p1, p2, 0); if (NO_ERROR != rc) { err = rc; return err; } m_NetUseSessionCreated = TRUE; } } return err; } HRESULT CUserSessionsDlg::BuildUserList() /*++ Routine Description: Call the FtpEnum api and build the list of currently connected users. Arguments: None Return Value: ERROR return code --*/ { CError err; LPIIS_USER_INFO_1 lpUserInfo = NULL; DWORD dwCount = 0L; m_oblFtpUsers.RemoveAll(); BeginWaitCursor(); err = ::IISEnumerateUsers( (LPTSTR)(LPCTSTR)m_strServerName, 1, INET_FTP_SVC_ID, m_dwInstance, &dwCount, (LPBYTE *)&lpUserInfo ); EndWaitCursor(); TRACEEOLID("IISEnumerateUsers returned " << err); if (err.Failed()) { // try to net use to the machine if this failed... if (ERROR_ACCESS_DENIED == err.Win32Error()) { err = ConnectToComputer(); // try again. BeginWaitCursor(); err = ::IISEnumerateUsers( (LPTSTR)(LPCTSTR)m_strServerName, 1, INET_FTP_SVC_ID, m_dwInstance, &dwCount, (LPBYTE *)&lpUserInfo ); EndWaitCursor(); if (err.Failed()) { return err; } } else { return err; } } try { for (DWORD i = 0; i < dwCount; ++i) { m_oblFtpUsers.AddTail(new CFtpUserInfo(lpUserInfo++)); } } catch(CMemoryException * e) { err = ERROR_NOT_ENOUGH_MEMORY; e->Delete(); } SortUsersList(); return err; } HRESULT CUserSessionsDlg::DisconnectUser(CFtpUserInfo * pUserInfo) /*++ Routine Description: Disconnect a single user Arguments: CFtpUserInfo * pUserInfo : User to disconnect Return Value: ERROR return code --*/ { CError err(::IISDisconnectUser( (LPTSTR)(LPCTSTR)m_strServerName, INET_FTP_SVC_ID, m_dwInstance, pUserInfo->QueryUserID() )); if (err.Win32Error() == NERR_UserNotFound) { // // As long as he's gone now, that's alright // err.Reset(); } return err; } void CUserSessionsDlg::UpdateTotalCount() /*++ Routine Description: Update the count of total users Arguments: None Return Value: None --*/ { CString str; str.Format(m_strTotalConnected, m_oblFtpUsers.GetCount() ); m_static_Total.SetWindowText(str); } void CUserSessionsDlg::FillListBox(CFtpUserInfo * pSelection) /*++ Routine Description: Show the users in the listbox Arguments: CFtpUserInfo * pSelection : Item to be selected or NULL Return Value: None --*/ { CObListIter obli(m_oblFtpUsers); const CFtpUserInfo * pUserEntry = NULL; m_list_Users.SetRedraw(FALSE); m_list_Users.ResetContent(); int cItems = 0; for ( /**/ ; pUserEntry = (CFtpUserInfo *)obli.Next(); ++cItems) { m_list_Users.AddItem(pUserEntry); } if (pSelection) { // // Select the desired entry // m_list_Users.SelectItem(pSelection); } m_list_Users.SetRedraw(TRUE); // // Update the count text on the dialog // UpdateTotalCount(); } HRESULT CUserSessionsDlg::RefreshUsersList() /*++ Routine Description: Rebuild the user list Arguments: None Return Value: Error return code --*/ { CError err; // // Add some friendly error message overrides // err.AddOverride(EPT_S_NOT_REGISTERED, IDS_ERR_RPC_NA); err.AddOverride(RPC_S_SERVER_UNAVAILABLE, IDS_FTP_SERVICE_NOT_STARTED); err.AddOverride(RPC_S_UNKNOWN_IF, IDS_FTP_SERVICE_NOT_STARTED); err.AddOverride(RPC_S_PROCNUM_OUT_OF_RANGE, IDS_ERR_INTERFACE); err = BuildUserList(); if (!err.MessageBoxOnFailure(m_hWnd)) { FillListBox(); SetControlStates(); } return err; } void CUserSessionsDlg::SetControlStates() /*++ Routine Description: Set the connect/disconnect buttons depending on the selection state in the listbox. Arguments: None Return Value: None --*/ { m_button_Disconnect.EnableWindow(m_list_Users.GetSelCount() > 0); m_button_DisconnectAll.EnableWindow(m_list_Users.GetCount() > 0); } // // Message Handlers // // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< void CUserSessionsDlg::OnButtonDisconnect() /*++ Routine Description: 'Disconnect User' button has been pressed. Disconnect the currently selected user. Arguments: None Return Value: None --*/ { // // Ask for confirmation // if (!NoYesMessageBox(IDS_CONFIRM_DISCONNECT_USER)) { // // Changed his mind // return; } CError err; m_list_Users.SetRedraw(FALSE); CWaitCursor wait; CFtpUserInfo * pUserEntry; int nSel = 0; BOOL fProblems = FALSE; while((pUserEntry = GetNextSelectedItem(&nSel)) != NULL) { err = DisconnectUser(pUserEntry); if (err.Failed()) { ++fProblems; if (err.MessageBoxFormat( m_hWnd, IDS_DISCONNECT_ERR, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2, NO_HELP_CONTEXT, (LPCTSTR)pUserEntry->QueryUserName() ) == IDYES) { // // Continue trying to delete // ++nSel; continue; } else { break; } } m_oblFtpUsers.RemoveIndex(nSel); m_list_Users.DeleteString(nSel); // // Don't advance counter to account for offset // } m_list_Users.SetRedraw(TRUE); UpdateTotalCount(); SetControlStates(); if (!fProblems) { // // Ensure button not disabled // GetDlgItem(IDC_BUTTON_REFRESH)->SetFocus(); } } void CUserSessionsDlg::OnButtonDisconnectAll() /*++ Routine Description: 'Disconnect All Users' button has been pressed. Disconnect all users Arguments: None Return Value: None --*/ { // // Ask for confirmation // if (!NoYesMessageBox(IDS_CONFIRM_DISCONNECT_ALL)) { // // Changed his mind // return; } CObListIter obli(m_oblFtpUsers); CFtpUserInfo * pUserEntry; m_list_Users.SetRedraw(FALSE); CWaitCursor wait; int cItems = 0; CError err; int nSel = 0; BOOL fProblems = FALSE; for ( /**/; pUserEntry = (CFtpUserInfo *)obli.Next(); ++cItems) { err = DisconnectUser(pUserEntry); if (err.Failed()) { ++fProblems; if (err.MessageBoxFormat( m_hWnd, IDS_DISCONNECT_ERR, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2, NO_HELP_CONTEXT, (LPCTSTR)pUserEntry->QueryUserName() ) == IDYES) { // // Continue trying to delete // ++nSel; continue; } else { break; } } m_oblFtpUsers.RemoveIndex(nSel); m_list_Users.DeleteString(nSel); } m_list_Users.SetRedraw(TRUE); UpdateTotalCount(); SetControlStates(); if (!fProblems) { // // Ensure button not disabled // GetDlgItem(IDC_BUTTON_REFRESH)->SetFocus(); } } void CUserSessionsDlg::OnButtonRefresh() /*++ Routine Description: 'Refresh' Button has been pressed. Refresh the user list Arguments: None Return Value: None --*/ { RefreshUsersList(); } void CUserSessionsDlg::OnSelchangeListUsers() /*++ Routine Description: Respond to a change in selection in the user listbox Arguments: None Return Value: None --*/ { SetControlStates(); } void CUserSessionsDlg::OnHeaderItemClick( IN UINT nID, IN NMHDR * pNMHDR, OUT LRESULT * plResult ) /*++ Routine Description: Header item has been clicked in the listbox. Change the sort key as appropriate. Arguments: None Return Value: None --*/ { HD_NOTIFY * pNotify = (HD_NOTIFY *)pNMHDR; TRACEEOLID("Header Button clicked."); // // Can't press a button out of range, surely... // ASSERT(pNotify->iItem < m_list_Users.QueryNumColumns()); int nOldSortColumn = m_nSortColumn; m_nSortColumn = pNotify->iItem; if(m_nSortColumn != nOldSortColumn) { // // Rebuild the list // SortUsersList(); CFtpUserInfo * pSelector = GetSelectedListItem(); FillListBox(pSelector); SetControlStates(); } // // Message Fully Handled // *plResult = 0; } BOOL CUserSessionsDlg::OnInitDialog() /*++ Routine Description: WM_INITDIALOG handler. Initialize the dialog. Arguments: None. Return Value: TRUE if no focus is to be set automatically, FALSE if the focus is already set. --*/ { CDialog::OnInitDialog(); if (!m_strAdminName.IsEmpty()) { CError err = ImpersonateUser(m_strAdminName, _T(""), m_strAdminPassword, &m_hImpToken, &m_hLogToken); } m_list_Users.Initialize(); if (RefreshUsersList() != ERROR_SUCCESS) { EndDialog(IDCANCEL); return FALSE; } return TRUE; } void CUserSessionsDlg::OnDestroy() { if (m_hImpToken != INVALID_HANDLE_VALUE || m_hLogToken != INVALID_HANDLE_VALUE) { UnImpersonateUser(m_hImpToken, m_hLogToken); } } CUserSessionsDlg::~CUserSessionsDlg() { if (m_NetUseSessionCreated) { CString server; server = m_strServerName; // check if lpMachineName starts with \\ // if it doesn't then make sure it does, or it's null (for local machine) server = _T("\\\\"); server += PURE_COMPUTER_NAME((LPCTSTR) m_strServerName); WNetCancelConnection2((LPTSTR)(LPCTSTR)server, 0, TRUE); m_NetUseSessionCreated = FALSE; } } HRESULT ImpersonateUser(LPCTSTR pszUserName, LPCTSTR pszDomain, LPCTSTR pszPassword, HANDLE *pCurImpToken, HANDLE *pLoggedOnUserToken) { HRESULT hr = S_OK; ASSERT(pCurImpToken); ASSERT(pLoggedOnUserToken); *pCurImpToken = INVALID_HANDLE_VALUE; *pLoggedOnUserToken = INVALID_HANDLE_VALUE; // logon the user. This creates an primary impersonation // token. If this fails, an error will be returned. if (!LogonUser((LPTSTR)pszUserName, (LPTSTR)pszDomain, (LPTSTR)pszPassword, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, pLoggedOnUserToken)) { hr = HRESULT_FROM_WIN32(GetLastError()); } // get the current impersonation token. If an error occurs, // that is OK. This just means that no impersonation on the // thread is occurring, so there is no need to do the RevertToSelf. else if (OpenThreadToken( GetCurrentThread(), TOKEN_READ | TOKEN_IMPERSONATE, TRUE, pCurImpToken)) { RevertToSelf(); } // if everything's been successful so far, than call // ImpersonateLoggedOnUser with the token created above. if (SUCCEEDED(hr)) { if (!ImpersonateLoggedOnUser(*pLoggedOnUserToken)) { hr = HRESULT_FROM_WIN32(GetLastError()); } } // if there were failures, clean up any tokens that we created // or hold. if (FAILED(hr)) { // cleanup the LogonUser token if (*pLoggedOnUserToken != INVALID_HANDLE_VALUE) { CloseHandle(*pLoggedOnUserToken); *pLoggedOnUserToken = INVALID_HANDLE_VALUE; } // cleanup the token from the OpenThreadToken call if (*pCurImpToken != INVALID_HANDLE_VALUE) { HANDLE hThread = GetCurrentThread(); SetThreadToken(&hThread, *pCurImpToken); CloseHandle(*pCurImpToken); *pCurImpToken = INVALID_HANDLE_VALUE; } } return hr; } HRESULT UnImpersonateUser(HANDLE hSavedImpToken, HANDLE hLoggedOnUser) { // if there is an hSavedImpToken, then call SetThreadToken to // restore it. if (hSavedImpToken != INVALID_HANDLE_VALUE) { HANDLE hThread = GetCurrentThread(); SetThreadToken(&hThread, hSavedImpToken); CloseHandle(hSavedImpToken); } // cleanup the LogonUser token if (hLoggedOnUser != INVALID_HANDLE_VALUE) { CloseHandle(hLoggedOnUser); } return S_OK; }