-var net = require('net'),
- tls = require('tls'),
- events = require('events'),
- util = require('util'),
- _ = require('lodash');
-
+var net = require('net'),
+ tls = require('tls'),
+ util = require('util'),
+ _ = require('lodash'),
+ EventEmitter2 = require('eventemitter2').EventEmitter2,
+ EventBinder = require('./eventbinder.js'),
+ IrcServer = require('./server.js'),
+ IrcChannel = require('./channel.js'),
+ IrcUser = require('./user.js'),
+ Socks;
+
+
+// Break the Node.js version down into usable parts
+var version_values = process.version.substr(1).split('.').map(function (item) {
+ return parseInt(item, 10);
+});
+
+// If we have a suitable Nodejs version, bring int he socks functionality
+if (version_values[1] >= 10) {
+ Socks = require('socksjs');
+}
-var IrcConnection = function (hostname, port, ssl, nick, user, pass) {
+var IrcConnection = function (hostname, port, ssl, nick, user, pass, state) {
var that = this;
- events.EventEmitter.call(this);
+
+ EventEmitter2.call(this,{
+ wildcard: true,
+ delimiter: ' '
+ });
+ this.setMaxListeners(0);
// Socket state
this.connected = false;
// User information
this.nick = nick;
this.user = user; // Contains users real hostname and address
- this.username = this.nick.replace(/[^0-9a-zA-Z\-_.]/, '');
+ this.username = this.nick.replace(/[^0-9a-zA-Z\-_.\/]/, '');
this.password = pass;
+
+ // State object
+ this.state = state;
+
+ // IrcServer object
+ this.server = new IrcServer(this, hostname, port);
+
+ // IrcUser objects
+ this.irc_users = Object.create(null);
+
+ // IrcChannel objects
+ this.irc_channels = Object.create(null);
// IRC connection information
this.irc_host = {hostname: hostname, port: port};
this.ssl = !(!ssl);
+
+ // SOCKS proxy details
+ // TODO: Wildcard matching of hostnames and/or CIDR ranges of IP addresses
+ if ((global.config.socks_proxy && global.config.socks_proxy.enabled) && ((global.config.socks_proxy.all) || (_.contains(global.config.socks_proxy.proxy_hosts, this.irc_host.hostname)))) {
+ this.socks = {
+ host: global.config.socks_proxy.address,
+ port: global.config.socks_proxy.port,
+ user: global.config.socks_proxy.user,
+ pass: global.config.socks_proxy.pass
+ };
+ } else {
+ this.socks = false;
+ }
// Options sent by the IRCd
this.options = Object.create(null);
this.hold_last = false;
this.held_data = '';
+ this.applyIrcEvents();
// Call any modules before making the connection
- global.modules.emit('irc:connecting', {connection: this})
+ global.modules.emit('irc connecting', {connection: this})
.done(function () {
that.connect();
});
};
-util.inherits(IrcConnection, events.EventEmitter);
+util.inherits(IrcConnection, EventEmitter2);
module.exports.IrcConnection = IrcConnection;
+IrcConnection.prototype.applyIrcEvents = function () {
+ // Listen for events on the IRC connection
+ this.irc_events = {
+ 'server * connect': onServerConnect,
+ 'channel * join': onChannelJoin,
+
+ // TODO: uncomment when using an IrcUser per nick
+ //'user:*:privmsg': onUserPrivmsg,
+ 'user * nick': onUserNick,
+ 'channel * part': onUserParts,
+ 'channel * quit': onUserParts,
+ 'channel * kick': onUserKick
+ };
+
+ EventBinder.bindIrcEvents('', this.irc_events, this, this);
+};
+
/**
* Start the connection to the IRCd
*/
IrcConnection.prototype.connect = function () {
var that = this;
+ var socks;
// The socket connect event to listener for
var socket_connect_event_name = 'connect';
// Make sure we don't already have an open connection
this.disposeSocket();
- // Open either a secure or plain text socket
- if (this.ssl) {
+ // Are we connecting through a SOCKS proxy?
+ if (this.socks) {
+ this.socket = Socks.connect({
+ host: this.irc_host.hostname,
+ port: this.irc_host.port,
+ ssl: this.ssl,
+ rejectUnauthorized: global.config.reject_unauthorised_certificates
+ }, {host: this.socks.host,
+ port: this.socks.port,
+ user: this.socks.user,
+ pass: this.socks.pass
+ });
+
+ } else if (this.ssl) {
this.socket = tls.connect({
host: this.irc_host.hostname,
port: this.irc_host.port,
port: this.irc_host.port
});
}
-
- this.socket.setEncoding('utf-8');
-
+
this.socket.on(socket_connect_event_name, function () {
that.connected = true;
- socketConnectHandler.apply(that, arguments);
+ socketConnectHandler.call(that);
});
-
+
+ this.socket.setEncoding('utf-8');
+
this.socket.on('error', function (event) {
that.emit('error', event);
});
};
-
+/**
+ * Send an event to the client
+ */
+IrcConnection.prototype.clientEvent = function (event_name, data, callback) {
+ data.server = this.con_num;
+ this.state.sendIrcCommand(event_name, data, callback);
+};
/**
* Write a line of data to the IRCd
* Clean up this IrcConnection instance and any sockets
*/
IrcConnection.prototype.dispose = function () {
+ _.each(this.irc_users, function (user) {
+ user.dispose();
+ });
+ _.each(this.irc_channels, function (chan) {
+ chan.dispose();
+ });
+ this.irc_users = undefined;
+ this.irc_channels = undefined;
+
+ this.server.dispose();
+ this.server = undefined;
+
+ EventBinder.unbindIrcEvents('', this.irc_events, this);
+
this.disposeSocket();
this.removeAllListeners();
};
*/
IrcConnection.prototype.disposeSocket = function () {
if (this.socket) {
+ this.socket.end();
this.socket.removeAllListeners();
this.socket = null;
}
+function onChannelJoin(event) {
+ var chan;
+
+ // Only deal with ourselves joining a channel
+ if (event.nick !== this.nick)
+ return;
+
+ // We should only ever get a JOIN command for a channel
+ // we're not already a member of.. but check we don't
+ // have this channel in case something went wrong somewhere
+ // at an earlier point
+ if (!this.irc_channels[event.channel]) {
+ chan = new IrcChannel(this, event.channel);
+ this.irc_channels[event.channel] = chan;
+ chan.irc_events.join.call(chan, event);
+ }
+}
+
+
+function onServerConnect(event) {
+ this.nick = event.nick;
+
+ // TODO: use `event.nick` instead of `'*'` when using an IrcUser per nick
+ this.irc_users[event.nick] = new IrcUser(this, '*');
+}
+
+
+function onUserPrivmsg(event) {
+ var user;
+
+ // Only deal with messages targetted to us
+ if (event.channel !== this.nick)
+ return;
+
+ if (!this.irc_users[event.nick]) {
+ user = new IrcUser(this, event.nick);
+ this.irc_users[event.nick] = user;
+ user.irc_events.privmsg.call(user, event);
+ }
+}
+
+
+function onUserNick(event) {
+ var user;
+
+ // Only deal with messages targetted to us
+ if (event.nick !== this.nick)
+ return;
+
+ this.nick = event.newnick;
+}
+
+
+function onUserParts(event) {
+ // Only deal with ourselves leaving a channel
+ if (event.nick !== this.nick)
+ return;
+
+ if (this.irc_channels[event.channel]) {
+ this.irc_channels[event.channel].dispose();
+ delete this.irc_channels[event.channel];
+ }
+}
+
+function onUserKick(event){
+ // Only deal with ourselves being kicked from a channel
+ if (event.kicked !== this.nick)
+ return;
+
+ if (this.irc_channels[event.channel]) {
+ this.irc_channels[event.channel].dispose();
+ delete this.irc_channels[event.channel];
+ }
+
+}
+
+
+
+
/**
* Handle the socket connect event, starting the IRCd registration
*/
// Let the webirc/etc detection modify any required parameters
connect_data = findWebIrc.call(this, connect_data);
- global.modules.emit('irc:authorize', connect_data).done(function () {
+ global.modules.emit('irc authorize', connect_data).done(function () {
// Send any initial data for webirc/etc
if (connect_data.prepend_data) {
_.each(connect_data.prepend_data, function(data) {
* Deviates from the RFC a little to support the '/' character now used in some
* IRCds
*/
-var parse_regex = /^(?:(?:(?:(@[^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/_]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
+var parse_regex = /^(?:(?:(?:(@[^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-*]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-*]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/_]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
var parse = function (data) {
var i,