Skip to content

Commit

Permalink
Features:
Browse files Browse the repository at this point in the history
- Added setting `gsudo config NewWindow.Force` to force every elevation in new window. Same as using `-n` every time.
- Added `--NoExit`: Keep elevated shell open after running {command}.
- Added `--NoClose`: When in new console, ask for keypress before closing the console.
- Added setting `gsudo config NewWindow.CloseBehaviour`: `OsDefault`, `KeepShellOpen` (--NoExit), PressKeyToClose(--NoClose)
  • Loading branch information
gerardog committed Oct 23, 2022
1 parent 034889a commit c338665
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 111 deletions.
8 changes: 4 additions & 4 deletions src/gsudo.Tests/CommandLineParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void CmdLine_Flags1()
{
var runCmd = result as RunCommand;
Assert.IsNotNull(runCmd);
Assert.AreEqual("cmd /c echo 1 2 3 4", string.Join(" ", runCmd.CommandToRun));
Assert.AreEqual("\"C:\\Windows\\system32\\cmd.EXE\" /c echo 1 2 3 4", string.Join(" ", runCmd.UserCommand));
Assert.IsTrue(InputArguments.NewWindow);
Assert.IsTrue(InputArguments.Wait);
Expand Down Expand Up @@ -44,7 +44,7 @@ public void CmdLine_Flags_System()

var runCmd = ret as RunCommand;
Assert.IsNotNull(runCmd);
Assert.AreEqual("cmd /c echo 1 2 3 4", string.Join(" ", runCmd.CommandToRun));
Assert.AreEqual("\"C:\\Windows\\system32\\cmd.EXE\" /c echo 1 2 3 4", string.Join(" ", runCmd.UserCommand));

Assert.IsTrue(InputArguments.NewWindow);
Assert.IsFalse(InputArguments.Wait);
Expand All @@ -65,7 +65,7 @@ public void CmdLine_TrustedInstaller()

var runCmd = ret as RunCommand;
Assert.IsNotNull(runCmd);
Assert.AreEqual("", string.Join(" ", runCmd.CommandToRun));
Assert.AreEqual("", string.Join(" ", runCmd.UserCommand));

Assert.IsFalse(InputArguments.NewWindow);
Assert.IsFalse(InputArguments.Wait);
Expand All @@ -84,7 +84,7 @@ public void CmdLine_OptionWithArguments()
{
var runCmd = result as RunCommand;
Assert.IsNotNull(runCmd);
CollectionAssert.AreEqual(new string[] { "notepad", "\"1 2 3 4\"" }, runCmd.CommandToRun.ToArray());
CollectionAssert.AreEqual(new string[] { "\"C:\\windows\\system32\\notepad.exe\"", "\"1 2 3 4\"" }, runCmd.UserCommand.ToArray(), StringComparer.OrdinalIgnoreCase);
Assert.IsTrue(InputArguments.NewWindow);
Assert.IsTrue(InputArguments.Wait);
Expand Down
16 changes: 16 additions & 0 deletions src/gsudo/AppSettings/Window.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace gsudo.AppSettings
{
// CacheMode = Disabled | Explicit | Auto
// Cache.Mode = Disabled | Manual | AutoStart | AutoStart
// Cache.Restriction = CallerPIDOnly | CallerPIDAndDescendants | AnySameUserPid

// NewWindow.Force = true
// NewWindow.CloseBehaviour = KeepShellOpen | PressKeyToClose | OsDefault

public enum CloseBehaviour
{
KeepShellOpen,
PressKeyToClose,
OsDefault,
}
}
2 changes: 2 additions & 0 deletions src/gsudo/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ gsudo [options] {command} [args]\tRuns {command} with elevated permissions
General options:
-n | --new Starts the command in a new console (and returns immediately).
--noexit Keep elevated shell open after running {command}.
--noclose When in new console, ask for keypress before closing the console.
-w | --wait When in new console, wait for the command to end.
Security options:
Expand Down
49 changes: 19 additions & 30 deletions src/gsudo/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ namespace gsudo.Commands
{
public class RunCommand : ICommand
{
public IList<string> CommandToRun { get; private set; }
private string GetArguments() => GetArgumentsString(CommandToRun, 1);
public IList<string> UserCommand { get; private set; }
CommandToRunBuilder commandBuilder;

public RunCommand(IList<string> commandToRun)
{
CommandToRun = commandToRun;
UserCommand = commandToRun;
commandBuilder = new CommandToRunBuilder(commandToRun);
}

public async Task<int> Execute()
Expand All @@ -32,36 +33,31 @@ public async Task<int> Execute()

bool isRunningAsDesiredUser = IsRunningAsDesiredUser();
bool isElevationRequired = IsElevationRequired();
bool isShellElevation = !CommandToRun.Any(); // are we auto elevating the current shell?
bool isShellElevation = !UserCommand.Any(); // are we auto elevating the current shell?

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)
throw new ApplicationException("Already running as the specified user/permission-level (and no command specified). Exiting...");

CommandToRun = CommandToRunGenerator.AugmentCommand(CommandToRun.ToArray());

bool isWindowsApp = ProcessFactory.IsWindowsApp(CommandToRun.FirstOrDefault());
var elevationMode = GetElevationMode();

CommandToRun = CommandToRunGenerator.FixCommandExceptions(CommandToRun);

var elevationMode = GetElevationMode();

if (!isRunningAsDesiredUser)
CommandToRun = CommandToRunGenerator.AddCopyEnvironment(CommandToRun, elevationMode);
commandBuilder.AddCopyEnvironment(elevationMode);

var exeName = CommandToRun.FirstOrDefault();
commandBuilder.Build();

int consoleHeight, consoleWidth;
ConsoleHelper.GetConsoleInfo(out consoleWidth, out consoleHeight, out _, out _);

var elevationRequest = new ElevationRequest()
{
FileName = exeName,
Arguments = GetArguments(),
FileName = commandBuilder.GetExeName(),
Arguments = commandBuilder.GetArgumentsAsString(),
StartFolder = Environment.CurrentDirectory,
NewWindow = InputArguments.NewWindow,
Wait = (!isWindowsApp && !InputArguments.NewWindow) || InputArguments.Wait,
Wait = (!commandBuilder.IsWindowsApp && !InputArguments.NewWindow) || InputArguments.Wait,
Mode = elevationMode,
ConsoleProcessId = Process.GetCurrentProcess().Id,
IntegrityLevel = InputArguments.GetIntegrityLevel(),
Expand All @@ -79,7 +75,7 @@ public async Task<int> Execute()

if (isRunningAsDesiredUser || !isElevationRequired) // already elevated or running as correct user. No service needed.
{
return RunWithoutService(exeName, GetArguments(), elevationRequest);
return RunWithoutService(elevationRequest);
}

return await RunUsingService(elevationRequest).ConfigureAwait(false);
Expand All @@ -93,7 +89,7 @@ private static void SetRequestPrompt(ElevationRequest elevationRequest)
elevationRequest.Prompt = Settings.PipedPrompt;
}

/// Starts a cache sessioBn
// Starts a cache session
private async Task<int> RunUsingService(ElevationRequest elevationRequest)
{
Logger.Instance.Log($"Using Console mode {elevationRequest.Mode}", LogLevel.Debug);
Expand All @@ -112,7 +108,7 @@ private async Task<int> RunUsingService(ElevationRequest elevationRequest)
var service = ServiceHelper.StartService(callingPid, singleUse: InputArguments.KillCache);
connection = await ServiceHelper.Connect(callingPid, service).ConfigureAwait(false);

if (connection == null) // service is not running or listening.
if (connection == null) // still not listening.
throw new ApplicationException("Unable to connect to the elevated service.");
}

Expand All @@ -131,7 +127,7 @@ private async Task<int> RunUsingService(ElevationRequest elevationRequest)
}
}

private static int RunWithoutService(string exeName, string args, ElevationRequest elevationRequest)
private static int RunWithoutService(ElevationRequest elevationRequest)
{
var sameIntegrity = (int)InputArguments.GetIntegrityLevel() == SecurityHelper.GetCurrentIntegrityLevel();
// No need to escalate. Run in-process
Expand All @@ -143,7 +139,7 @@ private static int RunWithoutService(string exeName, string args, ElevationReque
{
if (elevationRequest.NewWindow)
{
using (var process = ProcessFactory.StartDetached(exeName, args, Environment.CurrentDirectory, false))
using (var process = ProcessFactory.StartDetached(elevationRequest.FileName, elevationRequest.Arguments, Environment.CurrentDirectory, false))
{
if (elevationRequest.Wait)
{
Expand All @@ -157,7 +153,7 @@ private static int RunWithoutService(string exeName, string args, ElevationReque
}
else
{
using (Process process = ProcessFactory.StartAttached(exeName, args))
using (Process process = ProcessFactory.StartAttached(elevationRequest.FileName, elevationRequest.Arguments))
{
process.WaitForExit();
var exitCode = process.ExitCode;
Expand All @@ -171,7 +167,7 @@ private static int RunWithoutService(string exeName, string args, ElevationReque
if (elevationRequest.IntegrityLevel < IntegrityLevel.High && !elevationRequest.NewWindow)
RemoveAdminPrefixFromConsoleTitle();

var p = ProcessFactory.StartAttachedWithIntegrity(InputArguments.GetIntegrityLevel(), exeName, args, elevationRequest.StartFolder, InputArguments.NewWindow, !InputArguments.NewWindow);
var p = ProcessFactory.StartAttachedWithIntegrity(InputArguments.GetIntegrityLevel(), elevationRequest.FileName, elevationRequest.Arguments, elevationRequest.StartFolder, InputArguments.NewWindow, !InputArguments.NewWindow);
if (p == null || p.IsInvalid)
return Constants.GSUDO_ERROR_EXITCODE;

Expand Down Expand Up @@ -324,13 +320,6 @@ private static IProcessRenderer GetRenderer(Connection connection, ElevationRequ
return new VTClientRenderer(connection, elevationRequest);
}

private static string GetArgumentsString(IEnumerable<string> args, int v)
{
if (args == null) return null;
if (args.Count() <= v) return string.Empty;
return string.Join(" ", args.Skip(v).ToArray());
}

private static void RemoveAdminPrefixFromConsoleTitle()
{
var title = Console.Title;
Expand Down
16 changes: 13 additions & 3 deletions src/gsudo/Helpers/CommandLineParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using gsudo.Commands;
using gsudo.AppSettings;
using gsudo.Commands;
using System;
using System.Collections.Generic;
using System.Globalization;
Expand Down Expand Up @@ -27,7 +28,10 @@ public CommandLineParser(IEnumerable<string> argsEnumerable)
public ICommand Parse()
{
InputArguments.Clear();


if (Settings.NewWindow_Force)
InputArguments.NewWindow = true;

// syntax: gsudo [options] [verb] [command to run]:

return ParseOptions() // Parse [options]
Expand Down Expand Up @@ -109,6 +113,12 @@ ICommand ParseOption(string argChar, string argWord, out bool skipRemainingChars
}
else if (match("n", "--new")) { InputArguments.NewWindow = true; }
else if (match("w", "--wait")) { InputArguments.Wait = true; }

else if (match(null, "--noexit")) { InputArguments.NoExit = true; InputArguments.KeepWindowOpen = false; }
else if (match(null, "--exit")) { InputArguments.NoExit = false;}
else if (match(null, "--noclose")) { InputArguments.KeepWindowOpen = true; InputArguments.NoExit = false; }
else if (match(null, "--close")) { InputArguments.KeepWindowOpen = 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; }
Expand Down Expand Up @@ -145,7 +155,7 @@ private ICommand ParseVerb()
if (args.Count == 0)
{
if (InputArguments.KillCache
&& !InputArguments.NewWindow
&& (!InputArguments.NewWindow || Settings.NewWindow_Force)
&& !InputArguments.RunAsSystem
&& !InputArguments.Wait
&& !InputArguments.TrustedInstaller
Expand Down
Loading

0 comments on commit c338665

Please sign in to comment.