Created
December 5, 2019 04:09
-
-
Save Jimmy-Z/a2f3ef239932110b0febd82c1c5f66f7 to your computer and use it in GitHub Desktop.
bridge msys2/cygwin openssh to win32-openssh's ssh-agent, in node.js
This file contains hidden or 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
/* fucking ssh-agent, again | |
we have at least 4 incompatible ssh-agents on windows | |
there're numerous bridging tools between them but for the love of God I couldn't find the one I need | |
this thing bridges msys2/cygwin openssh to win32-openssh's ssh-agent | |
probably the crudest thing you've ever seen, no error handling what-so-ever | |
if in any rare case you need this too, run this and: | |
`export SSH_AUTH_SOCK=$LOCALAPPDATA\\ssh-agent-sock` | |
*/ | |
"use strict"; | |
const net = require("net"); | |
const crypto = require("crypto"); | |
const path = require("path"); | |
const fs = require("fs"); | |
const child_process = require("child_process"); | |
const SOCK_FILE_NAME = path.join(process.env.LOCALAPPDATA, "ssh-agent-sock"); | |
const LO = "127.0.0.1"; | |
const S_ID = 0; | |
const S_PID = 1; | |
// cygwin (emulated) unix domain socket | |
// https://stackoverflow.com/questions/23086038/what-mechanism-is-used-by-msys-cygwin-to-emulate-unix-domain-sockets | |
function c2w(id){ | |
return net.createServer(c => { | |
console.log(`new connection from ${c.remoteAddress}:${c.remotePort}`); | |
let state = S_ID; | |
c.on("data", data => { | |
console.log(`received ${data.length} bytes from ${c.remoteAddress}:${c.remotePort}`); | |
// this is not a correct way of handling tcp data, | |
if(state === S_ID && data.length === 16 && id.compare(data) === 0){ | |
console.log("\tgot correct id"); | |
state = S_PID; | |
c.write(data); | |
}else if(state === S_PID && data.length === 12){ | |
const pid = data.readUInt32LE(0); | |
const uid = data.readUInt32LE(4); | |
const gid = data.readUInt32LE(8); | |
console.log(`\tgot pid=${pid}, uid=${uid}, gid=${gid}`); | |
data.writeUInt32LE(process.pid, 0); | |
c.removeAllListeners("data"); | |
c.write(data); | |
const upstream = net.connect("\\\\?\\pipe\\openssh-ssh-agent"); | |
upstream.on("connect", () => { | |
console.log("\tconnected to upstream named pipe"); | |
upstream.pipe(c); | |
c.pipe(upstream); | |
}); | |
}else{ | |
console.error("\tunexpected data"); | |
c.end(); | |
} | |
}); | |
c.on("close", () => { | |
console.log(`connection from ${c.remoteAddress}:${c.remotePort} closed`); | |
}); | |
}); | |
} | |
// entry point | |
// I couldn't find a way to reliably do something(delete the socket desc file) on exit | |
fs.exists(SOCK_FILE_NAME, exists => { | |
if(exists){ | |
const sock_desc = parse_cygwin_unix_domain_socket_desc(fs.readFileSync(SOCK_FILE_NAME, {encoding: "ascii"})); | |
if(sock_desc === null){ | |
throw new Error("invalid socket file"); | |
}else{ | |
const d = c2w(sock_desc.id); | |
d.on("error", (e) => { | |
if(e.code === "EADDRINUSE"){ | |
console.log("socket address in use, presumably there is another instance running, will now quit"); | |
} | |
}); | |
d.listen(sock_desc.port, LO, () => { | |
// or should we start on a new port with a new id instead? | |
console.log(`reusing previous socket file, listening on ${sock_desc.port}`); | |
}); | |
} | |
}else{ | |
const id = crypto.randomBytes(16); | |
const d = c2w(id); | |
d.listen(0, LO, () => { | |
const port = d.address().port; | |
console.log(`listening on ${port}`); | |
fs.writeFile(SOCK_FILE_NAME, | |
generate_cygwin_unix_domain_socket_desc(port, id), | |
{encoding: "ascii"}, | |
() => { | |
child_process.exec(`attrib +s \"${SOCK_FILE_NAME}\"`); | |
}); | |
}); | |
} | |
}); | |
// helper functions | |
function parse_cygwin_unix_domain_socket_desc(s){ | |
const m = /^!<socket >(\d+) s ([0-9a-f]{8})-([0-9a-f]{8})-([0-9a-f]{8})-([0-9a-f]{8})/i.exec(s); | |
if (m === null){ | |
return null; | |
}else{ | |
const id = Buffer.alloc(16); | |
[0, 4, 8, 12].forEach((offset, index) => { | |
id.writeUInt32LE(parseInt(m[2 + index], 16), offset); | |
}); | |
return { port: parseInt(m[1]), id: id }; | |
} | |
} | |
function generate_cygwin_unix_domain_socket_desc(port, id){ | |
const id_str = [0, 4, 8, 12].map(o => { | |
const s = id.readUInt32LE(o).toString(16); | |
return "00000000".slice(s.length).concat(s); | |
}).join("-").toUpperCase() | |
return `!<socket >${port} s ${id_str}\0`; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment