Irc bound events fix
[KiwiIRC.git] / server / irc / connection.js
CommitLineData
cefa0900
JA
1var net = require('net'),
2 tls = require('tls'),
3 util = require('util'),
4 _ = require('lodash'),
5 EventEmitter2 = require('eventemitter2').EventEmitter2,
6 IrcServer = require('./server.js'),
7 IrcChannel = require('./channel.js'),
8 IrcUser = require('./user.js');
2a8e95d1 9
db8af19d 10
b09157de 11var IrcConnection = function (hostname, port, ssl, nick, user, pass, state) {
2a8e95d1 12 var that = this;
cefa0900
JA
13 EventEmitter2.call(this,{
14 wildcard: true,
15 delimiter: ':'
16 });
63008d5e 17
db8af19d 18 // Socket state
63008d5e 19 this.connected = false;
db8af19d
D
20
21 // If registeration with the IRCd has completed
63008d5e 22 this.registered = false;
db8af19d
D
23
24 // If we are in the CAP negotiation stage
63008d5e 25 this.cap_negotiation = true;
db8af19d
D
26
27 // User information
63008d5e
D
28 this.nick = nick;
29 this.user = user; // Contains users real hostname and address
db8af19d
D
30 this.username = this.nick.replace(/[^0-9a-zA-Z\-_.]/, '');
31 this.password = pass;
b09157de
JA
32
33 // State object
34 this.state = state;
cefa0900
JA
35
36 // IrcServer object
37 this.server = new IrcServer(this, hostname, port);
38
39 // IrcUser objects
40 this.irc_users = Object.create(null);
41
42 // IrcChannel objects
43 this.irc_channels = Object.create(null);
44
45 // Create IrcUser and IrcChannel objects when needed
46 // TODO: Remove IrcUser objects when they are no longer needed
47 this.on('server:*:connect', function (event) {
48 that.nick = event.nick;
49 that.irc_users[event.nick] = new IrcUser(that, event.nick);
50 });
51 this.on('channel:*:join', function (event) {
52 var chan;
53 if (event.nick === that.nick) {
54 chan = new IrcChannel(that, event.channel);
55 that.irc_channels[event.channel] = chan;
56 chan.irc_events.join.call(chan, event);
57 }
58 });
59
60 this.on('user:*:privmsg', function (event) {
61 var user;
62 if (event.channel === that.nick) {
63 if (!that.irc_users[event.nick]) {
64 user = new IrcUser(that, event.nick);
65 that.irc_users[event.nick] = user;
66 user.irc_events.privmsg.call(user, event);
67 }
68 }
69 });
db8af19d
D
70
71 // IRC connection information
63008d5e
D
72 this.irc_host = {hostname: hostname, port: port};
73 this.ssl = !(!ssl);
db8af19d
D
74
75 // Options sent by the IRCd
63008d5e
D
76 this.options = Object.create(null);
77 this.cap = {requested: [], enabled: []};
db8af19d
D
78
79 // Is SASL supported on the IRCd
63008d5e
D
80 this.sasl = false;
81
db8af19d 82 // Buffers for data sent from the IRCd
63008d5e
D
83 this.hold_last = false;
84 this.held_data = '';
85
db8af19d
D
86
87 // Call any modules before making the connection
88 global.modules.emit('irc:connecting', {connection: this})
89 .done(function () {
90 that.connect();
91 });
63008d5e 92};
cefa0900 93util.inherits(IrcConnection, EventEmitter2);
63008d5e
D
94
95module.exports.IrcConnection = IrcConnection;
96
f9ff7686 97
db8af19d
D
98
99
100/**
101 * Start the connection to the IRCd
102 */
63008d5e
D
103IrcConnection.prototype.connect = function () {
104 var that = this;
105
db8af19d
D
106 // The socket connect event to listener for
107 var socket_connect_event_name = 'connect';
108
109
110 // Make sure we don't already have an open connection
111 this.disposeSocket();
112
113 // Open either a secure or plain text socket
63008d5e 114 if (this.ssl) {
36108ca9 115 this.socket = tls.connect({
63008d5e
D
116 host: this.irc_host.hostname,
117 port: this.irc_host.port,
36108ca9 118 rejectUnauthorized: global.config.reject_unauthorised_certificates
36108ca9 119 });
db8af19d
D
120
121 socket_connect_event_name = 'secureConnect';
122
2a8e95d1 123 } else {
db8af19d
D
124 this.socket = net.connect({
125 host: this.irc_host.hostname,
126 port: this.irc_host.port
2a8e95d1
D
127 });
128 }
db8af19d
D
129
130 this.socket.setEncoding('utf-8');
131
132 this.socket.on(socket_connect_event_name, function () {
133 that.connected = true;
134 socketConnectHandler.apply(that, arguments);
135 });
136
d2d91c10
D
137 this.socket.on('error', function (event) {
138 that.emit('error', event);
db8af19d 139
2a8e95d1
D
140 });
141
2a8e95d1
D
142 this.socket.on('data', function () {
143 parse.apply(that, arguments);
144 });
145
db8af19d
D
146 this.socket.on('close', function (had_error) {
147 that.connected = false;
2a8e95d1 148 that.emit('close');
db8af19d
D
149
150 // Close the whole socket down
151 that.disposeSocket();
2a8e95d1 152 });
2a8e95d1 153};
2a8e95d1 154
3c91bff8
JA
155/**
156 * Send an event to the client
157 */
158IrcConnection.prototype.clientEvent = function (event_name, data, callback) {
159 data.server = this.con_num;
160 this.state.sendIrcCommand(event_name, data, callback);
161};
db8af19d
D
162
163/**
164 * Write a line of data to the IRCd
165 */
2a8e95d1 166IrcConnection.prototype.write = function (data, callback) {
db8af19d 167 this.socket.write(data + '\r\n', 'utf-8', callback);
2a8e95d1
D
168};
169
db8af19d
D
170
171
172/**
173 * Close the connection to the IRCd after sending one last line
174 */
2a8e95d1 175IrcConnection.prototype.end = function (data, callback) {
db8af19d
D
176 if (data)
177 this.write(data);
178
179 this.socket.end();
2a8e95d1
D
180};
181
db8af19d
D
182
183
184/**
185 * Clean up this IrcConnection instance and any sockets
186 */
c08717da 187IrcConnection.prototype.dispose = function () {
cefa0900
JA
188 _.each(this.irc_users, function (user) {
189 user.dispose();
190 });
191 _.each(this.irc_channels, function (chan) {
192 chan.dispose();
193 });
194 this.irc_users = null;
195 this.irc_channels = null;
db8af19d 196 this.disposeSocket();
c08717da
D
197 this.removeAllListeners();
198};
199
200
2a8e95d1 201
db8af19d
D
202/**
203 * Clean up any sockets for this IrcConnection
204 */
205IrcConnection.prototype.disposeSocket = function () {
206 if (this.socket) {
207 this.socket.removeAllListeners();
208 this.socket = null;
209 }
2a8e95d1
D
210};
211
212
db8af19d
D
213
214/**
215 * Handle the socket connect event, starting the IRCd registration
216 */
217var socketConnectHandler = function () {
15fefff7
D
218 var that = this,
219 connect_data;
220
221 // Build up data to be used for webirc/etc detection
222 connect_data = {
bd7196e1
D
223 connection: this,
224
225 // Array of lines to be sent to the IRCd before anything else
226 prepend_data: []
15fefff7
D
227 };
228
229 // Let the webirc/etc detection modify any required parameters
266b5087 230 connect_data = findWebIrc.call(this, connect_data);
15fefff7 231
bd7196e1
D
232 global.modules.emit('irc:authorize', connect_data).done(function () {
233 // Send any initial data for webirc/etc
234 if (connect_data.prepend_data) {
235 _.each(connect_data.prepend_data, function(data) {
236 that.write(data);
237 });
238 }
239
240 that.write('CAP LS');
241
db8af19d 242 if (that.password)
bd7196e1 243 that.write('PASS ' + that.password);
db8af19d 244
bd7196e1
D
245 that.write('NICK ' + that.nick);
246 that.write('USER ' + that.username + ' 0 0 :' + '[www.kiwiirc.com] ' + that.nick);
247
bd7196e1
D
248 that.emit('connected');
249 });
7dfe47c6 250};
2a8e95d1 251
15fefff7 252
db8af19d
D
253
254/**
255 * Load any WEBIRC or alternative settings for this connection
256 * Called in scope of the IrcConnection instance
257 */
15fefff7 258function findWebIrc(connect_data) {
db8af19d
D
259 var webirc_pass = global.config.webirc_pass,
260 ip_as_username = global.config.ip_as_username,
261 tmp;
262
15fefff7
D
263
264 // Do we have a WEBIRC password for this?
bd7196e1 265 if (webirc_pass && webirc_pass[this.irc_host.hostname]) {
db8af19d 266 // Build the WEBIRC line to be sent before IRC registration
bd7196e1
D
267 tmp = 'WEBIRC ' + webirc_pass[this.irc_host.hostname] + ' KiwiIRC ';
268 tmp += this.user.hostname + ' ' + this.user.address;
db8af19d 269
15fefff7
D
270 connect_data.prepend_data = [tmp];
271 }
272
273
274 // Check if we need to pass the users IP as its username/ident
bd7196e1 275 if (ip_as_username && ip_as_username.indexOf(this.irc_host.hostname) > -1) {
15fefff7 276 // Get a hex value of the clients IP
bd7196e1 277 this.username = this.user.address.split('.').map(function(i, idx){
15fefff7
D
278 return parseInt(i, 10).toString(16);
279 }).join('');
280
281 }
282
283 return connect_data;
284}
285
286
287
db8af19d
D
288/**
289 * The regex that parses a line of data from the IRCd
290 * Deviates from the RFC a little to support the '/' character now used in some
291 * IRCds
292 */
293var parse_regex = /^(?:(?:(?:(@[^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/_]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
294
2a8e95d1
D
295var parse = function (data) {
296 var i,
297 msg,
16e717f5
JA
298 msg2,
299 trm,
300 j,
301 tags = [],
302 tag;
2a8e95d1 303
db8af19d 304 if (this.hold_last && this.held_data !== '') {
2a8e95d1
D
305 data = this.held_data + data;
306 this.hold_last = false;
307 this.held_data = '';
308 }
db8af19d
D
309
310 // If the last line is incomplete, hold it until we have more data
2a8e95d1
D
311 if (data.substr(-1) !== '\n') {
312 this.hold_last = true;
313 }
db8af19d
D
314
315 // Process our data line by line
2a8e95d1
D
316 data = data.split("\n");
317 for (i = 0; i < data.length; i++) {
db8af19d
D
318 if (!data[i]) break;
319
320 // If flagged to hold the last line, store it and move on
321 if (this.hold_last && (i === data.length - 1)) {
322 this.held_data = data[i];
323 break;
324 }
325
326 // Parse the complete line, removing any carriage returns
327 msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, ''));
2a8e95d1 328
db8af19d
D
329 if (msg) {
330 if (msg[1]) {
331 tags = msg[1].split(';');
332 for (j = 0; j < tags.length; j++) {
333 tag = tags[j].split('=');
334 tags[j] = {tag: tag[0], value: tag[1]};
16e717f5 335 }
2a8e95d1 336 }
db8af19d
D
337 msg = {
338 tags: tags,
339 prefix: msg[2],
340 nick: msg[3],
341 ident: msg[4],
342 hostname: msg[5] || '',
343 command: msg[6],
344 params: msg[7] || '',
345 trailing: (msg[8]) ? msg[8].trim() : ''
346 };
347 msg.params = msg.params.split(' ');
348
349 this.emit('irc_' + msg.command.toUpperCase(), msg);
350
351 } else {
352
353 // The line was not parsed correctly, must be malformed
354 console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, ''));
2a8e95d1
D
355 }
356 }
357};