Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modernize administrator and Mono version checks #3933

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions Cmdline/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
}
51 changes: 10 additions & 41 deletions Cmdline/Options.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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*(?<major>[\d]+)\.(?<minor>\d+)\.(?<revision>\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());
}
}

Expand Down
7 changes: 3 additions & 4 deletions Cmdline/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,10 @@
<data name="MainUnknownCommand" xml:space="preserve"><value>Unknown command, try --help</value></data>
<data name="MainMissingInstance" xml:space="preserve"><value>I don't know where a game instance is installed.
Use 'ckan instance help' for assistance in setting this.</value></data>
<data name="OptionsRootError" xml:space="preserve"><value>You are trying to run CKAN as root.
<data name="OptionsRootError" xml:space="preserve"><value>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).</value></data>
<data name="OptionsRootWarning" xml:space="preserve"><value>Warning: Running CKAN as root!</value></data>
<data name="OptionsMonoWarning" xml:space="preserve"><value>Warning. Detected mono runtime of {0} is less than the recommended version of {1}.
Update recommended!</value></data>
<data name="OptionsRootWarning" xml:space="preserve"><value>Warning: CKAN is running as an administrator user!</value></data>
<data name="OptionsMonoWarning" xml:space="preserve"><value>Warning: Detected Mono {0}, recommended version is {1} or later!</value></data>
<data name="OptionsInstanceAndGameDir" xml:space="preserve"><value>--instance and --gamedir can't be specified at the same time</value></data>
<data name="OptionsInvalidInstance" xml:space="preserve"><value>Invalid game instance specified "{0}", use '--gamedir' to specify by path, or 'instance list' to see known game instances</value></data>
<data name="InstanceNotInstance" xml:space="preserve"><value>Sorry, {0} does not appear to be a game instance</value></data>
Expand Down
26 changes: 26 additions & 0 deletions Core/Extensions/RegexExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Text.RegularExpressions;

namespace CKAN.Extensions
{
public static class RegexExtensions
{
/// <summary>
/// Functional-friendly wrapper around Regex.Match
/// </summary>
/// <param name="regex">The regex to match</param>
/// <param name="value">The string to check</param>
/// <param name="match">Object representing the match, if any</param>
/// <returns>True if the regex matched the value, false otherwise</returns>
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;
}
}
}
62 changes: 37 additions & 25 deletions Core/Platform.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -17,12 +21,6 @@ namespace CKAN
/// </summary>
public static class Platform
{
static Platform()
{
// This call throws if we try to do it as a static initializer.
IsMonoFourOrLater = IsOnMonoFourOrLater();
}

/// <summary>
/// Are we on a Mac?
/// </summary>
Expand Down Expand Up @@ -56,39 +54,53 @@ static Platform()
/// </summary>
public static readonly bool IsMono = Type.GetType("Mono.Runtime") != null;

/// <summary>
/// Are we running on a Mono with major version 4 or later?
/// </summary>
public static readonly bool IsMonoFourOrLater;

/// <summary>
/// Are we running in an X11 environment?
/// </summary>
public static readonly bool IsX11 =
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*(?<majorVersion>\\d+)\\.\\d+\\.\\d+\\s*\\(",
new Regex("^\\s*(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\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);
}
}
1 change: 0 additions & 1 deletion Core/Relationships/RelationshipResolver.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using log4net;
Expand Down