Skip to content

Commit f1bdb88

Browse files
Mark Dietzerbenpye
authored andcommitted
Move to AF_UNIX listener instead of socat usage
1 parent 0e168a2 commit f1bdb88

File tree

3 files changed

+246
-27
lines changed

3 files changed

+246
-27
lines changed

Program.cs

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.IO;
23
using System.Net;
34
using System.Net.Sockets;
45
using System.Runtime.InteropServices;
@@ -79,7 +80,7 @@ struct COPYDATASTRUCT
7980
static readonly IntPtr AGENT_COPYDATA_ID = new IntPtr(0x804e50ba);
8081

8182
// Send ssh-agent query to Pageant, ssh-agent and Pageant use same messages
82-
static byte[] Query(byte[] buf)
83+
static byte[] Query(byte[] buf, int buflen)
8384
{
8485
var hwnd = FindWindowByCaption(IntPtr.Zero, "Pageant");
8586

@@ -89,17 +90,17 @@ static byte[] Query(byte[] buf)
8990

9091
var sharedMemory = MapViewOfFile(fileMap, FileMapAccess.FileMapWrite, 0, 0, UIntPtr.Zero);
9192

92-
Marshal.Copy(buf, 0, sharedMemory, buf.Length);
93+
Marshal.Copy(buf, 0, sharedMemory, buflen);
9394

9495
var cds = new COPYDATASTRUCT();
9596
cds.dwData = AGENT_COPYDATA_ID;
9697
cds.cbData = mapName.Length + 1;
97-
var foo = Encoding.UTF8.GetBytes(mapName);
98-
var bar = new byte[foo.Length + 1];
99-
foo.CopyTo(bar, 0);
100-
bar[bar.Length - 1] = 0;
101-
var gch = GCHandle.Alloc(bar);
102-
cds.lpData = Marshal.UnsafeAddrOfPinnedArrayElement(bar, 0);
98+
var mapNameBytes = Encoding.UTF8.GetBytes(mapName);
99+
var mapNameBytesZero = new byte[mapNameBytes.Length + 1];
100+
mapNameBytes.CopyTo(mapNameBytesZero, 0);
101+
mapNameBytesZero[mapNameBytesZero.Length - 1] = 0;
102+
var gch = GCHandle.Alloc(mapNameBytesZero);
103+
cds.lpData = Marshal.UnsafeAddrOfPinnedArrayElement(mapNameBytesZero, 0);
103104
var data = Marshal.AllocHGlobal(Marshal.SizeOf(cds));
104105
Marshal.StructureToPtr(cds, data, false);
105106
var rcode = SendMessage(hwnd, WM_COPYDATA, IntPtr.Zero, data);
@@ -117,21 +118,98 @@ static byte[] Query(byte[] buf)
117118
return ret;
118119
}
119120

121+
private static void Callback(Task<Socket> t, object state)
122+
{
123+
var client = t.Result;
124+
client.ReceiveTimeout = 1000;
125+
client.SendTimeout = 1000;
126+
127+
try
128+
{
129+
ServiceSocket(client);
130+
}
131+
catch (TimeoutException)
132+
{
133+
// Ignore timeouts, those should not explode our stuff
134+
}
135+
catch (SocketException e)
136+
{
137+
// Other socket errors can happen and shouldn't kill the app
138+
Console.Error.WriteLine(e);
139+
}
140+
141+
client.Dispose();
142+
}
143+
144+
private static bool ReadUntil(Socket client, byte[] buf, int buflen, int offset = 0)
145+
{
146+
int i;
147+
while ((i = client.Receive(buf, offset, buflen - offset, SocketFlags.None)) != 0)
148+
{
149+
offset += i;
150+
if (offset >= buflen)
151+
{
152+
return true;
153+
}
154+
}
155+
return false;
156+
}
157+
158+
private static void ServiceSocket(Socket client)
159+
{
160+
var lenBytes = new byte[4]; // uint32
161+
162+
while (true)
163+
{
164+
// Read length as uint32 (4 bytes)
165+
if (!ReadUntil(client, lenBytes, lenBytes.Length))
166+
{
167+
break;
168+
}
169+
var len = (lenBytes[0] << 24) |
170+
(lenBytes[1] << 16) |
171+
(lenBytes[2] << 8 ) |
172+
(lenBytes[3]);
173+
174+
// Read actual data and copy length to the start of it
175+
var bytes = new byte[4 + len];
176+
lenBytes.CopyTo(bytes, 0);
177+
if (!ReadUntil(client, bytes, bytes.Length, 4))
178+
{
179+
break;
180+
}
181+
182+
var msg = Query(bytes, bytes.Length);
183+
client.Send(msg, 0, msg.Length, SocketFlags.None);
184+
}
185+
}
186+
120187
static void Main(string[] args)
121188
{
122-
var outstream = Console.OpenStandardOutput();
123-
var instream = Console.OpenStandardInput();
189+
var socketPath = @".\ssh-agent.sock";
124190

125-
int i;
191+
if (args.Length == 1)
192+
socketPath = args[0];
193+
else if(args.Length != 0)
194+
{
195+
Console.WriteLine(@"wsl-ssh-agent.exe <path: .\ssh-agent.sock>");
196+
return;
197+
}
198+
199+
File.Delete(socketPath);
200+
var server = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
201+
server.Bind(new UnixEndPoint(socketPath));
202+
server.Listen(5);
126203

127-
// Buffer for reading data
128-
var bytes = new Byte[AGENT_MAX_MSGLEN];
204+
Console.WriteLine(@"Listening on {0}", socketPath);
129205

130-
// Loop to receive all the data sent by the client.
131-
while((i = instream.Read(bytes, 0, bytes.Length))!=0)
206+
// Enter the listening loop.
207+
while(true)
132208
{
133-
var msg = Query(bytes);
134-
outstream.Write(msg, 0, msg.Length);
209+
var t = server.AcceptAsync();
210+
t.ContinueWith(Callback, null);
211+
t.Wait();
135212
}
136213
}
137214
}
215+

Readme.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
# wsl-ssh-pageant
22

3-
**Now supports multiple ssh connections concurrently!**
4-
5-
A Pageant -> TCP bridge for use with WSL, allowing for Pageant to be used as an ssh-ageant within the WSL environment.
6-
7-
Also check out [NZSmartie's Go version](https://github.com/NZSmartie/wsl-ssh-pageant).
8-
9-
![Demo](demo.gif?raw=True)
3+
**Now uses freshly baked AF_UNIX support in Windows 10 insider**
104

115
## How to use
126

137
1. On the Windows side run Pageant (or compatible agent such as gpg4win).
148

15-
2. Ensure that the directory containing `wsl-ssh-pageant.exe` is on the `PATH` in WSL, for example my path contains `/mnt/c/git/wsl-ssh-pageant'
9+
2. Run wsl-ssh-pageant.exe on windows in a short path (max ~100 characters total!)
1610

1711
3. In WSL run the following
1812

1913
```
20-
$ socat UNIX-LISTEN:/tmp/wsl-ssh-pageant.socket,unlink-close,unlink-early,fork EXEC:"wsl-ssh-pageant.exe" &
21-
$ export SSH_AUTH_SOCK=/tmp/wsl-ssh-pageant.socket
14+
$ export SSH_AUTH_SOCK=/mnt/your-path-to-wsl-ssh-pageant.exe/ssh-agent.sock
15+
```
16+
For example, if you have wsl-ssh-pageant.exe in `C:\wsl-ssh-pageant`
17+
```
18+
$ export SSH_AUTH_SOCK=/mnt/c/wsl-ssh-pageant/ssh-agent.sock
2219
```
2320

2421
4. The SSH keys from Pageant should now be usable by `ssh`!

UnixEndPoint.cs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System;
2+
using System.Net;
3+
using System.Net.Sockets;
4+
using System.Text;
5+
6+
// copied from https://github.com/mono/mono/blob/master/mcs/class/Mono.Posix/Mono.Unix/UnixEndPoint.cs
7+
// modified by Mark "Doridian" Dietzer to work with AF_UNIX on win32 [requires 108 / UNIX_PATH_MAX size struct and fixes for RemoteEndPoint due to that]
8+
9+
//
10+
// Mono.Unix.UnixEndPoint: EndPoint derived class for AF_UNIX family sockets.
11+
//
12+
// Authors:
13+
// Gonzalo Paniagua Javier ([email protected])
14+
//
15+
// (C) 2003 Ximian, Inc (http://www.ximian.com)
16+
//
17+
18+
//
19+
// Permission is hereby granted, free of charge, to any person obtaining
20+
// a copy of this software and associated documentation files (the
21+
// "Software"), to deal in the Software without restriction, including
22+
// without limitation the rights to use, copy, modify, merge, publish,
23+
// distribute, sublicense, and/or sell copies of the Software, and to
24+
// permit persons to whom the Software is furnished to do so, subject to
25+
// the following conditions:
26+
//
27+
// The above copyright notice and this permission notice shall be
28+
// included in all copies or substantial portions of the Software.
29+
//
30+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34+
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36+
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37+
//
38+
39+
40+
public class UnixEndPoint : EndPoint
41+
{
42+
string filename;
43+
44+
const int UNIX_PATH_MAX = 108;
45+
46+
public UnixEndPoint(string filename)
47+
{
48+
if (filename == null)
49+
throw new ArgumentNullException("filename");
50+
51+
if (filename == "")
52+
throw new ArgumentException("Cannot be empty.", "filename");
53+
54+
this.filename = filename;
55+
}
56+
57+
public string Filename
58+
{
59+
get
60+
{
61+
return (filename);
62+
}
63+
set
64+
{
65+
filename = value;
66+
}
67+
}
68+
69+
public override AddressFamily AddressFamily
70+
{
71+
get { return AddressFamily.Unix; }
72+
}
73+
74+
public override EndPoint Create(SocketAddress socketAddress)
75+
{
76+
/*
77+
* Should also check this
78+
*
79+
int addr = (int) AddressFamily.Unix;
80+
if (socketAddress [0] != (addr & 0xFF))
81+
throw new ArgumentException ("socketAddress is not a unix socket address.");
82+
if (socketAddress [1] != ((addr & 0xFF00) >> 8))
83+
throw new ArgumentException ("socketAddress is not a unix socket address.");
84+
*/
85+
86+
int size = socketAddress.Size - 2;
87+
byte[] bytes = new byte[size];
88+
for (int i = 0; i < bytes.Length; i++)
89+
{
90+
bytes[i] = socketAddress[i + 2];
91+
// There may be junk after the null terminator, so ignore it all.
92+
if (bytes[i] == 0)
93+
{
94+
size = i;
95+
break;
96+
}
97+
}
98+
99+
if (size == 0)
100+
{
101+
// Empty filename.
102+
// Probably from RemoteEndPoint which on linux does not return the file name.
103+
UnixEndPoint uep = new UnixEndPoint("a");
104+
uep.filename = "";
105+
return uep;
106+
}
107+
108+
string name = Encoding.UTF8.GetString(bytes, 0, size);
109+
return new UnixEndPoint(name);
110+
}
111+
112+
public override SocketAddress Serialize()
113+
{
114+
byte[] bytes = Encoding.UTF8.GetBytes(filename);
115+
SocketAddress sa = new SocketAddress(AddressFamily, 2 + UNIX_PATH_MAX);
116+
// sa [0] -> family low byte, sa [1] -> family high byte
117+
for (int i = 0; i < bytes.Length; i++)
118+
sa[2 + i] = bytes[i];
119+
120+
//NULL suffix for non-abstract path
121+
sa[2 + bytes.Length] = 0;
122+
123+
return sa;
124+
}
125+
126+
public override string ToString()
127+
{
128+
return (filename);
129+
}
130+
131+
public override int GetHashCode()
132+
{
133+
return filename.GetHashCode();
134+
}
135+
136+
public override bool Equals(object o)
137+
{
138+
UnixEndPoint other = o as UnixEndPoint;
139+
if (other == null)
140+
return false;
141+
142+
return (other.filename == filename);
143+
}
144+
}

0 commit comments

Comments
 (0)