-
-
Save duebbert/4298b5f4eb7cc064b09e9d865dd490c9 to your computer and use it in GitHub Desktop.
Updated to better survive crashes and other unexpected behavior.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
""" | |
msysGit to Unix socket proxy | |
============================ | |
This small script is intended to help use msysGit sockets with the new Windows Linux Subsystem (aka Bash for Windows). | |
It was specifically designed to pass SSH keys from the KeeAgent module of KeePass secret management application to the | |
ssh utility running in the WSL (it only works with Linux sockets). However, my guess is that it will have uses for other | |
applications as well. | |
In order to efficiently use it, I add it at the end of the ~/.bashrc file, like this: | |
export SSH_AUTH_SOCK="/tmp/.ssh-auth-sock" | |
~/bin/msysgit2unix-socket.py /mnt/c/Users/User/keeagent.sock:$SSH_AUTH_SOCK | |
Command line usage: msysgit2unix-socket.py [-h] [--downstream-buffer-size N] | |
[--upstream-buffer-size N] [--listen-backlog N] | |
[--timeout N] [--pidfile FILE] | |
source:destination [source:destination ...] | |
Positional arguments: | |
source:destination A pair of a source msysGit and a destination Unix | |
sockets. | |
Optional arguments: | |
-h, --help show this help message and exit | |
--downstream-buffer-size N | |
Maximum number of bytes to read at a time from the | |
Unix socket. | |
--upstream-buffer-size N | |
Maximum number of bytes to read at a time from the | |
msysGit socket. | |
--listen-backlog N Maximum number of simultaneous connections to the Unix | |
socket. | |
--timeout N Timeout. | |
--pidfile FILE Where to write the PID file. | |
""" | |
import argparse | |
import asyncore | |
import os | |
import re | |
import signal | |
import socket | |
import sys | |
import errno | |
import atexit | |
# NOTE: Taken from http://stackoverflow.com/a/6940314 | |
def PidExists(pid): | |
"""Check whether pid exists in the current process table. | |
UNIX only. | |
""" | |
if pid < 0: | |
return False | |
if pid == 0: | |
# According to "man 2 kill" PID 0 refers to every process | |
# in the process group of the calling process. | |
# On certain systems 0 is a valid PID but we have no way | |
# to know that in a portable fashion. | |
raise ValueError('invalid PID 0') | |
try: | |
os.kill(pid, 0) | |
except OSError as err: | |
if err.errno == errno.ESRCH: | |
# ESRCH == No such process | |
return False | |
elif err.errno == errno.EPERM: | |
# EPERM clearly means there's a process to deny access to | |
return True | |
else: | |
# According to "man 2 kill" possible error values are | |
# (EINVAL, EPERM, ESRCH) | |
raise | |
else: | |
return True | |
class UpstreamHandler(asyncore.dispatcher_with_send): | |
""" | |
This class handles the connection to the TCP socket listening on localhost that makes the msysGit socket. | |
""" | |
def __init__(self, downstream_dispatcher, upstream_path): | |
asyncore.dispatcher.__init__(self) | |
self.out_buffer = b'' | |
self.downstream_dispatcher = downstream_dispatcher | |
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) | |
self.connect(('localhost', UpstreamHandler.load_tcp_port_from_msysgit_socket_file(upstream_path))) | |
@staticmethod | |
def load_tcp_port_from_msysgit_socket_file(path): | |
with open(path, 'r') as f: | |
m = re.search('>([0-9]+)', f.readline()) | |
return int(m.group(1)) | |
def handle_connect(self): | |
pass | |
def handle_close(self): | |
self.close() | |
self.downstream_dispatcher.close() | |
def handle_read(self): | |
data = self.recv(config.upstream_buffer_size) | |
if data: | |
self.downstream_dispatcher.send(data) | |
class DownstreamHandler(asyncore.dispatcher_with_send): | |
""" | |
This class handles the connections that are being accepted on the Unix socket. | |
""" | |
def __init__(self, downstream_socket, upstream_path): | |
asyncore.dispatcher.__init__(self, downstream_socket) | |
self.out_buffer = b'' | |
self.upstream_dispatcher = UpstreamHandler(self, upstream_path) | |
def handle_close(self): | |
self.close() | |
self.upstream_dispatcher.close() | |
def handle_read(self): | |
data = self.recv(config.downstream_buffer_size) | |
if data: | |
self.upstream_dispatcher.send(data) | |
class MSysGit2UnixSocketServer(asyncore.dispatcher): | |
""" | |
This is the "server" listening for connections on the Unix socket. | |
""" | |
def __init__(self, upstream_socket_path, unix_socket_path): | |
asyncore.dispatcher.__init__(self) | |
self.upstream_socket_path = upstream_socket_path | |
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) | |
self.bind(unix_socket_path) | |
self.listen(config.listen_backlog) | |
def handle_accept(self): | |
pair = self.accept() | |
if pair is not None: | |
sock, addr = pair | |
DownstreamHandler(sock, self.upstream_socket_path) | |
def build_config(): | |
class ProxyAction(argparse.Action): | |
def __call__(self, parser, namespace, values, option_string=None): | |
proxies = [] | |
for value in values: | |
src_dst = value.partition(':') | |
if src_dst[1] == '': | |
raise parser.error('Unable to parse sockets proxy pair "%s".' % value) | |
proxies.append([src_dst[0], src_dst[2]]) | |
setattr(namespace, self.dest, proxies) | |
parser = argparse.ArgumentParser( | |
description='Transforms msysGit compatible sockets to Unix sockets for the Windows Linux Subsystem.') | |
parser.add_argument('--downstream-buffer-size', default=8192, type=int, metavar='N', | |
help='Maximum number of bytes to read at a time from the Unix socket.') | |
parser.add_argument('--upstream-buffer-size', default=8192, type=int, metavar='N', | |
help='Maximum number of bytes to read at a time from the msysGit socket.') | |
parser.add_argument('--listen-backlog', default=100, type=int, metavar='N', | |
help='Maximum number of simultaneous connections to the Unix socket.') | |
parser.add_argument('--timeout', default=60, type=int, help='Timeout.', metavar='N') | |
parser.add_argument('--pidfile', default='/tmp/msysgit2unix-socket.pid', metavar='FILE', | |
help='Where to write the PID file.') | |
parser.add_argument('proxies', nargs='+', action=ProxyAction, metavar='source:destination', | |
help='A pair of a source msysGit and a destination Unix sockets.') | |
return parser.parse_args() | |
def daemonize(): | |
try: | |
pid = os.fork() | |
if pid > 0: | |
sys.exit() | |
except OSError: | |
sys.stderr.write('Fork #1 failed.') | |
sys.exit(1) | |
os.chdir('/') | |
os.setsid() | |
os.umask(0) | |
try: | |
pid = os.fork() | |
if pid > 0: | |
sys.exit() | |
except OSError: | |
sys.stderr.write('Fork #2 failed.') | |
sys.exit(1) | |
sys.stdout.flush() | |
sys.stderr.flush() | |
si = open('/dev/null', 'r') | |
so = open('/dev/null', 'a+') | |
se = open('/dev/null', 'a+') | |
os.dup2(si.fileno(), sys.stdin.fileno()) | |
os.dup2(so.fileno(), sys.stdout.fileno()) | |
os.dup2(se.fileno(), sys.stderr.fileno()) | |
pid = str(os.getpid()) | |
with open(config.pidfile, 'w+') as f: | |
f.write('%s\n' % pid) | |
def cleanup(): | |
try: | |
for pair in config.proxies: | |
if os.path.exists(pair[1]): | |
os.remove(pair[1]) | |
if os.path.exists(config.pidfile): | |
os.remove(config.pidfile) | |
except Exception as e: | |
sys.stderr.write('%s' % (e)) | |
if __name__ == '__main__': | |
config = build_config() | |
if os.path.exists(config.pidfile): | |
# Check if process is really running, if not run cleanup | |
f = open(config.pidfile, 'r') | |
if PidExists(int(f.readline().strip())): | |
sys.stderr.write('%s: Already running (or at least pidfile "%s" already exists).\n' % (sys.argv[0], config.pidfile)) | |
sys.exit(0) | |
else: | |
cleanup() | |
for pair in config.proxies: | |
MSysGit2UnixSocketServer(pair[0], pair[1]) | |
daemonize() | |
# Redundant cleanup :) | |
atexit.register(cleanup) | |
signal.signal(signal.SIGINT, cleanup) | |
signal.signal(signal.SIGTERM, cleanup) | |
asyncore.loop(config.timeout, True) |
i got this:
Traceback (most recent call last):
File "/mnt/g/EigeneDateien/ssh/msysgit2unix-socket.py", line 230, in <module>
MSysGit2UnixSocketServer(pair[0], pair[1])
File "/mnt/g/EigeneDateien/ssh/msysgit2unix-socket.py", line 136, in __init__
self.bind(unix_socket_path)
File "/usr/lib/python3.5/asyncore.py", line 329, in bind
return self.socket.bind(addr)
OSError: [Errno 98] Address already in use
and no idea whats happening :o
just remove the unix socket file
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for updating the script and for the howto. Two questions:
When the WSL bash window closes, the
/tmp/keeagent.../
temp directory remains, even if thepython3
script exits. It's easy enough to delete these manually, nothing is locking them (that I can find).Possible bug: whenever I start a WSL terminal (and the python3 script), the first
ssh
connection does not use the agent; if I break out (Ctrl-C
) and immediately reconnect, it uses the agent just fine. This happens with each instance of thepython3
script, so every terminal. When run asssh -vvv otherhost
, the difference in logs is only:Do you know what I can do to better troubleshoot this?
Thanks again!