UNPKG

12.5 kBJavaScriptView Raw
1var Socket = require('net').Socket;
2var EventEmitter = require('events').EventEmitter;
3var inherits = require('util').inherits;
4var path = require('path');
5var fs = require('fs');
6var cp = require('child_process');
7
8var readUInt32BE = require('./buffer-helpers').readUInt32BE;
9var writeUInt32BE = require('./buffer-helpers').writeUInt32BE;
10var writeUInt32LE = require('./buffer-helpers').writeUInt32LE;
11
12var REQUEST_IDENTITIES = 11;
13var IDENTITIES_ANSWER = 12;
14var SIGN_REQUEST = 13;
15var SIGN_RESPONSE = 14;
16var FAILURE = 5;
17
18var RE_CYGWIN_SOCK = /^\!<socket >(\d+) s ([A-Z0-9]{8}\-[A-Z0-9]{8}\-[A-Z0-9]{8}\-[A-Z0-9]{8})/;
19
20// Format of `//./pipe/ANYTHING`, with forward slashes and backward slashes being interchangeable
21var WINDOWS_PIPE_REGEX = /^[/\\][/\\]\.[/\\]pipe[/\\].+/;
22
23module.exports = function(sockPath, key, keyType, data, cb) {
24 var sock;
25 var error;
26 var sig;
27 var datalen;
28 var keylen = 0;
29 var isSigning = Buffer.isBuffer(key);
30 var type;
31 var count = 0;
32 var siglen = 0;
33 var nkeys = 0;
34 var keys;
35 var comlen = 0;
36 var comment = false;
37 var accept;
38 var reject;
39
40 if (typeof key === 'function' && typeof keyType === 'function') {
41 // agent forwarding
42 accept = key;
43 reject = keyType;
44 } else if (isSigning) {
45 keylen = key.length;
46 datalen = data.length;
47 } else {
48 cb = key;
49 key = undefined;
50 }
51
52 function onconnect() {
53 var buf;
54 if (isSigning) {
55 /*
56 byte SSH2_AGENTC_SIGN_REQUEST
57 string key_blob
58 string data
59 uint32 flags
60 */
61 var p = 9;
62 buf = Buffer.allocUnsafe(4 + 1 + 4 + keylen + 4 + datalen + 4);
63 writeUInt32BE(buf, buf.length - 4, 0);
64 buf[4] = SIGN_REQUEST;
65 writeUInt32BE(buf, keylen, 5);
66 key.copy(buf, p);
67 writeUInt32BE(buf, datalen, p += keylen);
68 data.copy(buf, p += 4);
69 writeUInt32BE(buf, 0, p += datalen);
70 sock.write(buf);
71 } else {
72 /*
73 byte SSH2_AGENTC_REQUEST_IDENTITIES
74 */
75 sock.write(Buffer.from([0, 0, 0, 1, REQUEST_IDENTITIES]));
76 }
77 }
78 function ondata(chunk) {
79 for (var i = 0, len = chunk.length; i < len; ++i) {
80 if (type === undefined) {
81 // skip over packet length
82 if (++count === 5) {
83 type = chunk[i];
84 count = 0;
85 }
86 } else if (type === SIGN_RESPONSE) {
87 /*
88 byte SSH2_AGENT_SIGN_RESPONSE
89 string signature_blob
90 */
91 if (!sig) {
92 siglen <<= 8;
93 siglen += chunk[i];
94 if (++count === 4) {
95 sig = Buffer.allocUnsafe(siglen);
96 count = 0;
97 }
98 } else {
99 sig[count] = chunk[i];
100 if (++count === siglen) {
101 sock.removeAllListeners('data');
102 return sock.destroy();
103 }
104 }
105 } else if (type === IDENTITIES_ANSWER) {
106 /*
107 byte SSH2_AGENT_IDENTITIES_ANSWER
108 uint32 num_keys
109
110 Followed by zero or more consecutive keys, encoded as:
111
112 string public key blob
113 string public key comment
114 */
115 if (keys === undefined) {
116 nkeys <<= 8;
117 nkeys += chunk[i];
118 if (++count === 4) {
119 keys = new Array(nkeys);
120 count = 0;
121 if (nkeys === 0) {
122 sock.removeAllListeners('data');
123 return sock.destroy();
124 }
125 }
126 } else {
127 if (!key) {
128 keylen <<= 8;
129 keylen += chunk[i];
130 if (++count === 4) {
131 key = Buffer.allocUnsafe(keylen);
132 count = 0;
133 }
134 } else if (comment === false) {
135 key[count] = chunk[i];
136 if (++count === keylen) {
137 keys[nkeys - 1] = key;
138 keylen = 0;
139 count = 0;
140 comment = true;
141 if (--nkeys === 0) {
142 key = undefined;
143 sock.removeAllListeners('data');
144 return sock.destroy();
145 }
146 }
147 } else if (comment === true) {
148 comlen <<= 8;
149 comlen += chunk[i];
150 if (++count === 4) {
151 count = 0;
152 if (comlen > 0)
153 comment = comlen;
154 else {
155 key = undefined;
156 comment = false;
157 }
158 comlen = 0;
159 }
160 } else {
161 // skip comments
162 if (++count === comment) {
163 comment = false;
164 count = 0;
165 key = undefined;
166 }
167 }
168 }
169 } else if (type === FAILURE) {
170 if (isSigning)
171 error = new Error('Agent unable to sign data');
172 else
173 error = new Error('Unable to retrieve list of keys from agent');
174 sock.removeAllListeners('data');
175 return sock.destroy();
176 }
177 }
178 }
179 function onerror(err) {
180 error = err;
181 }
182 function onclose() {
183 if (error)
184 cb(error);
185 else if ((isSigning && !sig) || (!isSigning && !keys))
186 cb(new Error('Unexpected disconnection from agent'));
187 else if (isSigning && sig)
188 cb(undefined, sig);
189 else if (!isSigning && keys)
190 cb(undefined, keys);
191 }
192
193 if (process.platform === 'win32' && !WINDOWS_PIPE_REGEX.test(sockPath)) {
194 if (sockPath === 'pageant') {
195 // Pageant (PuTTY authentication agent)
196 sock = new PageantSock();
197 } else {
198 // cygwin ssh-agent instance
199 var triedCygpath = false;
200 fs.readFile(sockPath, function readCygsocket(err, data) {
201 if (err) {
202 if (triedCygpath)
203 return cb(new Error('Invalid cygwin unix socket path'));
204 // try using `cygpath` to convert a possible *nix-style path to the
205 // real Windows path before giving up ...
206 cp.exec('cygpath -w "' + sockPath + '"',
207 function(err, stdout, stderr) {
208 if (err || stdout.length === 0)
209 return cb(new Error('Invalid cygwin unix socket path'));
210 triedCygpath = true;
211 sockPath = stdout.toString().replace(/[\r\n]/g, '');
212 fs.readFile(sockPath, readCygsocket);
213 });
214 return;
215 }
216
217 var m;
218 if (m = RE_CYGWIN_SOCK.exec(data.toString('ascii'))) {
219 var port;
220 var secret;
221 var secretbuf;
222 var state;
223 var bc = 0;
224 var isRetrying = false;
225 var inbuf = [];
226 var credsbuf = Buffer.allocUnsafe(12);
227 var i;
228 var j;
229
230 // use 0 for pid, uid, and gid to ensure we get an error and also
231 // a valid uid and gid from cygwin so that we don't have to figure it
232 // out ourselves
233 credsbuf.fill(0);
234
235 // parse cygwin unix socket file contents
236 port = parseInt(m[1], 10);
237 secret = m[2].replace(/\-/g, '');
238 secretbuf = Buffer.allocUnsafe(16);
239 for (i = 0, j = 0; j < 32; ++i,j+=2)
240 secretbuf[i] = parseInt(secret.substring(j, j + 2), 16);
241
242 // convert to host order (always LE for Windows)
243 for (i = 0; i < 16; i += 4)
244 writeUInt32LE(secretbuf, readUInt32BE(secretbuf, i), i);
245
246 function _onconnect() {
247 bc = 0;
248 state = 'secret';
249 sock.write(secretbuf);
250 }
251 function _ondata(data) {
252 bc += data.length;
253 if (state === 'secret') {
254 // the secret we sent is echoed back to us by cygwin, not sure of
255 // the reason for that, but we ignore it nonetheless ...
256 if (bc === 16) {
257 bc = 0;
258 state = 'creds';
259 sock.write(credsbuf);
260 }
261 } else if (state === 'creds') {
262 // if this is the first attempt, make sure to gather the valid
263 // uid and gid for our next attempt
264 if (!isRetrying)
265 inbuf.push(data);
266
267 if (bc === 12) {
268 sock.removeListener('connect', _onconnect);
269 sock.removeListener('data', _ondata);
270 sock.removeListener('close', _onclose);
271 if (isRetrying) {
272 addSockListeners();
273 sock.emit('connect');
274 } else {
275 isRetrying = true;
276 credsbuf = Buffer.concat(inbuf);
277 writeUInt32LE(credsbuf, process.pid, 0);
278 sock.destroy();
279 tryConnect();
280 }
281 }
282 }
283 }
284 function _onclose() {
285 cb(new Error('Problem negotiating cygwin unix socket security'));
286 }
287 function tryConnect() {
288 sock = new Socket();
289 sock.once('connect', _onconnect);
290 sock.on('data', _ondata);
291 sock.once('close', _onclose);
292 sock.connect(port);
293 }
294 tryConnect();
295 } else
296 cb(new Error('Malformed cygwin unix socket file'));
297 });
298 return;
299 }
300 } else
301 sock = new Socket();
302
303 function addSockListeners() {
304 if (!accept && !reject) {
305 sock.once('connect', onconnect);
306 sock.on('data', ondata);
307 sock.once('error', onerror);
308 sock.once('close', onclose);
309 } else {
310 var chan;
311 sock.once('connect', function() {
312 chan = accept();
313 var isDone = false;
314 function onDone() {
315 if (isDone)
316 return;
317 sock.destroy();
318 isDone = true;
319 }
320 chan.once('end', onDone)
321 .once('close', onDone)
322 .on('data', function(data) {
323 sock.write(data);
324 });
325 sock.on('data', function(data) {
326 chan.write(data);
327 });
328 });
329 sock.once('close', function() {
330 if (!chan)
331 reject();
332 });
333 }
334 }
335 addSockListeners();
336 sock.connect(sockPath);
337};
338
339
340// win32 only ------------------------------------------------------------------
341if (process.platform === 'win32') {
342 var RET_ERR_BADARGS = 10;
343 var RET_ERR_UNAVAILABLE = 11;
344 var RET_ERR_NOMAP = 12;
345 var RET_ERR_BINSTDIN = 13;
346 var RET_ERR_BINSTDOUT = 14;
347 var RET_ERR_BADLEN = 15;
348
349 var ERROR = {};
350 var EXEPATH = path.resolve(__dirname, '..', 'util/pagent.exe');
351 ERROR[RET_ERR_BADARGS] = new Error('Invalid pagent.exe arguments');
352 ERROR[RET_ERR_UNAVAILABLE] = new Error('Pageant is not running');
353 ERROR[RET_ERR_NOMAP] = new Error('pagent.exe could not create an mmap');
354 ERROR[RET_ERR_BINSTDIN] = new Error('pagent.exe could not set mode for stdin');
355 ERROR[RET_ERR_BINSTDOUT] = new Error('pagent.exe could not set mode for stdout');
356 ERROR[RET_ERR_BADLEN] = new Error('pagent.exe did not get expected input payload');
357
358 function PageantSock() {
359 this.proc = undefined;
360 this.buffer = null;
361 }
362 inherits(PageantSock, EventEmitter);
363
364 PageantSock.prototype.write = function(buf) {
365 if (this.buffer === null)
366 this.buffer = buf;
367 else {
368 this.buffer = Buffer.concat([this.buffer, buf],
369 this.buffer.length + buf.length);
370 }
371 // Wait for at least all length bytes
372 if (this.buffer.length < 4)
373 return;
374
375 var len = readUInt32BE(this.buffer, 0);
376 // Make sure we have a full message before querying pageant
377 if ((this.buffer.length - 4) < len)
378 return;
379
380 buf = this.buffer.slice(0, 4 + len);
381 if (this.buffer.length > (4 + len))
382 this.buffer = this.buffer.slice(4 + len);
383 else
384 this.buffer = null;
385
386 var self = this;
387 var proc;
388 var hadError = false;
389 proc = this.proc = cp.spawn(EXEPATH, [ buf.length ]);
390 proc.stdout.on('data', function(data) {
391 self.emit('data', data);
392 });
393 proc.once('error', function(err) {
394 if (!hadError) {
395 hadError = true;
396 self.emit('error', err);
397 }
398 });
399 proc.once('close', function(code) {
400 self.proc = undefined;
401 if (ERROR[code] && !hadError) {
402 hadError = true;
403 self.emit('error', ERROR[code]);
404 }
405 self.emit('close', hadError);
406 });
407 proc.stdin.end(buf);
408 };
409 PageantSock.prototype.end = PageantSock.prototype.destroy = function() {
410 this.buffer = null;
411 if (this.proc) {
412 this.proc.kill();
413 this.proc = undefined;
414 }
415 };
416 PageantSock.prototype.connect = function() {
417 this.emit('connect');
418 };
419}