-
Notifications
You must be signed in to change notification settings - Fork 13
/
sftpipe
executable file
·102 lines (84 loc) · 3.12 KB
/
sftpipe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#!/usr/bin/env python3
# sftpipe -- copy data from stdin to a remote file over SFTP
#
# sftpipe can be used to stream data to systems that don't support POSIX-style
# "cat > foo" remote commands (such as Windows, RouterOS, or OpenVMS).
import argparse
import paramiko
import re
import sys
import subprocess
import urllib.parse
def parse_dest(arg):
if arg.startswith("sftp://"):
u = urllib.parse.urlparse(arg)
if not u.hostname:
raise ValueError(f"URL {arg!r} has empty hostname")
return u.username, u.hostname, u.path[1:]
else:
if m := re.match(r"^([^:]+):(.+)$", arg):
host, path = m.groups()
else:
raise ValueError(f"could not parse {arg!r}")
if m := re.match(r"^(.+)@([^@]+)$", host):
user, host = m.groups()
else:
user = None
return user, host, path
class OpenSshSubsystemChannel():
"""
A socket-like object to be used in place of paramiko.channel.Channel(), in
order to use Paramiko SFTP client with OpenSSH host/user authentication.
"""
def __init__(self, endpoint, subsystem):
self.ep = endpoint
self.subsys = subsystem
self.sshcmd = ["ssh", "-q", "-s", endpoint, subsystem]
self.proc = subprocess.Popen(self.sshcmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
def get_name(self):
return f"[fake channel to {self.subsys!r} on {self.ep!r}]"
def send(self, buf):
n = self.proc.stdin.write(buf)
self.proc.stdin.flush()
return n
def recv(self, nbytes):
return self.proc.stdout.read(nbytes)
def close(self):
self.proc.stdin.close()
self.proc.wait()
parser = argparse.ArgumentParser()
parser.add_argument("-P", "--paramiko",
action="store_true",
help="use Paramiko as transport instead of OpenSSH")
parser.add_argument("-v", "--verbose",
action="store_true",
help="show connection progress")
parser.add_argument("dest",
metavar="HOST:PATH",
help="remote file (rcp-style path or sftp:// URL)")
args = parser.parse_args()
user, host, path = parse_dest(args.dest)
if args.verbose:
print(f"sftpipe: Connecting to {host!r}...", file=sys.stderr)
# For most uses, OpenSSH is a more useful transport as it reuses the same
# configuration as `sftp` (and automatically deals with key passphrases,
# SSH host certificates, etc).
if args.paramiko:
client = paramiko.client.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.client.WarningPolicy)
client.connect(host,
username=user,
gss_kex=True,
gss_auth=True)
sftp = client.open_sftp()
else:
ep = f"{user}@{host}" if user else f"{host}"
chan = OpenSshSubsystemChannel(ep, "sftp")
sftp = paramiko.sftp_client.SFTPClient(chan)
if args.verbose:
print(f"sftpipe: Uploading to {path!r}...", file=sys.stderr)
sftp.putfo(sys.stdin.buffer, path)
sftp.close()