1 var fs
= require('fs'),
3 util
= require('util'),
4 winston
= require('winston'),
5 WebListener
= require('./weblistener.js'),
6 config
= require('./configuration.js'),
7 modules
= require('./modules.js'),
8 Identd
= require('./identd.js'),
9 Proxy
= require('./proxy.js'),
10 ControlInterface
= require('./controlinterface.js');
14 process
.chdir(__dirname
+ '/../');
16 // Get our own version from package.json
17 global
.build_version
= require('../package.json').version
;
19 // Load the configuration
20 require('./helpers/configloader.js')();
23 // If we're not running in the forground and we have a log file.. switch
24 // console.log to output to a file
25 if (process
.argv
.indexOf('-f') === -1 && global
.config
&& global
.config
.log
) {
27 var log_file_name
= global
.config
.log
;
29 if (log_file_name
[0] !== '/') {
30 log_file_name
= __dirname
+ '/../' + log_file_name
;
33 winston
.add(winston
.transports
.File
, {
34 filename
: log_file_name
,
36 timestamp: function() {
37 var year
, month
, day
, time_string
,
40 year
= String(d
.getFullYear());
41 month
= String(d
.getMonth() + 1);
42 if (month
.length
=== 1) {
46 day
= String(d
.getDate());
47 if (day
.length
=== 1) {
51 // Take the time from the existing toTimeString() format
52 time_string
= (new Date()).toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1");
54 return year
+ "-" + month
+ "-" + day
+ ' ' + time_string
;
58 winston
.remove(winston
.transports
.Console
);
64 // Make sure we have a valid config file and at least 1 server
65 if (!global
.config
|| Object
.keys(global
.config
).length
=== 0) {
66 winston
.error('Couldn\'t find a valid config.js file (Did you copy the config.example.js file yet?)');
70 if ((!global
.config
.servers
) || (global
.config
.servers
.length
< 1)) {
71 winston
.error('No servers defined in config file');
78 // Create a plugin interface
79 global
.modules
= new modules
.Publisher();
81 // Register as the active interface
82 modules
.registerPublisher(global
.modules
);
84 // Load any modules in the config
85 if (global
.config
.module_dir
) {
86 (global
.config
.modules
|| []).forEach(function (module_name
) {
87 if (modules
.load(module_name
)) {
88 winston
.info('Module %s loaded successfully', module_name
);
90 winston
.warn('Module %s failed to load', module_name
);
98 // Holder for all the connected clients
100 clients
: Object
.create(null),
101 addresses
: Object
.create(null),
103 // Local and foriegn port pairs for identd lookups
104 // {'65483_6667': client_obj, '54356_6697': client_obj}
107 add: function (client
) {
108 this.clients
[client
.hash
] = client
;
109 if (typeof this.addresses
[client
.real_address
] === 'undefined') {
110 this.addresses
[client
.real_address
] = Object
.create(null);
112 this.addresses
[client
.real_address
][client
.hash
] = client
;
115 remove: function (client
) {
116 if (typeof this.clients
[client
.hash
] !== 'undefined') {
117 delete this.clients
[client
.hash
];
118 delete this.addresses
[client
.real_address
][client
.hash
];
119 if (Object
.keys(this.addresses
[client
.real_address
]).length
< 1) {
120 delete this.addresses
[client
.real_address
];
125 numOnAddress: function (addr
) {
126 if (typeof this.addresses
[addr
] !== 'undefined') {
127 return Object
.keys(this.addresses
[addr
]).length
;
133 broadcastKiwiCommand: function (command
, data
, callback
) {
136 // Get an array of clients for us to work with
137 for (var client
in global
.clients
.clients
) {
138 clients
.push(global
.clients
.clients
[client
]);
142 // Sending of the command in batches
143 var sendCommandBatch = function (list
) {
144 var batch_size
= 100,
147 if (list
.length
>= batch_size
) {
148 // If we have more clients than our batch size, call ourself with the next batch
149 setTimeout(function () {
150 sendCommandBatch(list
.slice(batch_size
));
156 cutoff
= list
.length
;
159 list
.slice(0, cutoff
).forEach(function (client
) {
160 if (!client
.disposed
) {
161 client
.sendKiwiCommand(command
, data
);
165 if (cutoff
=== list
.length
&& typeof callback
=== 'function') {
170 sendCommandBatch(clients
);
175 servers
: Object
.create(null),
177 addConnection: function (connection
) {
178 var host
= connection
.irc_host
.hostname
;
179 if (!this.servers
[host
]) {
180 this.servers
[host
] = [];
182 this.servers
[host
].push(connection
);
185 removeConnection: function (connection
) {
186 var host
= connection
.irc_host
.hostname
187 if (this.servers
[host
]) {
188 this.servers
[host
] = _
.without(this.servers
[host
], connection
);
189 if (this.servers
[host
].length
=== 0) {
190 delete this.servers
[host
];
195 numOnHost: function (host
) {
196 if (this.servers
[host
]) {
197 return this.servers
[host
].length
;
207 * When a new config is loaded, send out an alert to the clients so
208 * so they can reload it
210 config
.on('loaded', function () {
211 global
.clients
.broadcastKiwiCommand('reconfig');
219 if (global
.config
.identd
&& global
.config
.identd
.enabled
) {
220 var identd_resolve_user = function(port_here
, port_there
) {
221 var key
= port_here
.toString() + '_' + port_there
.toString();
223 if (typeof global
.clients
.port_pairs
[key
] == 'undefined') {
227 return global
.clients
.port_pairs
[key
].username
;
230 var identd_server
= new Identd({
231 bind_addr
: global
.config
.identd
.address
,
232 bind_port
: global
.config
.identd
.port
,
233 user_id
: identd_resolve_user
236 identd_server
.start();
247 // Start up a weblistener for each found in the config
248 _
.each(global
.config
.servers
, function (server
) {
249 if (server
.type
== 'proxy') {
250 // Start up a kiwi proxy server
251 var serv
= new Proxy
.ProxyServer();
252 serv
.listen(server
.port
, server
.address
, server
);
254 serv
.on('listening', function() {
255 winston
.info('Kiwi proxy listening on %s:%s %s SSL', server
.address
, server
.port
, (server
.ssl
? 'with' : 'without'));
258 serv
.on('socket_connected', function(pipe
) {
259 // SSL connections have the raw socket as a property
260 var socket
= pipe
.irc_socket
.socket
?
261 pipe
.irc_socket
.socket
:
264 pipe
.identd_pair
= socket
.localPort
.toString() + '_' + socket
.remotePort
.toString();
265 global
.clients
.port_pairs
[pipe
.identd_pair
] = pipe
.meta
;
268 serv
.on('connection_close', function(pipe
) {
269 delete global
.clients
.port_pairs
[pipe
.identd_pair
];
273 // Start up a kiwi web server
274 var wl
= new WebListener(server
, global
.config
.transports
);
276 wl
.on('connection', function (client
) {
280 wl
.on('client_dispose', function (client
) {
281 clients
.remove(client
);
284 wl
.on('listening', function () {
285 winston
.info('Listening on %s:%s %s SSL', server
.address
, server
.port
, (server
.ssl
? 'with' : 'without'));
286 webListenerRunning();
289 wl
.on('error', function (err
) {
290 winston
.info('Error listening on %s:%s: %s', server
.address
, server
.port
, err
.code
);
291 // TODO: This should probably be refactored. ^JA
292 webListenerRunning();
297 // Once all the listeners are listening, set the processes UID/GID
298 var num_listening
= 0;
299 function webListenerRunning() {
301 if (num_listening
=== global
.config
.servers
.length
) {
314 process
.title
= 'kiwiirc';
317 function setProcessUid() {
318 if ((global
.config
.group
) && (global
.config
.group
!== '')) {
319 process
.setgid(global
.config
.group
);
321 if ((global
.config
.user
) && (global
.config
.user
!== '')) {
322 process
.setuid(global
.config
.user
);
327 // Make sure Kiwi doesn't simply quit on an exception
328 process
.on('uncaughtException', function (e
) {
329 winston
.error('[Uncaught exception] %s', e
, {stack
: e
.stack
});
333 process
.on('SIGUSR1', function() {
334 if (config
.loadConfig()) {
335 winston
.info('New config file loaded');
337 winston
.info('No new config file was loaded');
342 process
.on('SIGUSR2', function() {
343 winston
.info('Connected clients: %s', _
.size(global
.clients
.clients
));
344 winston
.info('Num. remote hosts: %s', _
.size(global
.clients
.addresses
));
349 * Listen for runtime commands
351 process
.stdin
.resume();
352 new ControlInterface(process
.stdin
, process
.stdout
, {prompt
: ''});