Logo
blank Skip to main content

Monitoring of logon/logout in terminal and client sessions

C++

This article includes a description of the monitoring process for the session logon/logout, connect/disconnect (regarding terminal and client sessions). Obtained results will be then used in the next article โ€Creating process in the sessionโ€.

Introduction

This article deals with the session logon/logout notifications. For this purposes WtsApi functions are used in the code. Project, which is attached to this article, is a simple service implemented for demonstration that writes all changes of session state to the log file. It is recommended to set service to start automatically on system start so it would be able to register the first session logon. It is recommended also to set the service dependence of TermServ service, so the service will definitely get the first notification from WTS, otherwise if the function of WtsApi is called before the dependent services of Remote Desktop Services have started, the RPC_S_INVALID_BINDING error code may be returned.

This article has a connected article about creating process in logged session.

Read also: OS X Reverse Engineering

Sessions overview

We start with the brief overview of sessions. Every interactive user is associated with a session object. Sessions are identified by the session ID (SID). Sessions can be either local or remote. The local session is associated with the interactive user physically logged on to the machine. Service processes are also assigned to the local session. This session is always assigned the ID of 0, and is also referred to as the console. Here, I mean the first physical session, so if you use Fast User Switching on Windows XP, the session ID will be incremented for each new session. This is correct for the Windows operating systems until Windows Vista.  For these Windows versions, a console session can be any session with non-zero ID. Shortly, it was done to isolate services from interactive applications and prevent services from being hacked by end users. Anyway, we wonโ€™t go into detail of this theme. I just want to mention one nuance concerning the sessions running in the Terminal Services environment โ€“ there can be more than one interactive user in such session.

There are at least two ways to retrieve logon/logout event notification: System Event Notification Service (SENS) and the Windows Terminal Service (WTS). The connectivity functions and notifications of SENS are useful for applications written for mobile computers or computers connected to high latency local area networks, as it is written in msdn. We wonโ€™t stay for long on SENS description, because in this article we will use WTS API.

The Remote Desktop Services API (formerly known as Terminal Services) enables you to enumerate and manage Remote Desktop Session Host (RD Session Host) servers, client sessions, and processes; retrieve information about sessions and processes.

Sessions enumerating. WtsApi usage

For retrieving running session list and information about these sessions, the CSessionOwner class is implemented:

C++
class CSessionOwner
{
public:
    ~CSessionOwner();
    static void GetSessions(std::vector<CSessionOwner> & vecSessions);
    static void GetActiveSessions(std::set<CSessionOwner> & setSessions);
            bool operator < (const CSessionOwner & other) const
    {
        return m_sid < other.GetSid();
    }
    
    int GetSid() const;
    bool IsActive() const;
    double GetLogonTime() const;
    std::wstring GetSessionUserName() const;
    std::wstring GetDomainName() const;
    std::wstring GetFullUserName() const;
private:
    CSessionOwner(const int& sid);
    
    int m_sid;
};

The GetSessions function encapsulates use of the WTSEnumerateSessions function to obtain the list of running sessions in the system.

C++
void CSessionOwner::GetSessions(std::vector<CSessionOwner> & vecSessions)
{
    PWTS_SESSION_INFO pSessionInfo(NULL);
    DWORD count(0);
    if(!WTSEnumerateSessions( WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &count))
    {
        DWORD err = GetLastError();
        if (err == RPC_S_INVALID_BINDING)
            return;
        else
        {
            throw std::runtime_error("GetSessions failed");
        }
    }
    wts_resource<WTS_SESSION_INFO> wtsSessionInfo(pSessionInfo);
    for (DWORD i = 0; i < count; ++i)
    {
        int sid = wtsSessionInfo.get()[i].SessionId;
        vecSessions.push_back(CSessionOwner(sid));
    }
}

The wts_resource class is used in the method code. It is my simple class-guard for work with WTS resource, which has a pointer to a resource as a class member and calls the WTSFreeMemory function in its destructor. It has to be called because the WTSEnumerateSessions function allocates memory for the buffer and the calling side has to free the returned buffer.

C++
template<class ResourceType>
class wts_resource
{
    ResourceType * m_pResource;
public:
    wts_resource()
        : m_pResource(0)
    {
    }

    wts_resource(ResourceType * pResource)
        : m_pResource( pResource )
    {
    }

    ~wts_resource()
    {
        if (m_pResource)
            WTSFreeMemory(m_pResource);
    }

    void reset(ResourceType * pResource = 0);
    const ResourceType * get() const { return m_pResource; }
    ResourceType * get() { return m_pResource; }

    ResourceType * release();
    // other methods

private:
    wts_resource(wts_resource & otherResource);
};

As it was described above, in case of the WTSEnumerateSessions function failure, RPC_S_INVALID_BINDING error code may be the indicator of the situation when one of the dependent TermServ service is not started, so it is not an exceptional situation, and the best we can do is to add a log entry in this case.

Other CSessionOwner class methods are used to get information about the session, such as session ID, user name, connection state of session, etc. by calling WTSQuerySessionInformation with required flag specifying the type of information to retrieve.

Important information of session is its connection state, that is returned by the IsActive() function:

C++
bool CSessionOwner::IsActive() const
{
    DWORD bytesReturned(0);
    LPTSTR pBuf(NULL);
    if(!WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, 
		m_sid, WTSConnectState, &pBuf, &bytesReturned))
    {
        // this is not exceptional situation, the session may be closed at the moment, 
        // or close operation is pending on the session
        return false;
    }
    wts_resource<wchar_t> wtsPBuf(pBuf);
    int connectState = *(reinterpret_cast<int*> (wtsPBuf.get()));
    return (connectState == WTSActive);
}

All possible connection states of session is described in the _WTS_CONNECTSTATE_CLASS enum. An active session, no matter local or remote one, has connection state WTSActive. When remote desktop connection is enabled on the server, one more session appears immediately besides the current local session with the WTSListen state. Remote sessions that are not logged off but only closed, and the local session that is switched will have the WTSDisconnected connection state. There are some states that can be interesting for process management in the session. Now, when we can get the list of active sessions, letโ€™s describe how to monitor changes in it.

Monitoring of the user logon/logout, session connect/disconnect.

I use the WTSWaitSystemEvent function in the sample project to wait for an event, such as the creation of a client session or a user logging on to the RD session host server.

Letโ€™s take a look at the following class for work with WtsApi:

C++
class CSessionManager
{
    typedef std::set<Common::CSessionOwner> SessionsSet;
    SessionsSet m_SessionsPool;

public:
    void operator () ();

    void StopSessionManager()
    {
        DWORD dwEventsFlag(0);
        WTSWaitSystemEvent(WTS_CURRENT_SERVER_HANDLE,
                WTS_EVENT_FLUSH, &dwEventsFlag);
    }

    void GotSessionsSetGhanges(SessionsSet & addedSessions,
                SessionsSet & lostSessions);
    void UpdateSessionsState();
};

Functional code for sessions state monitoring is implemented in operator (). It is an operator to use it with boost::thread instead of a function. The following code has to run in the separate thread to get the notification about the changes of the session state. Here is sipmle illustration of the work with WTSWaitSystemEvent function:

C++
void CSessionManager::operator () ()
{
	Common::CSessionOwner::GetActiveSessions(m_SessionsPool);
	for(SessionsSet::iterator it = m_SessionsPool.begin(); 
		it != m_SessionsPool.end(); ++it)
	{
		// Write the list of the current active sessions to the log
	}

    for(;;)
    {
        DWORD dwEventsFlag(0);
        if(WTSWaitSystemEvent(WTS_CURRENT_SERVER_HANDLE, 
                              WTS_EVENT_LOGOFF | WTS_EVENT_LOGON | WTS_EVENT_CONNECT | 
			   WTS_EVENT_DISCONNECT
                               , &dwEventsFlag))
        {
            if( (WTS_EVENT_LOGON & dwEventsFlag) == WTS_EVENT_LOGON || 
		(WTS_EVENT_LOGOFF & dwEventsFlag) == WTS_EVENT_LOGOFF ||
                (WTS_EVENT_CONNECT & dwEventsFlag) == WTS_EVENT_CONNECT || 
		(WTS_EVENT_DISCONNECT & dwEventsFlag) == WTS_EVENT_DISCONNECT)
            {
                try
                {
                    UpdateSessionsState();
                }
                catch(const std::exception & ex)
                {
                    // write to the log 
                }
            }
            if (dwEventsFlag == WTS_EVENT_NONE)
            {
                return; // exit
            }
        }
        else
        {
            if(GetLastError() == ERROR_OPERATION_ABORTED)
            {
                // write to the log 
                return;
            }
            else
            {
                Sleep(100);
            }
        }
    }
} 

There is a set of events to be processed: session logon/logoff, connect/disconnect. Obviously, you can set the flags to wait for any event you want, but in this example we suppose that we want to get notifications only about these four events.

Here Iโ€™ll wander a bit from the main point and say a few words about boost threads. Boost enables to use multiple threads of execution with shared data in portable C++ code. It provides the boost::thread class for managing the threads, which is very simple and convenient to use.

The class given below is a wrapper for work with the boost thread:

C++
class CThreadManager
{
	sm::CSessionManager m_sessMgr;
	boost::thread m_thread;

public:
	CThreadManager()
		: m_thread(boost::ref(m_sessMgr))
	{
	}

	~CThreadManager()
	{
		m_sessMgr.StopSessionManager();
		m_thread.join();
	}
};

Now we return to the CSessionManager class and UpdateSessionState function. It writes the information about the session to the log fileโ€“ user name, log on or log out event:

C++
void CSessionManager::UpdateSessionsState()
{
    SessionsSet addedSessionsPool;
    SessionsSet lostSessionPool;
    GotSessionsSetGhanges(addedSessionsPool, lostSessionPool);
    for(SessionsSet::iterator it = addedSessionsPool.begin(); 
	it != addedSessionsPool.end(); ++it)
    {
        // write UserName, GetSid to the log 
    }
    for(SessionsSet::iterator it = lostSessionPool.begin(); 
	it != lostSessionPool.end(); ++it)
    {
        // write UserName, GetSid to the log 
    } 
}

The GotSessionsSetGhanges function is designed to watch for changes of the session list:

C++
void CSessionManager::GotSessionsSetGhanges
	(SessionsSet & addedSessions, SessionsSet & lostSessions)
{
    Common::CSessionOwner::GetActiveSessions(addedSessions);
    for(SessionsSet::iterator sessionIt= m_SessionsPool.begin(); 
	sessionIt != m_SessionsPool.end(); ++sessionIt)
    {
        SessionsSet::iterator currentSessionsIt = addedSessions.find(*sessionIt);
        if(currentSessionsIt == addedSessions.end())
        {
            std::pair<SessionsSet::iterator, bool> res = lostSessions.insert(*sessionIt);
            if(!res.second)
            {
                throw std::runtime_error("CSessionManager::GotSessionsSetGhanges error: 
			Cannot insert logged off session into pool");
            }
        }
        else
        {
            addedSessions.erase(*sessionIt);
        }
    }

    // add the new sessions to the session pool    
    for(SessionsSet::iterator it = addedSessions.begin(); it != addedSessions.end(); ++it)
    {
        std::pair<SessionsSet::iterator, bool> res = m_SessionsPool.insert(*it);
        if(!res.second)
        {
            throw std::runtime_error("CSessionManager::GotSessionsSetGhanges error: 
			Cannot insert logged off session into pool");
        }
    }
    
    // erase the lost sessions from the session pool
    for(SessionsSet::iterator it = lostSessions.begin(); it != lostSessions.end(); ++it)
    {
        m_SessionsPool.erase(*it);
    }
}

Conclusion

Resulting sample project represents a service that monitors logging on/out of the active user local/remote session and connect/disconnect (disconnect for the terminal sessions or fast user switching for the local ones) using WTS API functions and writes every event to the log file, located in the same directory that the executable file is. To build the sources you have to set boost environment variable. The project supports x86 and x64 platforms. To build the solution boost environment variable has to be set, and it has to be built for both platforms. In this article boost 1.40.0 version is used.

Download sources of the sample project (ZIP, 21KB).

Have a question?

Ask our expert!

Tell us about your project

Send us a request for proposal! Weโ€™ll get back to you with details and estimations.

Book an Exploratory Call

Do not have any specific task for us in mind but our skills seem interesting?

Get a quick Apriorit intro to better understand our team capabilities.

Book time slot

Contact us