Merge branch 'development'
[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('lodash');
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 if (this.password) {
109 this.write('PASS ' + this.password);
110 }
111 this.write('NICK ' + this.nick);
112 this.write('USER ' + this.username + ' 0 0 :' + '[www.kiwiirc.com] ' + this.nick);
113
114 this.connected = true;
115 this.emit('connected');
116 };
117
118
119 function findWebIrc(connect_data) {
120 var webirc_pass = global.config.webirc_pass;
121 var ip_as_username = global.config.ip_as_username;
122 var tmp;
123
124 // Do we have a WEBIRC password for this?
125 if (webirc_pass && webirc_pass[connect_data.irc_host.hostname]) {
126 tmp = 'WEBIRC ' + webirc_pass[connect_data.irc_host.hostname] + ' KiwiIRC ';
127 tmp += connect_data.user.hostname + ' ' + connect_data.user.address;
128 connect_data.prepend_data = [tmp];
129 }
130
131
132 // Check if we need to pass the users IP as its username/ident
133 if (ip_as_username && ip_as_username.indexOf(connect_data.irc_host.hostname) > -1) {
134 // Get a hex value of the clients IP
135 this.username = connect_data.user.address.split('.').map(function(i, idx){
136 return parseInt(i, 10).toString(16);
137 }).join('');
138
139 }
140
141 return connect_data;
142 }
143
144
145
146 parse_regex = /^(?:(?:(?:(@[^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
147 var parse = function (data) {
148 var i,
149 msg,
150 msg2,
151 trm,
152 j,
153 tags = [],
154 tag;
155
156 if ((this.hold_last) && (this.held_data !== '')) {
157 data = this.held_data + data;
158 this.hold_last = false;
159 this.held_data = '';
160 }
161 if (data.substr(-1) !== '\n') {
162 this.hold_last = true;
163 }
164 data = data.split("\n");
165 for (i = 0; i < data.length; i++) {
166 if (data[i]) {
167 if ((this.hold_last) && (i === data.length - 1)) {
168 this.held_data = data[i];
169 break;
170 }
171
172 // We have a complete line of data, parse it!
173 msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, ''));
174 if (msg) {
175 if (msg[1]) {
176 tags = msg[1].split(';');
177 for (j = 0; j < tags.length; j++) {
178 tag = tags[j].split('=');
179 tags[j] = {tag: tag[0], value: tag[1]};
180 }
181 }
182 msg = {
183 tags: tags,
184 prefix: msg[2],
185 nick: msg[3],
186 ident: msg[4],
187 hostname: msg[5] || '',
188 command: msg[6],
189 params: msg[7] || '',
190 trailing: (msg[8]) ? msg[8].trim() : ''
191 };
192 msg.params = msg.params.split(' ');
193
194 this.emit('irc_' + msg.command.toUpperCase(), msg);
195 } else {
196 console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, ''));
197 }
198 }
199 }
200 };