|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionFor quite a number of applications or services, it might be useful to know, whether a user is currently interactively logged on to a machine or not. Depending on a user currently logged on, a service might, for instance, decide to do heavy computations because sluggish response times because of heavy CPU load won't affect a logged in interactive user's "user experience". Or a network administrator might decide to remotely shut down or power off all those machines where currently nobody is logged in in order to save power or because he just remotely deployed software such as an OS patch which requires a reboot. There are numerous reasons, why it would be quite interesting to know, either from within a service running on a particular machine, or remotely, if an interactive user is currently logged in on that particular machine. This article provides source code for an interactive service that can be used to see the result of a logon session enumeration in a message box on the interactive desktop (including the Winlogon desktop, so you can enumerate logon sessions after having logged out from your machine). Additionally, source code and implementation of a WMI instance provider is presented in this article. Both the service and the WMI provider give you an accurate list of (interactive) logon sessions on NT4, W2K, WXP and W2K3 Server, including all the flavors that allow Terminal Services running. While the service can more or less be considered an educative toy, the WMI provider could really be used in a productive environment, and has been implemented with all the required robustness and security in mind, that a WMI provider, which runs in the context of a system service, should have. Background and historical perspectiveDetecting and enumerating logon sessions on NTesque Operating Systems (NT3.x, NT4, W2K,WXP, W2K3Server) has always been a daunting task for developers, and was the subject of numerous discussions on the Usenet or in programmer magazines over the years. For interactive logon sessions (as opposed to network, batch, or service style logon sessions), one thing that immediately comes to mind for that purpose is to monitor logon and logoff events: just have a counter variable that initially starts with a value of zero and is incremented with each "logon event" (whatever that is) and that is decremented with each "logoff event" (again, whatever that is). If it is zero, nobody is interactively logged in, if it is non-zero, someone is logged in. Unfortunately, the reality is not even close to being that easy, because there is no such thing as a "logon event" or a "logoff" event on NT4. So, people started tinkering with companion applications started from the autorun-keys in the registry, registering registry key notifications, polling the input desktop, testing for an instance of the shell application, and so on. Way back in 1998/1999, there were a couple of "Windows Developer's Journal" issues, where Paula Tomlinson discussed various of these ways to detect user logoffs and logons, and their drawbacks and shortcomings on NT4. In one of her first articles on this issue, she concludes with the following: "Detecting logon events from a service is unfortunately not as straightforward as it could or should be". Subsequent articles of hers prove that the same is also true for "logoff-events". For NT4 Workstation or Server, all of this is still true today and none of the methods Paula investigates in her series of articles is really 100% reliable, let alone satisfactorily from an efficiency point of view, which she frankly admits in her articles. Additionally, with the proliferation of Terminal Services, starting with the release of Windows NT4 Terminal Server Edition and culminating in today's Windows XP Fast User Switching Feature, which is a crippled form of Terminal Services, the problem got even more complex. Now, just throw into the mix, one of the various SU implementations or W2K's runas functionality, which allow to start another interactive logon session from an existing interactive logon session. Or imagine that a service impersonates a logged on user, the user logs off and the service is still acting on behalf of that user. Then the question "Is an interactive user currently logged in on that machine?" gets kind of a philosophical dimension. With the advent of Windows 2000, a new, much better way of detecting interactive logons or logoffs was introduced in the form of "Winlogon Notification Packages". This is just a fancy name for a DLL that has a number of special-named exported functions in conjunction with a registry key under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify and associated named values under that key. If you write such a DLL and register it appropriately with the required registry values in a key under the one named above, you really can monitor interactive logons or logoffs effectively and reliably. Even the official documentation of Winlogon Notification Packages states about the possible use-cases of Winlogon Notification Packages: "Winlogon notification packages are DLLs that receive and handle events generated by Winlogon [...] and respond to Winlogon events [...] for applications that need to perform additional processing during logon or logoff, or maintain state information that must be updated when Winlogon events occur." But what happens if a Winlogon Notification Package dutifully records all logoff and logon events, and a service in the background that maintains all of the current state information based on information it gets from the Winlogon Notification Package crashes or gets stopped, while in the meantime users log on and off? What about the login sessions that are generated on-the-fly from an already logged-in user via runas or an SU utility? In these cases, all of that previously recorded state information is just useless or does not accurately reflect the real situation. And another disadvantage comes to mind immediately: you definitely need an additional service as an active component that runs in the background and "survives" the user logon or logoff events. And still there is no remoting functionality available that would allow the machine in question to be queried for interactive logon sessions, ala WMI. WMI? Can't we do that with WMI? What about the built-in WMI functionality?The problem with the built-in WMI functionality is, well, uh..., that it is sometimes just not built-in. While NT's and W2K's implementation of the WMI core contains no WMI classes at all to enumerate logon sessions, XP has four of them: In order to solve the problem with stale logon session using WMI, the third and the fourth WMI class comes to the rescue: So, let's assume you have an XP box where two users A and B are logged in interactively in two "Fast User Switching" sessions. If you then query remotely from a second machine the instances of APIs for acquiring logon session informationThe two APIs that allow for enumerating logon sessions and retrieving information of logon sessions are How to filter out the stale logon sessionsFortunately, every process token contains a reference to its logon session ID. You can obtain a process' logon session ID by opening the process via How can it be done on NT4?On NT4, all the APIs that allow for logon session enumeration are either not documented or missing. In order to get a list of interactive logon sessions on NT4, a completely different approach is therefore necessary. Luckily, one person already found this approach: Felix Kasza, former Grandeur and Guru of the Microsoft kernel newsgroup, presents in one of his great code samples, a nice approach to identify interactive processes. Felix' basic idea is that you can identify an interactive process easily by enumerating the group SIDs of its process token: if it contains two well-known SIDs, namely the INTERACTIVE and the LOCAL group SID, and a logon SID, it is considered an interactive process. For the service and the WMI provider in this article, I adapted this idea and implemented it in the function Using the technique from Felix, it is possible to easily identify the user name, the logon domain, the user SID, and the logon session LUID of an interactive logon session. What cannot be done is retrieving the authentication package and the Terminal Services session ID. But at least, the Terminal Services session ID can be obtained using one of the WTS APIs that have been introduced with Service Pack 4 of Windows NT4 Terminal Server Edition: Using the codeThe code for this article comes as a Visual Studio 6 Workspace for the service and the WMI provider. All of the code compiles cleanly under W4. Where the usage of STL or the WMI Provider Framework requires W3, my own code is wrapped in a Using the service presented in this articleThe service can be found in the project named "enumeratelogonsessions". Build the debug or the release version of the service, and open a command prompt where you navigate to the directory of the binary (lsd.exe) that must have been built for you. In order to install the service, log in as a member of the local administrator's group and type: lsd.exe -install
followed by a: net start logonsessiondumper
in order to start the service. If the service is running, hold down the F2 key for a few seconds, and watch if you can see a message box being created for you. The message box might be created somewhere in the background for you, so you might have to do some Alt-Tabbing or minimize or close other windows on your desktop in order to see it. The message box might also be quite large, depending on the number of logon sessions. It might be so large that you can't even see the OK-button at the bottom. In that case, simply hit the Enter key to dismiss that message box. This first message box, started by F2, will show all the logon sessions that come from a call to net stop logonsessiondumper
which will stop the service, followed by a: lsd.exe -remove
which will remove the service from the Service Control Manager's database. Using the WMI provider presented in this articleThe WMI provider for this article is based on the WMI provider framework. If you have only Visual Studio 6 without Platform SDK installed, you might get some problems when compiling this provider because of missing libraries and header files. If this is the case, you should download the most recent Platform SDK and install it appropriately. I expect that the newer Visual Studio versions already have the required header files and libraries, but nevertheless, it is always a good idea to have the latest and greatest Platform SDK installed. After a successful compilation of the WMI provider, you should have a DLL named ilogprov.dll in your debug or release subdirectory under the wmiprov subdirectory of the project. Now, you have to register the provider in your system, and for this purpose, start a command prompt and navigate to the directory that contains the freshly built provider DLL. For this purpose, type: regsvr32.exe ilogprov.dll
in the command prompt. If you receive an error like "LoadLibrary failed.." when registering the debug build version, then it is very likely that the debug version of the WMI Provider Framework cannot be found. The name of that DLL is framedyd.dll, and it should be in the bin directory of your Platform SDK installation. Just copy it from there into your Windows directory or into a directory that is contained in your PATH environment variable, and you're probably done. A good idea might also be to copy it into %SYSTEMROOT%\SYSTEM32\WBEM, because this directory is normally contained in the PATH environment variable, and it is the directory where the release version of this DLL, framedyn.dll, resides. If you still have problems with the debug build, the debug version of the Visual C runtime might be missing, so you might need to find or reinstall an appropriate version of msvcrtd.dll for your development environment. If you now still have problems, it might be worthwhile to launch the Dependency Walker (depends.exe) tool which is very helpful in troubleshooting because it indicates in its left pane's tree view, those DLLs it can't find. On NT4, a missing psapi.dll might be a reason for failure, because the provider (as well as the service) requires PSAPI.DLL. PSAPI.DLL is available as a redistributable for NT4. After having registered the provider as a COM component in the system, you have to add the classes of this provider into the local CIMOM repository of your machine. For this purpose, open a command prompt and navigate to the wmiprov subdirectory of this article's project. You should find a file named ILogonProv.MOF there. Now type: mofcomp ILogonProv.MOF
on the command prompt, which should issue the following statement on stdout: Parsing MOF file: ILogonProv.MOF
MOF file has been successfully parsed
Storing data in the repository...
Done!
Now, the provider should be fully functional on your machine, and you can verify this by starting the WMI CIM Studio. For older Platform SDKs, the WMI CIM Studio was a file named studio.htm in the BIN\WMI directory of the Platform SDK installation and is today available as a separate download from Microsoft, dubbed "WMI tools". In case you cannot find it in your Platform SDK installation, do a Google Search for "WMI CIM STUDIO", and one of the first hits should be a download link from Microsoft for wmitools.exe, which is the self-extracting executable installation for the WMI Tools. Note that you have to open the studio.htm file with Microsoft Internet Explorer. If your default web browser happens to be Mozilla or Opera, opening this file won't work as expected, so you may have to reside to launching this file with a right-mouse button click in Explorer and choosing "Open With"-"Internet Explorer" from the context menu. After having started the WMI CIM Studio, you are presented with a dialog where you can provide the namespace to be examined. I decided to implement the class of my WMI provider in my own namespace named "Honeypie". So, in order to enumerate the instances of my class, you should provide the namespace root\honeypie in this dialog and click the OK button. Next comes another dialog box labeled "WMI CIM Studio Login". Just click OK here and you should get a tree control in the left pane of WMI CIM Studio. One of the tree items should be labeled "ILogonSession". Now, select this item. In the right pane, you should now see the class properties of the Points of Interest and Implementation DetailsI think the code contains a lot of interesting stuff for people who are inclined into security and systems programming. Because the service itself is only meant to be a toy to play with in order to get a feeling for the subject, it is not really coded with the desire for catching all errors or exceptions in mind. The usage of STL without being prepared for catching exceptions only illustrates this sloppiness of mine. However, for the shared code between the service and the WMI provider, and the provider itself, I would be very grateful if readers would review my code and point out potential security problems or similar, like for instance, bad propagation of error codes up the call chain, memory leaks, or other nasty things. Since this is my first WMI provider, I would also be very grateful if readers with a solid background on WMI provider writing would take a critical look at my code. As a side note: I chose to implement my provider with the WMI Provider Framework because it was really easy to use this framework and I had my provider up and running almost instantly. I know that today Microsoft discourages the use of the WMI Provider Framework in favor of .NET based providers or ATL-based providers. However, the first option would probably not run at all on NT4, and the second option would require redistribution of the ATL runtime which might be too much of an intrusion on NT4 based systems. In contrast, the WMI Provider Framework based provider in this article works out of the box with NT4 SP6 and the WMIcore for NT4 installed (plus the psapi.dll redistributable, if not yet installed). The WMI provider does a version check of the operating system and decides which functions to use for enumerating logon sessions. On NT4, it uses the aforementioned function /// #define EMULATE_NT4_BEHAVIOUR and remove the three slashes. HistoryInitial version, 06/20/2004. | ||||||||||||||||||||