X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=node%2Fkiwi.js;h=189301f414747387b51b654a378e4a9f2e1a9d40;hb=c89b9fdfa39466d416576727dbdaa305f07e856c;hp=05a0e0b3e4c04b9d9f86dbd2827fc2c1f94fb92f;hpb=e36e767b70a1e050321cf5849819a71a9259c4ca;p=KiwiIRC.git diff --git a/node/kiwi.js b/node/kiwi.js index 05a0e0b..189301f 100644 --- a/node/kiwi.js +++ b/node/kiwi.js @@ -1,362 +1,211 @@ -/*jslint regexp: true, confusion: true, undef: false, node: true, sloppy: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */ - +/*jslint continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */ +"use strict"; var tls = require('tls'), net = require('net'), http = require('http'), + https = require('https'), fs = require('fs'), + url = require('url'), + dns = require('dns'), + crypto = require('crypto'), ws = require('socket.io'), + jsp = require("uglify-js").parser, + pro = require("uglify-js").uglify, _ = require('./lib/underscore.min.js'), - starttls = require('./lib/starttls.js'); - -var config = JSON.parse(fs.readFileSync(__dirname + '/config.json', 'ascii')); - -var ircNumerics = { - RPL_WELCOME: '001', - RPL_ISUPPORT: '005', - RPL_WHOISUSER: '311', - RPL_WHOISSERVER: '312', - RPL_WHOISOPERATOR: '313', - RPL_WHOISIDLE: '317', - RPL_ENDOFWHOIS: '318', - RPL_WHOISCHANNELS: '319', - RPL_TOPIC: '332', - RPL_NAMEREPLY: '353', - RPL_ENDOFNAMES: '366', - RPL_MOTD: '372', - RPL_WHOISMODES: '379', - ERR_NOSUCHNICK: '401', - ERR_LINKCHANNEL: '470', - RPL_STARTTLS: '670' -}; + starttls = require('./lib/starttls.js'), + app = require(__dirname + '/app.js'); -var parseIRCMessage = function (websocket, ircSocket, data) { - /*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) { - msg = { - prefix: msg[1], - nick: msg[2], - ident: msg[3], - hostname: msg[4], - command: msg[5], - params: msg[6] || '', - trailing: (msg[7]) ? msg[7].trim() : '' - }; - switch (msg.command.toUpperCase()) { - case 'PING': - 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: - opts = msg.params.split(" "); - options = []; - for (i = 0; i < opts.length; i++) { - opt = opts[i].split("=", 2); - opt[0] = opt[0].toUpperCase(); - ircSocket.IRC.options[opt[0]] = opt[1] || true; - if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES'], opt[0])) { - if (opt[0] === 'PREFIX') { - regex = /\(([^)]*)\)(.*)/; - matches = regex.exec(opt[1]); - if ((matches) && (matches.length === 3)) { - ircSocket.IRC.options[opt[0]] = {}; - for (j = 0; j < matches[2].length; j++) { - ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j); - } - } + + + +// Libraries may need to know kiwi.js path as __dirname +// only gives that librarys path. Set it here for usage later. +this.kiwi_root = __dirname; + + + +/* + * Configuration and rehashing routines + */ +var config_filename = 'config.json', + config_dirs = ['/etc/kiwiirc/', this.kiwi_root + '/']; + +this.config = {}; +this.loadConfig = function () { + var i, j, + nconf = {}, + cconf = {}, + found_config = false; + + for (i in config_dirs) { + try { + if (fs.lstatSync(config_dirs[i] + config_filename).isDirectory() === false) { + found_config = true; + nconf = JSON.parse(fs.readFileSync(config_dirs[i] + config_filename, 'ascii')); + for (j in nconf) { + // If this has changed from the previous config, mark it as changed + if (!_.isEqual(this.config[j], nconf[j])) { + cconf[j] = nconf[j]; } + + this.config[j] = nconf[j]; } - } - 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_MOTD: - websocket.emit('message', {event: 'motd', server: '', "msg": msg.trailing}); - break; - case ircNumerics.RPL_NAMEREPLY: - params = msg.params.split(" "); - nick = params[0]; - chan = params[2]; - users = msg.trailing.split(" "); - prefixes = _.values(ircSocket.IRC.options.PREFIX); - nicklist = {}; - i = 0; - _.each(users, function (user) { - if (_.include(prefix, user.charAt(0))) { - prefix = user.charAt(0); - user = user.substring(1); - nicklist[user] = prefix; - } else { - nicklist[user] = ''; - } - if (i++ >= 50) { - websocket.emit('message', {event: 'userlist', server: '', "users": nicklist, channel: chan}); - nicklist = {}; - i = 0; - } - }); - if (i > 0) { - websocket.emit('message', {event: 'userlist', server: '', "users": nicklist, channel: chan}); - } else { - console.log("oops"); - } - break; - case ircNumerics.RPL_ENDOFNAMES: - websocket.emit('message', {event: 'userlist_end', server: '', channel: msg.params.split(" ")[1]}); - break; - case ircNumerics.ERR_LINKCHANNEL: - params = msg.params.split(" "); - websocket.emit('message', {event: 'channel_redirect', from: params[1], to: params[2]}); - break; - case ircNumerics.ERR_NOSUCHNICK: - //TODO: shit - break; - case 'JOIN': - websocket.emit('message', {event: 'join', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.trailing}); - if (msg.nick === ircSocket.IRC.nick) { - ircSocket.write('NAMES ' + msg.trailing + '\r\n'); - } - break; - case 'PART': - websocket.emit('message', {event: 'part', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing}); - break; - case 'KICK': - params = msg.params.split(" "); - websocket.emit('message', {event: 'kick', kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing}); - break; - case 'QUIT': - websocket.emit('message', {event: 'quit', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing}); - break; - case 'NOTICE': - websocket.emit('message', {event: 'notice', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing}); - break; - case 'NICK': - websocket.emit('message', {event: 'nick', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing}); - break; - case 'TOPIC': - websocket.emit('message', {event: 'topic', nick: msg.nick, channel: msg.params, topic: msg.trailing}); - break; - case ircNumerics.RPL_TOPIC: - websocket.emit('message', {event: 'topic', nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing}); - break; - case 'MODE': - opts = msg.params.split(" "); - params = {event: 'mode', nick: msg.nick}; - switch (opts.length) { - case 1: - params.effected_nick = opts[0]; - params.mode = msg.trailing; - break; - case 2: - params.channel = opts[0]; - params.mode = opts[1]; - break; - default: - params.channel = opts[0]; - params.mode = opts[1]; - params.effected_nick = opts[2]; + + console.log('Loaded config file ' + config_dirs[i] + config_filename); break; } - websocket.emit('message', params); - 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'); + } catch (e) { + switch (e.code) { + case 'ENOENT': // No file/dir break; + default: + console.log('An error occured parsing the config file ' + config_dirs[i] + config_filename + ': ' + e.message); + return false; } - 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;*/ + continue; } - } 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 (Object.keys(this.config).length === 0) { + if (!found_config) { + console.log('Couldn\'t find a config file!'); } + return false; } + return [nconf, cconf]; }; -//setup websocket listener -if (config.listen_ssl) { - var io = ws.listen('127.0.0.1:'+config.port.toString(), {secure: true, key: fs.readFileSync(__dirname + '/' + config.ssl_key), cert: fs.readFileSync(__dirname + '/' + config.ssl_cert)}); -} else { - var io = ws.listen('127.0.0.1:'+config.port.toString(), {secure: false}); + +// Reloads the config during runtime +this.rehash = function () { + return app.rehash(); } -io.sockets.on('connection', function (websocket) { - websocket.on('irc connect', function (nick, host, port, ssl, callback) { - var ircSocket; - //setup IRC connection - if (!ssl) { - ircSocket = net.createConnection(port, host); - } else { - ircSocket = tls.connect(port, host); - } - ircSocket.setEncoding('ascii'); - ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}}; - websocket.ircSocket = ircSocket; - ircSocket.holdLast = false; - ircSocket.held = ''; - - ircSocket.on('data', function (data) { - 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'); - - if ((callback) && (typeof (callback) === 'function')) { - callback(); - } - }); - websocket.on('message', function (msg, callback) { - var args; - try { - msg.data = JSON.parse(msg.data); - args = msg.data.args; - switch (msg.data.method) { - case 'msg': - if ((args.target) && (args.msg)) { - websocket.ircSocket.write('PRIVMSG ' + args.target + ' :' + args.msg + '\r\n'); - } - break; - case 'action': - if ((args.target) && (args.msg)) { - websocket.ircSocket.write('PRIVMSG ' + args.target + ' :ACTION ' + args.msg + '\r\n'); - } - break; - case 'raw': - websocket.ircSocket.write(args.data + '\r\n'); - break; - case 'join': - if (args.channel) { - _.each(args.channel.split(","), function (chan) { - websocket.ircSocket.write('JOIN ' + chan + '\r\n'); - }); - } - break; - case 'quit': - websocket.ircSocket.end('QUIT :' + args.message + '\r\n'); - websocket.sentQUIT = true; - 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')) { - callback(); - } - } catch (e) { - console.log("Caught error: " + e); - } - }); - websocket.on('disconnect', function () { - if ((!websocket.sentQUIT) && (websocket.ircSocket)) { - websocket.ircSocket.end('QUIT :' + config.quit_message + '\r\n'); - websocket.sentQUIT = true; - websocket.ircSocket.destroySoon(); - } - }); -}); + +// Reloads app.js during runtime for any recoding +this.recode = function () { + if (typeof require.cache[this.kiwi_root + '/app.js'] !== 'undefined'){ + delete require.cache[this.kiwi_root + '/app.js']; + } + + app = null; + app = require(__dirname + '/app.js'); + + var objs = {tls:tls, net:net, http:http, https:https, fs:fs, url:url, dns:dns, crypto:crypto, ws:ws, jsp:jsp, pro:pro, _:_, starttls:starttls}; + app.init(objs); + + return true; +} + + + + + + +/* + * Before we continue we need the config loaded + */ +if (!this.loadConfig()) { + process.exit(0); +} + + + + + + + +/* + * HTTP file serving + */ +if (this.config.handle_http) { + this.fileServer = new (require('node-static').Server)(__dirname + this.config.public_http); + this.jade = require('jade'); + this.cache = {alljs: '', html: []}; +} +this.httpServer = null; +this.httpHandler = function (request, response) { + return app.httpHandler(request, response); +} + + + + + + +/* + * Websocket handling + */ +this.connections = {}; +this.io = null; +this.websocketListen = function (port, host, handler, secure, key, cert) { + return app.websocketListen(port, host, handler, secure, key, cert); +} +this.websocketConnection = function (websocket) { + return app.websocketConnection(websocket); +} +this.websocketDisconnect = function () { + return app.websocketDisconnect(this); +} +this.websocketMessage = function (msg, callback) { + return app.websocketMessage(this, msg, callback); +} +this.websocketIRCConnect = function (nick, host, port, ssl, callback) { + return app.websocketIRCConnect(this, nick, host, port, ssl, callback); +} + + + + +/* + * IRC handling + */ +this.parseIRCMessage = function (websocket, ircSocket, data) { + return app.parseIRCMessage(websocket, ircSocket, data); +} +this.ircSocketDataHandler = function (data, websocket, ircSocket) { + return app.ircSocketDataHandler(data, websocket, ircSocket); +} + + + + + + + + + +/* + * Load up main application source + */ +if (!this.recode()) { + process.exit(0); +} + + + +// Set the process title +app.setTitle(); + + + +/* + * Load the modules as set in the config and print them out + */ +this.kiwi_mod = require('./lib/kiwi_mod.js'); +this.kiwi_mod.loadModules(this.kiwi_root, this.config); +this.kiwi_mod.printMods(); + + + + +// Start the server up +this.websocketListen(this.config.port, this.config.bind_address, this.httpHandler, this.config.listen_ssl, this.config.ssl_key, this.config.ssl_cert); + +// Now we're listening on the network, set our UID/GIDs if required +app.changeUser(); + +// Listen for controll messages +app.startControll(); + +