Skip to content

Commit 4f3de9e

Browse files
committed
Improve support for Windows pathnames when using ProxyCommand
This commit changes the parsing of a proxy_command (or ProxyCommand in SSH config files) to better cope with backslashes that might appear in pathnames on Windows. Thanks go to GitHub user chipolux for reporting the issue and investigating the existing OpenSSH parsing behavior.
1 parent a50f9b3 commit 4f3de9e

File tree

3 files changed

+24
-9
lines changed

3 files changed

+24
-9
lines changed

asyncssh/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ class SSHClientConfig(SSHConfig):
425425
"""Settings from an OpenSSH client config file"""
426426

427427
_conditionals = {'host', 'match'}
428-
_no_split = {'remotecommand'}
428+
_no_split = {'proxycommand', 'remotecommand'}
429429
_percent_expand = {'CertificateFile', 'IdentityAgent',
430430
'IdentityFile', 'ProxyCommand', 'RemoteCommand'}
431431

@@ -559,7 +559,7 @@ def _set_tokens(self) -> None:
559559
('PKCS11Provider', SSHConfig._set_string),
560560
('PreferredAuthentications', SSHConfig._set_string),
561561
('Port', SSHConfig._set_int),
562-
('ProxyCommand', SSHConfig._set_string_list),
562+
('ProxyCommand', SSHConfig._set_string),
563563
('ProxyJump', SSHConfig._set_string),
564564
('PubkeyAuthentication', SSHConfig._set_bool),
565565
('RekeyLimit', SSHConfig._set_rekey_limits),

asyncssh/connection.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
from .misc import TermModesArg, TermSizeArg
116116
from .misc import async_context_manager, construct_disc_error
117117
from .misc import get_symbol_names, ip_address, map_handler_name
118-
from .misc import parse_byte_count, parse_time_interval
118+
from .misc import parse_byte_count, parse_time_interval, split_args
119119

120120
from .packet import Boolean, Byte, NameList, String, UInt32, PacketDecodeError
121121
from .packet import SSHPacket, SSHPacketHandler, SSHPacketLogger
@@ -231,7 +231,7 @@ async def create_server(self, session_factory: TCPListenerFactory,
231231
_GlobalRequestResult = Tuple[int, SSHPacket]
232232
_KeyOrCertOptions = Mapping[str, object]
233233
_ListenerArg = Union[bool, SSHListener]
234-
_ProxyCommand = Optional[Sequence[str]]
234+
_ProxyCommand = Optional[Union[str, Sequence[str]]]
235235
_RequestPTY = Union[bool, str]
236236

237237
_TCPServerHandlerFactory = Callable[[str, int], SSHSocketSessionFactory]
@@ -7144,11 +7144,13 @@ def prepare(self, config: SSHConfig, # type: ignore
71447144
self.tunnel = tunnel if tunnel != () else config.get('ProxyJump')
71457145
self.passphrase = passphrase
71467146

7147+
if proxy_command == ():
7148+
proxy_command = cast(Optional[str], config.get('ProxyCommand'))
7149+
71477150
if isinstance(proxy_command, str):
7148-
proxy_command = shlex.split(proxy_command)
7151+
proxy_command = split_args(proxy_command)
71497152

7150-
self.proxy_command = proxy_command if proxy_command != () else \
7151-
cast(Sequence[str], config.get('ProxyCommand'))
7153+
self.proxy_command = proxy_command
71527154

71537155
self.family = cast(int, family if family != () else
71547156
config.get('AddressFamily', socket.AF_UNSPEC))
@@ -9224,7 +9226,7 @@ async def create_server(server_factory: _ServerFactory,
92249226
async def get_server_host_key(
92259227
host = '', port: DefTuple[int] = (), *,
92269228
tunnel: DefTuple[_TunnelConnector] = (),
9227-
proxy_command: DefTuple[str] = (), family: DefTuple[int] = (),
9229+
proxy_command: DefTuple[_ProxyCommand] = (), family: DefTuple[int] = (),
92289230
flags: int = 0, local_addr: DefTuple[HostPort] = (),
92299231
sock: Optional[socket.socket] = None,
92309232
client_version: DefTuple[BytesOrStr] = (),
@@ -9368,7 +9370,7 @@ def conn_factory() -> SSHClientConnection:
93689370
async def get_server_auth_methods(
93699371
host = '', port: DefTuple[int] = (), username: DefTuple[str] = (), *,
93709372
tunnel: DefTuple[_TunnelConnector] = (),
9371-
proxy_command: DefTuple[str] = (), family: DefTuple[int] = (),
9373+
proxy_command: DefTuple[_ProxyCommand] = (), family: DefTuple[int] = (),
93729374
flags: int = 0, local_addr: DefTuple[HostPort] = (),
93739375
sock: Optional[socket.socket] = None,
93749376
client_version: DefTuple[BytesOrStr] = (),

asyncssh/misc.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import functools
2424
import ipaddress
2525
import re
26+
import shlex
2627
import socket
2728
import sys
2829

@@ -269,6 +270,18 @@ def parse_time_interval(value: str) -> float:
269270
return _parse_units(value, _time_units, 'time interval')
270271

271272

273+
def split_args(command: str) -> Sequence[str]:
274+
"""Split a command string into a list of arguments"""
275+
276+
lex = shlex.shlex(command, posix=True)
277+
lex.whitespace_split = True
278+
279+
if sys.platform == 'win32': # pragma: no cover
280+
lex.escape = []
281+
282+
return list(lex)
283+
284+
272285
_ACM = TypeVar('_ACM', bound=AsyncContextManager, covariant=True)
273286

274287
class _ACMWrapper(Generic[_ACM]):

0 commit comments

Comments
 (0)