SSL support on the proxy
[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 modules = require('./modules.js'),
7 Identd = require('./identd.js'),
8 Proxy = require('./proxy.js'),
9 ControlInterface = require('./controlinterface.js');
10
11
12
13 process.chdir(__dirname + '/../');
14 config.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
19 if (process.argv.indexOf('-f') === -1 && global.config && global.config.log) {
20 (function () {
21 var log_file_name = global.config.log;
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
33 out = util.format.apply(util, arguments);
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 }
45
46
47
48 // Make sure we have a valid config file and at least 1 server
49 if (!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?)');
51 process.exit(1);
52 }
53
54 if ((!global.config.servers) || (global.config.servers.length < 1)) {
55 console.log('No servers defined in config file');
56 process.exit(2);
57 }
58
59
60
61
62 // Create a plugin interface
63 global.modules = new modules.Publisher();
64
65 // Register as the active interface
66 modules.registerPublisher(global.modules);
67
68 // Load any modules in the config
69 if (global.config.module_dir) {
70 (global.config.modules || []).forEach(function (module_name) {
71 if (modules.load(module_name)) {
72 console.log('Module ' + module_name + ' loaded successfuly');
73 } else {
74 console.log('Module ' + module_name + ' failed to load');
75 }
76 });
77 }
78
79
80
81
82 // Holder for all the connected clients
83 global.clients = {
84 clients: Object.create(null),
85 addresses: Object.create(null),
86
87 // Local and foriegn port pairs for identd lookups
88 // {'65483_6667': client_obj, '54356_6697': client_obj}
89 port_pairs: {},
90
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 },
98
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 },
108
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 }
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);
155 }
156 };
157
158 global.servers = {
159 servers: Object.create(null),
160
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 },
168
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 },
178
179 numOnHost: function (host) {
180 if (this.servers[host]) {
181 return this.servers[host].length;
182 } else {
183 return 0;
184 }
185 }
186 };
187
188
189
190 /**
191 * When a new config is loaded, send out an alert to the clients so
192 * so they can reload it
193 */
194 config.on('loaded', function () {
195 global.clients.broadcastKiwiCommand('reconfig');
196 });
197
198
199
200 /*
201 * Identd server
202 */
203 if (global.config.identd && global.config.identd.enabled) {
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({
215 bind_addr: global.config.identd.address,
216 bind_port: global.config.identd.port,
217 user_id: identd_resolve_user
218 });
219
220 identd_server.start();
221 }
222
223
224
225
226 /*
227 * Web listeners
228 */
229
230
231 // Start up a weblistener for each found in the config
232 _.each(global.config.servers, function (server) {
233 if (server.type == 'proxy') {
234 // Start up a kiwi proxy server
235 var serv = new Proxy.ProxyServer();
236 serv.listen(server.port, server.address, server);
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
242 serv.on('connection_open', function(pipe) {
243 pipe.identd_pair = pipe.irc_socket.localPort.toString() + '_' + pipe.irc_socket.remotePort.toString();
244 console.log('[IDENTD] opened ' + pipe.identd_pair);
245 global.clients.port_pairs[pipe.identd_pair] = pipe.meta;
246 });
247
248 serv.on('connection_close', function(pipe) {
249 console.log('[IDENTD] closed ' + pipe.identd_pair);
250 delete global.clients.port_pairs[pipe.identd_pair];
251 });
252
253 } else {
254 // Start up a kiwi web server
255 var wl = new WebListener(server, global.config.transports);
256
257 wl.on('connection', function (client) {
258 clients.add(client);
259 });
260
261 wl.on('client_dispose', function (client) {
262 clients.remove(client);
263 });
264
265 wl.on('listening', function () {
266 console.log('Listening on %s:%s %s SSL', server.address, server.port, (server.ssl ? 'with' : 'without'));
267 webListenerRunning();
268 });
269
270 wl.on('error', function (err) {
271 console.log('Error listening on %s:%s: %s', server.address, server.port, err.code);
272 // TODO: This should probably be refactored. ^JA
273 webListenerRunning();
274 });
275 }
276 });
277
278 // Once all the listeners are listening, set the processes UID/GID
279 var num_listening = 0;
280 function webListenerRunning() {
281 num_listening++;
282 if (num_listening === global.config.servers.length) {
283 setProcessUid();
284 }
285 }
286
287
288
289
290 /*
291 * Process settings
292 */
293
294 // Set process title
295 process.title = 'kiwiirc';
296
297 // Change UID/GID
298 function setProcessUid() {
299 if ((global.config.group) && (global.config.group !== '')) {
300 process.setgid(global.config.group);
301 }
302 if ((global.config.user) && (global.config.user !== '')) {
303 process.setuid(global.config.user);
304 }
305 }
306
307
308 // Make sure Kiwi doesn't simply quit on an exception
309 process.on('uncaughtException', function (e) {
310 console.log('[Uncaught exception] ' + e);
311 console.log(e.stack);
312 });
313
314
315 process.on('SIGUSR1', function() {
316 if (config.loadConfig()) {
317 console.log('New config file loaded');
318 } else {
319 console.log("No new config file was loaded");
320 }
321 });
322
323
324 process.on('SIGUSR2', function() {
325 console.log('Connected clients: ' + _.size(global.clients.clients).toString());
326 console.log('Num. remote hosts: ' + _.size(global.clients.addresses).toString());
327 });
328
329
330 /*
331 * Listen for runtime commands
332 */
333 process.stdin.resume();
334 new ControlInterface(process.stdin, process.stdout, {prompt: ''});