Who Accessed My File?
A few years ago, I recall needing to know (programmatically) which user has accessed a particular file. As part of a legacy system, this couldn't make the code base any worse. The idea was that there's a configuration file sitting on a network share; access to it is granted to only a single user at any given time. However, other users requesting access want to know why access is being denied - i.e., which user is holding them from accessing the file. This information would be displayed to them, and then they were able to go and (physically) kick that person off the file if they really needed it.
Apparently, I wasn't the first person on this planet to ever need something like that. Alik Levin outlines a very easy and elegant solution using the excellent Process Monitor tool from the SysInternals suite. In a previous post, he also mentions that you can use Group Policy MMC snap-in (or Local Security Policy) to enable auditing. After that, you can use the Event Viewer to see audit messages.
Nonetheless, a few years ago I found myself in a position when I needed to do all that programmatically. Equipped with nothing but Win32 and C++, I effectively needed to perform the following:
- Enable the SeSecurityPrivilege in the current process' token if it wasn't already enabled;
- Add a SACL ACE (System Access Control List Access Control Entry) representing the audit entry to the file being audited on the remote machine;
- Enable auditing on the remote machine (one-time manual procedure, didn't find a programmatic way to do it at the time);
- Traverse the event log on the remote machine whenever necessary to find the audit trail for the file access, and see the user on whose behalf it was being performed.
I vaguely remember that it took me the good part of two weeks to get everything right and tested (especially since I never was, and still am not a Windows security expert by any means). And after all, it was such a fragile solution.
Out of curiosity, I wanted to check how long a .NET implementation of the above will take, given that the framework encapsulates everything related to file system auditing and event log access in easy-to-use, well-documented types.
Well . . . it took me exactly 2 minutes to write the code that sets up a SACL ACE for a file of my choice, using FileSecurity and File.SetAccessControl. Which was beautiful - the sores inflicted upon me by that Win32 code from years ago have actually started to heal.
Next, I looked into retrieving the event records from the Vista event log. At long last (in .NET 3.5) we have a managed model to access Windows Eventing 6.0 functionality - my Vista event log has the audit train glaring at me with all its XML beauty, and I have my good friends in the System.Diagnostics.Eventing.Reader namespace to read it!
So, here's the query we should pass: *[System/EventID=4663 and EventData/Data='D:\Temp\1.txt']. After that, it's just about getting the Event/EventData/Data[@SubjectUserName] value from the event record XML. In code, the whole thing boils down to setting up an EventLogWatcher:
. . . and then handling the EventLogWatcher.EventRecordWritten event:
So this leaves us with two things to do: enable the SeSecurityPrivilege if it's not enabled already, and turn on "Audit object access" in the Local Security Policy. Enabling the privilege is a task best left to native code, with OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges. So is changing the audit policy - LsaSetInformationPolicy with POLICY_AUDIT_EVENT_INFO hand you this on a silver platter. This is not more complicated than interpreting MSDN documentation correctly - look at the source code if you're interested.
As always, you can download the sample code (Visual Studio 2008 solution).