Batch clients into sets of 100 for reconfig command
[KiwiIRC.git] / server / kiwi.js
1 var fs = require('fs'),
2 _ = require('lodash'),
3 util = require('util'),
4 WebListener = require('./weblistener.js'),
5 config = require('./configuration.js'),
6 rehash = require('./rehash.js'),
7 modules = require('./modules.js'),
8 Identd = require('./identd.js');
9
10
11
12 process.chdir(__dirname + '/../');
13 config.loadConfig();
14
15
16 // If we're not running in the forground and we have a log file.. switch
17 // console.log to output to a file
18 if (process.argv.indexOf('-f') === -1 && global.config && global.config.log) {
19 (function () {
20 var log_file_name = global.config.log;
21
22 if (log_file_name[0] !== '/') {
23 log_file_name = __dirname + '/../' + log_file_name;
24 }
25
26
27
28 console.log = function() {
29 var logfile = fs.openSync(log_file_name, 'a'),
30 out;
31
32 out = util.format.apply(util, arguments);
33
34 // Make sure we out somthing to log and we have an open file
35 if (!out || !logfile) return;
36
37 out += '\n';
38 fs.writeSync(logfile, out, null);
39
40 fs.closeSync(logfile);
41 };
42 })();
43 }
44
45
46
47 // Make sure we have a valid config file and at least 1 server
48 if (!global.config || Object.keys(global.config).length === 0) {
49 console.log('Couldn\'t find a valid config.js file (Did you copy the config.example.js file yet?)');
50 process.exit(1);
51 }
52
53 if ((!global.config.servers) || (global.config.servers.length < 1)) {
54 console.log('No servers defined in config file');
55 process.exit(2);
56 }
57
58
59
60
61 // Create a plugin interface
62 global.modules = new modules.Publisher();
63
64 // Register as the active interface
65 modules.registerPublisher(global.modules);
66
67 // Load any modules in the config
68 if (global.config.module_dir) {
69 (global.config.modules || []).forEach(function (module_name) {
70 if (modules.load(module_name)) {
71 console.log('Module ' + module_name + ' loaded successfuly');
72 } else {
73 console.log('Module ' + module_name + ' failed to load');
74 }
75 });
76 }
77
78
79
80
81 // Holder for all the connected clients
82 global.clients = {
83 clients: Object.create(null),
84 addresses: Object.create(null),
85
86 // Local and foriegn port pairs for identd lookups
87 // {'65483_6667': client_obj, '54356_6697': client_obj}
88 port_pairs: {},
89
90 add: function (client) {
91 this.clients[client.hash] = client;
92 if (typeof this.addresses[client.real_address] === 'undefined') {
93 this.addresses[client.real_address] = Object.create(null);
94 }
95 this.addresses[client.real_address][client.hash] = client;
96 },
97
98 remove: function (client) {
99 if (typeof this.clients[client.hash] !== 'undefined') {
100 delete this.clients[client.hash];
101 delete this.addresses[client.real_address][client.hash];
102 if (Object.keys(this.addresses[client.real_address]).length < 1) {
103 delete this.addresses[client.real_address];
104 }
105 }
106 },
107
108 numOnAddress: function (addr) {
109 if (typeof this.addresses[addr] !== 'undefined') {
110 return Object.keys(this.addresses[addr]).length;
111 } else {
112 return 0;
113 }
114 }
115 };
116
117 global.servers = {
118 servers: Object.create(null),
119
120 addConnection: function (connection) {
121 var host = connection.irc_host.hostname;
122 if (!this.servers[host]) {
123 this.servers[host] = [];
124 }
125 this.servers[host].push(connection);
126 },
127
128 removeConnection: function (connection) {
129 var host = connection.irc_host.hostname
130 if (this.servers[host]) {
131 this.servers[host] = _.without(this.servers[host], connection);
132 if (this.servers[host].length === 0) {
133 delete this.servers[host];
134 }
135 }
136 },
137
138 numOnHost: function (host) {
139 if (this.servers[host]) {
140 return this.servers[host].length;
141 } else {
142 return 0;
143 }
144 }
145 };
146
147
148
149 config.on('loaded', function () {
150 var clients = [];
151 for (var client in global.clients.clients) {
152 clients.push(global.clients.clients[client]);
153 }
154 var sendReconfigCommand = function (list) {
155 var cutoff;
156 if (list.length >= 100) {
157 setTimeout(function () {
158 sendReconfigCommand(list.slice(100));
159 }, 200);
160 cutoff = 100;
161 } else {
162 cutoff = list.length;
163 }
164 list.slice(0, cutoff).forEach(function (client) {
165 if (!client.disposed) {
166 client.sendKiwiCommand('reconfig');
167 }
168 });
169 };
170
171 sendReconfigCommand(clients);
172 });
173
174
175
176 /*
177 * Identd server
178 */
179 if (global.config.identd && global.config.identd.enabled) {
180 var identd_resolve_user = function(port_here, port_there) {
181 var key = port_here.toString() + '_' + port_there.toString();
182
183 if (typeof global.clients.port_pairs[key] == 'undefined') {
184 return;
185 }
186
187 return global.clients.port_pairs[key].username;
188 };
189
190 var identd_server = new Identd({
191 bind_addr: global.config.identd.address,
192 bind_port: global.config.identd.port,
193 user_id: identd_resolve_user
194 });
195
196 identd_server.start();
197 }
198
199
200
201
202 /*
203 * Web listeners
204 */
205
206
207 // Start up a weblistener for each found in the config
208 _.each(global.config.servers, function (server) {
209 var wl = new WebListener(server, global.config.transports);
210
211 wl.on('connection', function (client) {
212 clients.add(client);
213 });
214
215 wl.on('client_dispose', function (client) {
216 clients.remove(client);
217 });
218
219 wl.on('listening', function () {
220 console.log('Listening on %s:%s %s SSL', server.address, server.port, (server.ssl ? 'with' : 'without'));
221 webListenerRunning();
222 });
223
224 wl.on('error', function (err) {
225 console.log('Error listening on %s:%s: %s', server.address, server.port, err.code);
226 // TODO: This should probably be refactored. ^JA
227 webListenerRunning();
228 });
229 });
230
231 // Once all the listeners are listening, set the processes UID/GID
232 var num_listening = 0;
233 function webListenerRunning() {
234 num_listening++;
235 if (num_listening === global.config.servers.length) {
236 setProcessUid();
237 }
238 }
239
240
241
242
243 /*
244 * Process settings
245 */
246
247 // Set process title
248 process.title = 'kiwiirc';
249
250 // Change UID/GID
251 function setProcessUid() {
252 if ((global.config.group) && (global.config.group !== '')) {
253 process.setgid(global.config.group);
254 }
255 if ((global.config.user) && (global.config.user !== '')) {
256 process.setuid(global.config.user);
257 }
258 }
259
260
261 // Make sure Kiwi doesn't simply quit on an exception
262 process.on('uncaughtException', function (e) {
263 console.log('[Uncaught exception] ' + e);
264 console.log(e.stack);
265 });
266
267
268 process.on('SIGUSR1', function() {
269 if (config.loadConfig()) {
270 console.log('New config file loaded');
271 } else {
272 console.log("No new config file was loaded");
273 }
274 });
275
276
277 process.on('SIGUSR2', function() {
278 console.log('Connected clients: ' + _.size(global.clients.clients).toString());
279 console.log('Num. remote hosts: ' + _.size(global.clients.addresses).toString());
280 });
281
282
283 /*
284 * Listen for runtime commands
285 */
286
287 process.stdin.resume();
288 process.stdin.on('data', function (buffered) {
289 var data = buffered.toString().trim();
290
291 switch (data) {
292 case 'stats':
293 console.log('Connected clients: ' + _.size(global.clients.clients).toString());
294 console.log('Num. remote hosts: ' + _.size(global.clients.addresses).toString());
295 break;
296
297 case 'reconfig':
298 if (config.loadConfig()) {
299 console.log('New config file loaded');
300 } else {
301 console.log("No new config file was loaded");
302 }
303
304 break;
305
306
307 case 'rehash':
308 (function () {
309 rehash.rehashAll();
310 console.log('Rehashed');
311 })();
312
313 break;
314
315 default:
316 console.log('Unrecognised command: ' + data);
317 }
318 });