var tls = require('tls'),
net = require('net'),
http = require('http'),
+ https = require('https'),
+ fs = require('fs'),
+ url = require('url'),
ws = require('socket.io'),
- _ = require('./underscore.min.js');
+ _ = require('./lib/underscore.min.js'),
+ starttls = require('./lib/starttls.js');
+
+var config = JSON.parse(fs.readFileSync(__dirname + '/config.json', 'ascii'));
+
+if (config.handle_http) {
+ var static_server = require('node-static'),
+ jade = require('jade');
+}
var ircNumerics = {
RPL_WELCOME: '001',
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;
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) {
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:
regex = /\(([^)]*)\)(.*)/;
matches = regex.exec(opt[1]);
if ((matches) && (matches.length === 3)) {
- options[opt[0]] = {};
+ ircSocket.IRC.options[opt[0]] = {};
for (j = 0; j < matches[2].length; j++) {
- options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
+ ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
}
}
}
}
}
- websocket.emit('message', {event: 'options', server: '', "options": options});
+ websocket.emit('message', {event: 'options', server: '', "options": ircSocket.IRC.options});
break;
case ircNumerics.RPL_WHOISUSER:
case ircNumerics.RPL_WHOISSERVER:
case ircNumerics.RPL_WHOISOPERATOR:
- case ircNumerics.RPL_WHOISIDLE:
case ircNumerics.RPL_ENDOFWHOIS:
case ircNumerics.RPL_WHOISCHANNELS:
case ircNumerics.RPL_WHOISMODES:
websocket.emit('message', {event: 'whois', server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
break;
+ case ircNumerics.RPL_WHOISIDLE:
+ params = msg.params.split(" ", 3);
+ websocket.emit('message', {event: 'whois', server: '', nick: params[1], "msg": params[2] + ' ' + msg.trailing});
case ircNumerics.RPL_MOTD:
websocket.emit('message', {event: 'motd', server: '', "msg": msg.trailing});
break;
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 = config.cap_options;
+ 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;
+ if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
+ data = ircSocket.held + data;
+ ircSocket.holdLast = false;
+ ircSocket.held = '';
+ }
+ if (data.substr(-2) === '\r\n') {
+ ircSocket.holdLast = true;
+ }
+ data = data.split("\r\n");
+ for (i = 0; i < data.length; i++) {
+ if (data[i]) {
+ if ((ircSocket.holdLast) && (i === data.length - 1)) {
+ ircSocket.held = data[i];
+ break;
+ }
+ console.log("->" + data[i]);
+ parseIRCMessage(websocket, ircSocket, data[i]);
+ }
+ }
+};
+
+if (config.handle_http) {
+ var fileServer = new (static_server.Server)(__dirname + config.public_http);
+}
+
+var httpHandler = function (request, response) {
+ var uri, subs, useragent, agent, server_set, server, nick, debug, touchscreen;
+ if (config.handle_http) {
+ uri = url.parse(request.url);
+ subs = uri.pathname.substr(0, 4);
+ if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
+ request.addListener('end', function () {
+ fileServer.serve(request, response);
+ });
+ } else if (uri.pathname === '/') {
+ useragent = (response.headers) ? response.headers['user-agent']: '';
+ if (useragent.indexOf('android') !== -1) {
+ agent = 'android';
+ touchscreen = true;
+ } else if (useragent.indexOf('iphone') !== -1) {
+ agent = 'iphone';
+ touchscreen = true;
+ } else if (useragent.indexOf('ipad') !== -1) {
+ agent = 'ipad';
+ touchscreen = true;
+ } else if (useragent.indexOf('ipod') !== -1) {
+ agent = 'ipod';
+ touchscreen = true;
+ } else {
+ agent = 'normal';
+ touchscreen = false;
+ }
+ if (uri.query) {
+ server_set = (uri.query.server !== '');
+ server = uri.query.server || 'irc.anonnet.org';
+ nick = uri.query.nick || '';
+ debug = (uri.query.debug !== '');
+ } else {
+ server = 'irc.anonnet.org';
+ nick = '';
+ }
+ response.setHeader('Connection', 'close');
+ response.setHeader('X-Generated-By', 'KiwiIRC');
+ jade.renderFile(__dirname + '/client/index.html.jade', { locals: { "touchscreen": touchscreen, "debug": debug, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": config }}, function (err, html) {
+ if (!err) {
+ response.write(html);
+ } else {
+ response.statusCode = 500;
+ }
+ response.end();
+ });
+ } else if (uri.pathname.substr(0, 10) === '/socket.io') {
+ // Do nothing!
+ } else {
+ response.statusCode = 404;
+ response.end();
+ }
+ }
+};
+
//setup websocket listener
-var io = ws.listen(7777, {secure: true});
-io.sockets.on('connection', function (websocket) {
+if (config.listen_ssl) {
+ var httpServer = https.createServer({key: fs.readFileSync(__dirname + '/' + config.ssl_key), cert: fs.readFileSync(__dirname + '/' + config.ssl_cert)}, httpHandler);
+ var io = ws.listen(httpServer, {secure: true});
+ httpServer.listen(config.port, config.bind_address);
+} else {
+ var httpServer = http.createServer(httpHandler);
+ var io = ws.listen(httpServer, {secure: false});
+ httpServer.listen(config.port, config.bind_address);
+}
+io.of('/kiwi').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);
ircSocket = tls.connect(port, host);
}
ircSocket.setEncoding('ascii');
- ircSocket.IRC = {options: {}};
+ ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}};
websocket.ircSocket = ircSocket;
+ ircSocket.holdLast = false;
+ ircSocket.held = '';
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');
websocket.ircSocket.destroySoon();
websocket.disconnect();
break;
+ case 'notice':
+ if ((args.target) && (args.msg)) {
+ websocket.ircSocket.write('NOTICE ' + args.target + ' :' + args.msg + '\r\n');
+ }
+ break;
default:
}
if ((callback) && (typeof (callback) === 'function')) {
});
websocket.on('disconnect', function () {
if ((!websocket.sentQUIT) && (websocket.ircSocket)) {
- websocket.ircSocket.end('QUIT :KiwiIRC\r\n');
+ websocket.ircSocket.end('QUIT :' + config.quit_message + '\r\n');
websocket.sentQUIT = true;
websocket.ircSocket.destroySoon();
}