1 var stream
= require('stream'),
2 util
= require('util'),
3 events
= require('events'),
10 ProxyServer
: ProxyServer
,
11 ProxySocket
: ProxySocket
15 //console.log.apply(console, arguments);
18 // Socket connection responses
19 var RESPONSE_ERROR
= '0';
20 var RESPONSE_OK
= '1';
21 var RESPONSE_ECONNRESET
= '2';
22 var RESPONSE_ECONNREFUSED
= '3';
23 var RESPONSE_ENOTFOUND
= '4';
24 var RESPONSE_ETIMEDOUT
= '5';
30 * Listens for connections from a kiwi server, dispatching ProxyPipe
31 * instances for each connection
33 function ProxyServer() {
34 events
.EventEmitter
.call(this);
36 util
.inherits(ProxyServer
, events
.EventEmitter
);
39 ProxyServer
.prototype.listen = function(listen_port
, listen_addr
, opts
) {
42 connection_event
= 'connection';
49 key
: fs
.readFileSync(opts
.ssl_key
),
50 cert
: fs
.readFileSync(opts
.ssl_cert
)
53 // Do we have an intermediate certificate?
54 if (typeof opts
.ssl_ca
!== 'undefined') {
56 if (typeof opts
.ssl_ca
.map
!== 'undefined') {
57 serv_opts
.ca
= opts
.ssl_ca
.map(function (f
) { return fs
.readFileSync(f
); });
60 serv_opts
.ca
= fs
.readFileSync(opts
.ssl_ca
);
64 this.server
= tls
.createServer(serv_opts
);
66 connection_event
= 'secureConnection';
70 // No SSL, start a simple clear text server
72 this.server
= new net
.Server();
75 this.server
.listen(listen_port
, listen_addr
, function() {
76 that
.emit('listening');
79 this.server
.on(connection_event
, function(socket
) {
80 new ProxyPipe(socket
, that
);
85 ProxyServer
.prototype.close = function(callback
) {
87 return this.server
.close(callback
);
90 if (typeof callback
=== 'function')
99 * Takes connections from a kiwi server, then:
100 * 1. Reads its meta data such as username for identd lookups
101 * 2. Make the connection to the IRC server
102 * 3. Reply to the kiwi server with connection status
103 * 4. If all ok, pipe data between the 2 sockets as a proxy
105 function ProxyPipe(kiwi_socket
, proxy_server
) {
106 debug('[KiwiProxy] New Kiwi connection');
108 this.kiwi_socket
= kiwi_socket
;
109 this.proxy_server
= proxy_server
;
110 this.irc_socket
= null;
114 kiwi_socket
.on('readable', this.kiwiSocketOnReadable
.bind(this));
118 ProxyPipe
.prototype.destroy = function() {
122 if (this.irc_socket
) {
123 this.irc_socket
.destroy();
124 this.irc_socket
.removeAllListeners();
125 this.irc_socket
= null;
128 if (this.kiwi_socket
) {
129 this.kiwi_socket
.destroy();
130 this.kiwi_socket
.removeAllListeners();
131 this.kiwi_socket
= null;
136 ProxyPipe
.prototype.kiwiSocketOnReadable = function() {
137 var chunk
, buffer
, meta
;
139 while ((chunk
= this.kiwi_socket
.read()) !== null) {
140 this.buffers
.push(chunk
);
143 // Not got a complete line yet? Wait some more
144 chunk
= this.buffers
[this.buffers
.length
-1];
145 if (!chunk
|| chunk
[chunk
.length
-1] !== 0x0A)
148 buffer
= new Buffer
.concat(this.buffers
);
152 debug('[KiwiProxy] Found a complete line in the buffer');
153 meta
= JSON
.parse(buffer
.toString('utf8'));
155 debug('[KiwiProxy] Error parsing meta');
160 if (!meta
.username
) {
161 debug('[KiwiProxy] Meta does not contain a username');
167 this.kiwi_socket
.removeAllListeners('readable');
169 this.makeIrcConnection();
173 ProxyPipe
.prototype.makeIrcConnection = function() {
174 debug('[KiwiProxy] Opening proxied connection to: ' + this.meta
.host
+ ':' + this.meta
.port
.toString());
176 var local_address
= this.meta
.interface ?
177 this.meta
.interface :
181 this.irc_socket
= tls
.connect({
182 port
: parseInt(this.meta
.port
, 10),
183 host
: this.meta
.host
,
184 rejectUnauthorized
: global
.config
.reject_unauthorised_certificates
,
185 localAddress
: local_address
186 }, this._onSocketConnect
.bind(this));
189 this.irc_socket
= net
.connect({
190 port
: parseInt(this.meta
.port
, 10),
191 host
: this.meta
.host
,
192 localAddress
: local_address
193 }, this._onSocketConnect
.bind(this));
196 this.irc_socket
.setTimeout(10000);
197 this.irc_socket
.on('error', this._onSocketError
.bind(this));
198 this.irc_socket
.on('timeout', this._onSocketTimeout
.bind(this));
200 // We need the raw socket connect event, not after any SSL handshakes or anything
201 if (this.irc_socket
.socket
) {
202 this.irc_socket
.socket
.on('connect', this._onRawSocketConnect
.bind(this));
204 this.irc_socket
.on('connect', this._onRawSocketConnect
.bind(this));
209 ProxyPipe
.prototype._onRawSocketConnect = function() {
210 this.proxy_server
.emit('socket_connected', this);
214 ProxyPipe
.prototype._onSocketConnect = function() {
215 debug('[KiwiProxy] ProxyPipe::_onSocketConnect()');
217 this.proxy_server
.emit('connection_open', this);
219 // Now that we're connected to the detination, return no
220 // error back to the kiwi server and start piping
221 this.kiwi_socket
.write(new Buffer(RESPONSE_OK
.toString()), this.startPiping
.bind(this));
225 ProxyPipe
.prototype._onSocketError = function(err
) {
227 ECONNRESET
: RESPONSE_ECONNRESET
,
228 ECONNREFUSED
: RESPONSE_ECONNREFUSED
,
229 ENOTFOUND
: RESPONSE_ENOTFOUND
,
230 ETIMEDOUT
: RESPONSE_ETIMEDOUT
232 debug('[KiwiProxy] IRC Error ' + err
.code
);
233 this.kiwi_socket
.write(new Buffer((replies
[err
.code
] || RESPONSE_ERROR
).toString()), 'UTF8', this.destroy
.bind(this));
237 ProxyPipe
.prototype._onSocketTimeout = function() {
238 this.has_timed_out
= true;
239 debug('[KiwiProxy] IRC Timeout');
240 this.irc_socket
.destroy();
241 this.kiwi_socket
.write(new Buffer(RESPONSE_ETIMEDOUT
.toString()), 'UTF8', this.destroy
.bind(this));
245 ProxyPipe
.prototype._onSocketClose = function() {
246 debug('[KiwiProxy] IRC Socket closed');
247 this.proxy_server
.emit('connection_close', this);
252 ProxyPipe
.prototype.startPiping = function() {
253 debug('[KiwiProxy] ProxyPipe::startPiping()');
255 // Let the piping handle socket closures
256 this.irc_socket
.removeAllListeners('error');
257 this.irc_socket
.removeAllListeners('timeout');
259 this.irc_socket
.on('close', this._onSocketClose
.bind(this));
261 this.kiwi_socket
.pipe(this.irc_socket
);
262 this.irc_socket
.pipe(this.kiwi_socket
);
271 * Transparent socket interface to a kiwi proxy
273 function ProxySocket(proxy_port
, proxy_addr
, meta
, proxy_opts
) {
274 stream
.Duplex
.call(this);
276 this.connected_fn
= null;
277 this.proxy_addr
= proxy_addr
;
278 this.proxy_port
= proxy_port
;
279 this.proxy_opts
= proxy_opts
|| {};
281 this.setMeta(meta
|| {});
283 this.state
= 'disconnected';
286 util
.inherits(ProxySocket
, stream
.Duplex
);
289 ProxySocket
.prototype.setMeta = function(meta
) {
294 ProxySocket
.prototype.connectTls = function() {
295 this.meta
.ssl
= true;
296 return this.connect
.apply(this, arguments
);
300 ProxySocket
.prototype.connect = function(dest_port
, dest_addr
, connected_fn
) {
301 this.meta
.host
= dest_addr
;
302 this.meta
.port
= dest_port
;
303 this.connected_fn
= connected_fn
;
305 if (!this.meta
.host
|| !this.meta
.port
) {
306 debug('[KiwiProxy] Invalid destination addr/port', this.meta
);
310 debug('[KiwiProxy] Connecting to proxy ' + this.proxy_addr
+ ':' + this.proxy_port
.toString() + ' SSL: ' + (!!this.proxy_opts
.ssl
).toString());
311 if (this.proxy_opts
.ssl
) {
312 this.socket
= tls
.connect({
313 port
: this.proxy_port
,
314 host
: this.proxy_addr
,
315 rejectUnauthorized
: !!global
.config
.reject_unauthorised_certificates
,
316 }, this._onSocketConnect
.bind(this));
319 this.socket
= net
.connect(this.proxy_port
, this.proxy_addr
, this._onSocketConnect
.bind(this));
322 this.socket
.setTimeout(10000);
323 this.socket
.on('data', this._onSocketData
.bind(this));
324 this.socket
.on('close', this._onSocketClose
.bind(this));
325 this.socket
.on('error', this._onSocketError
.bind(this));
331 ProxySocket
.prototype.destroySocket = function() {
335 this.socket
.removeAllListeners();
336 this.socket
.destroy();
339 debug('[KiwiProxy] Destroying socket');
343 ProxySocket
.prototype._read = function() {
346 if (this.state
=== 'connected' && this.socket
) {
347 while ((data
= this.socket
.read()) !== null) {
348 if (this.push(data
) === false) {
358 ProxySocket
.prototype._write = function(chunk
, encoding
, callback
) {
359 if (this.state
=== 'connected' && this.socket
) {
360 return this.socket
.write(chunk
, encoding
, callback
);
362 debug('[KiwiProxy] Trying to write to an unfinished socket. State=' + this.state
);
363 callback('Not connected');
368 ProxySocket
.prototype._onSocketConnect = function() {
369 var meta
= this.meta
|| {};
371 this.state
= 'handshaking';
373 debug('[KiwiProxy] Connected to proxy, sending meta');
374 this.socket
.write(JSON
.stringify(meta
) + '\n');
378 ProxySocket
.prototype._onSocketData = function(data
) {
379 if (this.state
=== 'connected') {
380 this.emit('data', data
);
384 var buffer_str
= data
.toString(),
385 status
= buffer_str
[0],
389 error_codes
[RESPONSE_ERROR
] = 'ERROR';
390 error_codes
[RESPONSE_ECONNRESET
] = 'ECONNRESET';
391 error_codes
[RESPONSE_ECONNREFUSED
] = 'ECONNREFUSED';
392 error_codes
[RESPONSE_ENOTFOUND
] = 'ENOTFOUND';
393 error_codes
[RESPONSE_ETIMEDOUT
] = 'ETIMEDOUT';
395 debug('[KiwiProxy] Recieved socket status: ' + data
.toString());
396 if (status
=== RESPONSE_OK
) {
397 debug('[KiwiProxy] Remote socket connected OK');
398 this.state
= 'connected';
400 if (typeof this.connected_fn
=== 'function')
403 this.emit('connect');
406 this.destroySocket();
408 error_code
= error_codes
[status
] || error_codes
[RESPONSE_ERROR
];
409 debug('[KiwiProxy] Error: ' + error_code
);
410 this.emit('error', new Error(error_code
));
415 ProxySocket
.prototype._onSocketClose = function(had_error
) {
416 debug('[KiwiProxy] _onSocketClose() had_error=' + had_error
.toString());
417 if (this.state
=== 'connected') {
418 this.emit('close', had_error
);
422 if (!this.ignore_close
)
423 this.emit('error', new Error(RESPONSE_ERROR
));
427 ProxySocket
.prototype._onSocketError = function(err
) {
428 debug('[KiwiProxy] _onSocketError() err=' + err
.toString());
429 this.ignore_close
= true;
430 this.emit('error', err
);