From 4136eb3871550dfa4706f59c9972d6965239fe20 Mon Sep 17 00:00:00 2001 From: Jack Allnutt Date: Fri, 15 Jul 2011 14:55:45 +0100 Subject: [PATCH] Support for CAP, socket.io now supplying the client code, moved underscore to lib folder. Added starttls.js for STARTTLS support but implementation commented out because it's broken --- index.php | 2 +- node/kiwi.js | 99 ++++++++++++++++++++++++++++---- node/lib/starttls.js | 68 ++++++++++++++++++++++ node/{ => lib}/underscore.min.js | 0 4 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 node/lib/starttls.js rename node/{ => lib}/underscore.min.js (100%) diff --git a/index.php b/index.php index cd43e8a..a2c1dee 100644 --- a/index.php +++ b/index.php @@ -45,7 +45,7 @@ - + diff --git a/node/kiwi.js b/node/kiwi.js index 0558546..f46eed5 100644 --- a/node/kiwi.js +++ b/node/kiwi.js @@ -4,7 +4,8 @@ var tls = require('tls'), net = require('net'), http = require('http'), ws = require('socket.io'), - _ = require('./underscore.min.js'); + _ = require('./lib/underscore.min.js'), + starttls = require('./lib/starttls.js'); var ircNumerics = { RPL_WELCOME: '001', @@ -21,12 +22,14 @@ var ircNumerics = { RPL_MOTD: '372', RPL_WHOISMODES: '379', ERR_NOSUCHNICK: '401', - ERR_LINKCHANNEL: '470' + ERR_LINKCHANNEL: '470', + RPL_STARTTLS: '670' }; var parseIRCMessage = function (websocket, ircSocket, data) { - var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, params, prefix, prefixes, nicklist; + /*global ircSocketDataHandler */ + var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, params, prefix, prefixes, nicklist, caps, IRC, listeners, ssl_socket; regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@([a-z0-9\.\-:]+)) )?([a-z0-9]+)(?:(?: ([^:]+))?(?: :(.+))?)$/i; msg = regex.exec(data); if (msg) { @@ -44,6 +47,11 @@ var parseIRCMessage = function (websocket, ircSocket, data) { ircSocket.write('PONG ' + msg.trailing + '\r\n'); break; case ircNumerics.RPL_WELCOME: + if (ircSocket.IRC.CAP.negotiating) { + ircSocket.IRC.CAP.negotiating = false; + ircSocket.IRC.CAP.enabled = []; + ircSocket.IRC.CAP.requested = []; + } websocket.emit('message', {event: 'connect', connected: true, host: null}); break; case ircNumerics.RPL_ISUPPORT: @@ -169,17 +177,89 @@ var parseIRCMessage = function (websocket, ircSocket, data) { case 'PRIVMSG': websocket.emit('message', {event: 'msg', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing}); break; + case 'CAP': + caps = []; + options = msg.trailing.split(" "); + switch (_.first(msg.params.split(" "))) { + case 'LS': + opts = ''; + _.each(_.intersect(caps, options), function (cap) { + if (opts !== '') { + opts += " "; + } + opts += cap; + ircSocket.IRC.CAP.requested.push(cap); + }); + if (opts.length > 0) { + ircSocket.write('CAP REQ :' + opts + '\r\n'); + } else { + ircSocket.write('CAP END\r\n'); + } + // TLS is special + /*if (_.include(options, 'tls')) { + ircSocket.write('STARTTLS\r\n'); + ircSocket.IRC.CAP.requested.push('tls'); + }*/ + break; + case 'ACK': + _.each(options, function (cap) { + ircSocket.IRC.CAP.enabled.push(cap); + }); + if (_.last(msg.params.split(" ")) !== '*') { + ircSocket.IRC.CAP.requested = []; + ircSocket.IRC.CAP.negotiating = false; + ircSocket.write('CAP END\r\n'); + } + break; + case 'NAK': + ircSocket.IRC.CAP.requested = []; + ircSocket.IRC.CAP.negotiating = false; + ircSocket.write('CAP END\r\n'); + break; + } + break; + /*case ircNumerics.RPL_STARTTLS: + try { + IRC = ircSocket.IRC; + listeners = ircSocket.listeners('data'); + ircSocket.removeAllListeners('data'); + ssl_socket = starttls(ircSocket, {}, function () { + ssl_socket.on("data", function (data) { + ircSocketDataHandler(data, websocket, ssl_socket); + }); + ircSocket = ssl_socket; + ircSocket.IRC = IRC; + _.each(listeners, function (listener) { + ircSocket.addListener('data', listener); + }); + }); + //console.log(ircSocket); + } catch (e) { + console.log(e); + } + break;*/ } } else { console.log("Unknown command.\r\n"); } }; +var ircSocketDataHandler = function (data, websocket, ircSocket) { + var i; + data = data.split("\r\n"); + for (i = 0; i < data.length; i++) { + if (data[i]) { + console.log("->" + data[i]); + parseIRCMessage(websocket, ircSocket, data[i]); + } + } +}; + //setup websocket listener var io = ws.listen(7777, {secure: true}); io.sockets.on('connection', function (websocket) { websocket.on('irc connect', function (nick, host, port, ssl, callback) { - var ircSocket, i; + var ircSocket; //setup IRC connection if (!ssl) { ircSocket = net.createConnection(port, host); @@ -187,21 +267,16 @@ io.sockets.on('connection', function (websocket) { ircSocket = tls.connect(port, host); } ircSocket.setEncoding('ascii'); - ircSocket.IRC = {options: {}}; + ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}}; websocket.ircSocket = ircSocket; ircSocket.on('data', function (data) { - data = data.split("\r\n"); - for (i = 0; i < data.length; i++) { - if (data[i]) { - console.log("->" + data[i]); - parseIRCMessage(websocket, ircSocket, data[i]); - } - } + ircSocketDataHandler(data, websocket, ircSocket); }); ircSocket.IRC.nick = nick; // Send the login data + ircSocket.write('CAP LS\r\n'); ircSocket.write('NICK ' + nick + '\r\n'); ircSocket.write('USER ' + nick + '_kiwi 0 0 :' + nick + '\r\n'); diff --git a/node/lib/starttls.js b/node/lib/starttls.js new file mode 100644 index 0000000..e7f2240 --- /dev/null +++ b/node/lib/starttls.js @@ -0,0 +1,68 @@ +// Target API: +// +// var s = require('net').createStream(25, 'smtp.example.com'); +// s.on('connect', function() { +// require('starttls')(s, options, function() { +// if (!s.authorized) { +// s.destroy(); +// return; +// } +// +// s.end("hello world\n"); +// }); +// }); +// +// +module.exports = function starttls(socket, options, cb) { + + var sslcontext = require('crypto').createCredentials(options); + + var pair = require('tls').createSecurePair(sslcontext, false); + + var cleartext = pipe(pair, socket); + + pair.on('secure', function() { + var verifyError = pair.ssl.verifyError(); + + if (verifyError) { + cleartext.authorized = false; + cleartext.authorizationError = verifyError; + } else { + cleartext.authorized = true; + } + + if (cb) cb(); + }); + + cleartext._controlReleased = true; + return cleartext; +}; + + +function pipe(pair, socket) { + pair.encrypted.pipe(socket); + socket.pipe(pair.encrypted); + + pair.fd = socket.fd; + var cleartext = pair.cleartext; + cleartext.socket = socket; + cleartext.encrypted = pair.encrypted; + cleartext.authorized = false; + + function onerror(e) { + if (cleartext._controlReleased) { + cleartext.emit('error', e); + } + } + + function onclose() { + socket.removeListener('error', onerror); + socket.removeListener('close', onclose); + } + + socket.on('error', onerror); + socket.on('close', onclose); + + return cleartext; +} + diff --git a/node/underscore.min.js b/node/lib/underscore.min.js similarity index 100% rename from node/underscore.min.js rename to node/lib/underscore.min.js -- 2.25.1