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