diff --git a/build/Version.props b/build/Version.props index b169916252..11f29c9ea4 100644 --- a/build/Version.props +++ b/build/Version.props @@ -2,9 +2,9 @@ - 4.2.1 - 6.2.0 - 6.1.0 + 4.2.2 + 6.3.0 + 6.2.0 5.1.1 0.4.0 1.1.0 diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 9de878067c..7b34459278 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -471,5 +471,17 @@ namespace Tgstation.Server.Api.Models /// [Description("Cannot set both softShutdown and softReboot at once!")] DreamDaemonDoubleSoft, + + /// + /// Attempted to launch DreamDaemon on a user account that had the BYOND pager running. + /// + [Description("Cannot start DreamDaemon headless with the BYOND pager running!")] + DeploymentPagerRunning, + + /// + /// Could not bind to port we wanted to launch DreamDaemon on. + /// + [Description("Could not bind to requested DreamDaemon port! Is there another service running on that port?")] + DreamDaemonPortInUse, } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 8ef9daa01a..9e7052bcfb 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -225,7 +225,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers return channelModel; } - Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel.GetType()); + Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType()); return null; } diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs index bb7d85414c..e0a397fae3 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs @@ -10,11 +10,11 @@ namespace Tgstation.Server.Host.Components.Interop.Topic /// /// The text to reply with as the result of a request, if any. /// - public string CommandResponseMessage { get; private set; } + public string CommandResponseMessage { get; set; } /// /// The s to send as the result of a request, if any. /// - public ICollection ChatResponses { get; private set; } + public ICollection ChatResponses { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 92a76a973b..be732f40f8 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -3,6 +3,8 @@ using Microsoft.Extensions.Logging; using System; using System.Globalization; using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -110,6 +112,24 @@ namespace Tgstation.Server.Host.Components.Session }; } + /// + /// Check if a given can be bound to. + /// + /// The port number to test. + static void PortBindTest(ushort port) + { + using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + try + { + socket.Bind(new IPEndPoint(IPAddress.Loopback, port)); + } + catch (Exception ex) + { + throw new JobException(ErrorCode.DreamDaemonPortInUse, ex); + } + } + /// /// Construct a /// @@ -201,7 +221,8 @@ namespace Tgstation.Server.Host.Components.Session if (launchParameters.SecurityLevel == DreamDaemonSecurity.Trusted) await byondLock.TrustDmbPath(ioManager.ConcatPath(basePath, dmbProvider.DmbName), cancellationToken).ConfigureAwait(false); - CheckPagerIsNotRunning(); + PortBindTest(portToUse.Value); + await CheckPagerIsNotRunning(cancellationToken).ConfigureAwait(false); var accessIdentifier = cryptographySuite.GetSecureString(); @@ -214,7 +235,7 @@ namespace Tgstation.Server.Host.Components.Session // important to run on all ports to allow port changing var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4} -public -params \"{5}\"", dmbProvider.DmbName, - primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, + portToUse, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel.Value), visibility, @@ -403,10 +424,24 @@ namespace Tgstation.Server.Host.Components.Session /// /// Make sure the BYOND pager is not running. /// - void CheckPagerIsNotRunning() + /// The for the operation. + /// A representing the running operation. + async Task CheckPagerIsNotRunning(CancellationToken cancellationToken) { - if (platformIdentifier.IsWindows && processExecutor.IsProcessWithNameRunning("byond")) - throw new JobException("Cannot start DreamDaemon headless with the BYOND pager running!"); + if (!platformIdentifier.IsWindows) + return; + + using var otherProcess = processExecutor.GetProcessByName("byond"); + if (otherProcess == null) + return; + + var otherUsernameTask = otherProcess.GetExecutingUsername(cancellationToken); + using var ourProcess = processExecutor.GetCurrentProcess(); + var ourUserName = await ourProcess.GetExecutingUsername(cancellationToken).ConfigureAwait(false); + var otherUserName = await otherUsernameTask.ConfigureAwait(false); + + if(otherUserName.Equals(ourUserName, StringComparison.Ordinal)) + throw new JobException(ErrorCode.DeploymentPagerRunning); } } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 17ebbe5a5e..bcc44ee87f 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -471,6 +471,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles var scriptOutput = script.GetCombinedOutput(); if (exitCode != 0) throw new JobException($"Script {I} exited with code {exitCode}:{Environment.NewLine}{scriptOutput}"); + else + logger.LogDebug("Script output:{0}{1}", Environment.NewLine, scriptOutput); } } } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 60ed998fd7..c4e1dc86cf 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -235,7 +235,7 @@ namespace Tgstation.Server.Host.Controllers PrimaryPort = 1337, SecondaryPort = 1338, SecurityLevel = DreamDaemonSecurity.Safe, - StartupTimeout = 20, + StartupTimeout = 60, HeartbeatSeconds = 60 }, DreamMakerSettings = new DreamMakerSettings diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index ca23b3e6f2..f0780d6815 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -254,7 +254,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); @@ -267,7 +267,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); } @@ -369,6 +369,7 @@ namespace Tgstation.Server.Host.Core { applicationBuilder.UseSwagger(); applicationBuilder.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TGS API V4")); + logger.LogTrace("Swagger API generation enabled"); } // Set up CORS based on configuration if necessary diff --git a/src/Tgstation.Server.Host/System/IProcess.cs b/src/Tgstation.Server.Host/System/IProcess.cs index 655374fbfa..b813d95bbc 100644 --- a/src/Tgstation.Server.Host/System/IProcess.cs +++ b/src/Tgstation.Server.Host/System/IProcess.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Host.System { @@ -39,5 +40,12 @@ namespace Tgstation.Server.Host.System /// Terminates the process /// void Terminate(); + + /// + /// Get the name of the account executing the . + /// + /// The for the operation. + /// A resulting in the name of the account executing the . + Task GetExecutingUsername(CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/System/IProcessExecutor.cs b/src/Tgstation.Server.Host/System/IProcessExecutor.cs index 6b732915dd..3dea91680b 100644 --- a/src/Tgstation.Server.Host/System/IProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/IProcessExecutor.cs @@ -18,17 +18,23 @@ IProcess LaunchProcess(string fileName, string workingDirectory, string arguments = null, bool readOutput = false, bool readError = false, bool noShellExecute = false); /// - /// Get a by + /// Get a representing the running executable. /// - /// The - /// The represented by on success, on failure + /// The current . + IProcess GetCurrentProcess(); + + /// + /// Get a by . + /// + /// The . + /// The represented by on success, on failure. IProcess GetProcess(int id); /// - /// Check if a with a given is running. + /// Get a with a given . /// - /// The name of the process without the extension. - /// if the process is running, otherwise. - bool IsProcessWithNameRunning(string name); + /// The name of the process executable without the extension. + /// The represented by on success, on failure. + IProcess GetProcessByName(string name); } } diff --git a/src/Tgstation.Server.Host/System/IProcessFeatures.cs b/src/Tgstation.Server.Host/System/IProcessFeatures.cs new file mode 100644 index 0000000000..c56d03f5d0 --- /dev/null +++ b/src/Tgstation.Server.Host/System/IProcessFeatures.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.System +{ + /// + /// Abstraction for suspending and resuming processes. + /// + interface IProcessFeatures + { + /// + /// Get the name of the user executing a given . + /// + /// The . + /// The for the operation. + /// The name of the user executing . + Task GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken); + + /// + /// Suspend a given . + /// + /// The to suspend. + void SuspendProcess(global::System.Diagnostics.Process process); + + /// + /// Resume a given suspended . + /// + /// The to susperesumend. + void ResumeProcess(global::System.Diagnostics.Process process); + } +} diff --git a/src/Tgstation.Server.Host/System/IProcessSuspender.cs b/src/Tgstation.Server.Host/System/IProcessSuspender.cs deleted file mode 100644 index 8493b720ee..0000000000 --- a/src/Tgstation.Server.Host/System/IProcessSuspender.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Tgstation.Server.Host.System -{ - /// - /// Abstraction for suspending and resuming processes. - /// - interface IProcessSuspender - { - /// - /// Suspend a given . - /// - /// The to suspend. - void SuspendProcess(global::System.Diagnostics.Process process); - - /// - /// Resume a given suspended . - /// - /// The to susperesumend. - void ResumeProcess(global::System.Diagnostics.Process process); - } -} diff --git a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs new file mode 100644 index 0000000000..751bd47eff --- /dev/null +++ b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs @@ -0,0 +1,97 @@ +using Microsoft.Extensions.Logging; +using Mono.Unix; +using Mono.Unix.Native; +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; + +namespace Tgstation.Server.Host.System +{ + /// + sealed class PosixProcessFeatures : IProcessFeatures + { + /// + /// The for the . + /// + readonly IIOManager ioManager; + + /// + /// The for the . + /// + readonly ILogger logger; + + /// + /// Initializes a new instance of the . + /// + /// The value of . + /// The value of . + public PosixProcessFeatures(IIOManager ioManager, ILogger logger) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + public void ResumeProcess(global::System.Diagnostics.Process process) + { + try + { + var result = Syscall.kill(process.Id, Signum.SIGCONT); + if (result != 0) + throw new UnixIOException(result); + logger.LogTrace("Resumed PID {0}", process.Id); + } + catch (Exception e) + { + logger.LogError(e, "Failed to resume PID {0}!", process.Id); + throw; + } + } + + /// + public void SuspendProcess(global::System.Diagnostics.Process process) + { + try + { + var result = Syscall.kill(process.Id, Signum.SIGSTOP); + if (result != 0) + throw new UnixIOException(result); + logger.LogTrace("Resumed PID {0}", process.Id); + } + catch (Exception e) + { + logger.LogError(e, "Failed to suspend PID {0}!", process.Id); + throw; + } + } + + /// + public async Task GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken) + { + if (process == null) + throw new ArgumentNullException(nameof(process)); + + // Need to read /proc/[pid]/status + // http://man7.org/linux/man-pages/man5/proc.5.html + // https://unix.stackexchange.com/questions/102676/why-is-uid-information-not-in-proc-x-stat + var pid = process.Id; + var statusFile = ioManager.ConcatPath("/proc", pid.ToString(CultureInfo.InvariantCulture), "status"); + var statusBytes = await ioManager.ReadAllBytes(statusFile, cancellationToken).ConfigureAwait(false); + var statusText = Encoding.UTF8.GetString(statusBytes); + var splits = statusText.Split('\n', StringSplitOptions.RemoveEmptyEntries); + var entry = splits.FirstOrDefault(x => x.Trim().StartsWith("Uid:", StringComparison.Ordinal)); + if (entry == default) + return "UNKNOWN"; + + return entry + .Substring(4) + .Split(' ', StringSplitOptions.RemoveEmptyEntries) + .FirstOrDefault(x => !String.IsNullOrWhiteSpace(x)) + ?? "UNPARSABLE"; + } + } +} diff --git a/src/Tgstation.Server.Host/System/PosixProcessSuspender.cs b/src/Tgstation.Server.Host/System/PosixProcessSuspender.cs deleted file mode 100644 index 8ddf1fad90..0000000000 --- a/src/Tgstation.Server.Host/System/PosixProcessSuspender.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.Extensions.Logging; -using Mono.Unix; -using Mono.Unix.Native; -using System; - -namespace Tgstation.Server.Host.System -{ - /// - sealed class PosixProcessSuspender : IProcessSuspender - { - /// - /// The for the . - /// - readonly ILogger logger; - - /// - /// Initializes a new instance of the . - /// - /// The value of . - public PosixProcessSuspender(ILogger logger) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - public void ResumeProcess(global::System.Diagnostics.Process process) - { - try - { - var result = Syscall.kill(process.Id, Signum.SIGCONT); - if (result != 0) - throw new UnixIOException(result); - logger.LogTrace("Resumed PID {0}", process.Id); - } - catch (Exception e) - { - logger.LogError(e, "Failed to resume PID {0}!", process.Id); - throw; - } - } - - /// - public void SuspendProcess(global::System.Diagnostics.Process process) - { - try - { - var result = Syscall.kill(process.Id, Signum.SIGSTOP); - if (result != 0) - throw new UnixIOException(result); - logger.LogTrace("Resumed PID {0}", process.Id); - } - catch (Exception e) - { - logger.LogError(e, "Failed to suspend PID {0}!", process.Id); - throw; - } - } - } -} diff --git a/src/Tgstation.Server.Host/System/Process.cs b/src/Tgstation.Server.Host/System/Process.cs index a722179341..044f5fb34c 100644 --- a/src/Tgstation.Server.Host/System/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.System @@ -19,9 +20,9 @@ namespace Tgstation.Server.Host.System public Task Lifetime { get; } /// - /// The for the . + /// The for the . /// - readonly IProcessSuspender processSuspender; + readonly IProcessFeatures processFeatures; /// /// The for the @@ -37,7 +38,7 @@ namespace Tgstation.Server.Host.System /// /// Construct a /// - /// The value of + /// The value of /// The value of /// The value of /// The value of @@ -46,7 +47,7 @@ namespace Tgstation.Server.Host.System /// The value of /// If was NOT just created public Process( - IProcessSuspender processSuspender, + IProcessFeatures processFeatures, global::System.Diagnostics.Process handle, Task lifetime, StringBuilder outputStringBuilder, @@ -55,7 +56,7 @@ namespace Tgstation.Server.Host.System ILogger logger, bool preExisting) { - this.processSuspender = processSuspender ?? throw new ArgumentNullException(nameof(processSuspender)); + this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures)); this.handle = handle ?? throw new ArgumentNullException(nameof(handle)); this.outputStringBuilder = outputStringBuilder; @@ -152,9 +153,17 @@ namespace Tgstation.Server.Host.System } /// - public void Suspend() => processSuspender.SuspendProcess(handle); + public void Suspend() => processFeatures.SuspendProcess(handle); /// - public void Resume() => processSuspender.ResumeProcess(handle); + public void Resume() => processFeatures.ResumeProcess(handle); + + /// + public async Task GetExecutingUsername(CancellationToken cancellationToken) + { + var result = await processFeatures.GetExecutingUsername(handle, cancellationToken).ConfigureAwait(false); + logger.LogTrace("PID {0} Username: {1}", Id, result); + return result; + } } } diff --git a/src/Tgstation.Server.Host/System/ProcessExecutor.cs b/src/Tgstation.Server.Host/System/ProcessExecutor.cs index 772c754131..9284a0703e 100644 --- a/src/Tgstation.Server.Host/System/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/ProcessExecutor.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Logging; using System; -using System.Linq; using System.Text; using System.Threading.Tasks; @@ -10,9 +9,9 @@ namespace Tgstation.Server.Host.System sealed class ProcessExecutor : IProcessExecutor { /// - /// The for the . + /// The for the . /// - readonly IProcessSuspender processSuspender; + readonly IProcessFeatures processFeatures; /// /// The for the @@ -55,15 +54,15 @@ namespace Tgstation.Server.Host.System /// /// Construct a /// - /// The value of . + /// The value of . /// The value of /// The value of public ProcessExecutor( - IProcessSuspender processSuspender, + IProcessFeatures processFeatures, ILogger logger, ILoggerFactory loggerFactory) { - this.processSuspender = processSuspender ?? throw new ArgumentNullException(nameof(processSuspender)); + this.processFeatures = processFeatures ?? throw new ArgumentNullException(nameof(processFeatures)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } @@ -83,23 +82,15 @@ namespace Tgstation.Server.Host.System return null; } - try - { - return new Process( - processSuspender, - handle, - AttachExitHandler(handle), - null, - null, - null, - loggerFactory.CreateLogger(), - true); - } - catch - { - handle.Dispose(); - throw; - } + return CreateFromExistingHandle(handle); + } + + /// + public IProcess GetCurrentProcess() + { + logger.LogTrace("Getting current process..."); + var handle = global::System.Diagnostics.Process.GetCurrentProcess(); + return CreateFromExistingHandle(handle); } /// @@ -215,7 +206,7 @@ namespace Tgstation.Server.Host.System catch (InvalidOperationException) { } return new Process( - processSuspender, + processFeatures, handle, lifetimeTask, outputStringBuilder, @@ -231,13 +222,50 @@ namespace Tgstation.Server.Host.System } /// - public bool IsProcessWithNameRunning(string name) + public IProcess GetProcessByName(string name) { + logger.LogTrace("GetProcessByName: {0}...", name ?? throw new ArgumentNullException(nameof(name))); var procs = global::System.Diagnostics.Process.GetProcessesByName(name); + global::System.Diagnostics.Process handle = null; foreach (var proc in procs) - proc.Dispose(); + if (handle == null) + handle = proc; + else + { + logger.LogTrace("Disposing extra found PID: {0}", proc.Id); + proc.Dispose(); + } - return procs.Any(); + if (handle == null) + return null; + + return CreateFromExistingHandle(handle); + } + + /// + /// Create a given an existing . + /// + /// The to create a from. + /// The based on . + private IProcess CreateFromExistingHandle(global::System.Diagnostics.Process handle) + { + try + { + return new Process( + processFeatures, + handle, + AttachExitHandler(handle), + null, + null, + null, + loggerFactory.CreateLogger(), + true); + } + catch + { + handle.Dispose(); + throw; + } } } } diff --git a/src/Tgstation.Server.Host/System/WindowsProcessSuspender.cs b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs similarity index 57% rename from src/Tgstation.Server.Host/System/WindowsProcessSuspender.cs rename to src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs index 9f00857fdb..4ea3a49a74 100644 --- a/src/Tgstation.Server.Host/System/WindowsProcessSuspender.cs +++ b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs @@ -2,22 +2,26 @@ using Microsoft.Extensions.Logging; using System; using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Host.System { /// - sealed class WindowsProcessSuspender : IProcessSuspender + sealed class WindowsProcessFeatures : IProcessFeatures { /// - /// The for the . + /// The for the . /// - readonly ILogger logger; + readonly ILogger logger; /// - /// Initializes a new instance of the . + /// Initializes a new instance of the . /// /// The value of . - public WindowsProcessSuspender(ILogger logger) + public WindowsProcessFeatures(ILogger logger) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -25,6 +29,9 @@ namespace Tgstation.Server.Host.System /// public void ResumeProcess(global::System.Diagnostics.Process process) { + if (process == null) + throw new ArgumentNullException(nameof(process)); + try { foreach (ProcessThread thread in process.Threads) @@ -51,6 +58,9 @@ namespace Tgstation.Server.Host.System /// public void SuspendProcess(global::System.Diagnostics.Process process) { + if (process == null) + throw new ArgumentNullException(nameof(process)); + try { foreach (ProcessThread thread in process.Threads) @@ -73,5 +83,32 @@ namespace Tgstation.Server.Host.System throw; } } + + /// + public Task GetExecutingUsername(global::System.Diagnostics.Process process, CancellationToken cancellationToken) + { + string query = $"SELECT * FROM Win32_Process WHERE ProcessId = {process?.Id ?? throw new ArgumentNullException(nameof(process))}"; + using var searcher = new ManagementObjectSearcher(query); + foreach (ManagementObject obj in searcher.Get()) + { + var argList = new string[] { String.Empty, String.Empty }; + var returnString = obj.InvokeMethod( + "GetOwner", + argList) + ?.ToString(); + + if (!Int32.TryParse(returnString, out var returnVal)) + return Task.FromResult($"BAD RETURN PARSE: {returnString}"); + + if (returnVal == 0) + { + // return DOMAIN\user + string owner = argList.Last() + "\\" + argList.First(); + return Task.FromResult(owner); + } + } + + return Task.FromResult("NO OWNER"); + } } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index dcea518351..ef26e99047 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -89,6 +89,7 @@ + diff --git a/tests/Tgstation.Server.Host.Tests/System/TestProcessFeatures.cs b/tests/Tgstation.Server.Host.Tests/System/TestProcessFeatures.cs new file mode 100644 index 0000000000..065813a50c --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/System/TestProcessFeatures.cs @@ -0,0 +1,37 @@ +using Castle.Core.Logging; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; + +namespace Tgstation.Server.Host.System.Tests +{ + /// + /// Tests for . + /// + [TestClass] + public sealed class TestProcessFeatures + { + IProcessFeatures features; + + [TestInitialize] + public void Init() + { + features = new PlatformIdentifier().IsWindows + ? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of>()) + : new PosixProcessFeatures(new DefaultIOManager(), Mock.Of>()); + } + + [TestMethod] + public async Task TestGetUsername() + { + if (!String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TRAVIS"))) + Assert.Inconclusive("This test doesn't work on TRAVIS CI!"); + + var username = await features.GetExecutingUsername(global::System.Diagnostics.Process.GetCurrentProcess(), default); + Assert.IsTrue(username.Contains(Environment.UserName), $"Exepcted a string containing \"{Environment.UserName}\", got \"{username}\""); + } + } +} diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index 7111026da4..f7a8ed3e55 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -12,6 +12,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Client; using Tgstation.Server.Client.Components; using Tgstation.Server.Host.Components.Interop; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.System; namespace Tgstation.Server.Tests.Instance @@ -101,8 +102,8 @@ namespace Tgstation.Server.Tests.Instance using var ddProc = ddProcs.Single(); using var ourProcessHandler = new ProcessExecutor( new PlatformIdentifier().IsWindows - ? (IProcessSuspender)new WindowsProcessSuspender(Mock.Of>()) - : new PosixProcessSuspender(Mock.Of>()), + ? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of>()) + : new PosixProcessFeatures(Mock.Of(), Mock.Of>()), Mock.Of>(), LoggerFactory.Create(x => { })) .GetProcess(ddProc.Id); diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 63e61fe328..06ea881442 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -301,7 +301,7 @@ namespace Tgstation.Server.Tests { var platformIdentifier = new PlatformIdentifier(); var processExecutor = new ProcessExecutor( - Mock.Of(), + Mock.Of(), Mock.Of>(), LoggerFactory.Create(x => { })); diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index fa80d30927..7b7df505cc 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -48,7 +48,7 @@ namespace Tgstation.Server.Tests } System.IO.Directory.CreateDirectory(Directory); - const string UrlString = "http://localhost:5001"; + const string UrlString = "http://localhost:5010"; Url = new Uri(UrlString); //so we need a db