1 | var Socket = require('net').Socket;
|
2 | var EventEmitter = require('events').EventEmitter;
|
3 | var inherits = require('util').inherits;
|
4 | var path = require('path');
|
5 | var fs = require('fs');
|
6 | var cp = require('child_process');
|
7 |
|
8 | var readUInt32BE = require('./buffer-helpers').readUInt32BE;
|
9 | var writeUInt32BE = require('./buffer-helpers').writeUInt32BE;
|
10 | var writeUInt32LE = require('./buffer-helpers').writeUInt32LE;
|
11 |
|
12 | var REQUEST_IDENTITIES = 11;
|
13 | var IDENTITIES_ANSWER = 12;
|
14 | var SIGN_REQUEST = 13;
|
15 | var SIGN_RESPONSE = 14;
|
16 | var FAILURE = 5;
|
17 |
|
18 | var RE_CYGWIN_SOCK = /^\!<socket >(\d+) s ([A-Z0-9]{8}\-[A-Z0-9]{8}\-[A-Z0-9]{8}\-[A-Z0-9]{8})/;
|
19 |
|
20 |
|
21 | var WINDOWS_PIPE_REGEX = /^[/\\][/\\]\.[/\\]pipe[/\\].+/;
|
22 |
|
23 | module.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 |
|
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 |
|
57 |
|
58 |
|
59 |
|
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 |
|
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 |
|
82 | if (++count === 5) {
|
83 | type = chunk[i];
|
84 | count = 0;
|
85 | }
|
86 | } else if (type === SIGN_RESPONSE) {
|
87 | |
88 |
|
89 |
|
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 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
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 |
|
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 |
|
196 | sock = new PageantSock();
|
197 | } else {
|
198 |
|
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 |
|
205 |
|
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 |
|
231 |
|
232 |
|
233 | credsbuf.fill(0);
|
234 |
|
235 |
|
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 |
|
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 |
|
255 |
|
256 | if (bc === 16) {
|
257 | bc = 0;
|
258 | state = 'creds';
|
259 | sock.write(credsbuf);
|
260 | }
|
261 | } else if (state === 'creds') {
|
262 |
|
263 |
|
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 |
|
341 | if (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 |
|
372 | if (this.buffer.length < 4)
|
373 | return;
|
374 |
|
375 | var len = readUInt32BE(this.buffer, 0);
|
376 |
|
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 | }
|