Commit | Line | Data |
---|---|---|
80c584e7 | 1 | /*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */ |
2fc64ec2 | 2 | /*globals kiwi_root */ |
f236196a | 3 | /* Fuck you, git. */ |
55c7f2af D |
4 | var tls = null, |
5 | net = null, | |
6 | http = null, | |
7 | https = null, | |
8 | fs = null, | |
9 | url = null, | |
10 | dns = null, | |
11 | crypto = null, | |
12 | events = null, | |
13 | util = null, | |
14 | ws = null, | |
15 | jsp = null, | |
16 | pro = null, | |
17 | _ = null, | |
18 | starttls = null, | |
19 | kiwi = null; | |
fd779420 D |
20 | |
21 | this.init = function (objs) { | |
1fce4b40 JA |
22 | tls = objs.tls; |
23 | net = objs.net; | |
24 | http = objs.http; | |
25 | https = objs.https; | |
26 | fs = objs.fs; | |
27 | url = objs.url; | |
28 | dns = objs.dns; | |
29 | crypto = objs.crypto; | |
897abfc3 | 30 | events = objs.events; |
bf371d45 | 31 | util = objs.util; |
1fce4b40 JA |
32 | ws = objs.ws; |
33 | jsp = objs.jsp; | |
34 | pro = objs.pro; | |
35 | _ = objs._; | |
36 | starttls = objs.starttls; | |
37 | kiwi = require('./kiwi.js'); | |
55c7f2af D |
38 | |
39 | util.inherits(this.IRCConnection, events.EventEmitter); | |
2fc64ec2 | 40 | }; |
fd779420 D |
41 | |
42 | ||
43 | ||
44 | ||
45 | ||
46 | ||
47 | /* | |
48 | * Some process changes | |
49 | */ | |
50 | this.setTitle = function () { | |
1fce4b40 | 51 | process.title = 'kiwiirc'; |
2fc64ec2 | 52 | }; |
fd779420 D |
53 | |
54 | this.changeUser = function () { | |
55 | if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') { | |
56 | try { | |
57 | process.setgid(kiwi.config.group); | |
58 | } catch (err) { | |
9970e1d7 | 59 | kiwi.log('Failed to set gid: ' + err); |
fd779420 D |
60 | process.exit(); |
61 | } | |
62 | } | |
63 | ||
64 | if (typeof kiwi.config.user !== 'undefined' && kiwi.config.user !== '') { | |
65 | try { | |
66 | process.setuid(kiwi.config.user); | |
67 | } catch (e) { | |
9970e1d7 | 68 | kiwi.log('Failed to set uid: ' + e); |
fd779420 D |
69 | process.exit(); |
70 | } | |
71 | } | |
72 | }; | |
73 | ||
74 | ||
75 | ||
76 | ||
77 | ||
78 | ||
79 | ||
80 | ||
81 | ||
82 | var ircNumerics = { | |
83 | RPL_WELCOME: '001', | |
337c9866 | 84 | RPL_MYINFO: '004', |
fd779420 D |
85 | RPL_ISUPPORT: '005', |
86 | RPL_WHOISUSER: '311', | |
87 | RPL_WHOISSERVER: '312', | |
88 | RPL_WHOISOPERATOR: '313', | |
89 | RPL_WHOISIDLE: '317', | |
90 | RPL_ENDOFWHOIS: '318', | |
91 | RPL_WHOISCHANNELS: '319', | |
b10afa2d D |
92 | RPL_LISTSTART: '321', |
93 | RPL_LIST: '322', | |
94 | RPL_LISTEND: '323', | |
fd779420 D |
95 | RPL_NOTOPIC: '331', |
96 | RPL_TOPIC: '332', | |
2a4e22fa | 97 | RPL_TOPICWHOTIME: '333', |
fd779420 D |
98 | RPL_NAMEREPLY: '353', |
99 | RPL_ENDOFNAMES: '366', | |
936eabe7 JA |
100 | RPL_BANLIST: '367', |
101 | RPL_ENDOFBANLIST: '368', | |
fd779420 | 102 | RPL_MOTD: '372', |
2955d2b2 D |
103 | RPL_MOTDSTART: '375', |
104 | RPL_ENDOFMOTD: '376', | |
fd779420 D |
105 | RPL_WHOISMODES: '379', |
106 | ERR_NOSUCHNICK: '401', | |
107 | ERR_CANNOTSENDTOCHAN: '404', | |
108 | ERR_TOOMANYCHANNELS: '405', | |
109 | ERR_NICKNAMEINUSE: '433', | |
110 | ERR_USERNOTINCHANNEL: '441', | |
111 | ERR_NOTONCHANNEL: '442', | |
112 | ERR_NOTREGISTERED: '451', | |
113 | ERR_LINKCHANNEL: '470', | |
114 | ERR_CHANNELISFULL: '471', | |
115 | ERR_INVITEONLYCHAN: '473', | |
116 | ERR_BANNEDFROMCHAN: '474', | |
117 | ERR_BADCHANNELKEY: '475', | |
118 | ERR_CHANOPRIVSNEEDED: '482', | |
119 | RPL_STARTTLS: '670' | |
120 | }; | |
121 | ||
122 | ||
123 | ||
bf371d45 | 124 | this.bindIRCCommands = function (irc_connection, websocket) { |
55c7f2af | 125 | var bound_events = []; |
897abfc3 | 126 | |
bf371d45 JA |
127 | irc_connection.on('irc_PING', function (msg) { |
128 | websocket.sendServerLine('PONG ' + msg.trailing); | |
129 | }); | |
55c7f2af | 130 | bound_events.push('irc_PING'); |
8343584e | 131 | |
897abfc3 | 132 | irc_connection.on('irc_' + ircNumerics.RPL_WELCOME, function (msg) { |
bf371d45 JA |
133 | if (irc_connection.IRC.CAP.negotiating) { |
134 | irc_connection.IRC.CAP.negotiating = false; | |
135 | irc_connection.IRC.CAP.enabled = []; | |
136 | irc_connection.IRC.CAP.requested = []; | |
137 | irc_connection.IRC.registered = true; | |
138 | } | |
139 | var nick = msg.params.split(' ')[0]; | |
140 | websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick}); | |
141 | }); | |
55c7f2af | 142 | bound_events.push('irc_' + ircNumerics.RPL_WELCOME); |
337c9866 | 143 | |
bf371d45 JA |
144 | irc_connection.on('irc_' + ircNumerics.RPL_ISUPPORT, function (msg) { |
145 | var opts = msg.params.split(" "), | |
bf371d45 JA |
146 | opt, |
147 | i, | |
897abfc3 | 148 | j, |
bf371d45 JA |
149 | regex, |
150 | matches; | |
151 | for (i = 0; i < opts.length; i++) { | |
152 | opt = opts[i].split("=", 2); | |
153 | opt[0] = opt[0].toUpperCase(); | |
154 | irc_connection.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true; | |
155 | if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'NAMESX'], opt[0])) { | |
156 | if (opt[0] === 'PREFIX') { | |
157 | regex = /\(([^)]*)\)(.*)/; | |
158 | matches = regex.exec(opt[1]); | |
159 | if ((matches) && (matches.length === 3)) { | |
160 | irc_connection.IRC.options[opt[0]] = []; | |
161 | for (j = 0; j < matches[2].length; j++) { | |
162 | irc_connection.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)}); | |
fd779420 | 163 | } |
bf371d45 | 164 | |
fd779420 | 165 | } |
bf371d45 JA |
166 | } |
167 | if (opt[0] === 'NAMESX') { | |
168 | websocket.sendServerLine('PROTOCTL NAMESX'); | |
fd779420 D |
169 | } |
170 | } | |
bf371d45 | 171 | } |
337c9866 | 172 | |
bf371d45 | 173 | websocket.sendClientEvent('options', {server: '', "options": irc_connection.IRC.options}); |
897abfc3 | 174 | }); |
55c7f2af | 175 | bound_events.push('irc_' + ircNumerics.RPL_ISUPPORT); |
a146029e | 176 | |
bf371d45 JA |
177 | irc_connection.on('irc_' + ircNumerics.RPL_ENDOFWHOIS, function (msg) { |
178 | websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: true}); | |
179 | }); | |
55c7f2af | 180 | bound_events.push('irc_' + ircNumerics.RPL_ENDOFWHOIS); |
897abfc3 | 181 | |
bf371d45 JA |
182 | irc_connection.on('irc_' + ircNumerics.RPL_WHOISUSER, function (msg) { |
183 | websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); | |
184 | }); | |
55c7f2af | 185 | bound_events.push('irc_' + ircNumerics.RPL_WHOISUSER); |
897abfc3 | 186 | |
bf371d45 JA |
187 | irc_connection.on('irc_' + ircNumerics.RPL_WHOISSERVER, function (msg) { |
188 | websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); | |
189 | }); | |
55c7f2af | 190 | bound_events.push('irc_' + ircNumerics.RPL_WHOISSERVER); |
897abfc3 | 191 | |
bf371d45 JA |
192 | irc_connection.on('irc_' + ircNumerics.RPL_WHOISOPERATOR, function (msg) { |
193 | websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); | |
194 | }); | |
55c7f2af | 195 | bound_events.push('irc_' + ircNumerics.RPL_WHOISOPERATOR); |
897abfc3 | 196 | |
bf371d45 JA |
197 | irc_connection.on('irc_' + ircNumerics.RPL_WHOISCHANNELS, function (msg) { |
198 | websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); | |
199 | }); | |
55c7f2af | 200 | bound_events.push('irc_' + ircNumerics.RPL_WHOISCHANNELS); |
897abfc3 | 201 | |
bf371d45 JA |
202 | irc_connection.on('irc_' + ircNumerics.RPL_WHOISMODES, function (msg) { |
203 | websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); | |
204 | }); | |
55c7f2af | 205 | bound_events.push('irc_' + ircNumerics.RPL_WHOISMODES); |
b10afa2d | 206 | |
bf371d45 JA |
207 | irc_connection.on('irc_' + ircNumerics.RPL_LISTSTART, function (msg) { |
208 | websocket.sendClientEvent('list_start', {server: ''}); | |
209 | websocket.kiwi.buffer.list = []; | |
210 | }); | |
55c7f2af | 211 | bound_events.push('irc_' + ircNumerics.RPL_LISTSTART); |
897abfc3 | 212 | |
bf371d45 JA |
213 | irc_connection.on('irc_' + ircNumerics.RPL_LISTEND, function (msg) { |
214 | if (websocket.kiwi.buffer.list.length > 0) { | |
215 | websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) { | |
216 | return channel.num_users; | |
217 | }); | |
218 | websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list}); | |
219 | websocket.kiwi.buffer.list = []; | |
220 | } | |
221 | websocket.sendClientEvent('list_end', {server: ''}); | |
222 | }); | |
55c7f2af | 223 | bound_events.push('irc_' + ircNumerics.RPL_LISTEND); |
8343584e | 224 | |
bf371d45 | 225 | irc_connection.on('irc_' + ircNumerics.RPL_LIST, function (msg) { |
897abfc3 | 226 | var parts, channel, num_users, topic; |
bf371d45 JA |
227 | |
228 | parts = msg.params.split(' '); | |
229 | channel = parts[1]; | |
230 | num_users = parts[2]; | |
231 | topic = msg.trailing; | |
232 | ||
233 | //websocket.sendClientEvent('list_channel', { | |
234 | websocket.kiwi.buffer.list.push({ | |
235 | server: '', | |
236 | channel: channel, | |
237 | topic: topic, | |
238 | //modes: modes, | |
239 | num_users: parseInt(num_users, 10) | |
240 | }); | |
8343584e | 241 | |
bf371d45 JA |
242 | if (websocket.kiwi.buffer.list.length > 200) { |
243 | websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) { | |
244 | return channel.num_users; | |
245 | }); | |
246 | websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list}); | |
247 | websocket.kiwi.buffer.list = []; | |
248 | } | |
249 | }); | |
55c7f2af | 250 | bound_events.push('irc_' + ircNumerics.RPL_LIST); |
8343584e | 251 | |
bf371d45 | 252 | irc_connection.on('irc_' + ircNumerics.RPL_WHOISIDLE, function (msg) { |
897abfc3 JA |
253 | var params = msg.params.split(" ", 4), |
254 | rtn = {server: '', nick: params[1], idle: params[2]}; | |
bf371d45 JA |
255 | if (params[3]) { |
256 | rtn.logon = params[3]; | |
257 | } | |
258 | websocket.sendClientEvent('whois', rtn); | |
259 | }); | |
55c7f2af | 260 | bound_events.push('irc_' + ircNumerics.RPL_WHOISIDLE); |
b10afa2d | 261 | |
bf371d45 JA |
262 | irc_connection.on('irc_' + ircNumerics.RPL_MOTD, function (msg) { |
263 | websocket.kiwi.buffer.motd += msg.trailing + '\n'; | |
264 | }); | |
55c7f2af | 265 | bound_events.push('irc_' + ircNumerics.RPL_MOTD); |
bf371d45 JA |
266 | |
267 | irc_connection.on('irc_' + ircNumerics.RPL_MOTDSTART, function (msg) { | |
268 | websocket.kiwi.buffer.motd = ''; | |
269 | }); | |
55c7f2af | 270 | bound_events.push('irc_' + ircNumerics.RPL_MOTDSTART); |
bf371d45 JA |
271 | |
272 | irc_connection.on('irc_' + ircNumerics.RPL_ENDOFMOTD, function (msg) { | |
273 | websocket.sendClientEvent('motd', {server: '', 'msg': websocket.kiwi.buffer.motd}); | |
274 | }); | |
55c7f2af | 275 | bound_events.push('irc_' + ircNumerics.RPL_ENDOFMOTD); |
bf371d45 JA |
276 | |
277 | irc_connection.on('irc_' + ircNumerics.RPL_NAMEREPLY, function (msg) { | |
897abfc3 JA |
278 | var params = msg.params.split(" "), |
279 | chan = params[2], | |
280 | users = msg.trailing.split(" "), | |
281 | nicklist = [], | |
282 | i = 0; | |
283 | ||
bf371d45 JA |
284 | _.each(users, function (user) { |
285 | var j, k, modes = []; | |
286 | for (j = 0; j < user.length; j++) { | |
897abfc3 JA |
287 | for (k = 0; k < irc_connection.IRC.options.PREFIX.length; k++) { |
288 | if (user.charAt(j) === irc_connection.IRC.options.PREFIX[k].symbol) { | |
289 | modes.push(irc_connection.IRC.options.PREFIX[k].mode); | |
8343584e | 290 | } |
fd779420 | 291 | } |
fd779420 | 292 | } |
bf371d45 JA |
293 | nicklist.push({nick: user, modes: modes}); |
294 | if (i++ >= 50) { | |
295 | websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan}); | |
296 | nicklist = []; | |
297 | i = 0; | |
fd779420 | 298 | } |
bf371d45 JA |
299 | }); |
300 | if (i > 0) { | |
301 | websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan}); | |
302 | } else { | |
303 | kiwi.log("oops"); | |
304 | } | |
305 | }); | |
55c7f2af | 306 | bound_events.push('irc_' + ircNumerics.RPL_NAMEREPLY); |
fd779420 | 307 | |
bf371d45 JA |
308 | irc_connection.on('irc_' + ircNumerics.RPL_ENDOFNAMES, function (msg) { |
309 | websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]}); | |
310 | }); | |
55c7f2af | 311 | bound_events.push('irc_' + ircNumerics.RPL_ENDOFNAMES); |
bf371d45 JA |
312 | |
313 | irc_connection.on('irc_' + ircNumerics.ERR_LINKCHANNEL, function (msg) { | |
897abfc3 | 314 | var params = msg.params.split(" "); |
bf371d45 JA |
315 | websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]}); |
316 | }); | |
55c7f2af | 317 | bound_events.push('irc_' + ircNumerics.ERR_LINKCHANNEL); |
bf371d45 JA |
318 | |
319 | irc_connection.on('irc_' + ircNumerics.ERR_NOSUCHNICK, function (msg) { | |
320 | websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing}); | |
321 | }); | |
55c7f2af | 322 | bound_events.push('irc_' + ircNumerics.ERR_NOSUCHNICK); |
bf371d45 JA |
323 | |
324 | irc_connection.on('irc_' + ircNumerics.RPL_BANLIST, function (msg) { | |
897abfc3 | 325 | var params = msg.params.split(" "); |
bf371d45 JA |
326 | kiwi.log(params); |
327 | websocket.sendClientEvent('banlist', {server: '', channel: params[1], banned: params[2], banned_by: params[3], banned_at: params[4]}); | |
328 | }); | |
55c7f2af | 329 | bound_events.push('irc_' + ircNumerics.RPL_BANLIST); |
bf371d45 JA |
330 | |
331 | irc_connection.on('irc_' + ircNumerics.RPL_ENDOFBANLIST, function (msg) { | |
332 | websocket.sendClientEvent('banlist_end', {server: '', channel: msg.params.split(" ")[1]}); | |
333 | }); | |
55c7f2af | 334 | bound_events.push('irc_' + ircNumerics.RPL_ENDOFBANLIST); |
bf371d45 JA |
335 | |
336 | irc_connection.on('irc_JOIN', function (msg) { | |
897abfc3 JA |
337 | var channel; |
338 | ||
bf371d45 JA |
339 | // Some BNC's send malformed JOIN causing the channel to be as a |
340 | // parameter instead of trailing. | |
341 | if (typeof msg.trailing === 'string' && msg.trailing !== '') { | |
342 | channel = msg.trailing; | |
343 | } else if (typeof msg.params === 'string' && msg.params !== '') { | |
344 | channel = msg.params; | |
345 | } | |
346 | ||
347 | websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel}); | |
897abfc3 | 348 | if (msg.nick === irc_connection.IRC.nick) { |
bf371d45 JA |
349 | websocket.sendServerLine('NAMES ' + msg.trailing); |
350 | } | |
351 | }); | |
55c7f2af | 352 | bound_events.push('irc_JOIN'); |
bf371d45 JA |
353 | |
354 | irc_connection.on('irc_PART', function (msg) { | |
897abfc3 | 355 | websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing}); |
bf371d45 | 356 | }); |
55c7f2af | 357 | bound_events.push('irc_PART'); |
bf371d45 JA |
358 | |
359 | irc_connection.on('irc_KICK', function (msg) { | |
897abfc3 | 360 | var params = msg.params.split(" "); |
bf371d45 JA |
361 | websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing}); |
362 | }); | |
55c7f2af | 363 | bound_events.push('irc_KICK'); |
bf371d45 JA |
364 | |
365 | irc_connection.on('irc_QUIT', function (msg) { | |
366 | websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing}); | |
367 | }); | |
55c7f2af | 368 | bound_events.push('irc_QUIT'); |
bf371d45 JA |
369 | |
370 | irc_connection.on('irc_NOTICE', function (msg) { | |
371 | if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) { | |
372 | // It's a CTCP response | |
373 | websocket.sendClientEvent('ctcp_response', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)}); | |
374 | } else { | |
375 | websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing}); | |
376 | } | |
377 | }); | |
55c7f2af | 378 | bound_events.push('irc_NOTICE'); |
bf371d45 JA |
379 | |
380 | irc_connection.on('irc_NICK', function (msg) { | |
381 | websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing}); | |
382 | }); | |
55c7f2af | 383 | bound_events.push('irc_NICK'); |
bf371d45 JA |
384 | |
385 | irc_connection.on('irc_TOPIC', function (msg) { | |
897abfc3 | 386 | var obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing}; |
bf371d45 JA |
387 | websocket.sendClientEvent('topic', obj); |
388 | }); | |
55c7f2af | 389 | bound_events.push('irc_TOPIC'); |
bf371d45 JA |
390 | |
391 | irc_connection.on('irc_' + ircNumerics.RPL_TOPIC, function (msg) { | |
897abfc3 | 392 | var obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing}; |
bf371d45 JA |
393 | websocket.sendClientEvent('topic', obj); |
394 | }); | |
55c7f2af | 395 | bound_events.push('irc_' + ircNumerics.RPL_TOPIC); |
bf371d45 JA |
396 | |
397 | irc_connection.on('irc_' + ircNumerics.RPL_NOTOPIC, function (msg) { | |
897abfc3 JA |
398 | var obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''}; |
399 | websocket.sendClientEvent('topic', obj); | |
bf371d45 | 400 | }); |
55c7f2af | 401 | bound_events.push('irc_' + ircNumerics.RPL_NOTOPIC); |
bf371d45 JA |
402 | |
403 | irc_connection.on('irc_' + ircNumerics.RPL_TOPICWHOTIME, function (msg) { | |
404 | var parts = msg.params.split(' '), | |
405 | nick = parts[2], | |
406 | channel = parts[1], | |
897abfc3 | 407 | when = parts[3], |
bf371d45 | 408 | obj = {nick: nick, channel: channel, when: when}; |
897abfc3 | 409 | websocket.sendClientEvent('topicsetby', obj); |
bf371d45 | 410 | }); |
55c7f2af | 411 | bound_events.push('irc_' + ircNumerics.RPL_TOPICWHOTIME); |
bf371d45 JA |
412 | |
413 | irc_connection.on('irc_MODE', function (msg) { | |
897abfc3 JA |
414 | var opts = msg.params.split(" "), |
415 | params = {nick: msg.nick}; | |
416 | ||
bf371d45 JA |
417 | switch (opts.length) { |
418 | case 1: | |
419 | params.effected_nick = opts[0]; | |
420 | params.mode = msg.trailing; | |
fd779420 | 421 | break; |
bf371d45 JA |
422 | case 2: |
423 | params.channel = opts[0]; | |
424 | params.mode = opts[1]; | |
2a4e22fa | 425 | break; |
bf371d45 JA |
426 | default: |
427 | params.channel = opts[0]; | |
428 | params.mode = opts[1]; | |
429 | params.effected_nick = opts[2]; | |
fd779420 | 430 | break; |
bf371d45 JA |
431 | } |
432 | websocket.sendClientEvent('mode', params); | |
433 | }); | |
55c7f2af | 434 | bound_events.push('irc_MODE'); |
bf371d45 JA |
435 | |
436 | irc_connection.on('irc_PRIVMSG', function (msg) { | |
897abfc3 | 437 | var tmp, namespace, obj; |
bf371d45 JA |
438 | if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) { |
439 | // It's a CTCP request | |
440 | if (msg.trailing.substr(1, 6) === 'ACTION') { | |
441 | websocket.sendClientEvent('action', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(7, msg.trailing.length - 2)}); | |
442 | } else if (msg.trailing.substr(1, 4) === 'KIWI') { | |
443 | tmp = msg.trailing.substr(6, msg.trailing.length - 2); | |
444 | namespace = tmp.split(' ', 1)[0]; | |
445 | websocket.sendClientEvent('kiwi', {namespace: namespace, data: tmp.substr(namespace.length + 1)}); | |
446 | ||
447 | } else if (msg.trailing.substr(1, 7) === 'VERSION') { | |
897abfc3 | 448 | irc_connection.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n'); |
fd779420 | 449 | } else { |
bf371d45 | 450 | websocket.sendClientEvent('ctcp_request', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)}); |
fd779420 | 451 | } |
bf371d45 JA |
452 | } else { |
453 | obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing}; | |
454 | websocket.sendClientEvent('msg', obj); | |
455 | } | |
456 | }); | |
55c7f2af | 457 | bound_events.push('irc_PRIVMSG'); |
bf371d45 JA |
458 | |
459 | irc_connection.on('irc_CAP', function (msg) { | |
897abfc3 JA |
460 | var caps = kiwi.config.cap_options, |
461 | options = msg.trailing.split(" "), | |
462 | opts; | |
463 | ||
bf371d45 JA |
464 | switch (_.last(msg.params.split(" "))) { |
465 | case 'LS': | |
466 | opts = ''; | |
467 | _.each(_.intersect(caps, options), function (cap) { | |
468 | if (opts !== '') { | |
469 | opts += " "; | |
fd779420 | 470 | } |
bf371d45 | 471 | opts += cap; |
897abfc3 | 472 | irc_connection.IRC.CAP.requested.push(cap); |
bf371d45 JA |
473 | }); |
474 | if (opts.length > 0) { | |
475 | websocket.sendServerLine('CAP REQ :' + opts); | |
476 | } else { | |
477 | websocket.sendServerLine('CAP END'); | |
478 | } | |
479 | // TLS is special | |
480 | /*if (_.include(options, 'tls')) { | |
481 | websocket.sendServerLine('STARTTLS'); | |
482 | ircSocket.IRC.CAP.requested.push('tls'); | |
483 | }*/ | |
484 | break; | |
485 | case 'ACK': | |
486 | _.each(options, function (cap) { | |
897abfc3 | 487 | irc_connection.IRC.CAP.enabled.push(cap); |
bf371d45 JA |
488 | }); |
489 | if (_.last(msg.params.split(" ")) !== '*') { | |
897abfc3 JA |
490 | irc_connection.IRC.CAP.requested = []; |
491 | irc_connection.IRC.CAP.negotiating = false; | |
fd779420 | 492 | websocket.sendServerLine('CAP END'); |
fd779420 D |
493 | } |
494 | break; | |
bf371d45 | 495 | case 'NAK': |
897abfc3 JA |
496 | irc_connection.IRC.CAP.requested = []; |
497 | irc_connection.IRC.CAP.negotiating = false; | |
bf371d45 JA |
498 | websocket.sendServerLine('CAP END'); |
499 | break; | |
500 | } | |
501 | }); | |
55c7f2af | 502 | bound_events.push('irc_CAP'); |
fd779420 D |
503 | /*case ircNumerics.RPL_STARTTLS: |
504 | try { | |
505 | IRC = ircSocket.IRC; | |
506 | listeners = ircSocket.listeners('data'); | |
507 | ircSocket.removeAllListeners('data'); | |
508 | ssl_socket = starttls(ircSocket, {}, function () { | |
509 | ssl_socket.on("data", function (data) { | |
510 | ircSocketDataHandler(data, websocket, ssl_socket); | |
511 | }); | |
512 | ircSocket = ssl_socket; | |
513 | ircSocket.IRC = IRC; | |
514 | _.each(listeners, function (listener) { | |
515 | ircSocket.addListener('data', listener); | |
516 | }); | |
517 | }); | |
9970e1d7 | 518 | //log(ircSocket); |
fd779420 | 519 | } catch (e) { |
9970e1d7 | 520 | kiwi.log(e); |
fd779420 D |
521 | } |
522 | break;*/ | |
bf371d45 | 523 | irc_connection.on('irc_' + ircNumerics.ERR_CANNOTSENDTOCHAN, function (msg) { |
897abfc3 | 524 | websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing}); |
bf371d45 | 525 | }); |
55c7f2af | 526 | bound_events.push('irc_' + ircNumerics.ERR_CANNOTSENDTOCHAN); |
fd779420 | 527 | |
bf371d45 JA |
528 | irc_connection.on('irc_' + ircNumerics.ERR_TOOMANYCHANNELS, function (msg) { |
529 | websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing}); | |
530 | }); | |
55c7f2af | 531 | bound_events.push('irc_' + ircNumerics.ERR_TOOMANYCHANNELS); |
fd779420 | 532 | |
bf371d45 | 533 | irc_connection.on('irc_' + ircNumerics.ERR_USERNOTINCHANNEL, function (msg) { |
897abfc3 | 534 | var params = msg.params.split(" "); |
bf371d45 JA |
535 | websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling}); |
536 | }); | |
55c7f2af | 537 | bound_events.push('irc_' + ircNumerics.ERR_USERNOTINCHANNEL); |
fd779420 | 538 | |
bf371d45 JA |
539 | irc_connection.on('irc_' + ircNumerics.ERR_NOTONCHANNEL, function (msg) { |
540 | websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing}); | |
541 | }); | |
55c7f2af | 542 | bound_events.push('irc_' + ircNumerics.ERR_NOTONCHANNEL); |
fd779420 | 543 | |
bf371d45 JA |
544 | irc_connection.on('irc_' + ircNumerics.ERR_CHANNELISFULL, function (msg) { |
545 | websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing}); | |
546 | }); | |
55c7f2af | 547 | bound_events.push('irc_' + ircNumerics.ERR_CHANNELISFULL); |
fd779420 | 548 | |
bf371d45 JA |
549 | irc_connection.on('irc_' + ircNumerics.ERR_INVITEONLYCHAN, function (msg) { |
550 | websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing}); | |
551 | }); | |
55c7f2af | 552 | bound_events.push('irc_' + ircNumerics.ERR_INVITEONLYCHAN); |
fd779420 | 553 | |
bf371d45 JA |
554 | irc_connection.on('irc_' + ircNumerics.ERR_BANNEDFROMCHAN, function (msg) { |
555 | websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing}); | |
556 | }); | |
55c7f2af | 557 | bound_events.push('irc_' + ircNumerics.ERR_BANNEDFROMCHAN); |
fd779420 | 558 | |
bf371d45 JA |
559 | irc_connection.on('irc_' + ircNumerics.ERR_BADCHANNELKEY, function (msg) { |
560 | websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing}); | |
561 | }); | |
55c7f2af | 562 | bound_events.push('irc_' + ircNumerics.ERR_BADCHANNELKEY); |
fd779420 | 563 | |
bf371d45 JA |
564 | irc_connection.on('irc_' + ircNumerics.ERR_CHANOPRIVSNEEDED, function (msg) { |
565 | websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing}); | |
566 | }); | |
55c7f2af | 567 | bound_events.push('irc_' + ircNumerics.ERR_CHANOPRIVSNEEDED); |
fd779420 | 568 | |
bf371d45 JA |
569 | irc_connection.on('irc_' + ircNumerics.ERR_NICKNAMEINUSE, function (msg) { |
570 | websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing}); | |
571 | }); | |
55c7f2af | 572 | bound_events.push('irc_' + ircNumerics.ERR_NICKNAMEINUSE); |
bf371d45 JA |
573 | |
574 | irc_connection.on('irc_ERROR', function (msg) { | |
897abfc3 | 575 | irc_connection.end(); |
bf371d45 JA |
576 | websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing}); |
577 | websocket.disconnect(); | |
578 | }); | |
55c7f2af | 579 | bound_events.push('irc_ERROR'); |
fd779420 | 580 | |
bf371d45 | 581 | irc_connection.on('irc_' + ircNumerics.ERR_NOTREGISTERED, function (msg) { |
897abfc3 | 582 | if (irc_connection.IRC.registered) { |
bf371d45 JA |
583 | kiwi.log('Kiwi thinks user is registered, but the IRC server thinks differently'); |
584 | } | |
585 | }); | |
55c7f2af | 586 | bound_events.push('irc_' + ircNumerics.ERR_NOTREGISTERED); |
f52d8543 | 587 | |
55c7f2af | 588 | return bound_events; |
f52d8543 JA |
589 | }; |
590 | ||
591 | this.rebindIRCCommands = function () { | |
592 | _.each(kiwi.connections, function (con) { | |
593 | _.each(con.sockets, function (sock) { | |
594 | sock.ircConnection.rebindIRCCommands(); | |
595 | }); | |
596 | }); | |
bf371d45 | 597 | }; |
fd779420 D |
598 | |
599 | ||
600 | this.httpHandler = function (request, response) { | |
8397d902 | 601 | var uri, uri_parts, subs, useragent, agent, server_set, server, nick, debug, touchscreen, hash, |
897abfc3 | 602 | min = {}, public_http_path, port, ssl, obj, args, ircuri, target, modifiers, query, |
9f6954dc | 603 | secure = (typeof request.client.encrypted === 'object'); |
0622748f | 604 | |
480d2fea | 605 | try { |
0622748f | 606 | if (kiwi.config.handle_http) { |
f97fe3d2 | 607 | // Run through any plugins.. |
3010bd6c | 608 | args = {request: request, response: response, ssl: secure}; |
f97fe3d2 D |
609 | obj = kiwi.kiwi_mod.run('http', args); |
610 | if (obj === null) { | |
611 | return; | |
612 | } | |
613 | response = args.response; | |
614 | ||
0622748f D |
615 | uri = url.parse(request.url, true); |
616 | uri_parts = uri.pathname.split('/'); | |
617 | ||
618 | subs = uri.pathname.substr(0, 4); | |
5638a960 | 619 | public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http; |
897abfc3 | 620 | |
cbcccdbd D |
621 | if (typeof uri.query.ircuri !== 'undefined') { |
622 | ircuri = url.parse(uri.query.ircuri, true); | |
623 | if (ircuri.protocol === 'irc:') { | |
624 | uri_parts = /^\/([^,\?]*)((,[^,\?]*)*)?$/.exec(ircuri.pathname); | |
625 | target = uri_parts[1]; | |
626 | modifiers = (typeof uri_parts[2] !== 'undefined') ? uri_parts[2].split(',') : []; | |
627 | query = ircuri.query; | |
628 | ||
629 | nick = _.detect(modifiers, function (mod) { | |
630 | return mod === ',isnick'; | |
631 | }); | |
9970e1d7 | 632 | kiwi.log(request.headers); |
cbcccdbd D |
633 | response.statusCode = 303; |
634 | response.setHeader('Location', 'http' + ((secure) ? 's' : '') + '://' + request.headers.host + '/client/' + ircuri.host + '/' + ((!nick) ? target : '')); | |
635 | response.end(); | |
636 | } | |
637 | } else if (uri.pathname === '/js/all.js') { | |
0622748f | 638 | if (kiwi.cache.alljs === '') { |
0622748f | 639 | |
1fce4b40 | 640 | min.underscore = fs.readFileSync(public_http_path + 'js/underscore.min.js'); |
0622748f D |
641 | min.util = fs.readFileSync(public_http_path + 'js/util.js'); |
642 | min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js'); | |
643 | min.front = fs.readFileSync(public_http_path + 'js/front.js'); | |
b41a381f D |
644 | min.front_events = fs.readFileSync(public_http_path + 'js/front.events.js'); |
645 | min.front_ui = fs.readFileSync(public_http_path + 'js/front.ui.js'); | |
0622748f | 646 | min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js'); |
b41a381f | 647 | min.ast = jsp.parse(min.underscore + min.util + min.gateway + min.front + min.front_events + min.front_ui + min.iscroll); |
0622748f D |
648 | min.ast = pro.ast_mangle(min.ast); |
649 | min.ast = pro.ast_squeeze(min.ast); | |
650 | min.final_code = pro.gen_code(min.ast); | |
651 | kiwi.cache.alljs = min.final_code; | |
652 | hash = crypto.createHash('md5').update(kiwi.cache.alljs); | |
653 | kiwi.cache.alljs_hash = hash.digest('base64'); | |
654 | } | |
655 | if (request.headers['if-none-match'] === kiwi.cache.alljs_hash) { | |
656 | response.statusCode = 304; | |
657 | } else { | |
658 | response.setHeader('Content-type', 'application/javascript'); | |
659 | response.setHeader('ETag', kiwi.cache.alljs_hash); | |
660 | response.write(kiwi.cache.alljs); | |
661 | } | |
662 | response.end(); | |
663 | } else if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) { | |
664 | request.addListener('end', function () { | |
665 | kiwi.fileServer.serve(request, response); | |
666 | }); | |
667 | } else if (uri.pathname === '/' || uri_parts[1] === 'client') { | |
668 | useragent = (typeof request.headers === 'string') ? request.headers['user-agent'] : ''; | |
669 | if (useragent.match(/android/i) !== -1) { | |
670 | agent = 'android'; | |
671 | touchscreen = true; | |
672 | } else if (useragent.match(/iphone/) !== -1) { | |
673 | agent = 'iphone'; | |
674 | touchscreen = true; | |
675 | } else if (useragent.match(/ipad/) !== -1) { | |
676 | agent = 'ipad'; | |
677 | touchscreen = true; | |
678 | } else if (useragent.match(/ipod/) !== -1) { | |
679 | agent = 'ipod'; | |
680 | touchscreen = true; | |
681 | } else { | |
682 | agent = 'normal'; | |
683 | touchscreen = false; | |
684 | } | |
fd779420 D |
685 | agent = 'normal'; |
686 | touchscreen = false; | |
91726016 | 687 | |
0622748f | 688 | debug = (typeof uri.query.debug !== 'undefined'); |
8343584e | 689 | |
d3b6433a D |
690 | ssl = secure; // ssl is passed to the client |
691 | port = ssl ? kiwi.config.client_defaults.port_ssl : kiwi.config.client_defaults.port; | |
0622748f D |
692 | if (uri_parts[1] !== 'client') { |
693 | if (uri.query) { | |
694 | server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== '')); | |
d3b6433a | 695 | server = uri.query.server || kiwi.config.client_defaults.server; |
0622748f D |
696 | nick = uri.query.nick || ''; |
697 | } else { | |
698 | server_set = false; | |
d3b6433a | 699 | server = kiwi.config.client_defaults.server; |
0622748f D |
700 | nick = ''; |
701 | } | |
8397d902 | 702 | } else { |
0622748f | 703 | server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== '')); |
d3b6433a | 704 | server = server_set ? uri_parts[2] : kiwi.config.client_defaults.server; |
c512925c D |
705 | if (server.search(/:/) > 0) { |
706 | port = server.substring(server.search(/:/) + 1); | |
707 | server = server.substring(0, server.search(/:/)); | |
897abfc3 | 708 | if (port[0] === '+') { |
c512925c D |
709 | port = port.substring(1); |
710 | ssl = true; | |
4023f19f D |
711 | } else { |
712 | ssl = false; | |
c512925c D |
713 | } |
714 | } | |
0622748f | 715 | nick = uri.query.nick || ''; |
8397d902 | 716 | } |
8397d902 | 717 | |
5c845ac2 | 718 | // Set the default nick if one isn't provided |
897abfc3 | 719 | if (nick === '') { |
5c845ac2 D |
720 | nick = 'kiwi_?'; |
721 | } | |
722 | ||
723 | // Set any random numbers if needed | |
897abfc3 | 724 | nick = nick.replace('?', Math.floor(Math.random() * 100000).toString()); |
5c845ac2 | 725 | |
0622748f D |
726 | response.setHeader('X-Generated-By', 'KiwiIRC'); |
727 | hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f') | |
728 | .update(debug ? 't' : 'f') | |
729 | .update(server_set ? 't' : 'f') | |
730 | .update(secure ? 't' : 'f') | |
731 | .update(server) | |
e8d7c021 | 732 | .update(port.toString()) |
c512925c | 733 | .update(ssl ? 't' : 'f') |
0622748f D |
734 | .update(nick) |
735 | .update(agent) | |
736 | .update(JSON.stringify(kiwi.config)) | |
737 | .digest('base64'); | |
738 | if (kiwi.cache.html[hash]) { | |
739 | if (request.headers['if-none-match'] === kiwi.cache.html[hash].hash) { | |
740 | response.statusCode = 304; | |
fd779420 | 741 | } else { |
0622748f D |
742 | response.setHeader('Etag', kiwi.cache.html[hash].hash); |
743 | response.setHeader('Content-type', 'text/html'); | |
744 | response.write(kiwi.cache.html[hash].html); | |
fd779420 D |
745 | } |
746 | response.end(); | |
0622748f | 747 | } else { |
5638a960 | 748 | fs.readFile(public_http_path + 'index.html.jade', 'utf8', function (err, str) { |
0622748f D |
749 | var html, hash2; |
750 | if (!err) { | |
9dc939b7 | 751 | try { |
c512925c | 752 | html = kiwi.jade.compile(str)({ "touchscreen": touchscreen, "debug": debug, "secure": secure, "server_set": server_set, "server": server, "port": port, "ssl": ssl, "nick": nick, "agent": agent, "config": kiwi.config }); |
9dc939b7 JA |
753 | hash2 = crypto.createHash('md5').update(html).digest('base64'); |
754 | kiwi.cache.html[hash] = {"html": html, "hash": hash2}; | |
755 | if (request.headers['if-none-match'] === hash2) { | |
756 | response.statusCode = 304; | |
757 | } else { | |
758 | response.setHeader('Etag', hash2); | |
759 | response.setHeader('Content-type', 'text/html'); | |
760 | response.write(html); | |
761 | } | |
762 | } catch (e) { | |
763 | response.statusCode = 500; | |
9970e1d7 | 764 | kiwi.log(e); |
0622748f D |
765 | } |
766 | } else { | |
9970e1d7 | 767 | kiwi.log(err); |
0622748f D |
768 | response.statusCode = 500; |
769 | } | |
770 | response.end(); | |
771 | }); | |
772 | } | |
773 | } else if (uri.pathname.substr(0, 10) === '/socket.io') { | |
774 | return; | |
775 | } else { | |
776 | response.statusCode = 404; | |
777 | response.end(); | |
fd779420 | 778 | } |
fd779420 | 779 | } |
0622748f | 780 | |
480d2fea | 781 | } catch (e) { |
9970e1d7 D |
782 | kiwi.log('ERROR app.httpHandler()'); |
783 | kiwi.log(e); | |
480d2fea | 784 | } |
fd779420 D |
785 | }; |
786 | ||
787 | ||
788 | ||
789 | ||
d423ee18 | 790 | this.websocketListen = function (servers, handler) { |
9f6954dc JA |
791 | if (kiwi.httpServers.length > 0) { |
792 | _.each(kiwi.httpServers, function (hs) { | |
793 | hs.close(); | |
794 | }); | |
795 | kiwi.httpsServers = []; | |
fd779420 | 796 | } |
8343584e | 797 | |
d423ee18 D |
798 | _.each(servers, function (server) { |
799 | var hs, opts; | |
800 | if (server.secure === true) { | |
801 | // Start some SSL server up | |
802 | opts = { | |
803 | key: fs.readFileSync(__dirname + '/' + server.ssl_key), | |
804 | cert: fs.readFileSync(__dirname + '/' + server.ssl_cert) | |
805 | }; | |
806 | ||
807 | // Do we have an intermediate certificate? | |
808 | if (typeof server.ssl_ca !== 'undefined') { | |
809 | opts.ca = fs.readFileSync(__dirname + '/' + server.ssl_ca); | |
810 | } | |
811 | ||
812 | hs = https.createServer(opts, handler); | |
9f6954dc | 813 | kiwi.io.push(ws.listen(hs, {secure: true})); |
d423ee18 | 814 | hs.listen(server.port, server.address); |
deadd643 | 815 | kiwi.log('Listening on ' + server.address + ':' + server.port.toString() + ' with SSL'); |
9f6954dc | 816 | } else { |
d423ee18 | 817 | // Start some plain-text server up |
9f6954dc JA |
818 | hs = http.createServer(handler); |
819 | kiwi.io.push(ws.listen(hs, {secure: false})); | |
d423ee18 | 820 | hs.listen(server.port, server.address); |
deadd643 | 821 | kiwi.log('Listening on ' + server.address + ':' + server.port.toString() + ' without SSL'); |
fd779420 | 822 | } |
f97fe3d2 D |
823 | |
824 | kiwi.httpServers.push(hs); | |
9f6954dc | 825 | }); |
8343584e | 826 | |
9f6954dc JA |
827 | _.each(kiwi.io, function (io) { |
828 | io.set('log level', 1); | |
829 | io.enable('browser client minification'); | |
830 | io.enable('browser client etag'); | |
831 | io.set('transports', kiwi.config.transports); | |
1fce4b40 | 832 | |
9f6954dc JA |
833 | io.of('/kiwi').authorization(function (handshakeData, callback) { |
834 | var address = handshakeData.address.address; | |
835 | if (typeof kiwi.connections[address] === 'undefined') { | |
836 | kiwi.connections[address] = {count: 0, sockets: []}; | |
837 | } | |
838 | callback(null, true); | |
839 | }).on('connection', kiwi.websocketConnection); | |
840 | }); | |
fd779420 D |
841 | }; |
842 | ||
843 | ||
844 | ||
845 | ||
846 | ||
847 | ||
848 | this.websocketConnection = function (websocket) { | |
849 | var con; | |
9970e1d7 | 850 | kiwi.log("New connection!"); |
5c7ac96f | 851 | websocket.kiwi = {address: websocket.handshake.address.address, buffer: {list: []}}; |
fd779420 D |
852 | con = kiwi.connections[websocket.kiwi.address]; |
853 | ||
854 | if (con.count >= kiwi.config.max_client_conns) { | |
855 | websocket.emit('too_many_connections'); | |
856 | websocket.disconnect(); | |
857 | } else { | |
858 | con.count += 1; | |
859 | con.sockets.push(websocket); | |
860 | ||
861 | websocket.sendClientEvent = function (event_name, data) { | |
4f06269b | 862 | var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this}); |
2fc64ec2 JA |
863 | if (ev === null) { |
864 | return; | |
865 | } | |
4f06269b | 866 | |
fd779420 D |
867 | data.event = event_name; |
868 | websocket.emit('message', data); | |
869 | }; | |
870 | ||
871 | websocket.sendServerLine = function (data, eol) { | |
872 | eol = (typeof eol === 'undefined') ? '\r\n' : eol; | |
abb46d2d D |
873 | |
874 | try { | |
bf371d45 | 875 | websocket.ircConnection.write(data + eol); |
abb46d2d | 876 | } catch (e) { } |
fd779420 D |
877 | }; |
878 | ||
897abfc3 JA |
879 | websocket.on('irc connect', function (nick, host, port, ssl, password, callback) { |
880 | websocket.ircConnection = new kiwi.IRCConnection(this, nick, host, port, ssl, password, callback); | |
bf371d45 | 881 | }); |
fd779420 D |
882 | websocket.on('message', kiwi.websocketMessage); |
883 | websocket.on('disconnect', kiwi.websocketDisconnect); | |
884 | } | |
885 | }; | |
886 | ||
887 | ||
bf371d45 JA |
888 | this.IRCConnection = function (websocket, nick, host, port, ssl, password, callback) { |
889 | var ircSocket, | |
890 | that = this, | |
63eaef31 | 891 | regex, |
f52d8543 | 892 | onConnectHandler, |
55c7f2af | 893 | bound_events; |
fd779420 | 894 | |
bf371d45 | 895 | events.EventEmitter.call(this); |
f52d8543 | 896 | |
63eaef31 JA |
897 | onConnectHandler = function () { |
898 | that.IRC.nick = nick; | |
899 | // Send the login data | |
900 | dns.reverse(websocket.kiwi.address, function (err, domains) { | |
901 | websocket.kiwi.hostname = (err) ? websocket.kiwi.address : _.first(domains); | |
902 | if ((kiwi.config.webirc) && (kiwi.config.webirc_pass[host])) { | |
903 | websocket.sendServerLine('WEBIRC ' + kiwi.config.webirc_pass[host] + ' KiwiIRC ' + websocket.kiwi.hostname + ' ' + websocket.kiwi.address); | |
904 | } | |
905 | if (password) { | |
906 | websocket.sendServerLine('PASS ' + password); | |
907 | } | |
908 | websocket.sendServerLine('CAP LS'); | |
909 | websocket.sendServerLine('NICK ' + nick); | |
910 | websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick); | |
911 | ||
d62bbe08 | 912 | that.connected = true; |
f52d8543 | 913 | that.emit('connect'); |
63eaef31 | 914 | }); |
63eaef31 | 915 | }; |
f52d8543 | 916 | |
fd779420 D |
917 | if (!ssl) { |
918 | ircSocket = net.createConnection(port, host); | |
63eaef31 | 919 | ircSocket.on('connect', onConnectHandler); |
fd779420 | 920 | } else { |
63eaef31 | 921 | ircSocket = tls.connect(port, host, {}, onConnectHandler); |
fd779420 | 922 | } |
897abfc3 | 923 | |
0a401b0e | 924 | ircSocket.setEncoding('utf-8'); |
897abfc3 JA |
925 | this.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false}; |
926 | ||
bf371d45 | 927 | this.on('error', function (e) { |
897abfc3 | 928 | if (that.IRC.registered) { |
fd779420 D |
929 | websocket.emit('disconnect'); |
930 | } else { | |
931 | websocket.emit('error', e.message); | |
932 | } | |
933 | }); | |
897abfc3 | 934 | |
bf371d45 | 935 | ircSocket.on('error', function (e) { |
d62bbe08 | 936 | that.connected = false; |
bf371d45 | 937 | that.emit('error', e); |
d62bbe08 | 938 | that.destroySoon(); |
bf371d45 | 939 | }); |
f52d8543 JA |
940 | |
941 | if (typeof callback === 'function') { | |
942 | this.on('connect', callback); | |
943 | } | |
944 | ||
bf371d45 | 945 | regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i; |
fd779420 D |
946 | ircSocket.holdLast = false; |
947 | ircSocket.held = ''; | |
fd779420 | 948 | ircSocket.on('data', function (data) { |
897abfc3 | 949 | var i, msg; |
bf371d45 JA |
950 | if ((ircSocket.holdLast) && (ircSocket.held !== '')) { |
951 | data = ircSocket.held + data; | |
952 | ircSocket.holdLast = false; | |
953 | ircSocket.held = ''; | |
fd779420 | 954 | } |
bf371d45 JA |
955 | if (data.substr(-1) !== '\n') { |
956 | ircSocket.holdLast = true; | |
e8a707e7 | 957 | } |
bf371d45 JA |
958 | data = data.split("\n"); |
959 | for (i = 0; i < data.length; i++) { | |
960 | if (data[i]) { | |
961 | if ((ircSocket.holdLast) && (i === data.length - 1)) { | |
962 | ircSocket.held = data[i]; | |
963 | break; | |
964 | } | |
fd779420 | 965 | |
bf371d45 | 966 | // We have a complete line of data, parse it! |
07e97a5a | 967 | msg = regex.exec(data[i].replace(/^\r+|\r+$/, '')); |
bf371d45 JA |
968 | if (msg) { |
969 | msg = { | |
970 | prefix: msg[1], | |
971 | nick: msg[2], | |
972 | ident: msg[3], | |
973 | hostname: msg[4] || '', | |
974 | command: msg[5], | |
975 | params: msg[6] || '', | |
976 | trailing: (msg[7]) ? msg[7].trim() : '' | |
977 | }; | |
978 | that.emit('irc_' + msg.command.toUpperCase(), msg); | |
07e97a5a JA |
979 | if (that.listeners('irc_' + msg.command.toUpperCase()).length < 1) { |
980 | kiwi.log("Unknown command (" + String(msg.command).toUpperCase() + ")"); | |
bf371d45 JA |
981 | } |
982 | } else { | |
07e97a5a | 983 | kiwi.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, '')); |
bf371d45 JA |
984 | } |
985 | } | |
fd779420 D |
986 | } |
987 | }); | |
897abfc3 | 988 | |
bf371d45 JA |
989 | if (callback) { |
990 | ircSocket.on('connect', callback); | |
991 | } | |
897abfc3 | 992 | |
bf371d45 | 993 | ircSocket.on('end', function () { |
d62bbe08 | 994 | that.connected = false; |
bf371d45 JA |
995 | that.emit('disconnect', false); |
996 | }); | |
897abfc3 | 997 | |
bf371d45 | 998 | ircSocket.on('close', function (had_error) { |
d62bbe08 | 999 | that.connected = false; |
bf371d45 JA |
1000 | that.emit('disconnect', had_error); |
1001 | }); | |
897abfc3 | 1002 | |
bf371d45 JA |
1003 | ircSocket.on('timeout', function () { |
1004 | ircSocket.destroy(); | |
d62bbe08 | 1005 | that.connected = false; |
897abfc3 | 1006 | that.emit('error', {message: 'Connection timed out'}); |
bf371d45 | 1007 | }); |
f52d8543 | 1008 | |
bf371d45 JA |
1009 | this.write = function (data, encoding, callback) { |
1010 | ircSocket.write(data, encoding, callback); | |
1011 | }; | |
897abfc3 | 1012 | |
07e97a5a | 1013 | this.end = function (data, encoding, callback) { |
d62bbe08 | 1014 | that.connected = false; |
07e97a5a JA |
1015 | ircSocket.end(data, encoding, callback); |
1016 | }; | |
1017 | ||
1018 | this.destroySoon = function () { | |
1019 | ircSocket.destroySoon(); | |
1020 | }; | |
897abfc3 | 1021 | |
55c7f2af | 1022 | bound_events = kiwi.bindIRCCommands(this, websocket); |
f52d8543 JA |
1023 | |
1024 | this.rebindIRCCommands = function () { | |
55c7f2af | 1025 | _.each(bound_events, function (event) { |
f52d8543 JA |
1026 | that.removeAllListeners(event); |
1027 | }); | |
55c7f2af | 1028 | bound_events = kiwi.bindIRCCommands(that, websocket); |
f52d8543 | 1029 | }; |
fd779420 D |
1030 | }; |
1031 | ||
1032 | ||
1033 | ||
1034 | this.websocketMessage = function (websocket, msg, callback) { | |
312d2d7c | 1035 | var args, obj, channels, keys; |
fd779420 D |
1036 | try { |
1037 | msg.data = JSON.parse(msg.data); | |
1038 | args = msg.data.args; | |
1039 | switch (msg.data.method) { | |
312d2d7c | 1040 | case 'privmsg': |
fd779420 D |
1041 | if ((args.target) && (args.msg)) { |
1042 | obj = kiwi.kiwi_mod.run('msgsend', args, {websocket: websocket}); | |
1043 | if (obj !== null) { | |
1044 | websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg); | |
1045 | } | |
1046 | } | |
1047 | break; | |
312d2d7c JA |
1048 | case 'ctcp': |
1049 | if ((args.target) && (args.type)) { | |
1050 | if (args.request) { | |
1051 | websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + args.type.toUpperCase() + ' ' + args.params + String.fromCharCode(1)); | |
1052 | } else { | |
1053 | websocket.sendServerLine('NOTICE ' + args.target + ' :' + String.fromCharCode(1) + args.type.toUpperCase() + ' ' + args.params + String.fromCharCode(1)); | |
1054 | } | |
abb46d2d D |
1055 | } |
1056 | break; | |
1057 | ||
fd779420 D |
1058 | case 'raw': |
1059 | websocket.sendServerLine(args.data); | |
1060 | break; | |
312d2d7c | 1061 | |
fd779420 | 1062 | case 'join': |
312d2d7c JA |
1063 | if (args.channel) { |
1064 | channels = args.channel.split(","); | |
1065 | keys = (args.key) ? args.key.split(",") : []; | |
1066 | _.each(channels, function (chan, index) { | |
1067 | websocket.sendServerLine('JOIN ' + chan + ' ' + (keys[index] || '')); | |
1068 | }); | |
1069 | } | |
1070 | break; | |
1071 | ||
1072 | case 'part': | |
fd779420 D |
1073 | if (args.channel) { |
1074 | _.each(args.channel.split(","), function (chan) { | |
312d2d7c | 1075 | websocket.sendServerLine('PART ' + chan); |
fd779420 D |
1076 | }); |
1077 | } | |
1078 | break; | |
312d2d7c | 1079 | |
fd779420 D |
1080 | case 'topic': |
1081 | if (args.channel) { | |
312d2d7c JA |
1082 | if (args.topic) { |
1083 | websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic); | |
1084 | } else { | |
1085 | websocket.sendServerLine('TOPIC ' + args.channel); | |
1086 | } | |
fd779420 D |
1087 | } |
1088 | break; | |
312d2d7c JA |
1089 | |
1090 | case 'kick': | |
1091 | if ((args.channel) && (args.nick)) { | |
1092 | websocket.sendServerLine('KICK ' + args.channel + ' ' + args.nick + ':' + args.reason); | |
fd779420 D |
1093 | } |
1094 | break; | |
312d2d7c | 1095 | |
fd779420 | 1096 | case 'quit': |
07e97a5a | 1097 | websocket.ircConnection.end('QUIT :' + args.message + '\r\n'); |
fd779420 | 1098 | websocket.sentQUIT = true; |
07e97a5a | 1099 | websocket.ircConnection.destroySoon(); |
fd779420 D |
1100 | websocket.disconnect(); |
1101 | break; | |
312d2d7c | 1102 | |
fd779420 D |
1103 | case 'notice': |
1104 | if ((args.target) && (args.msg)) { | |
1105 | websocket.sendServerLine('NOTICE ' + args.target + ' :' + args.msg); | |
1106 | } | |
1107 | break; | |
312d2d7c JA |
1108 | |
1109 | case 'mode': | |
1110 | if ((args.target) && (args.mode)) { | |
1111 | websocket.sendServerLine('MODE ' + args.target + ' ' + args.mode + ' ' + args.params); | |
1112 | } | |
1113 | break; | |
1114 | ||
1115 | case 'nick': | |
1116 | if (args.nick) { | |
1117 | websocket.sendServerLine('NICK ' + args.nick); | |
1118 | } | |
1119 | break; | |
1120 | ||
5db5955b JA |
1121 | case 'kiwi': |
1122 | if ((args.target) && (args.data)) { | |
1123 | websocket.sendServerLine('PRIVMSG ' + args.target + ': ' + String.fromCharCode(1) + 'KIWI ' + args.data + String.fromCharCode(1)); | |
1124 | } | |
1125 | break; | |
fd779420 D |
1126 | default: |
1127 | } | |
1128 | if ((callback) && (typeof (callback) === 'function')) { | |
1129 | callback(); | |
1130 | } | |
1131 | } catch (e) { | |
9970e1d7 | 1132 | kiwi.log("Caught error: " + e); |
fd779420 D |
1133 | } |
1134 | }; | |
1135 | ||
1136 | ||
1137 | ||
1138 | this.websocketDisconnect = function (websocket) { | |
1fce4b40 | 1139 | var con; |
fd779420 | 1140 | |
d62bbe08 | 1141 | if ((!websocket.sentQUIT) && (websocket.ircConnection.connected)) { |
fd779420 | 1142 | try { |
d62bbe08 | 1143 | websocket.ircConnection.end('QUIT :' + kiwi.config.quit_message + '\r\n'); |
fd779420 | 1144 | websocket.sentQUIT = true; |
d62bbe08 | 1145 | websocket.ircConnection.destroySoon(); |
fd779420 D |
1146 | } catch (e) { |
1147 | } | |
1148 | } | |
1149 | con = kiwi.connections[websocket.kiwi.address]; | |
1150 | con.count -= 1; | |
1151 | con.sockets = _.reject(con.sockets, function (sock) { | |
1152 | return sock === websocket; | |
1153 | }); | |
1154 | }; | |
1155 | ||
1156 | ||
1157 | ||
1158 | ||
1159 | ||
1160 | ||
1161 | this.rehash = function () { | |
1162 | var changes, i, | |
1163 | reload_config = kiwi.loadConfig(); | |
1164 | ||
1165 | // If loading the new config errored out, dont attempt any changes | |
1166 | if (reload_config === false) { | |
1167 | return false; | |
1168 | } | |
8343584e | 1169 | |
fd779420 D |
1170 | // We just want the settings that have been changed |
1171 | changes = reload_config[1]; | |
1172 | ||
1173 | if (Object.keys(changes).length !== 0) { | |
9970e1d7 | 1174 | kiwi.log('%s config changes: \n', Object.keys(changes).length, changes); |
fd779420 D |
1175 | for (i in changes) { |
1176 | switch (i) { | |
9f6954dc | 1177 | case 'ports': |
fd779420 | 1178 | case 'bind_address': |
fd779420 D |
1179 | case 'ssl_key': |
1180 | case 'ssl_cert': | |
d423ee18 | 1181 | kiwi.websocketListen(kiwi.config.servers, kiwi.httpHandler); |
9f6954dc | 1182 | delete changes.ports; |
fd779420 | 1183 | delete changes.bind_address; |
fd779420 D |
1184 | delete changes.ssl_key; |
1185 | delete changes.ssl_cert; | |
1186 | break; | |
1187 | case 'user': | |
1188 | case 'group': | |
1189 | kiwi.changeUser(); | |
1190 | delete changes.user; | |
1191 | delete changes.group; | |
1192 | break; | |
1193 | case 'module_dir': | |
1194 | case 'modules': | |
1195 | kiwi.kiwi_mod.loadModules(kiwi_root, kiwi.config); | |
1196 | kiwi.kiwi_mod.printMods(); | |
1197 | delete changes.module_dir; | |
1198 | delete changes.modules; | |
1199 | break; | |
1200 | } | |
1201 | } | |
1202 | } | |
1203 | ||
1204 | // Also clear the kiwi.cached javascript and HTML | |
1205 | if (kiwi.config.handle_http) { | |
1206 | kiwi.cache = {alljs: '', html: []}; | |
1207 | } | |
1208 | ||
1209 | return true; | |
1210 | }; | |
1211 | ||
1212 | ||
1213 | ||
1214 | ||
1215 | ||
1216 | /* | |
1217 | * KiwiIRC controlling via STDIN | |
1218 | */ | |
87a6abbe | 1219 | this.manageControll = function (data) { |
2fc64ec2 JA |
1220 | var parts = data.toString().trim().split(' '), |
1221 | connections_cnt = 0, | |
1222 | i; | |
87a6abbe D |
1223 | switch (parts[0]) { |
1224 | case 'rehash': | |
9970e1d7 D |
1225 | kiwi.log('Rehashing...'); |
1226 | kiwi.log(kiwi.rehash() ? 'Rehash complete' : 'Rehash failed'); | |
87a6abbe D |
1227 | break; |
1228 | ||
1fce4b40 | 1229 | case 'recode': |
9970e1d7 D |
1230 | kiwi.log('Recoding...'); |
1231 | kiwi.log(kiwi.recode() ? 'Recode complete' : 'Recode failed'); | |
87a6abbe D |
1232 | break; |
1233 | ||
1234 | case 'mod': | |
1235 | if (parts[1] === 'reload') { | |
9970e1d7 D |
1236 | if (!parts[2]) { |
1237 | kiwi.log('Usage: mod reload module_name'); | |
1238 | return; | |
1239 | } | |
1240 | ||
1241 | kiwi.log('Reloading module (' + parts[2] + ')..'); | |
87a6abbe D |
1242 | kiwi.kiwi_mod.reloadModule(parts[2]); |
1243 | } | |
1244 | break; | |
4f06269b | 1245 | |
87a6abbe D |
1246 | case 'cache': |
1247 | if (parts[1] === 'clear') { | |
1248 | kiwi.cache.html = {}; | |
604c5174 | 1249 | kiwi.cache.alljs = ''; |
9970e1d7 | 1250 | kiwi.log('HTML cache cleared'); |
87a6abbe D |
1251 | } |
1252 | break; | |
1253 | ||
1254 | case 'status': | |
2fc64ec2 | 1255 | for (i in kiwi.connections) { |
87a6abbe D |
1256 | connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10); |
1257 | } | |
9970e1d7 | 1258 | kiwi.log(connections_cnt.toString() + ' connected clients'); |
87a6abbe | 1259 | break; |
c89b9fdf | 1260 | |
87a6abbe | 1261 | default: |
9970e1d7 | 1262 | kiwi.log('Unknown command \'' + parts[0] + '\''); |
87a6abbe | 1263 | } |
2fc64ec2 | 1264 | }; |