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