Skip to content

Mysterious quotes appearing when running non interactive command #1273

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

Closed
masaeedu opened this issue Oct 16, 2018 · 16 comments
Closed

Mysterious quotes appearing when running non interactive command #1273

masaeedu opened this issue Oct 16, 2018 · 16 comments
Milestone

Comments

@masaeedu
Copy link

masaeedu commented Oct 16, 2018

"OpenSSH for Windows" version
7.7.2.0

Server OperatingSystem
Windows Server 2012 R2 Standard

Client OperatingSystem
Arch Linux

What is failing

ssh -i ~/.ssh/test_machine_rsa [email protected] cmd /c echo foo

Expected output

foo

Actual output

foo"

Additional info
Things work correctly if I interactively run cmd and then run cmd /c echo foo. They also work correctly if I just run echo foo instead of cmd /c echo foo. Here's a transcript of various things I tried:

➜  ~ ssh -i ~/.ssh/test_machine_rsa [email protected] cmd
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

administrator@VM-5IBKOK8EE6UU C:\Users\Administrator>cmd /c echo foo
cmd /c echo foo
foo

administrator@VM-5IBKOK8EE6UU C:\Users\Administrator>exit
exit
➜  ~ ssh -i ~/.ssh/test_machine_rsa [email protected] echo foo
foo
➜  ~ ssh -i ~/.ssh/test_machine_rsa [email protected] cmd /c echo foo
foo"
@bagajjal
Copy link
Collaborator

@bingbing8 - Could you please look into this. This might be related to the issue you are currently working.

@bingbing8
Copy link
Contributor

This is because for non-pty, we added double quotes for those cmdline before pass it to cmd.exe. The cmd.exe did not preserve the quotes well:

cmd /c "cmd /c echo foo"
foo"

but the double quotes are required to execute other executable.
@masaeedu, can you workaround by setting powershell as default shell? If I set powershell as default shell, this works fine:

ssh.exe -p 47002 localhost cmd /c echo foo
Enter passphrase for key 'C:\Users\yawang/.ssh/id_ed25519':
foo

@masaeedu
Copy link
Author

@bagajjal Could you give me a link to what this issue is?

Found another weird situation that might be related:

➜ ~ ssh -i ~/.ssh/test_machine_rsa [email protected] cmd /c dir
'dir"' is not recognized as an internal or external command,
operable program or batch file.

@masaeedu
Copy link
Author

@bingbing8 I'll try it out

@bingbing8
Copy link
Contributor

bingbing8 commented Oct 16, 2018

@masaeedu, #1082 and #1211 are the issue I am working one. If you are interested, this is the PR PowerShell/openssh-portable#349.
the dir issue with cmd.exe as default shell due to the same quotes issue in cmd.exe

@masaeedu
Copy link
Author

Thanks @bingbing8. I changed the default shell to Powershell using:

New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force

which makes both cmd /c echo foo which makes both cmd /c dir work nicely.

However (and perhaps this belongs in a different issue), I then tried:

ssh -i ~/.ssh/test_machine_rsa [email protected] cmd /c "cd C:/ && dir"

which produces:

At line:1 char:15
+ cmd /c cd C:/ && dir
+               ~~
The token '&&' is not a valid statement separator in this version.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordEx
   ception
    + FullyQualifiedErrorId : InvalidEndOfLine

Adding explicit quotes to the third argument (cmd /c "\"cd C:/ && dir\"") doesn't seem to have an effect, and produces the same error. This is again a situation where just entering an interactive shell and running the same command will work correctly:

➜  ~ ssh -i ~/.ssh/test_machine_rsa [email protected] cmd /c "cd C:/ && dir"
At line:1 char:15
+ cmd /c cd C:/ && dir
+               ~~
The token '&&' is not a valid statement separator in this version.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordEx
   ception
    + FullyQualifiedErrorId : InvalidEndOfLine

➜  ~ ssh -i ~/.ssh/test_machine_rsa [email protected] cmd /c "\"cd C:/ && dir\""
At line:1 char:15
+ cmd /c cd C:/ && dir
+               ~~
The token '&&' is not a valid statement separator in this version.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordEx
   ception
    + FullyQualifiedErrorId : InvalidEndOfLine

➜  ~ ssh -i ~/.ssh/test_machine_rsa [email protected] powershell
Windows PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.

PS C:\Users\Administrator> cmd /c "cd C:/ && dir"
cmd /c "cd C:/ && dir"
 Volume in drive C has no label.
 Volume Serial Number is C0B0-34AB

 Directory of C:\

08/22/2013  11:52 AM    <DIR>          PerfLogs
10/10/2018  07:40 PM    <DIR>          Program Files
11/16/2016  01:30 PM    <DIR>          Program Files (x86)
10/16/2018  04:54 PM                19 test.txt
06/13/2018  05:42 AM    <DIR>          Users
10/01/2018  01:13 PM    <DIR>          Windows
               1 File(s)             19 bytes
               5 Dir(s)  89,130,852,352 bytes free
PS C:\Users\Administrator> exit
exit

@masaeedu
Copy link
Author

My current need is to be able to take the name of an executable and a verbatim string of arguments and get ssh to start the executable with the specified argument string (without any extra processing).

@bingbing8
Copy link
Contributor

bingbing8 commented Oct 16, 2018

@masaeedu, since the default shell is powershell, powershell use ` to escape characters.
Below works with powershell as default shell

ssh -p 47002 localhost cmd /c "cd C:/ `& dir"
Enter passphrase for key 'C:\Users\yawang/.ssh/id_ed25519':
 Volume in drive C has no label.
 Volume Serial Number is DC7D-3230

 Directory of C:\

@masaeedu
Copy link
Author

masaeedu commented Oct 16, 2018

@bingbing8 Is that ssh ... cmd /c "cd C:/ `& dir"? I don't think that is being expanded by cmd itself, because running cd C:/ `& dir in an interactive cmd session just produces an error.

➜  ~ ssh -i ~/.ssh/test_machine_rsa [email protected] cmd
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

administrator@VM-5IBKOK8EE6UU C:\Users\Administrator>cd C:/ `& dir
cd C:/ `& dir
The system cannot find the path specified.
 Volume in drive C has no label.
 Volume Serial Number is C0B0-34AB

 Directory of C:\Users\Administrator

...
               0 File(s)              0 bytes
              15 Dir(s)  89,130,852,352 bytes free

administrator@VM-5IBKOK8EE6UU C:\Users\Administrator>cd C:/ && dir
cd C:/ && dir
 Volume in drive C has no label.
 Volume Serial Number is C0B0-34AB

 Directory of C:\

...
               1 File(s)             19 bytes
               5 Dir(s)  89,130,852,352 bytes free

administrator@VM-5IBKOK8EE6UU C:\>

@bingbing8
Copy link
Contributor

bingbing8 commented Oct 16, 2018

since your remote shell is powershell instead of cmd. on remote, it runs

powershell -c  "cmd /c cd C:/ `& dir"

@masaeedu
Copy link
Author

I see. I need to avoid interacting with the details of shell escaping and expansion, since the executable and argument list are read from a config file and are unknown up front. I can't rely on the fact that the default shell on a system will be Powershell and that it will expand `& in a particular way before actually passing the arguments to the executable.

Maybe I can write a different default shell that will take an argument string, split it as "<executable> <arguments>", and then start the executable, passing the remaining arguments verbatim.

@masaeedu
Copy link
Author

masaeedu commented Oct 16, 2018

If I run ssh ... cmd /c "\"echo foo\"" (the first layer of quotes will be erased by the local shell), are these arguments simply surrounded by an extra layer of quotes in the remote invocation, like so: powershell -c "cmd /c "echo foo""? Or is there some further escaping of quotes to produce something like: powershell -c "cmd /c \"echo foo\""?

@bingbing8
Copy link
Contributor

bingbing8 commented Oct 16, 2018

@masaeedu, if your remote shell is cmd, this also works:
ssh localhost cmd /c "cd /d C:/ & dir"

@bingbing8
Copy link
Contributor

@masaeedu, we do further escaping for quotes to preserve the quotes

@masaeedu masaeedu reopened this Oct 17, 2018
@masaeedu
Copy link
Author

Since the appropriate escaping is dependent on the default shell application to which the arguments are going to be passed (different rules for cmd and powershell), is there a way we can simply disable this escaping and pass the values verbatim to a custom default shell?

I wrote a little .NET console application to see exactly what sshd passes as the command line arguments to the default shell.

using System;

namespace echoargs
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Raw invocation:");
            Console.WriteLine(Environment.CommandLine);
        }
    }
}

I compiled it to a file echoargs.exe, and then set it as my default shell using:

New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Users\Administrator\echoargs\binary\echoargs.exe" -PropertyType String -Force

Then I tried running the commands I've been having trouble with over SSH:

➜  ~ echo 'cmd /c "cd C:/ && dir"'
cmd /c "cd C:/ && dir"
➜  ~ ssh -i ~/.ssh/test_machine_rsa [email protected] 'cmd /c "cd C:/ && dir"'
Raw invocation:
c:\users\administrator\echoargs\binary\echoargs.dll -c "cmd /c cd" C:/ && dir

Leaving aside the executable's own name and the -c, you can see the difference between what was sent (cmd /c "cd C:/ && dir") and the garbled output that arrived ("cmd /c cd" C:/ && dir).

I want to entirely disable any shell processing and escaping, and simply start a process verbatim using the command line string sent through ssh. Ideally I'd be able to use something like https://github.com/cubiclesoft/createprocess-windows as the default shell, so that it's as close as possible to just calling the relevant CreateProcess* API with the command line string directly.

@bingbing8
Copy link
Contributor

@masaeedu, as you see I mentioned in in #1082 Here is the currently implementation:

  1. for existing common shells, like powershell, bash, cygwin, we add double quotes to the path of default shell and its arguments, escape the double quotes and backslash inside args.
  2. For cmd and ssh-shellhost.exe, we simplily add double quotes to shell and its arguments without any escaping.
  3. For shell other than above, we add a registry value DefaultShellEscapeArguments" under "HKEY_LOCAL_MACHINE\SOFTWARE\OpenSSH". The default value when missing is 1, which mean the escaping will happen like no.1 above. If user does not want escaping, they need to explicitly set this value to 0. in later case, only double quotes are added to shell and args, like no.2 above.
    You request should fit into Wrong user profile folder created on first logon through ssh #3 if you set the DefaultShellEscapeArguments to 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants