Server module hooks for IRC events
[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
D
7 Identd = require('./identd.js'),
8 ControlInterface = require('./controlinterface.js');
fd779420 9
186531ed
D
10
11
11dbb00f
D
12process.chdir(__dirname + '/../');
13config.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
38ef7086 18if (process.argv.indexOf('-f') === -1 && global.config && global.config.log) {
11dbb00f 19 (function () {
b737610b 20 var log_file_name = global.config.log;
11dbb00f
D
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
2aeb721f 32 out = util.format.apply(util, arguments);
11dbb00f
D
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}
4a30a583 44
ab15f618 45
ab15f618 46
186531ed 47// Make sure we have a valid config file and at least 1 server
1f49a029
D
48if (!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?)');
a8bf3ea4 50 process.exit(1);
fd779420
D
51}
52
b737610b 53if ((!global.config.servers) || (global.config.servers.length < 1)) {
a8bf3ea4
JA
54 console.log('No servers defined in config file');
55 process.exit(2);
fd779420
D
56}
57
58
186531ed
D
59
60
1920a38e 61// Create a plugin interface
ae64bf07 62global.modules = new modules.Publisher();
1920a38e 63
874f28a5 64// Register as the active interface
ae64bf07 65modules.registerPublisher(global.modules);
1920a38e 66
991091b7 67// Load any modules in the config
d8002ae0
D
68if (global.config.module_dir) {
69 (global.config.modules || []).forEach(function (module_name) {
5a18896d 70 if (modules.load(module_name)) {
d8002ae0
D
71 console.log('Module ' + module_name + ' loaded successfuly');
72 } else {
73 console.log('Module ' + module_name + ' failed to load');
74 }
75 });
76}
1920a38e
D
77
78
79
186531ed 80
186531ed 81// Holder for all the connected clients
26322e8f
D
82global.clients = {
83 clients: Object.create(null),
c36ed4eb 84 addresses: Object.create(null),
26322e8f 85
cc3c5d04
D
86 // Local and foriegn port pairs for identd lookups
87 // {'65483_6667': client_obj, '54356_6697': client_obj}
88 port_pairs: {},
89
c36ed4eb
JA
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 },
26322e8f 97
c36ed4eb
JA
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 },
26322e8f 107
c36ed4eb
JA
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 }
09c26937
D
114 },
115
116 broadcastKiwiCommand: function (command, data, callback) {
117 var clients = [];
118
119 // Get an array of clients for us to work with
120 for (var client in global.clients.clients) {
121 clients.push(global.clients.clients[client]);
122 }
123
124
125 // Sending of the command in batches
126 var sendCommandBatch = function (list) {
127 var batch_size = 100,
128 cutoff;
129
130 if (list.length >= batch_size) {
131 // If we have more clients than our batch size, call ourself with the next batch
132 setTimeout(function () {
133 sendCommandBatch(list.slice(batch_size));
134 }, 200);
135
136 cutoff = batch_size;
137
138 } else {
139 cutoff = list.length;
140 }
141
142 list.slice(0, cutoff).forEach(function (client) {
143 if (!client.disposed) {
144 client.sendKiwiCommand(command, data);
145 }
146 });
147
148 if (cutoff === list.length && typeof callback === 'function') {
149 callback();
150 }
151 };
152
153 sendCommandBatch(clients);
c36ed4eb
JA
154 }
155};
186531ed 156
8838bdd6
JA
157global.servers = {
158 servers: Object.create(null),
09c26937 159
8838bdd6
JA
160 addConnection: function (connection) {
161 var host = connection.irc_host.hostname;
162 if (!this.servers[host]) {
163 this.servers[host] = [];
164 }
165 this.servers[host].push(connection);
166 },
09c26937 167
8838bdd6
JA
168 removeConnection: function (connection) {
169 var host = connection.irc_host.hostname
170 if (this.servers[host]) {
171 this.servers[host] = _.without(this.servers[host], connection);
172 if (this.servers[host].length === 0) {
173 delete this.servers[host];
174 }
175 }
176 },
09c26937 177
8838bdd6
JA
178 numOnHost: function (host) {
179 if (this.servers[host]) {
180 return this.servers[host].length;
181 } else {
182 return 0;
183 }
184 }
185};
186
26322e8f
D
187
188
09c26937
D
189/**
190 * When a new config is loaded, send out an alert to the clients so
191 * so they can reload it
192 */
645fe41b 193config.on('loaded', function () {
09c26937 194 global.clients.broadcastKiwiCommand('reconfig');
645fe41b
JA
195});
196
197
26322e8f 198
adefb6bd
D
199/*
200 * Identd server
201 */
202if (global.config.identd && global.config.identd.enabled) {
cc3c5d04
D
203 var identd_resolve_user = function(port_here, port_there) {
204 var key = port_here.toString() + '_' + port_there.toString();
205
206 if (typeof global.clients.port_pairs[key] == 'undefined') {
207 return;
208 }
209
210 return global.clients.port_pairs[key].username;
211 };
212
213 var identd_server = new Identd({
adefb6bd 214 bind_addr: global.config.identd.address,
cc3c5d04
D
215 bind_port: global.config.identd.port,
216 user_id: identd_resolve_user
217 });
218
219 identd_server.start();
adefb6bd
D
220}
221
222
223
224
26322e8f
D
225/*
226 * Web listeners
227 */
228
229
186531ed 230// Start up a weblistener for each found in the config
b737610b
JA
231_.each(global.config.servers, function (server) {
232 var wl = new WebListener(server, global.config.transports);
170a3d85 233
a8bf3ea4 234 wl.on('connection', function (client) {
c36ed4eb 235 clients.add(client);
a8bf3ea4 236 });
26322e8f 237
57566ca2 238 wl.on('client_dispose', function (client) {
c36ed4eb 239 clients.remove(client);
a8bf3ea4 240 });
e380bc9e 241
d558508c
JA
242 wl.on('listening', function () {
243 console.log('Listening on %s:%s %s SSL', server.address, server.port, (server.ssl ? 'with' : 'without'));
244 webListenerRunning();
245 });
246
247 wl.on('error', function (err) {
248 console.log('Error listening on %s:%s: %s', server.address, server.port, err.code);
249 // TODO: This should probably be refactored. ^JA
250 webListenerRunning();
251 });
a8bf3ea4 252});
68ad40c6 253
e380bc9e
D
254// Once all the listeners are listening, set the processes UID/GID
255var num_listening = 0;
256function webListenerRunning() {
257 num_listening++;
258 if (num_listening === global.config.servers.length) {
259 setProcessUid();
260 }
261}
b0ad9f0a 262
f52d8543 263
186531ed
D
264
265
266/*
267 * Process settings
268 */
269
270// Set process title
271process.title = 'kiwiirc';
272
273// Change UID/GID
e380bc9e
D
274function setProcessUid() {
275 if ((global.config.group) && (global.config.group !== '')) {
276 process.setgid(global.config.group);
277 }
278 if ((global.config.user) && (global.config.user !== '')) {
279 process.setuid(global.config.user);
280 }
fd779420 281}
709031df 282
186531ed 283
0bb21ab3
D
284// Make sure Kiwi doesn't simply quit on an exception
285process.on('uncaughtException', function (e) {
286 console.log('[Uncaught exception] ' + e);
caff44fe 287 console.log(e.stack);
0bb21ab3
D
288});
289
290
88f14637
D
291process.on('SIGUSR1', function() {
292 if (config.loadConfig()) {
293 console.log('New config file loaded');
294 } else {
295 console.log("No new config file was loaded");
296 }
297});
298
299
5658034d
D
300process.on('SIGUSR2', function() {
301 console.log('Connected clients: ' + _.size(global.clients.clients).toString());
302 console.log('Num. remote hosts: ' + _.size(global.clients.addresses).toString());
303});
88f14637 304
186531ed
D
305
306/*
307 * Listen for runtime commands
308 */
87a6abbe 309process.stdin.resume();
4eba7612 310new ControlInterface(process.stdin, process.stdout, {prompt: ''});