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

Added --chdir and other ms-sudo compatibilities. #355

Merged
merged 7 commits into from
May 28, 2024
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Note: `gsudo.exe` is portable. No windows service is required or system change i
## Usage

``` powershell
gsudo [options] # Elevates your current shell
gsudo [options] # Starts your current shell elevated
gsudo [options] {command} [args] # Runs {command} with elevated permissions
gsudo cache [on | off | help] # Starts/Stops a credentials cache session. (less UAC popups)
gsudo status [--json | filter ] # Shows current user, cache and console status.
Expand Down Expand Up @@ -106,6 +106,7 @@ Other options:
--debug # Enable debug mode.
--copyns # Connect network drives to the elevated user. Warning: Verbose, interactive asks for credentials
--copyev # (deprecated) Copy environment variables to the elevated process. (not needed on default console mode)
--chdir {dir} # Change the current directory to {dir} before running the command.
```

**Note:** You can use anywhere **the `sudo` alias** created by the installers.
Expand Down
1 change: 1 addition & 0 deletions docs/docs/usage/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Other options:
--debug # Enable debug mode.
--copyns # Connect network drives to the elevated user. Warning: Verbose, interactive asks for credentials
--copyev # (deprecated) Copy environment variables to the elevated process. (not needed on default console mode)
--chdir {dir} # Change the current directory to {dir} before running the command.

```

Expand Down
6 changes: 6 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "7.0.0",
"rollForward": "latestFeature"
}
}
2 changes: 1 addition & 1 deletion src/gsudo/AppSettings/PathPrecedenceSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal class PathPrecedenceSetting : RegistrySetting<bool>
{
public PathPrecedenceSetting():
base("PathPrecedence", false, bool.Parse, RegistrySettingScope.GlobalOnly,
description: "Prioritize gsudo over Microsoft Sudo in the PATH environment variable.")
description: "Prioritize gsudo over Microsoft Sudo in the PATH environment variable")
{

}
Expand Down
14 changes: 7 additions & 7 deletions src/gsudo/AppSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Settings
= new RegistrySetting<CacheMode>(nameof(CacheMode), CredentialsCache.CacheMode.Explicit,
deserializer: ExtensionMethods.ParseEnum< CacheMode>,
scope: RegistrySettingScope.GlobalOnly,
description: "Defines how gsudo credentials cache works: Auto, Explicit (Manual), Disabled" );
description: "Defines how gsudo credentials cache works: Auto, Explicit (default), Disabled" );

public static RegistrySetting<TimeSpan> CacheDuration { get; }
= new RegistrySetting<TimeSpan>(nameof(CacheDuration),
Expand All @@ -33,14 +33,14 @@ class Settings
= new RegistrySetting<string>(nameof(PipedPrompt),
defaultValue: DefaultAsciiPrompt,
deserializer: (s) => s,
description: "Prompt to be used when gsudo uses piped mode."
description: "CMD Prompt to be used when gsudo uses piped mode"
);

public static RegistrySetting<string> Prompt { get; }
= new RegistrySetting<string>(nameof(Prompt),
defaultValue: GetPromptDefaultValue,
deserializer: (s) => s,
description: "Prompt to be used when gsudo uses standard mode."
description: "CMD Prompt to be used when gsudo uses standard mode"
);

public static RegistrySetting<LogLevel> LogLevel { get; }
Expand All @@ -54,7 +54,7 @@ class Settings
= new RegistrySetting<bool>(nameof(ForcePipedConsole),
defaultValue: false,
deserializer: bool.Parse,
description: "Forces gsudo to use legacy piped mode. Not recommended."
description: "Forces gsudo to use legacy piped mode. Not recommended"
);

public static RegistrySetting<bool> ForceAttachedConsole { get; }
Expand Down Expand Up @@ -97,7 +97,7 @@ class Settings
defaultValue: false,
deserializer: bool.Parse,
scope: RegistrySettingScope.GlobalOnly,
description: "Elevates but with the input handle closed. More secure, less convenient. To be implemented soon also as --disableInput"
description: "Elevates but with the input handle closed. More secure, but less convenient. Same as --disableInput"
);

public static RegistrySetting<string> ExceptionList { get; } =
Expand All @@ -113,14 +113,14 @@ class Settings
defaultValue: false,
deserializer: bool.Parse,
scope: RegistrySettingScope.Any,
description: "Always elevate in new window. Same as --new");
description: "Always elevate in new window. (Equivalent to --new)");

public static RegistrySetting<CloseBehaviour> NewWindow_CloseBehaviour { get; } =
new RegistrySetting<CloseBehaviour>(nameof(NewWindow_CloseBehaviour),
defaultValue: CloseBehaviour.OsDefault,
deserializer: ExtensionMethods.ParseEnum<CloseBehaviour>,
scope: RegistrySettingScope.Any,
description: "When elevating in new window, let the window auto-close (OsDefault), KeepShellOpen or PressKeyToClose"
description: "When elevating in a new window, defines what happens when the process ends: OsDefault (let the window auto-close), KeepShellOpen or PressKeyToClose"
);

public static RegistrySetting<bool> PathOverrideSetting = new PathPrecedenceSetting();
Expand Down
5 changes: 3 additions & 2 deletions src/gsudo/Commands/ConfigCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@ public Task<int> Execute()

if (key == null)
{
Console.ForegroundColor = ConsoleColor. Yellow;
// print all configs Descriptions
foreach (var k in Settings.AllKeys)
{
Console.ForegroundColor = ConsoleColor.Yellow;
if (Settings.LogLevel <= LogLevel.Info)
{
Console.WriteLine($"# {k.Value.Name}: {k.Value.Description}");
}
Console.ResetColor();
}
Console.WriteLine();
Console.ResetColor();

// print all config values
foreach (var k in Settings.AllKeys)
Expand Down
1 change: 1 addition & 0 deletions src/gsudo/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ gsudo status {key} [--no-output]\tShows status filtered by json {key}. Boolean k
--debug Enable debug mode.
--copyns Connect network drives to the elevated user. Warning: Interactive asks for credentials
--copyev (deprecated) Copy all environment variables to the elevated process.
--chdir {dir} Change the current directory to {dir} before running the command.

Configuration:
gsudo config\t\t\t\tShow current config settings & values.
Expand Down
11 changes: 5 additions & 6 deletions src/gsudo/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public async Task<int> Execute()
if (isElevationRequired & SecurityHelper.GetCurrentIntegrityLevel() < (int)IntegrityLevel.Medium)
throw new ApplicationException("Sorry, gsudo doesn't allow to elevate from low integrity level."); // This message is not a security feature, but a nicer error message. It would have failed anyway since the named pipe's ACL restricts it.

if (isRunningAsDesiredUser && isShellElevation && !InputArguments.NewWindow)
if (isRunningAsDesiredUser && isShellElevation && !InputArguments.NewWindow && !InputArguments.Direct && InputArguments.StartingDirectory == null)
throw new ApplicationException("Already running as the specified user/permission-level (and no command specified). Exiting...");

var elevationMode = GetElevationMode();
Expand All @@ -58,7 +58,7 @@ public async Task<int> Execute()
{
FileName = commandBuilder.GetExeName(),
Arguments = commandBuilder.GetArgumentsAsString(),
StartFolder = Environment.CurrentDirectory,
StartFolder = InputArguments.StartingDirectory ?? Environment.CurrentDirectory,
NewWindow = InputArguments.NewWindow,
Wait = (!commandBuilder.IsWindowsApp && !InputArguments.NewWindow) || InputArguments.Wait,
Mode = elevationMode,
Expand All @@ -69,7 +69,7 @@ public async Task<int> Execute()
IsInputRedirected = Console.IsInputRedirected
};

if (isElevationRequired && Settings.SecurityEnforceUacIsolation)
if (isElevationRequired && (Settings.SecurityEnforceUacIsolation || InputArguments.DisableInput))
AdjustUacIsolationRequest(elevationRequest, isShellElevation);

SetRequestPrompt(elevationRequest);
Expand Down Expand Up @@ -223,9 +223,8 @@ private void AdjustUacIsolationRequest(ElevationRequest elevationRequest, bool i
}
else
{
// force raw mode (that disables user input with SecurityEnforceUacIsolation)
elevationRequest.Mode = ElevationRequest.ConsoleMode.Piped;
Logger.Instance.Log("User Input disabled because of SecurityEnforceUacIsolation. Press Ctrl-C three times to abort. Or use -n argument to elevate in new window.", LogLevel.Info);
// Disables user input with SecurityEnforceUacIsolation
elevationRequest.DisableInput = true;
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/gsudo/ElevationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ class ElevationRequest
public bool KillCache { get; set; }
public IntegrityLevel IntegrityLevel { get; set; }

public bool IsInputRedirected { get; set; }

public bool IsInputRedirected { get; set; }
public bool DisableInput { get; set; }

[Serializable]
internal enum ConsoleMode {
/// <summary>
Expand Down
22 changes: 21 additions & 1 deletion src/gsudo/Helpers/CommandLineParser.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using gsudo.AppSettings;
using gsudo.Commands;
using gsudo.Native;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Principal;

Expand Down Expand Up @@ -132,7 +134,6 @@ ICommand ParseOption(string argChar, string argWord, out bool skipRemainingChars
else if (match(null, "--close")) { InputArguments.CloseNewWindow = true; InputArguments.KeepWindowOpen = false; InputArguments.KeepShellOpen = false; }

else if (match("s", "--system")) { InputArguments.RunAsSystem = true; }
else if (match("d", "--direct")) { InputArguments.Direct = true; }
else if (match("k", "--reset-timestamp")) { InputArguments.KillCache = true; }
else if (match(null, "--global")) { InputArguments.Global = true; }
else if (match(null, "--ti")) { InputArguments.TrustedInstaller = InputArguments.RunAsSystem = true; }
Expand All @@ -145,6 +146,25 @@ ICommand ParseOption(string argChar, string argWord, out bool skipRemainingChars
else if (match(null, "--debug")) { Settings.LogLevel.Value = LogLevel.All; InputArguments.Debug = true; }
else if (match("v", "--version")) { return new ShowVersionHelpCommand(); }
else if (match("h", "--help")) return new HelpCommand();

// ms-sudo compat:
else if (match(null, "--preserve-env")) { Settings.CopyEnvironmentVariables.Value = true; }
else if (match(null, "--new-window")) { InputArguments.NewWindow = true; }
// case sensitive -D {dir}
else if (argChar == "D" && argWord == "-D" && FileApi.PathExists(args.FirstOrDefault())) { InputArguments.StartingDirectory = DeQueueArg(); }
else if (match(null, "--chdir"))
{
InputArguments.StartingDirectory = DeQueueArg();
if (!FileApi.PathExists(InputArguments.StartingDirectory))
{
throw new ApplicationException($"Invalid directory: {InputArguments.StartingDirectory}");
}
}
else if (match(null, "--inline")) { InputArguments.NewWindow = false; }
else if (argWord.In("--disable-input", "--disableInput")) { InputArguments.DisableInput = true; }

// rest
else if (match("d", "--direct")) { InputArguments.Direct = true; }
else if (argWord.StartsWith("-", StringComparison.Ordinal))
{
if (argChar != null)
Expand Down
7 changes: 4 additions & 3 deletions src/gsudo/Helpers/CommandToRunAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,14 +403,15 @@ internal void Build()
postCommands.Add("exit /b !errl!");
}

bool bNetworkfolder = Environment.CurrentDirectory.StartsWith(@"\\", StringComparison.Ordinal);
string startupFolder = InputArguments.StartingDirectory ?? Environment.CurrentDirectory;
bool bNetworkfolder = startupFolder.StartsWith(@"\\", StringComparison.Ordinal);
bool bIsCmdExe = ArgumentsHelper.UnQuote(command.First()).EndsWith("cmd.exe", StringComparison.OrdinalIgnoreCase);

if (bNetworkfolder && (bIsCmdExe || mustWrap))
{
Logger.Instance.Log($"The current directory '{Environment.CurrentDirectory}' is a network folder. Mapping as a network drive.", LogLevel.Debug);
Logger.Instance.Log($"The path '{startupFolder}' is a network folder. Mapping as a network drive.", LogLevel.Debug);
// Prepending PUSHD command. It maps network folders magically!
preCommands.Insert(0, $"pushd \"{Environment.CurrentDirectory}\"");
preCommands.Insert(0, $"pushd \"{startupFolder}\"");
postCommands.Add("popd");
// And set current directory to local folder to avoid CMD warning message
Environment.CurrentDirectory = Environment.GetEnvironmentVariable("SystemRoot");
Expand Down
Loading
Loading