Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Updated to better survive crashes and other unexpected behavior.
#!/usr/bin/python
"""
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((b'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(b'>([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] == b'':
raise parser.error(b'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(b'--downstream-buffer-size', default=8192, type=int, metavar=b'N',
help=b'Maximum number of bytes to read at a time from the Unix socket.')
parser.add_argument(b'--upstream-buffer-size', default=8192, type=int, metavar=b'N',
help=b'Maximum number of bytes to read at a time from the msysGit socket.')
parser.add_argument(b'--listen-backlog', default=100, type=int, metavar=b'N',
help=b'Maximum number of simultaneous connections to the Unix socket.')
parser.add_argument(b'--timeout', default=60, type=int, help=b'Timeout.', metavar=b'N')
parser.add_argument(b'--pidfile', default=b'/tmp/msysgit2unix-socket.pid', metavar=b'FILE',
help=b'Where to write the PID file.')
parser.add_argument(b'proxies', nargs=b'+', action=ProxyAction, metavar='source:destination',
help=b'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(b'Fork #1 failed.')
sys.exit(1)
os.chdir(b'/')
os.setsid()
os.umask(0)
try:
pid = os.fork()
if pid > 0:
sys.exit()
except OSError:
sys.stderr.write(b'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(b'%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(b'%s' % (e))
if __name__ == b'__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(b'%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)
@tonygambone

This comment has been minimized.

Copy link

@tonygambone tonygambone commented Jan 7, 2017

Thanks @FlorinAsavoaie @kevinvalk - this is great!

@duebbert

This comment has been minimized.

Copy link

@duebbert duebbert commented Jul 12, 2017

@FlorinAsavoaie @kevinvalk - I fixed problems with strings incorrectly marked as bytes. See my fork: https://gist.github.com/duebbert/4298b5f4eb7cc064b09e9d865dd490c9

@strarsis

This comment has been minimized.

Copy link

@strarsis strarsis commented Jan 14, 2018

@duebbert, @kevenvalk: I get a notice each time I open an extra console:

/home/build/bin/msysgit2unix-socket.py: Already running (or at least pidfile "/tmp/msysgit2unix-socket.pid" already exists).

I think this notice may be helpful, but in this case (on startup) it is rather annoying.

@nazarewk

This comment has been minimized.

Copy link

@nazarewk nazarewk commented Jan 14, 2019

@kevinvalk this should use either /usr/bin/python2 or /usr/bin/env python2

@olegbzk

This comment has been minimized.

Copy link

@olegbzk olegbzk commented Nov 10, 2019

I tried to use this script in WSL2 and got an error when trying to read keys. I have WSL and WSL2 with the same configurations.
WSL
prart of output strace ssh-add -l

socket(AF_UNIX, SOCK_STREAM, 0)         = 3
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
connect(3, {sa_family=AF_UNIX, sun_path="/tmp/.ssh-auth-sock"}, 110) = 0
write(3, "\0\0\0\1", 4)                 = 4
write(3, "\v", 1)                       = 1
read(3, "\0\0\6\241", 4)                = 4
read(3, "\f\0\0\0\4\0\0\2\27\0\0\0\7ssh-rsa\0\0\0\3\1\0\1\0\0\2\1\0"..., 1024) = 1024
read(3, "\216\213\305HQ\226\241\177,O\374\315\333\33\302\273\341\272\2526\34\3\260\263g\327\17\330\25\17\360\36"..., 673) = 673
fstat(1, {st_mode=S_IFCHR|0660, st_rdev=makedev(4, 3), ...}) = 0
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
write(1, "4096 SHA256:ks4Ep+nc/QHwE0iWtVbE"..., 694096 SHA256:ks4Ep+nc/QHwE0iWtBbEexmECco2rO06W3dIWlndtgU id_rsa (RSA)
) = 69
write(1, "4096 SHA256:KBU39i4Ca+4AQo7yviJt"..., 734096 SHA256:KBU39i4Ca+4AQo7yvTJtlNm/Iyxv/BaqHAI7lP0z6js ..tfat1 (RSA)
) = 73
close(3)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

WSL2
strace ssh-add -l

socket(AF_UNIX, SOCK_STREAM, 0)         = 3
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
connect(3, {sa_family=AF_UNIX, sun_path="/tmp/.ssh-auth-sock"}, 110) = 0
write(3, "\0\0\0\1", 4)                 = 4
write(3, "\v", 1)                       = 1
read(3, "", 4)                          = 0
write(2, "error fetching identities: commu"..., 59error fetching identities: communication with agent failed
) = 59
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++
@ColOfAbRiX

This comment has been minimized.

Copy link

@ColOfAbRiX ColOfAbRiX commented Feb 6, 2020

You might find this Ansible role to install and configure the script useful: https://github.com/ColOfAbRiX/role-wsl-keeagent

I also made few changes to the script (original script linked) https://github.com/ColOfAbRiX/role-wsl-keeagent/blob/master/files/msysgit2unix-socket.py and added a SystemV init script https://github.com/ColOfAbRiX/role-wsl-keeagent/blob/master/templates/wsl-keeagent.j2

@musm

This comment has been minimized.

Copy link

@musm musm commented Jun 18, 2020

Any guide on how to get WSL2 working with KeeAgent?

@musm

This comment has been minimized.

Copy link

@musm musm commented Jun 20, 2020

Update to use pthon3 ? Python2 no longer included in ubuntu

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