Restore SEBPatch
This commit is contained in:
409
SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs
Normal file
409
SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs
Normal file
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications.Events;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Applications
|
||||
{
|
||||
public class ApplicationMonitor : IApplicationMonitor
|
||||
{
|
||||
private readonly IList<BlacklistApplication> blacklist;
|
||||
private readonly ILogger logger;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly IProcessFactory processFactory;
|
||||
private readonly Timer timer;
|
||||
private readonly IList<WhitelistApplication> whitelist;
|
||||
|
||||
private Guid? captureHookId;
|
||||
private Guid? foregroundHookId;
|
||||
private IList<IProcess> processes;
|
||||
private Window activeWindow;
|
||||
|
||||
public event ExplorerStartedEventHandler ExplorerStarted;
|
||||
public event InstanceStartedEventHandler InstanceStarted;
|
||||
public event TerminationFailedEventHandler TerminationFailed;
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
public InitializationResult Initialize(ApplicationSettings settings)
|
||||
{
|
||||
var result = new InitializationResult();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
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}.");
|
||||
|
||||
foregroundHookId = nativeMethods.RegisterSystemForegroundEvent(SystemEvent_WindowChanged);
|
||||
logger.Info($"Registered system foreground event with ID = {foregroundHookId}.");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
timer.Stop();
|
||||
timer.Elapsed -= Timer_Elapsed;
|
||||
logger.Info("Stopped monitoring applications.");
|
||||
|
||||
if (captureHookId.HasValue)
|
||||
{
|
||||
nativeMethods.DeregisterSystemEventHook(captureHookId.Value);
|
||||
logger.Info($"Unregistered system capture start event with ID = {captureHookId}.");
|
||||
}
|
||||
|
||||
if (foregroundHookId.HasValue)
|
||||
{
|
||||
nativeMethods.DeregisterSystemEventHook(foregroundHookId.Value);
|
||||
logger.Info($"Unregistered system foreground event with ID = {foregroundHookId}.");
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetActiveApplication(out ActiveApplication application)
|
||||
{
|
||||
application = default;
|
||||
|
||||
if (activeWindow != default && TryGetProcessFor(activeWindow, out var process))
|
||||
{
|
||||
var window = new Window
|
||||
{
|
||||
Handle = activeWindow.Handle,
|
||||
Title = nativeMethods.GetWindowTitle(activeWindow.Handle)
|
||||
};
|
||||
|
||||
application = new ActiveApplication(process, window);
|
||||
}
|
||||
|
||||
return application != default;
|
||||
}
|
||||
|
||||
public bool TryTerminate(RunningApplication application)
|
||||
{
|
||||
var success = true;
|
||||
|
||||
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private void SystemEvent_WindowChanged(IntPtr handle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void AddFailed(IProcess process, List<RunningApplication> failed)
|
||||
{
|
||||
var name = blacklist.First(a => BelongsToApplication(process, a)).ExecutableName;
|
||||
var application = failed.FirstOrDefault(a => a.Name == name);
|
||||
|
||||
if (application == default(RunningApplication))
|
||||
{
|
||||
application = new RunningApplication(name);
|
||||
failed.Add(application);
|
||||
}
|
||||
|
||||
application.Processes.Add(process);
|
||||
}
|
||||
|
||||
private void AddFailed(string name, IProcess process, InitializationResult result)
|
||||
{
|
||||
var application = result.FailedAutoTerminations.FirstOrDefault(a => a.Name == name);
|
||||
|
||||
if (application == default(RunningApplication))
|
||||
{
|
||||
application = new RunningApplication(name);
|
||||
result.FailedAutoTerminations.Add(application);
|
||||
}
|
||||
|
||||
application.Processes.Add(process);
|
||||
logger.Error($"Process {process} belongs to application '{application.Name}' and could not be terminated automatically!");
|
||||
}
|
||||
|
||||
private void AddForTermination(string name, IProcess process, InitializationResult result)
|
||||
{
|
||||
var application = result.RunningApplications.FirstOrDefault(a => a.Name == name);
|
||||
|
||||
if (application == default(RunningApplication))
|
||||
{
|
||||
application = new RunningApplication(name);
|
||||
result.RunningApplications.Add(application);
|
||||
}
|
||||
|
||||
application.Processes.Add(process);
|
||||
logger.Debug($"Process {process} belongs to application '{application.Name}' and needs to be terminated.");
|
||||
}
|
||||
|
||||
private bool BelongsToApplication(IProcess process, BlacklistApplication application)
|
||||
{
|
||||
var sameName = process.Name.Equals(application.ExecutableName, StringComparison.OrdinalIgnoreCase);
|
||||
var sameOriginalName = process.OriginalName?.Equals(application.OriginalName, StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
return sameName || sameOriginalName;
|
||||
}
|
||||
|
||||
private bool BelongsToApplication(IProcess process, WhitelistApplication application)
|
||||
{
|
||||
var ignoreOriginalName = string.IsNullOrWhiteSpace(application.OriginalName);
|
||||
var ignoreSignature = string.IsNullOrWhiteSpace(application.Signature);
|
||||
var sameName = process.Name.Equals(application.ExecutableName, StringComparison.OrdinalIgnoreCase);
|
||||
var sameOriginalName = process.OriginalName?.Equals(application.OriginalName, StringComparison.OrdinalIgnoreCase) == true;
|
||||
var sameSignature = process.Signature?.Equals(application.Signature?.ToLower(), StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
return sameName && (ignoreOriginalName || sameOriginalName) && (ignoreSignature || sameSignature);
|
||||
}
|
||||
|
||||
private bool BelongsToSafeExamBrowser(IProcess process)
|
||||
{
|
||||
var isClient = true;
|
||||
var isRuntime = true;
|
||||
|
||||
isClient &= process.Name == "SafeExamBrowser.Client.exe";
|
||||
isClient &= process.OriginalName == "SafeExamBrowser.Client.exe";
|
||||
|
||||
isRuntime &= process.Name == "SafeExamBrowser.exe";
|
||||
isRuntime &= process.OriginalName == "SafeExamBrowser.exe";
|
||||
|
||||
#if !DEBUG
|
||||
isClient &= process.Signature == "2bc82fe8e56a39f96bc6c4b91d6703a0379b76a2";
|
||||
isRuntime &= process.Signature == "2bc82fe8e56a39f96bc6c4b91d6703a0379b76a2";
|
||||
#endif
|
||||
|
||||
return isClient || isRuntime;
|
||||
}
|
||||
|
||||
private void Close(Window window)
|
||||
{
|
||||
nativeMethods.SendCloseMessageTo(window.Handle);
|
||||
logger.Info($"Sent close message to window {window}.");
|
||||
}
|
||||
|
||||
private void HandleExplorerStart(IProcess process)
|
||||
{
|
||||
logger.Warn($"A new instance of Windows Explorer {process} has been started!");
|
||||
Task.Run(() => ExplorerStarted?.Invoke());
|
||||
}
|
||||
|
||||
private void HandleInstanceStart(Guid applicationId, IProcess process)
|
||||
{
|
||||
logger.Debug($"Detected start of whitelisted application instance {process}.");
|
||||
Task.Run(() => InstanceStarted?.Invoke(applicationId, process));
|
||||
}
|
||||
|
||||
private void InitializeProcesses()
|
||||
{
|
||||
processes = processFactory.GetAllRunning();
|
||||
logger.Debug($"Initialized {processes.Count} currently running processes.");
|
||||
}
|
||||
|
||||
private void InitializeBlacklist(ApplicationSettings settings, InitializationResult result)
|
||||
{
|
||||
foreach (var application in settings.Blacklist)
|
||||
{
|
||||
blacklist.Add(application);
|
||||
}
|
||||
|
||||
logger.Debug($"Initialized blacklist with {blacklist.Count} applications{(blacklist.Any() ? $": {string.Join(", ", blacklist.Select(a => a.ExecutableName))}" : ".")}");
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
foreach (var application in blacklist)
|
||||
{
|
||||
var isBlacklisted = BelongsToApplication(process, application);
|
||||
|
||||
if (isBlacklisted)
|
||||
{
|
||||
if (!application.AutoTerminate)
|
||||
{
|
||||
AddForTermination(application.ExecutableName, process, result);
|
||||
}
|
||||
else if (application.AutoTerminate && !TryTerminate(process))
|
||||
{
|
||||
AddFailed(application.ExecutableName, process, result);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeWhitelist(ApplicationSettings settings, InitializationResult result)
|
||||
{
|
||||
foreach (var application in settings.Whitelist)
|
||||
{
|
||||
whitelist.Add(application);
|
||||
}
|
||||
|
||||
logger.Debug($"Initialized whitelist with {whitelist.Count} applications{(whitelist.Any() ? $": {string.Join(", ", whitelist.Select(a => a.ExecutableName))}" : ".")}");
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
foreach (var application in whitelist)
|
||||
{
|
||||
var isWhitelisted = BelongsToApplication(process, application);
|
||||
|
||||
if (isWhitelisted)
|
||||
{
|
||||
if (!application.AllowRunning && !application.AutoTerminate)
|
||||
{
|
||||
AddForTermination(application.ExecutableName, process, result);
|
||||
}
|
||||
else if (!application.AllowRunning && application.AutoTerminate && !TryTerminate(process))
|
||||
{
|
||||
AddFailed(application.ExecutableName, process, result);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAllowed(IProcess process)
|
||||
{
|
||||
foreach (var application in blacklist)
|
||||
{
|
||||
if (BelongsToApplication(process, application))
|
||||
{
|
||||
logger.Warn($"Process {process} belongs to blacklisted application '{application.ExecutableName}'!");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAllowed(Window window)
|
||||
{
|
||||
var allowed = false;
|
||||
|
||||
if (TryGetProcessFor(window, out var process))
|
||||
{
|
||||
allowed = BelongsToSafeExamBrowser(process) || IsWhitelisted(process, out _);
|
||||
}
|
||||
|
||||
if (!allowed)
|
||||
{
|
||||
logger.Warn($"Window {window} belongs to not whitelisted process '{process?.Name ?? "n/a"}'!");
|
||||
}
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
private bool IsWhitelisted(IProcess process, out Guid? applicationId)
|
||||
{
|
||||
applicationId = default;
|
||||
|
||||
foreach (var application in whitelist)
|
||||
{
|
||||
if (BelongsToApplication(process, application))
|
||||
{
|
||||
applicationId = application.Id;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetProcessFor(Window window, out IProcess process)
|
||||
{
|
||||
var processId = Convert.ToInt32(nativeMethods.GetProcessIdFor(window.Handle));
|
||||
|
||||
if (!processFactory.TryGetById(processId, out process))
|
||||
{
|
||||
logger.Error($"Could not find process for window {window} and process ID = {processId}!");
|
||||
}
|
||||
|
||||
return process != default;
|
||||
}
|
||||
|
||||
private bool TryHide(Window window)
|
||||
{
|
||||
var success = nativeMethods.HideWindow(window.Handle);
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info($"Successfully hid window {window}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"Failed to hide window {window}!");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryTerminate(IProcess process)
|
||||
{
|
||||
const int MAX_ATTEMPTS = 5;
|
||||
const int TIMEOUT = 500;
|
||||
|
||||
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.TryKill(TIMEOUT))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.HasTerminated)
|
||||
{
|
||||
logger.Info($"Successfully terminated process {process}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"Failed to terminate process {process}!");
|
||||
}
|
||||
|
||||
return process.HasTerminated;
|
||||
}
|
||||
}
|
||||
}
|
24
SafeExamBrowser.Monitoring/Applications/Window.cs
Normal file
24
SafeExamBrowser.Monitoring/Applications/Window.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Applications
|
||||
{
|
||||
internal class Window : IWindow
|
||||
{
|
||||
public IntPtr Handle { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"'{Title}' ({Handle})";
|
||||
}
|
||||
}
|
||||
}
|
74
SafeExamBrowser.Monitoring/Clipboard.cs
Normal file
74
SafeExamBrowser.Monitoring/Clipboard.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Timers;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts;
|
||||
using SafeExamBrowser.Settings.Security;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring
|
||||
{
|
||||
public class Clipboard : IClipboard
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly Timer timer;
|
||||
|
||||
private ClipboardPolicy policy;
|
||||
|
||||
public Clipboard(ILogger logger, INativeMethods nativeMethods, int timeout_ms = 50)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.timer = new Timer(timeout_ms);
|
||||
}
|
||||
|
||||
public void Initialize(ClipboardPolicy policy)
|
||||
{
|
||||
this.policy = policy;
|
||||
logger.Debug("Cleared clipboard.");
|
||||
|
||||
if (policy != ClipboardPolicy.Allow)
|
||||
{
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
timer.Start();
|
||||
|
||||
logger.Debug($"Started clipboard monitoring with interval {timer.Interval} ms.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("Clipboard is allowed, not starting monitoring.");
|
||||
}
|
||||
|
||||
logger.Info($"Initialized clipboard for policy '{policy}'.");
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
logger.Debug("Cleared clipboard.");
|
||||
|
||||
if (policy != ClipboardPolicy.Allow)
|
||||
{
|
||||
timer.Stop();
|
||||
logger.Debug("Stopped clipboard monitoring.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("Clipboard monitoring was not active.");
|
||||
}
|
||||
|
||||
logger.Info($"Finalized clipboard.");
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
20
SafeExamBrowser.Monitoring/Display/Bounds.cs
Normal file
20
SafeExamBrowser.Monitoring/Display/Bounds.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Display
|
||||
{
|
||||
internal class Bounds : IBounds
|
||||
{
|
||||
public int Left { get; set; }
|
||||
public int Top { get; set; }
|
||||
public int Right { get; set; }
|
||||
public int Bottom { get; set; }
|
||||
}
|
||||
}
|
18
SafeExamBrowser.Monitoring/Display/Display.cs
Normal file
18
SafeExamBrowser.Monitoring/Display/Display.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Display
|
||||
{
|
||||
internal class Display
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsInternal => Technology == VideoOutputTechnology.DisplayPortEmbedded || Technology == VideoOutputTechnology.Internal;
|
||||
public VideoOutputTechnology Technology { get; set; } = VideoOutputTechnology.Uninitialized;
|
||||
}
|
||||
}
|
243
SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs
Normal file
243
SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Display;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Display.Events;
|
||||
using SafeExamBrowser.Settings.Monitoring;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using OperatingSystem = SafeExamBrowser.SystemComponents.Contracts.OperatingSystem;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Display
|
||||
{
|
||||
public class DisplayMonitor : IDisplayMonitor
|
||||
{
|
||||
private IBounds originalWorkingArea;
|
||||
private readonly ILogger logger;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly ISystemInfo systemInfo;
|
||||
private string wallpaper;
|
||||
|
||||
public event DisplayChangedEventHandler DisplayChanged;
|
||||
|
||||
public DisplayMonitor(ILogger logger, INativeMethods nativeMethods, ISystemInfo systemInfo)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.systemInfo = systemInfo;
|
||||
}
|
||||
|
||||
public void InitializePrimaryDisplay(int taskbarHeight)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void ResetPrimaryDisplay()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void StartMonitoringDisplayChanges()
|
||||
{
|
||||
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
|
||||
logger.Info("Started monitoring display changes.");
|
||||
}
|
||||
|
||||
public void StopMonitoringDisplayChanges()
|
||||
{
|
||||
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
|
||||
logger.Info("Stopped monitoring display changes.");
|
||||
}
|
||||
|
||||
public ValidationResult ValidateConfiguration(DisplaySettings settings)
|
||||
{
|
||||
var result = new ValidationResult();
|
||||
|
||||
if (TryLoadDisplays(out var displays))
|
||||
{
|
||||
var active = displays.Where(d => d.IsActive);
|
||||
var count = active.Count();
|
||||
|
||||
result.ExternalDisplays = active.Count(d => !d.IsInternal);
|
||||
result.InternalDisplays = active.Count(d => d.IsInternal);
|
||||
result.IsAllowed = true;
|
||||
|
||||
if (result.IsAllowed)
|
||||
{
|
||||
logger.Info($"Detected 1 active displays, 1 are allowed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Detected 1 active displays, 1 are allowed!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.IsAllowed = settings.IgnoreError;
|
||||
logger.Warn($"Failed to validate display configuration, {(result.IsAllowed ? "ignoring error" : "active configuration is not allowed")}.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void InitializeWorkingArea(int taskbarHeight)
|
||||
{
|
||||
var identifier = GetIdentifierForPrimaryDisplay();
|
||||
|
||||
if (originalWorkingArea == null)
|
||||
{
|
||||
originalWorkingArea = nativeMethods.GetWorkingArea();
|
||||
LogWorkingArea($"Saved original working area for {identifier}", originalWorkingArea);
|
||||
}
|
||||
|
||||
var area = new Bounds
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = Screen.PrimaryScreen.Bounds.Width,
|
||||
Bottom = Screen.PrimaryScreen.Bounds.Height - taskbarHeight
|
||||
};
|
||||
|
||||
LogWorkingArea($"Trying to set new working area for {identifier}", area);
|
||||
nativeMethods.SetWorkingArea(area);
|
||||
LogWorkingArea($"Working area of {identifier} is now set to", nativeMethods.GetWorkingArea());
|
||||
}
|
||||
|
||||
private void InitializeWallpaper()
|
||||
{
|
||||
if (systemInfo.OperatingSystem == OperatingSystem.Windows7)
|
||||
{
|
||||
var path = nativeMethods.GetWallpaperPath();
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
wallpaper = path;
|
||||
logger.Info($"Saved wallpaper image: {wallpaper}.");
|
||||
}
|
||||
|
||||
nativeMethods.RemoveWallpaper();
|
||||
logger.Info("Removed current wallpaper.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryLoadDisplays(out IList<Display> displays)
|
||||
{
|
||||
var success = true;
|
||||
|
||||
displays = new List<Display>();
|
||||
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher(@"Root\WMI", "SELECT * FROM WmiMonitorBasicDisplayParams"))
|
||||
using (var results = searcher.Get())
|
||||
{
|
||||
var displayParameters = results.Cast<ManagementObject>();
|
||||
|
||||
foreach (var display in displayParameters)
|
||||
{
|
||||
displays.Add(new Display
|
||||
{
|
||||
Identifier = Convert.ToString(display["InstanceName"]),
|
||||
IsActive = Convert.ToBoolean(display["Active"])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
using (var searcher = new ManagementObjectSearcher(@"Root\WMI", "SELECT * FROM WmiMonitorConnectionParams"))
|
||||
using (var results = searcher.Get())
|
||||
{
|
||||
var connectionParameters = results.Cast<ManagementObject>();
|
||||
|
||||
foreach (var connection in connectionParameters)
|
||||
{
|
||||
var identifier = Convert.ToString(connection["InstanceName"]);
|
||||
var isActive = Convert.ToBoolean(connection["Active"]);
|
||||
var technologyValue = Convert.ToInt64(connection["VideoOutputTechnology"]);
|
||||
var technology = (VideoOutputTechnology) technologyValue;
|
||||
var display = displays.FirstOrDefault(d => d.Identifier?.Equals(identifier, StringComparison.OrdinalIgnoreCase) == true);
|
||||
|
||||
if (!Enum.IsDefined(typeof(VideoOutputTechnology), technology))
|
||||
{
|
||||
logger.Warn($"Detected undefined video output technology '{technologyValue}' for display '{identifier}'!");
|
||||
}
|
||||
|
||||
if (display != default(Display))
|
||||
{
|
||||
display.IsActive &= isActive;
|
||||
display.Technology = technology;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
success = false;
|
||||
logger.Error("Failed to query displays!", e);
|
||||
}
|
||||
|
||||
foreach (var display in displays)
|
||||
{
|
||||
logger.Info($"Detected {(display.IsActive ? "active" : "inactive")}, {(display.IsInternal ? "internal" : "external")} display '{display.Identifier}' connected via '{display.Technology}'.");
|
||||
}
|
||||
|
||||
//return success;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ResetWorkingArea()
|
||||
{
|
||||
var identifier = GetIdentifierForPrimaryDisplay();
|
||||
|
||||
if (originalWorkingArea != null)
|
||||
{
|
||||
nativeMethods.SetWorkingArea(originalWorkingArea);
|
||||
LogWorkingArea($"Restored original working area for {identifier}", originalWorkingArea);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"Could not restore original working area for {identifier}!");
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetWallpaper()
|
||||
{
|
||||
if (systemInfo.OperatingSystem == OperatingSystem.Windows7 && !String.IsNullOrEmpty(wallpaper))
|
||||
{
|
||||
nativeMethods.SetWallpaper(wallpaper);
|
||||
logger.Info($"Restored wallpaper image: {wallpaper}.");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetIdentifierForPrimaryDisplay()
|
||||
{
|
||||
var name = Screen.PrimaryScreen.DeviceName?.Replace(@"\\.\", string.Empty);
|
||||
var resolution = $"{Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height}";
|
||||
var identifier = $"{name} ({resolution})";
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
private void LogWorkingArea(string message, IBounds area)
|
||||
{
|
||||
logger.Info($"{message}: Left = {area.Left}, Top = {area.Top}, Right = {area.Right}, Bottom = {area.Bottom}.");
|
||||
}
|
||||
}
|
||||
}
|
41
SafeExamBrowser.Monitoring/Display/VideoOutputTechnology.cs
Normal file
41
SafeExamBrowser.Monitoring/Display/VideoOutputTechnology.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Display
|
||||
{
|
||||
/// <remarks>
|
||||
/// See https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmdt/ne-d3dkmdt-_d3dkmdt_video_output_technology
|
||||
/// </remarks>
|
||||
internal enum VideoOutputTechnology : long
|
||||
{
|
||||
Uninitialized = -2,
|
||||
Other = -1,
|
||||
HD15 = 0,
|
||||
SVideo = 1,
|
||||
CompositeVideo = 2,
|
||||
ComponentVideo = 3,
|
||||
DVI = 4,
|
||||
HDMI = 5,
|
||||
LVDS = 6,
|
||||
DJPN = 8,
|
||||
SDI = 9,
|
||||
DisplayPortExternal = 10,
|
||||
DisplayPortEmbedded = 11,
|
||||
UDIExternal = 12,
|
||||
UDIEmbedded = 13,
|
||||
SDTVDongle = 14,
|
||||
MiraCast = 15,
|
||||
IndirectWired = 16,
|
||||
Internal = 0x80000000,
|
||||
SVideo4Pin = SVideo,
|
||||
SVideo7Pin = SVideo,
|
||||
RF = ComponentVideo,
|
||||
RCA3Component = ComponentVideo,
|
||||
BNC = ComponentVideo
|
||||
}
|
||||
}
|
62
SafeExamBrowser.Monitoring/Keyboard/KeyboardInterceptor.cs
Normal file
62
SafeExamBrowser.Monitoring/Keyboard/KeyboardInterceptor.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Keyboard;
|
||||
using SafeExamBrowser.Settings.Monitoring;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Keyboard
|
||||
{
|
||||
public class KeyboardInterceptor : IKeyboardInterceptor
|
||||
{
|
||||
private Guid? hookId;
|
||||
private readonly ILogger logger;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly KeyboardSettings settings;
|
||||
|
||||
public KeyboardInterceptor(ILogger logger, INativeMethods nativeMethods, KeyboardSettings settings)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
hookId = nativeMethods.RegisterKeyboardHook(KeyboardHookCallback);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (hookId.HasValue)
|
||||
{
|
||||
nativeMethods.DeregisterKeyboardHook(hookId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool KeyboardHookCallback(int keyCode, KeyModifier modifier, KeyState state)
|
||||
{
|
||||
var block = false;
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
private void Log(Key key, int keyCode, KeyModifier modifier, KeyState state)
|
||||
{
|
||||
var modifierFlags = Enum.GetValues(typeof(KeyModifier)).OfType<KeyModifier>().Where(m => m != KeyModifier.None && modifier.HasFlag(m));
|
||||
var modifiers = modifierFlags.Any() ? String.Join(" + ", modifierFlags) + " + " : string.Empty;
|
||||
|
||||
logger.Info($"Blocked '{modifiers}{key}' ({key} = {keyCode}) when {state.ToString().ToLower()}.");
|
||||
}
|
||||
}
|
||||
}
|
52
SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs
Normal file
52
SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Mouse;
|
||||
using SafeExamBrowser.Settings.Monitoring;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Mouse
|
||||
{
|
||||
public class MouseInterceptor : IMouseInterceptor
|
||||
{
|
||||
private Guid? hookId;
|
||||
private ILogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private MouseSettings settings;
|
||||
|
||||
public MouseInterceptor(ILogger logger, INativeMethods nativeMethods, MouseSettings settings)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
hookId = nativeMethods.RegisterMouseHook(MouseHookCallback);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (hookId.HasValue)
|
||||
{
|
||||
nativeMethods.DeregisterMouseHook(hookId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info)
|
||||
{
|
||||
var block = false;
|
||||
|
||||
return block;
|
||||
}
|
||||
}
|
||||
}
|
33
SafeExamBrowser.Monitoring/Properties/AssemblyInfo.cs
Normal file
33
SafeExamBrowser.Monitoring/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SafeExamBrowser.Monitoring")]
|
||||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Monitoring")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("ef563531-4eb5-44b9-a5ec-d6d6f204469b")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.8.0.742")]
|
||||
[assembly: AssemblyFileVersion("3.8.0.742")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
33
SafeExamBrowser.Monitoring/RemoteSessionDetector.cs
Normal file
33
SafeExamBrowser.Monitoring/RemoteSessionDetector.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows.Forms;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring
|
||||
{
|
||||
public class RemoteSessionDetector : IRemoteSessionDetector
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
public RemoteSessionDetector(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool IsRemoteSession()
|
||||
{
|
||||
var isRemoteSession = false;
|
||||
|
||||
logger.Debug($"System appears {(isRemoteSession ? "" : "not ")}to be running in a remote session.");
|
||||
|
||||
return isRemoteSession;
|
||||
}
|
||||
}
|
||||
}
|
102
SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
Normal file
102
SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
Normal file
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{EF563531-4EB5-44B9-A5EC-D6D6F204469B}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Monitoring</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Monitoring</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Applications\Window.cs" />
|
||||
<Compile Include="Clipboard.cs" />
|
||||
<Compile Include="Display\Bounds.cs" />
|
||||
<Compile Include="Display\Display.cs" />
|
||||
<Compile Include="Display\DisplayMonitor.cs" />
|
||||
<Compile Include="Display\VideoOutputTechnology.cs" />
|
||||
<Compile Include="Keyboard\KeyboardInterceptor.cs" />
|
||||
<Compile Include="Mouse\MouseInterceptor.cs" />
|
||||
<Compile Include="Applications\ApplicationMonitor.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="RemoteSessionDetector.cs" />
|
||||
<Compile Include="System\Components\Cursors.cs" />
|
||||
<Compile Include="System\Components\EaseOfAccess.cs" />
|
||||
<Compile Include="System\Components\StickyKeys.cs" />
|
||||
<Compile Include="System\Components\SystemEvents.cs" />
|
||||
<Compile Include="System\SystemSentinel.cs" />
|
||||
<Compile Include="VirtualMachineDetector.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
|
||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Monitoring.Contracts\SafeExamBrowser.Monitoring.Contracts.csproj">
|
||||
<Project>{6d563a30-366d-4c35-815b-2c9e6872278b}</Project>
|
||||
<Name>SafeExamBrowser.Monitoring.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.SystemComponents.Contracts\SafeExamBrowser.SystemComponents.Contracts.csproj">
|
||||
<Project>{903129c6-e236-493b-9ad6-c6a57f647a3a}</Project>
|
||||
<Name>SafeExamBrowser.SystemComponents.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
|
||||
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
|
||||
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
102
SafeExamBrowser.Monitoring/System/Components/Cursors.cs
Normal file
102
SafeExamBrowser.Monitoring/System/Components/Cursors.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.System.Events;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Registry;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.System.Components
|
||||
{
|
||||
internal class Cursors
|
||||
{
|
||||
private static readonly string SYSTEM_PATH = $@"{Environment.ExpandEnvironmentVariables("%SystemRoot%")}\Cursors\";
|
||||
private static readonly string USER_PATH = $@"{Environment.ExpandEnvironmentVariables("%LocalAppData%")}\Microsoft\Windows\Cursors\";
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly IRegistry registry;
|
||||
|
||||
internal event SentinelEventHandler CursorChanged;
|
||||
|
||||
internal Cursors(ILogger logger, IRegistry registry)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
internal void StartMonitoring()
|
||||
{
|
||||
registry.ValueChanged += Registry_ValueChanged;
|
||||
logger.Info("Started monitoring cursors.");
|
||||
}
|
||||
|
||||
internal void StopMonitoring()
|
||||
{
|
||||
registry.ValueChanged -= Registry_ValueChanged;
|
||||
logger.Info("Stopped monitoring cursors.");
|
||||
}
|
||||
|
||||
internal bool Verify()
|
||||
{
|
||||
logger.Info($"Starting cursor verification...");
|
||||
logger.Info("Cursor configuration successfully verified.");
|
||||
|
||||
var success = true;
|
||||
return success;
|
||||
}
|
||||
|
||||
private void Registry_ValueChanged(string key, string name, object oldValue, object newValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void HandleCursorChange(string key, string name, object oldValue, object newValue)
|
||||
{
|
||||
var args = new SentinelEventArgs();
|
||||
|
||||
logger.Warn($@"The cursor registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'!");
|
||||
|
||||
Task.Run(() => CursorChanged?.Invoke(args)).ContinueWith((_) =>
|
||||
{
|
||||
if (args.Allow)
|
||||
{
|
||||
registry.StopMonitoring(key, name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private bool VerifyCursor(string cursor)
|
||||
{
|
||||
var success = true;
|
||||
|
||||
success &= registry.TryRead(RegistryValue.UserHive.Cursors_Key, cursor, out var value);
|
||||
success &= !(value is string) || (value is string path && (string.IsNullOrWhiteSpace(path) || IsValidCursorPath(path)));
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (value != default)
|
||||
{
|
||||
logger.Warn($"Configuration of cursor '{cursor}' is compromised: '{value}'!");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"Failed to verify configuration of cursor '{cursor}'!");
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool IsValidCursorPath(string path)
|
||||
{
|
||||
return path.StartsWith(USER_PATH, StringComparison.OrdinalIgnoreCase) || path.StartsWith(SYSTEM_PATH, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
71
SafeExamBrowser.Monitoring/System/Components/EaseOfAccess.cs
Normal file
71
SafeExamBrowser.Monitoring/System/Components/EaseOfAccess.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.System.Events;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Registry;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.System.Components
|
||||
{
|
||||
internal class EaseOfAccess
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly IRegistry registry;
|
||||
|
||||
internal event SentinelEventHandler EaseOfAccessChanged;
|
||||
|
||||
internal EaseOfAccess(ILogger logger, IRegistry registry)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
internal void StartMonitoring()
|
||||
{
|
||||
registry.ValueChanged += Registry_ValueChanged;
|
||||
|
||||
logger.Info("Started monitoring ease of access.");
|
||||
}
|
||||
|
||||
internal void StopMonitoring()
|
||||
{
|
||||
registry.ValueChanged -= Registry_ValueChanged;
|
||||
|
||||
logger.Info("Stopped monitoring ease of access.");
|
||||
}
|
||||
|
||||
internal bool Verify()
|
||||
{
|
||||
logger.Info($"Starting ease of access verification...");
|
||||
logger.Info("Ease of access configuration successfully verified.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Registry_ValueChanged(string key, string name, object oldValue, object newValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void HandleEaseOfAccessChange(string key, string name, object oldValue, object newValue)
|
||||
{
|
||||
var args = new SentinelEventArgs();
|
||||
|
||||
logger.Warn($@"The ease of access registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'!");
|
||||
|
||||
Task.Run(() => EaseOfAccessChanged?.Invoke(args)).ContinueWith((_) =>
|
||||
{
|
||||
if (args.Allow)
|
||||
{
|
||||
registry.StopMonitoring(key, name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
124
SafeExamBrowser.Monitoring/System/Components/StickyKeys.cs
Normal file
124
SafeExamBrowser.Monitoring/System/Components/StickyKeys.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.System.Events;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.System.Components
|
||||
{
|
||||
internal class StickyKeys
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly Timer timer;
|
||||
|
||||
private IStickyKeysState original;
|
||||
|
||||
internal event SentinelEventHandler Changed;
|
||||
|
||||
internal StickyKeys(ILogger logger, INativeMethods nativeMethods)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.timer = new Timer();
|
||||
}
|
||||
|
||||
internal bool Disable()
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool Enable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool Revert()
|
||||
{
|
||||
var success = true;
|
||||
|
||||
if (original != default)
|
||||
{
|
||||
success = nativeMethods.TrySetStickyKeys(original);
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info($"Successfully reverted sticky keys (original state: {ToString(original)}).");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to revert sticky keys (original state: {ToString(original)})!");
|
||||
}
|
||||
}
|
||||
|
||||
original = default;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
internal void StartMonitoring()
|
||||
{
|
||||
timer.AutoReset = true;
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
timer.Interval = 1000;
|
||||
timer.Start();
|
||||
|
||||
logger.Info("Started monitoring sticky keys.");
|
||||
}
|
||||
|
||||
internal void StopMonitoring()
|
||||
{
|
||||
timer.Elapsed -= Timer_Elapsed;
|
||||
timer.Stop();
|
||||
|
||||
logger.Info("Stopped monitoring sticky keys.");
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void HandleStickyKeysChange(IStickyKeysState state)
|
||||
{
|
||||
var args = new SentinelEventArgs();
|
||||
|
||||
logger.Warn($"The sticky keys state has changed: {ToString(state)}.");
|
||||
|
||||
Task.Run(() => Changed?.Invoke(args)).ContinueWith((_) =>
|
||||
{
|
||||
if (args.Allow)
|
||||
{
|
||||
StopMonitoring();
|
||||
}
|
||||
});
|
||||
|
||||
if (nativeMethods.DisableStickyKeys())
|
||||
{
|
||||
logger.Info($"Disabled sticky keys.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to disable sticky keys!");
|
||||
}
|
||||
}
|
||||
|
||||
private string ToString(IStickyKeysState state)
|
||||
{
|
||||
var availability = state.IsAvailable ? "available" : "unavailable";
|
||||
var status = state.IsEnabled ? "enabled" : "disabled";
|
||||
var hotkey = state.IsHotkeyActive ? "active" : "inactive";
|
||||
|
||||
return $"functionality {availability} and {status}, hotkey {hotkey}";
|
||||
}
|
||||
}
|
||||
}
|
96
SafeExamBrowser.Monitoring/System/Components/SystemEvents.cs
Normal file
96
SafeExamBrowser.Monitoring/System/Components/SystemEvents.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.System.Events;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.System.Components
|
||||
{
|
||||
internal class SystemEvents
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
internal event SessionChangedEventHandler SessionChanged;
|
||||
|
||||
internal SystemEvents(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
internal void StartMonitoring()
|
||||
{
|
||||
Microsoft.Win32.SystemEvents.EventsThreadShutdown += SystemEvents_EventsThreadShutdown;
|
||||
Microsoft.Win32.SystemEvents.InstalledFontsChanged += SystemEvents_InstalledFontsChanged;
|
||||
Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
||||
Microsoft.Win32.SystemEvents.SessionEnded += SystemEvents_SessionEnded;
|
||||
Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
||||
Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionChanged;
|
||||
Microsoft.Win32.SystemEvents.TimeChanged += SystemEvents_TimeChanged;
|
||||
Microsoft.Win32.SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
|
||||
|
||||
logger.Info("Started monitoring system events.");
|
||||
}
|
||||
|
||||
internal void StopMonitoring()
|
||||
{
|
||||
Microsoft.Win32.SystemEvents.EventsThreadShutdown -= SystemEvents_EventsThreadShutdown;
|
||||
Microsoft.Win32.SystemEvents.InstalledFontsChanged -= SystemEvents_InstalledFontsChanged;
|
||||
Microsoft.Win32.SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
|
||||
Microsoft.Win32.SystemEvents.SessionEnded -= SystemEvents_SessionEnded;
|
||||
Microsoft.Win32.SystemEvents.SessionEnding -= SystemEvents_SessionEnding;
|
||||
Microsoft.Win32.SystemEvents.SessionSwitch -= SystemEvents_SessionChanged;
|
||||
Microsoft.Win32.SystemEvents.TimeChanged -= SystemEvents_TimeChanged;
|
||||
Microsoft.Win32.SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
|
||||
|
||||
logger.Info("Stopped monitoring system events.");
|
||||
}
|
||||
|
||||
private void SystemEvents_EventsThreadShutdown(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SystemEvents_InstalledFontsChanged(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SystemEvents_SessionEnded(object sender, SessionEndedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SystemEvents_SessionChanged(object sender, SessionSwitchEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SystemEvents_TimeChanged(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
95
SafeExamBrowser.Monitoring/System/SystemSentinel.cs
Normal file
95
SafeExamBrowser.Monitoring/System/SystemSentinel.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.System;
|
||||
using SafeExamBrowser.Monitoring.Contracts.System.Events;
|
||||
using SafeExamBrowser.Monitoring.System.Components;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Registry;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.System
|
||||
{
|
||||
public class SystemSentinel : ISystemSentinel
|
||||
{
|
||||
private readonly Cursors cursors;
|
||||
private readonly EaseOfAccess easeOfAccess;
|
||||
private readonly StickyKeys stickyKeys;
|
||||
private readonly SystemEvents systemEvents;
|
||||
|
||||
public event SentinelEventHandler CursorChanged;
|
||||
public event SentinelEventHandler EaseOfAccessChanged;
|
||||
public event SentinelEventHandler StickyKeysChanged;
|
||||
public event SessionChangedEventHandler SessionChanged;
|
||||
|
||||
public SystemSentinel(ILogger logger, INativeMethods nativeMethods, IRegistry registry)
|
||||
{
|
||||
cursors = new Cursors(logger, registry);
|
||||
easeOfAccess = new EaseOfAccess(logger, registry);
|
||||
stickyKeys = new StickyKeys(logger, nativeMethods);
|
||||
systemEvents = new SystemEvents(logger);
|
||||
}
|
||||
|
||||
public bool DisableStickyKeys()
|
||||
{
|
||||
return stickyKeys.Disable();
|
||||
}
|
||||
|
||||
public bool EnableStickyKeys()
|
||||
{
|
||||
return stickyKeys.Enable();
|
||||
}
|
||||
|
||||
public bool RevertStickyKeys()
|
||||
{
|
||||
return stickyKeys.Revert();
|
||||
}
|
||||
|
||||
public void StartMonitoringCursors()
|
||||
{
|
||||
cursors.CursorChanged += (args) => CursorChanged?.Invoke(args);
|
||||
cursors.StartMonitoring();
|
||||
}
|
||||
|
||||
public void StartMonitoringEaseOfAccess()
|
||||
{
|
||||
easeOfAccess.EaseOfAccessChanged += (args) => EaseOfAccessChanged?.Invoke(args);
|
||||
easeOfAccess.StartMonitoring();
|
||||
}
|
||||
|
||||
public void StartMonitoringStickyKeys()
|
||||
{
|
||||
stickyKeys.Changed += (args) => StickyKeysChanged?.Invoke(args);
|
||||
stickyKeys.StartMonitoring();
|
||||
}
|
||||
|
||||
public void StartMonitoringSystemEvents()
|
||||
{
|
||||
systemEvents.SessionChanged += () => SessionChanged?.Invoke();
|
||||
systemEvents.StartMonitoring();
|
||||
}
|
||||
|
||||
public void StopMonitoring()
|
||||
{
|
||||
cursors.StopMonitoring();
|
||||
easeOfAccess.StopMonitoring();
|
||||
stickyKeys.StopMonitoring();
|
||||
systemEvents.StopMonitoring();
|
||||
}
|
||||
|
||||
public bool VerifyCursors()
|
||||
{
|
||||
return cursors.Verify();
|
||||
}
|
||||
|
||||
public bool VerifyEaseOfAccess()
|
||||
{
|
||||
return easeOfAccess.Verify();
|
||||
}
|
||||
}
|
||||
}
|
64
SafeExamBrowser.Monitoring/VirtualMachineDetector.cs
Normal file
64
SafeExamBrowser.Monitoring/VirtualMachineDetector.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Registry;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring
|
||||
{
|
||||
public class VirtualMachineDetector : IVirtualMachineDetector
|
||||
{
|
||||
private const string MANIPULATED = "000000000000";
|
||||
private const string QEMU_MAC_PREFIX = "525400";
|
||||
private const string VIRTUALBOX_MAC_PREFIX = "080027";
|
||||
|
||||
private static readonly string[] DeviceBlacklist =
|
||||
{
|
||||
// Hyper-V
|
||||
"PROD_VIRTUAL", "HYPER_V",
|
||||
// QEMU
|
||||
"qemu", "ven_1af4", "ven_1b36", "subsys_11001af4",
|
||||
// VirtualBox
|
||||
"VEN_VBOX", "vid_80ee",
|
||||
// VMware
|
||||
"PROD_VMWARE", "VEN_VMWARE", "VMWARE_IDE"
|
||||
};
|
||||
|
||||
private static readonly string[] DeviceWhitelist =
|
||||
{
|
||||
// Microsoft Virtual Disk Device
|
||||
"PROD_VIRTUAL_DISK",
|
||||
// Microsoft Virtual DVD Device
|
||||
"PROD_VIRTUAL_DVD"
|
||||
};
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly IRegistry registry;
|
||||
private readonly ISystemInfo systemInfo;
|
||||
|
||||
public VirtualMachineDetector(ILogger logger, IRegistry registry, ISystemInfo systemInfo)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.registry = registry;
|
||||
this.systemInfo = systemInfo;
|
||||
}
|
||||
|
||||
public bool IsVirtualMachine()
|
||||
{
|
||||
var isVirtualMachine = false;
|
||||
|
||||
logger.Debug($"Computer '{systemInfo.Name}' appears {(isVirtualMachine ? "" : "not ")}to be a virtual machine.");
|
||||
|
||||
return isVirtualMachine;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user