SASL support
[KiwiIRC.git] / server / irc / connection.js
CommitLineData
2a8e95d1
D
1var net = require('net'),
2 tls = require('tls'),
3 events = require('events'),
15fefff7 4 util = require('util'),
795014b1 5 _ = require('underscore');
2a8e95d1 6
15fefff7 7var IrcConnection = function (hostname, port, ssl, nick, user, pass) {
2a8e95d1
D
8 var that = this;
9 events.EventEmitter.call(this);
10
11 if (ssl) {
831b41b0 12 this.socket = tls.connect({host: hostname, port: port, rejectUnauthorized: global.config.reject_unauthorised_certificates}, connect_handler);
2a8e95d1
D
13 } else {
14 this.socket = net.createConnection(port, hostname);
15 this.socket.on('connect', function () {
16 connect_handler.apply(that, arguments);
17 });
18 }
19
d2d91c10
D
20 this.socket.on('error', function (event) {
21 that.emit('error', event);
2a8e95d1
D
22 });
23
24 this.socket.setEncoding('utf-8');
25
26 this.socket.on('data', function () {
27 parse.apply(that, arguments);
28 });
29
30 this.socket.on('close', function () {
31 that.emit('close');
32 });
33
34 this.connected = false;
35 this.registered = false;
eec8c71e 36 this.cap_negotiation = true;
2a8e95d1
D
37 this.nick = nick;
38 this.user = user;
15fefff7 39 this.irc_host = {hostname: hostname, port: port};
2a8e95d1
D
40 this.ssl = !(!ssl);
41 this.options = Object.create(null);
eec8c71e 42 this.cap = {requested: [], enabled: []};
7dfe47c6 43 this.sasl = false;
2a8e95d1 44
2a8e95d1
D
45 this.password = pass;
46 this.hold_last = false;
47 this.held_data = '';
48};
49util.inherits(IrcConnection, events.EventEmitter);
50
51module.exports.IrcConnection = IrcConnection;
52
53
54IrcConnection.prototype.write = function (data, callback) {
55 write.call(this, data + '\r\n', 'utf-8', callback);
56};
57
58IrcConnection.prototype.end = function (data, callback) {
2a8e95d1
D
59 end.call(this, data + '\r\n', 'utf-8', callback);
60};
61
c08717da
D
62IrcConnection.prototype.dispose = function () {
63 this.removeAllListeners();
64};
65
66
2a8e95d1
D
67var write = function (data, encoding, callback) {
68 this.socket.write(data, encoding, callback);
69};
70
71var end = function (data, encoding, callback) {
72 this.socket.end(data, encoding, callback);
73};
74
75
76var connect_handler = function () {
15fefff7
D
77 var that = this,
78 connect_data;
79
80 // Build up data to be used for webirc/etc detection
81 connect_data = {
82 user: this.user,
83 nick: this.nick,
84 realname: '[www.kiwiirc.com] ' + this.nick,
85 username: this.nick.replace(/[^0-9a-zA-Z\-_.]/, ''),
86 irc_host: this.irc_host
87 };
88
89 // Let the webirc/etc detection modify any required parameters
90 connect_data = findWebIrc(connect_data);
91
92 // Send any initial data for webirc/etc
93 if (connect_data.prepend_data) {
94 _.each(connect_data.prepend_data, function(data) {
95 that.write(data);
96 });
2a8e95d1 97 }
15fefff7 98
eec8c71e 99 this.write('CAP LS');
7dfe47c6
JA
100
101 this.registration_timeout = setTimeout(function () {
102 that.register.call(that);
103 }, 1000);
2a8e95d1
D
104
105 this.connected = true;
2a8e95d1
D
106 this.emit('connected');
107};
108
7dfe47c6
JA
109IrcConnection.prototype.register = function () {
110 if (this.registration_timeout !== null) {
111 clearTimeout(this.registeration_timeout);
112 this.registration_timeout = null;
113 }
114 if ((this.password) && (!_.contains(this.cap.enabled, 'sasl'))) {
115 this.write('PASS ' + this.password);
116 }
117 this.write('NICK ' + this.nick);
118 this.write('USER ' + this.nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + '[www.kiwiirc.com] ' + this.nick);
119 if (this.cap_negotation) {
120 this.write('CAP END');
121 }
122};
2a8e95d1 123
15fefff7
D
124
125
126function findWebIrc(connect_data) {
b737610b
JA
127 var webirc_pass = global.config.webirc_pass;
128 var ip_as_username = global.config.ip_as_username;
15fefff7
D
129 var tmp;
130
131 // Do we have a WEBIRC password for this?
132 if (webirc_pass && webirc_pass[connect_data.irc_host.hostname]) {
133 tmp = 'WEBIRC ' + webirc_pass[connect_data.irc_host.hostname] + ' KiwiIRC ';
134 tmp += connect_data.user.hostname + ' ' + connect_data.user.address;
135 connect_data.prepend_data = [tmp];
136 }
137
138
139 // Check if we need to pass the users IP as its username/ident
140 if (ip_as_username && ip_as_username.indexOf(connect_data.irc_host.hostname) > -1) {
141 // Get a hex value of the clients IP
142 connect_data.username = connect_data.user.address.split('.').map(function(i, idx){
143 return parseInt(i, 10).toString(16);
144 }).join('');
145
146 }
147
148 return connect_data;
149}
150
151
152
16e717f5 153parse_regex = /^(?:(?:(?:(@[^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
2a8e95d1
D
154var parse = function (data) {
155 var i,
156 msg,
16e717f5
JA
157 msg2,
158 trm,
159 j,
160 tags = [],
161 tag;
2a8e95d1
D
162
163 if ((this.hold_last) && (this.held_data !== '')) {
164 data = this.held_data + data;
165 this.hold_last = false;
166 this.held_data = '';
167 }
168 if (data.substr(-1) !== '\n') {
169 this.hold_last = true;
170 }
171 data = data.split("\n");
172 for (i = 0; i < data.length; i++) {
173 if (data[i]) {
174 if ((this.hold_last) && (i === data.length - 1)) {
175 this.held_data = data[i];
176 break;
177 }
178
179 // We have a complete line of data, parse it!
180 msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, ''));
181 if (msg) {
16e717f5
JA
182 if (msg[1]) {
183 tags = msg[1].split(';');
184 for (j = 0; j < tags.length; j++) {
185 tag = tags[j].split('=');
186 tags[j] = {tag: tag[0], value: tag[1]};
187 }
188 }
2a8e95d1 189 msg = {
16e717f5
JA
190 tags: tags,
191 prefix: msg[2],
192 nick: msg[3],
193 ident: msg[4],
194 hostname: msg[5] || '',
195 command: msg[6],
196 params: msg[7] || '',
197 trailing: (msg[8]) ? msg[8].trim() : ''
2a8e95d1
D
198 };
199 msg.params = msg.params.split(' ');
200
201 this.emit('irc_' + msg.command.toUpperCase(), msg);
202 } else {
203 console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, ''));
204 }
205 }
206 }
207};