From d268bf0042b2038f8fd6136cd441438ed6d7bea4 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Thu, 30 Nov 2023 12:24:29 -0600 Subject: [PATCH] Modernize administrator and Mono version checks --- Cmdline/Main.cs | 10 ---- Cmdline/Options.cs | 51 ++++-------------- Cmdline/Properties/Resources.resx | 7 ++- Core/Extensions/RegexExtensions.cs | 26 +++++++++ Core/Platform.cs | 62 +++++++++++++--------- Core/Relationships/RelationshipResolver.cs | 1 - 6 files changed, 76 insertions(+), 81 deletions(-) create mode 100644 Core/Extensions/RegexExtensions.cs diff --git a/Cmdline/Main.cs b/Cmdline/Main.cs index a978c45aca..1e2afa16b2 100644 --- a/Cmdline/Main.cs +++ b/Cmdline/Main.cs @@ -7,7 +7,6 @@ using System.Net; using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; #if NET5_0_OR_GREATER using System.Runtime.Versioning; #endif @@ -369,13 +368,4 @@ public class NoGameInstanceKraken : Kraken { public NoGameInstanceKraken() { } } - - public class CmdLineUtil - { - public static uint GetUID() - => Platform.IsUnix || Platform.IsMac ? getuid() : 1; - - [DllImport("libc")] - private static extern uint getuid(); - } } diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs index c0cdf7f006..12c8683687 100644 --- a/Cmdline/Options.cs +++ b/Cmdline/Options.cs @@ -1,9 +1,6 @@ using System; -using System.IO; using System.Linq; -using System.Reflection; using System.Collections.Generic; -using System.Text.RegularExpressions; using log4net; using log4net.Core; @@ -219,23 +216,21 @@ public class CommonOptions [Option("headless", DefaultValue = false, HelpText = "Set to disable all prompts")] public bool Headless { get; set; } - [Option("asroot", DefaultValue = false, HelpText = "Allows CKAN to run as root on Linux-based systems")] + [Option("asroot", DefaultValue = false, HelpText = "Allow CKAN to run as administrator")] public bool AsRoot { get; set; } [HelpVerbOption] public string GetUsage(string verb) - { - return HelpText.AutoBuild(this, verb); - } + => HelpText.AutoBuild(this, verb); public virtual int Handle(GameInstanceManager manager, IUser user) { - CheckMonoVersion(user, 3, 1, 0); + CheckMonoVersion(user); // Processes in Docker containers normally run as root. // If we are running in a Docker container, do not require --asroot. // Docker creates a .dockerenv file in the root of each container. - if ((Platform.IsUnix || Platform.IsMac) && CmdLineUtil.GetUID() == 0 && !File.Exists("/.dockerenv")) + if (Platform.IsAdministrator()) { if (!AsRoot) { @@ -286,40 +281,14 @@ public void Merge(CommonOptions otherOpts) } } - private static void CheckMonoVersion(IUser user, int rec_major, int rec_minor, int rec_patch) + private static void CheckMonoVersion(IUser user) { - try - { - Type type = Type.GetType("Mono.Runtime"); - if (type == null) - { - return; - } - - MethodInfo display_name = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); - if (display_name != null) - { - var version_string = (string) display_name.Invoke(null, null); - var match = Regex.Match(version_string, @"^\D*(?[\d]+)\.(?\d+)\.(?\d+).*$"); - - if (match.Success) - { - int major = int.Parse(match.Groups["major"].Value); - int minor = int.Parse(match.Groups["minor"].Value); - int patch = int.Parse(match.Groups["revision"].Value); - - if (major < rec_major || (major == rec_major && minor < rec_minor)) - { - user.RaiseMessage(Properties.Resources.OptionsMonoWarning, - string.Join(".", major, minor, patch), - string.Join(".", rec_major, rec_minor, rec_patch)); - } - } - } - } - catch (Exception) + if (Platform.MonoVersion != null + && Platform.MonoVersion < Platform.RecommendedMonoVersion) { - // Ignored. This may be fragile and is just a warning method + user.RaiseMessage(Properties.Resources.OptionsMonoWarning, + Platform.MonoVersion.ToString(), + Platform.RecommendedMonoVersion.ToString()); } } diff --git a/Cmdline/Properties/Resources.resx b/Cmdline/Properties/Resources.resx index 1bcf812c03..f7f6633e5e 100644 --- a/Cmdline/Properties/Resources.resx +++ b/Cmdline/Properties/Resources.resx @@ -127,11 +127,10 @@ Unknown command, try --help I don't know where a game instance is installed. Use 'ckan instance help' for assistance in setting this. - You are trying to run CKAN as root. + You are trying to run CKAN as an administrator user. This is a bad idea and there is absolutely no good reason to do it. Please run CKAN from a user account (or use --asroot if you are feeling brave). - Warning: Running CKAN as root! - Warning. Detected mono runtime of {0} is less than the recommended version of {1}. -Update recommended! + Warning: CKAN is running as an administrator user! + Warning: Detected Mono {0}, recommended version is {1} or later! --instance and --gamedir can't be specified at the same time Invalid game instance specified "{0}", use '--gamedir' to specify by path, or 'instance list' to see known game instances Sorry, {0} does not appear to be a game instance diff --git a/Core/Extensions/RegexExtensions.cs b/Core/Extensions/RegexExtensions.cs new file mode 100644 index 0000000000..22e6ee58b6 --- /dev/null +++ b/Core/Extensions/RegexExtensions.cs @@ -0,0 +1,26 @@ +using System.Text.RegularExpressions; + +namespace CKAN.Extensions +{ + public static class RegexExtensions + { + /// + /// Functional-friendly wrapper around Regex.Match + /// + /// The regex to match + /// The string to check + /// Object representing the match, if any + /// True if the regex matched the value, false otherwise + public static bool TryMatch(this Regex regex, string value, out Match match) + { + if (value == null) + { + // Nothing matches null + match = null; + return false; + } + match = regex.Match(value); + return match.Success; + } + } +} diff --git a/Core/Platform.cs b/Core/Platform.cs index 4a6dbd9816..97776775cf 100644 --- a/Core/Platform.cs +++ b/Core/Platform.cs @@ -1,10 +1,14 @@ using System; +using System.IO; using System.Reflection; using System.Runtime.InteropServices; #if NET6_0_OR_GREATER using System.Runtime.Versioning; #endif using System.Text.RegularExpressions; +using System.Security.Principal; + +using CKAN.Extensions; namespace CKAN { @@ -17,12 +21,6 @@ namespace CKAN /// public static class Platform { - static Platform() - { - // This call throws if we try to do it as a static initializer. - IsMonoFourOrLater = IsOnMonoFourOrLater(); - } - /// /// Are we on a Mac? /// @@ -56,11 +54,6 @@ static Platform() /// public static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; - /// - /// Are we running on a Mono with major version 4 or later? - /// - public static readonly bool IsMonoFourOrLater; - /// /// Are we running in an X11 environment? /// @@ -68,27 +61,46 @@ static Platform() IsUnix && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DISPLAY")); - private static bool IsOnMonoFourOrLater() + public static bool IsAdministrator() { - if (!IsMono) + if (File.Exists("/.dockerenv")) { + // Treat as non-admin in a docker container, regardless of platform return false; } - - // Get Mono's display name and parse the version - string display_name = - (string)Type.GetType("Mono.Runtime") - .GetMethod("GetDisplayName", - BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(null, null); - - var match = versionMatcher.Match(display_name); - return match.Success - && int.Parse(match.Groups["majorVersion"].Value) >= 4; + if (IsWindows) + { + // On Windows, check if we have administrator or system roles + using (var identity = WindowsIdentity.GetCurrent()) + { + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator) + || principal.IsInRole(WindowsBuiltInRole.SystemOperator); + } + } + // Otherwise Unix-like; are we root? + return getuid() == 0; } + [DllImport("libc")] + private static extern uint getuid(); + private static readonly Regex versionMatcher = - new Regex("^\\s*(?\\d+)\\.\\d+\\.\\d+\\s*\\(", + new Regex("^\\s*(?\\d+)\\.(?\\d+)\\.(?\\d+)\\s*\\(", RegexOptions.Compiled); + + public static readonly Version MonoVersion + = versionMatcher.TryMatch((string)Type.GetType("Mono.Runtime") + ?.GetMethod("GetDisplayName", + BindingFlags.NonPublic + | BindingFlags.Static) + ?.Invoke(null, null), + out Match match) + ? new Version(int.Parse(match.Groups["major"].Value), + int.Parse(match.Groups["minor"].Value), + int.Parse(match.Groups["patch"].Value)) + : null; + + public static readonly Version RecommendedMonoVersion = new Version(5, 0, 0); } } diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index e002cfdd32..4c7bf749bf 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using log4net;