channel:join + channel:leave plugin events
[KiwiIRC.git] / server / kiwi.js
CommitLineData
a8bf3ea4 1var fs = require('fs'),
f9ff7686 2 _ = require('lodash'),
2aeb721f 3 util = require('util'),
94b00b2d 4 winston = require('winston'),
1360a454 5 WebListener = require('./weblistener.js'),
1286229a 6 config = require('./configuration.js'),
adefb6bd 7 modules = require('./modules.js'),
4eba7612 8 Identd = require('./identd.js'),
9556a028 9 Proxy = require('./proxy.js'),
4eba7612 10 ControlInterface = require('./controlinterface.js');
fd779420 11
186531ed
D
12
13
11dbb00f 14process.chdir(__dirname + '/../');
da0ec4f7 15
9b95f5f1
D
16// Get our own version from package.json
17global.build_version = require('../package.json').version;
18
3a1649c5
JA
19// Load the configuration
20require('./helpers/configloader.js')();
11dbb00f
D
21
22
23// If we're not running in the forground and we have a log file.. switch
24// console.log to output to a file
38ef7086 25if (process.argv.indexOf('-f') === -1 && global.config && global.config.log) {
11dbb00f 26 (function () {
b737610b 27 var log_file_name = global.config.log;
11dbb00f
D
28
29 if (log_file_name[0] !== '/') {
30 log_file_name = __dirname + '/../' + log_file_name;
31 }
32
61428716
D
33 winston.add(winston.transports.File, {
34 filename: log_file_name,
35 json: false,
36 timestamp: function() {
37 var year, month, day, time_string,
38 d = new Date();
39
40 year = String(d.getFullYear());
41 month = String(d.getMonth() + 1);
42 if (month.length === 1) {
43 month = "0" + month;
44 }
45
46 day = String(d.getDate());
47 if (day.length === 1) {
48 day = "0" + day;
49 }
50
51 // Take the time from the existing toTimeString() format
52 time_string = (new Date()).toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1");
53
54 return year + "-" + month + "-" + day + ' ' + time_string;
55 }
56 });
57
94b00b2d 58 winston.remove(winston.transports.Console);
11dbb00f
D
59 })();
60}
4a30a583 61
ab15f618 62
ab15f618 63
186531ed 64// Make sure we have a valid config file and at least 1 server
1f49a029 65if (!global.config || Object.keys(global.config).length === 0) {
94b00b2d 66 winston.error('Couldn\'t find a valid config.js file (Did you copy the config.example.js file yet?)');
a8bf3ea4 67 process.exit(1);
fd779420
D
68}
69
b737610b 70if ((!global.config.servers) || (global.config.servers.length < 1)) {
94b00b2d 71 winston.error('No servers defined in config file');
a8bf3ea4 72 process.exit(2);
fd779420
D
73}
74
75
186531ed
D
76
77
1920a38e 78// Create a plugin interface
ae64bf07 79global.modules = new modules.Publisher();
1920a38e 80
874f28a5 81// Register as the active interface
ae64bf07 82modules.registerPublisher(global.modules);
1920a38e 83
991091b7 84// Load any modules in the config
d8002ae0
D
85if (global.config.module_dir) {
86 (global.config.modules || []).forEach(function (module_name) {
5a18896d 87 if (modules.load(module_name)) {
94b00b2d 88 winston.info('Module %s loaded successfully', module_name);
d8002ae0 89 } else {
94b00b2d 90 winston.warn('Module %s failed to load', module_name);
d8002ae0
D
91 }
92 });
93}
1920a38e
D
94
95
96
186531ed 97
186531ed 98// Holder for all the connected clients
26322e8f
D
99global.clients = {
100 clients: Object.create(null),
c36ed4eb 101 addresses: Object.create(null),
26322e8f 102
cc3c5d04
D
103 // Local and foriegn port pairs for identd lookups
104 // {'65483_6667': client_obj, '54356_6697': client_obj}
105 port_pairs: {},
106
c36ed4eb
JA
107 add: function (client) {
108 this.clients[client.hash] = client;
109 if (typeof this.addresses[client.real_address] === 'undefined') {
110 this.addresses[client.real_address] = Object.create(null);
111 }
112 this.addresses[client.real_address][client.hash] = client;
113 },
26322e8f 114
c36ed4eb
JA
115 remove: function (client) {
116 if (typeof this.clients[client.hash] !== 'undefined') {
117 delete this.clients[client.hash];
118 delete this.addresses[client.real_address][client.hash];
119 if (Object.keys(this.addresses[client.real_address]).length < 1) {
120 delete this.addresses[client.real_address];
121 }
122 }
123 },
26322e8f 124
c36ed4eb
JA
125 numOnAddress: function (addr) {
126 if (typeof this.addresses[addr] !== 'undefined') {
127 return Object.keys(this.addresses[addr]).length;
128 } else {
129 return 0;
130 }
09c26937
D
131 },
132
133 broadcastKiwiCommand: function (command, data, callback) {
134 var clients = [];
135
136 // Get an array of clients for us to work with
137 for (var client in global.clients.clients) {
138 clients.push(global.clients.clients[client]);
139 }
140
141
142 // Sending of the command in batches
143 var sendCommandBatch = function (list) {
144 var batch_size = 100,
145 cutoff;
146
147 if (list.length >= batch_size) {
148 // If we have more clients than our batch size, call ourself with the next batch
149 setTimeout(function () {
150 sendCommandBatch(list.slice(batch_size));
151 }, 200);
152
153 cutoff = batch_size;
154
155 } else {
156 cutoff = list.length;
157 }
158
159 list.slice(0, cutoff).forEach(function (client) {
160 if (!client.disposed) {
161 client.sendKiwiCommand(command, data);
162 }
163 });
164
165 if (cutoff === list.length && typeof callback === 'function') {
166 callback();
167 }
168 };
169
170 sendCommandBatch(clients);
c36ed4eb
JA
171 }
172};
186531ed 173
8838bdd6
JA
174global.servers = {
175 servers: Object.create(null),
09c26937 176
8838bdd6
JA
177 addConnection: function (connection) {
178 var host = connection.irc_host.hostname;
179 if (!this.servers[host]) {
180 this.servers[host] = [];
181 }
182 this.servers[host].push(connection);
183 },
09c26937 184
8838bdd6
JA
185 removeConnection: function (connection) {
186 var host = connection.irc_host.hostname
187 if (this.servers[host]) {
188 this.servers[host] = _.without(this.servers[host], connection);
189 if (this.servers[host].length === 0) {
190 delete this.servers[host];
191 }
192 }
193 },
09c26937 194
8838bdd6
JA
195 numOnHost: function (host) {
196 if (this.servers[host]) {
197 return this.servers[host].length;
198 } else {
199 return 0;
200 }
201 }
202};
203
26322e8f
D
204
205
09c26937
D
206/**
207 * When a new config is loaded, send out an alert to the clients so
208 * so they can reload it
209 */
645fe41b 210config.on('loaded', function () {
09c26937 211 global.clients.broadcastKiwiCommand('reconfig');
645fe41b
JA
212});
213
214
26322e8f 215
adefb6bd
D
216/*
217 * Identd server
218 */
219if (global.config.identd && global.config.identd.enabled) {
cc3c5d04
D
220 var identd_resolve_user = function(port_here, port_there) {
221 var key = port_here.toString() + '_' + port_there.toString();
222
223 if (typeof global.clients.port_pairs[key] == 'undefined') {
224 return;
225 }
226
227 return global.clients.port_pairs[key].username;
228 };
229
230 var identd_server = new Identd({
adefb6bd 231 bind_addr: global.config.identd.address,
cc3c5d04
D
232 bind_port: global.config.identd.port,
233 user_id: identd_resolve_user
234 });
235
236 identd_server.start();
adefb6bd
D
237}
238
239
240
241
26322e8f
D
242/*
243 * Web listeners
244 */
245
246
186531ed 247// Start up a weblistener for each found in the config
b737610b 248_.each(global.config.servers, function (server) {
9556a028
D
249 if (server.type == 'proxy') {
250 // Start up a kiwi proxy server
251 var serv = new Proxy.ProxyServer();
585d3189 252 serv.listen(server.port, server.address, server);
9556a028
D
253
254 serv.on('listening', function() {
94b00b2d 255 winston.info('Kiwi proxy listening on %s:%s %s SSL', server.address, server.port, (server.ssl ? 'with' : 'without'));
9556a028
D
256 });
257
96ecb5e7 258 serv.on('socket_connected', function(pipe) {
7cc8c4ad
D
259 // SSL connections have the raw socket as a property
260 var socket = pipe.irc_socket.socket ?
261 pipe.irc_socket.socket :
262 pipe.irc_socket;
263
264 pipe.identd_pair = socket.localPort.toString() + '_' + socket.remotePort.toString();
9556a028
D
265 global.clients.port_pairs[pipe.identd_pair] = pipe.meta;
266 });
267
268 serv.on('connection_close', function(pipe) {
9556a028
D
269 delete global.clients.port_pairs[pipe.identd_pair];
270 });
271
272 } else {
273 // Start up a kiwi web server
274 var wl = new WebListener(server, global.config.transports);
275
276 wl.on('connection', function (client) {
277 clients.add(client);
278 });
279
280 wl.on('client_dispose', function (client) {
281 clients.remove(client);
282 });
283
284 wl.on('listening', function () {
94b00b2d 285 winston.info('Listening on %s:%s %s SSL', server.address, server.port, (server.ssl ? 'with' : 'without'));
9556a028
D
286 webListenerRunning();
287 });
288
289 wl.on('error', function (err) {
94b00b2d 290 winston.info('Error listening on %s:%s: %s', server.address, server.port, err.code);
9556a028
D
291 // TODO: This should probably be refactored. ^JA
292 webListenerRunning();
293 });
294 }
a8bf3ea4 295});
68ad40c6 296
e380bc9e
D
297// Once all the listeners are listening, set the processes UID/GID
298var num_listening = 0;
299function webListenerRunning() {
300 num_listening++;
301 if (num_listening === global.config.servers.length) {
302 setProcessUid();
303 }
304}
b0ad9f0a 305
f52d8543 306
186531ed
D
307
308
309/*
310 * Process settings
311 */
312
313// Set process title
314process.title = 'kiwiirc';
315
316// Change UID/GID
e380bc9e
D
317function setProcessUid() {
318 if ((global.config.group) && (global.config.group !== '')) {
319 process.setgid(global.config.group);
320 }
321 if ((global.config.user) && (global.config.user !== '')) {
322 process.setuid(global.config.user);
323 }
fd779420 324}
709031df 325
186531ed 326
0bb21ab3
D
327// Make sure Kiwi doesn't simply quit on an exception
328process.on('uncaughtException', function (e) {
94b00b2d 329 winston.error('[Uncaught exception] %s', e, {stack: e.stack});
0bb21ab3
D
330});
331
332
88f14637
D
333process.on('SIGUSR1', function() {
334 if (config.loadConfig()) {
94b00b2d 335 winston.info('New config file loaded');
88f14637 336 } else {
94b00b2d 337 winston.info('No new config file was loaded');
88f14637
D
338 }
339});
340
341
5658034d 342process.on('SIGUSR2', function() {
94b00b2d
JA
343 winston.info('Connected clients: %s', _.size(global.clients.clients));
344 winston.info('Num. remote hosts: %s', _.size(global.clients.addresses));
5658034d 345});
88f14637 346
186531ed
D
347
348/*
349 * Listen for runtime commands
350 */
87a6abbe 351process.stdin.resume();
4eba7612 352new ControlInterface(process.stdin, process.stdout, {prompt: ''});