SEBWIN-313: Started implementing blacklist monitoring.
This commit is contained in:
parent
8d0d1832a9
commit
d3d98c7df7
10 changed files with 136 additions and 102 deletions
|
@ -94,7 +94,7 @@ namespace SafeExamBrowser.Client
|
|||
taskbar = BuildTaskbar();
|
||||
terminationActivator = new TerminationActivator(ModuleLogger(nameof(TerminationActivator)));
|
||||
|
||||
var applicationMonitor = new ApplicationMonitor(ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory))));
|
||||
var applicationMonitor = new ApplicationMonitor(FIVE_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory))));
|
||||
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
||||
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
||||
var hashAlgorithm = new HashAlgorithm();
|
||||
|
|
|
@ -14,7 +14,6 @@ using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
|||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
|
||||
namespace SafeExamBrowser.Client.Operations
|
||||
|
@ -110,18 +109,18 @@ namespace SafeExamBrowser.Client.Operations
|
|||
|
||||
private void StartMonitor()
|
||||
{
|
||||
if (Context.Settings.KioskMode != KioskMode.None)
|
||||
{
|
||||
//TODO: if (Context.Settings.KioskMode != KioskMode.None)
|
||||
//{
|
||||
applicationMonitor.Start();
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
private void StopMonitor()
|
||||
{
|
||||
if (Context.Settings.KioskMode != KioskMode.None)
|
||||
{
|
||||
//TODO: if (Context.Settings.KioskMode != KioskMode.None)
|
||||
//{
|
||||
applicationMonitor.Stop();
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
private OperationResult TryTerminate(IEnumerable<RunningApplication> runningApplications)
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Threading;
|
||||
using System.Timers;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications.Events;
|
||||
|
@ -24,21 +23,24 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
private IntPtr activeWindow;
|
||||
private IList<BlacklistApplication> blacklist;
|
||||
private Guid? captureHookId;
|
||||
private ManagementEventWatcher explorerWatcher;
|
||||
private Guid? foregroundHookId;
|
||||
private ILogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private IList<IProcess> processes;
|
||||
private IProcessFactory processFactory;
|
||||
private Timer timer;
|
||||
private IList<WhitelistApplication> whitelist;
|
||||
|
||||
public event ExplorerStartedEventHandler ExplorerStarted;
|
||||
|
||||
public ApplicationMonitor(ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
|
||||
public ApplicationMonitor(int interval_ms, ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
|
||||
{
|
||||
this.blacklist = new List<BlacklistApplication>();
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.processes = new List<IProcess>();
|
||||
this.processFactory = processFactory;
|
||||
this.timer = new Timer(interval_ms);
|
||||
this.whitelist = new List<WhitelistApplication>();
|
||||
}
|
||||
|
||||
|
@ -59,17 +61,19 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
logger.Debug($"Initialized blacklist with {blacklist.Count} applications{(blacklist.Any() ? $": {string.Join(", ", blacklist.Select(a => a.ExecutableName))}" : ".")}");
|
||||
logger.Debug($"Initialized whitelist with {whitelist.Count} applications{(whitelist.Any() ? $": {string.Join(", ", whitelist.Select(a => a.ExecutableName))}" : ".")}");
|
||||
|
||||
foreach (var process in processFactory.GetAllRunning())
|
||||
processes = processFactory.GetAllRunning();
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
foreach (var application in blacklist)
|
||||
{
|
||||
var isMatch = BelongsToApplication(process, application);
|
||||
var isBlacklisted = BelongsToApplication(process, application);
|
||||
|
||||
if (isMatch && !application.AutoTerminate)
|
||||
if (isBlacklisted && !application.AutoTerminate)
|
||||
{
|
||||
AddForTermination(application.ExecutableName, process, result);
|
||||
}
|
||||
else if (isMatch && application.AutoTerminate && !TryTerminate(process))
|
||||
else if (isBlacklisted && application.AutoTerminate && !TryTerminate(process))
|
||||
{
|
||||
AddFailed(application.ExecutableName, process, result);
|
||||
}
|
||||
|
@ -86,13 +90,10 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
|
||||
public void Start()
|
||||
{
|
||||
// TODO: Start monitoring blacklist...
|
||||
|
||||
// TODO: Remove WMI event and use timer mechanism!
|
||||
explorerWatcher = new ManagementEventWatcher(@"\\.\root\CIMV2", GetQueryFor("explorer.exe"));
|
||||
explorerWatcher.EventArrived += new EventArrivedEventHandler(ExplorerWatcher_EventArrived);
|
||||
explorerWatcher.Start();
|
||||
logger.Info("Started monitoring process 'explorer.exe'.");
|
||||
timer.AutoReset = false;
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
timer.Start();
|
||||
logger.Info("Started monitoring applications.");
|
||||
|
||||
captureHookId = nativeMethods.RegisterSystemCaptureStartEvent(SystemEvent_WindowChanged);
|
||||
logger.Info($"Registered system capture start event with ID = {captureHookId}.");
|
||||
|
@ -103,8 +104,9 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
|
||||
public void Stop()
|
||||
{
|
||||
explorerWatcher?.Stop();
|
||||
logger.Info("Stopped monitoring 'explorer.exe'.");
|
||||
timer.Stop();
|
||||
timer.Elapsed -= Timer_Elapsed;
|
||||
logger.Info("Stopped monitoring applications.");
|
||||
|
||||
if (captureHookId.HasValue)
|
||||
{
|
||||
|
@ -142,7 +144,7 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
}
|
||||
|
||||
application.Processes.Add(process);
|
||||
logger.Error($"Process '{process.Name}' belongs to application '{application.Name}' and could not be terminated automatically!");
|
||||
logger.Error($"Process {process} belongs to application '{application.Name}' and could not be terminated automatically!");
|
||||
}
|
||||
|
||||
private void AddForTermination(string name, IProcess process, InitializationResult result)
|
||||
|
@ -156,7 +158,7 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
}
|
||||
|
||||
application.Processes.Add(process);
|
||||
logger.Debug($"Process '{process.Name}' belongs to application '{application.Name}' and needs to be terminated.");
|
||||
logger.Debug($"Process {process} belongs to application '{application.Name}' and needs to be terminated.");
|
||||
}
|
||||
|
||||
private bool BelongsToApplication(IProcess process, BlacklistApplication application)
|
||||
|
@ -231,63 +233,73 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
private bool TryTerminate(IProcess process)
|
||||
{
|
||||
const int MAX_ATTEMPTS = 5;
|
||||
const int TIMEOUT = 100;
|
||||
const int TIMEOUT = 500;
|
||||
|
||||
try
|
||||
for (var attempt = 0; attempt < MAX_ATTEMPTS; attempt++)
|
||||
{
|
||||
if (process.TryClose(TIMEOUT))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!process.HasTerminated)
|
||||
{
|
||||
for (var attempt = 0; attempt < MAX_ATTEMPTS; attempt++)
|
||||
{
|
||||
if (process.TryClose())
|
||||
if (process.TryKill(TIMEOUT))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
if (!process.HasTerminated)
|
||||
{
|
||||
for (var attempt = 0; attempt < MAX_ATTEMPTS; attempt++)
|
||||
{
|
||||
if (process.TryKill())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(TIMEOUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.HasTerminated)
|
||||
{
|
||||
logger.Info($"Successfully terminated process '{process.Name}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"Failed to terminate process '{process.Name}'!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
if (process.HasTerminated)
|
||||
{
|
||||
logger.Error($"An error occurred while attempting to terminate process '{process.Name}'!", e);
|
||||
logger.Info($"Successfully terminated process {process}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"Failed to terminate process {process}!");
|
||||
}
|
||||
|
||||
return process.HasTerminated;
|
||||
}
|
||||
|
||||
private void ExplorerWatcher_EventArrived(object sender, EventArrivedEventArgs e)
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
var eventName = e.NewEvent.ClassPath.ClassName;
|
||||
var running = processFactory.GetAllRunning();
|
||||
var started = running.Where(r => processes.All(p => p.Id != r.Id)).ToList();
|
||||
var terminated = processes.Where(p => running.All(r => r.Id != p.Id)).ToList();
|
||||
|
||||
if (eventName == "__InstanceCreationEvent")
|
||||
foreach (var process in started)
|
||||
{
|
||||
logger.Warn("A new instance of Windows explorer has been started!");
|
||||
ExplorerStarted?.Invoke();
|
||||
logger.Debug($"Process {process} has been started.");
|
||||
processes.Add(process);
|
||||
|
||||
foreach (var application in blacklist)
|
||||
{
|
||||
if (BelongsToApplication(process, application))
|
||||
{
|
||||
logger.Warn($"Process {process} belongs to blacklisted application '{application.ExecutableName}'! Attempting termination...");
|
||||
|
||||
var success = TryTerminate(process);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// TODO: Invoke event -> Show lock screen!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var process in terminated)
|
||||
{
|
||||
logger.Debug($"Process {process} has been terminated.");
|
||||
processes.Remove(process);
|
||||
}
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private void SystemEvent_WindowChanged(IntPtr window)
|
||||
|
@ -299,15 +311,5 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
Check(window);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetQueryFor(string processName)
|
||||
{
|
||||
return $@"
|
||||
SELECT *
|
||||
FROM __InstanceOperationEvent
|
||||
WITHIN 2
|
||||
WHERE TargetInstance ISA 'Win32_Process'
|
||||
AND TargetInstance.Name = '{processName}'";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,6 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="WindowsBase" />
|
||||
|
|
|
@ -172,7 +172,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
proxy.Verify(p => p.InitiateShutdown(), Times.Once);
|
||||
proxy.Verify(p => p.Disconnect(), Times.Once);
|
||||
process.Verify(p => p.TryKill(), Times.Never);
|
||||
process.Verify(p => p.TryKill(default(int)), Times.Never);
|
||||
|
||||
Assert.IsNull(sessionContext.ClientProcess);
|
||||
Assert.IsNull(sessionContext.ClientProxy);
|
||||
|
@ -181,12 +181,12 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestMethod]
|
||||
public void Revert_MustKillClientIfStoppingFailed()
|
||||
{
|
||||
process.Setup(p => p.TryKill()).Callback(() => process.SetupGet(p => p.HasTerminated).Returns(true));
|
||||
process.Setup(p => p.TryKill(default(int))).Callback(() => process.SetupGet(p => p.HasTerminated).Returns(true));
|
||||
|
||||
PerformNormally();
|
||||
sut.Revert();
|
||||
|
||||
process.Verify(p => p.TryKill(), Times.AtLeastOnce);
|
||||
process.Verify(p => p.TryKill(default(int)), Times.AtLeastOnce);
|
||||
|
||||
Assert.IsNull(sessionContext.ClientProcess);
|
||||
Assert.IsNull(sessionContext.ClientProxy);
|
||||
|
@ -198,7 +198,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
PerformNormally();
|
||||
sut.Revert();
|
||||
|
||||
process.Verify(p => p.TryKill(), Times.Exactly(5));
|
||||
process.Verify(p => p.TryKill(default(int)), Times.Exactly(5));
|
||||
|
||||
Assert.IsNotNull(sessionContext.ClientProcess);
|
||||
Assert.IsNotNull(sessionContext.ClientProxy);
|
||||
|
@ -213,7 +213,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
proxy.Verify(p => p.InitiateShutdown(), Times.Never);
|
||||
proxy.Verify(p => p.Disconnect(), Times.Never);
|
||||
process.Verify(p => p.TryKill(), Times.Never);
|
||||
process.Verify(p => p.TryKill(default(int)), Times.Never);
|
||||
|
||||
Assert.IsNull(sessionContext.ClientProcess);
|
||||
Assert.IsNull(sessionContext.ClientProxy);
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
proxy.Verify(p => p.InitiateShutdown(), Times.Once);
|
||||
proxy.Verify(p => p.Disconnect(), Times.Once);
|
||||
process.Verify(p => p.TryKill(), Times.Never);
|
||||
process.Verify(p => p.TryKill(default(int)), Times.Never);
|
||||
|
||||
Assert.IsNull(sessionContext.ClientProcess);
|
||||
Assert.IsNull(sessionContext.ClientProxy);
|
||||
|
|
|
@ -41,13 +41,16 @@ namespace SafeExamBrowser.WindowsApi.Contracts
|
|||
event ProcessTerminatedEventHandler Terminated;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to gracefully terminate the process by closing its main window. This will only work for interactive processes which have a main window.
|
||||
/// Attempts to gracefully terminate the process by closing its main window. This will only work for interactive processes which have a main
|
||||
/// window. Optionally waits the specified amount of time for the process to terminate. Returns <c>true</c> if the process has terminated,
|
||||
/// otherwise <c>false</c>.
|
||||
/// </summary>
|
||||
bool TryClose();
|
||||
bool TryClose(int timeout_ms = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to immediately kill the process.
|
||||
/// Attempts to immediately kill the process. Optionally waits the specified amount of time for the process to terminate. Returns <c>true</c>
|
||||
/// if the process has terminated, otherwise <c>false</c>.
|
||||
/// </summary>
|
||||
bool TryKill();
|
||||
bool TryKill(int timeout_ms = 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace SafeExamBrowser.WindowsApi.Contracts
|
|||
/// <summary>
|
||||
/// Retrieves all currently running processes.
|
||||
/// </summary>
|
||||
IEnumerable<IProcess> GetAllRunning();
|
||||
IList<IProcess> GetAllRunning();
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new process with the given command-line arguments.
|
||||
|
|
|
@ -62,18 +62,25 @@ namespace SafeExamBrowser.WindowsApi
|
|||
this.originalNameInitialized = true;
|
||||
}
|
||||
|
||||
public bool TryClose()
|
||||
public bool TryClose(int timeout_ms = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.Debug("Attempting to close process...");
|
||||
process.Refresh();
|
||||
|
||||
if (!process.HasExited)
|
||||
var success = process.CloseMainWindow();
|
||||
|
||||
if (success)
|
||||
{
|
||||
process.CloseMainWindow();
|
||||
logger.Debug("Successfully sent close message to main window.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Failed to send close message to main window!");
|
||||
}
|
||||
|
||||
return process.HasExited;
|
||||
return success && WaitForTermination(timeout_ms);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -83,18 +90,16 @@ namespace SafeExamBrowser.WindowsApi
|
|||
return false;
|
||||
}
|
||||
|
||||
public bool TryKill()
|
||||
public bool TryKill(int timeout_ms = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.Debug("Attempting to kill process...");
|
||||
|
||||
process.Refresh();
|
||||
process.Kill();
|
||||
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
return process.HasExited;
|
||||
return WaitForTermination(timeout_ms);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -104,6 +109,11 @@ namespace SafeExamBrowser.WindowsApi
|
|||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"'{Name}' ({Id})";
|
||||
}
|
||||
|
||||
private bool IsTerminated()
|
||||
{
|
||||
try
|
||||
|
@ -127,6 +137,7 @@ namespace SafeExamBrowser.WindowsApi
|
|||
eventInitialized = true;
|
||||
process.Exited += Process_Exited;
|
||||
process.EnableRaisingEvents = true;
|
||||
logger.Debug("Initialized termination event.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,9 +176,26 @@ namespace SafeExamBrowser.WindowsApi
|
|||
return originalName;
|
||||
}
|
||||
|
||||
private bool WaitForTermination(int timeout_ms)
|
||||
{
|
||||
var terminated = process.WaitForExit(timeout_ms);
|
||||
|
||||
if (terminated)
|
||||
{
|
||||
logger.Debug($"Process has terminated within {timeout_ms}ms.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"Process failed to terminate within {timeout_ms}ms!");
|
||||
}
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private void Process_Exited(object sender, EventArgs e)
|
||||
{
|
||||
TerminatedEvent?.Invoke(process.ExitCode);
|
||||
logger.Debug("Process has terminated.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,17 +32,20 @@ namespace SafeExamBrowser.WindowsApi
|
|||
this.logger = logger;
|
||||
}
|
||||
|
||||
public IEnumerable<IProcess> GetAllRunning()
|
||||
public IList<IProcess> GetAllRunning()
|
||||
{
|
||||
var processes = System.Diagnostics.Process.GetProcesses();
|
||||
var processes = new List<IProcess>();
|
||||
var running = System.Diagnostics.Process.GetProcesses();
|
||||
var originalNames = LoadOriginalNames();
|
||||
|
||||
foreach (var process in processes)
|
||||
foreach (var process in running)
|
||||
{
|
||||
var originalName = originalNames.FirstOrDefault(n => n.processId == process.Id).originalName;
|
||||
|
||||
yield return new Process(process, originalName, LoggerFor(process));
|
||||
processes.Add(new Process(process, originalName, LoggerFor(process)));
|
||||
}
|
||||
|
||||
return processes;
|
||||
}
|
||||
|
||||
public IProcess StartNew(string path, params string[] args)
|
||||
|
|
Loading…
Reference in a new issue