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