Server: Splitting State / IrcConnection / IrcCommands better
[KiwiIRC.git] / server / weblistener.js
1 var ws = require('socket.io'),
2 events = require('events'),
3 http = require('http'),
4 https = require('https'),
5 util = require('util'),
6 fs = require('fs'),
7 dns = require('dns'),
8 url = require('url'),
9 _ = require('lodash'),
10 spdy = require('spdy'),
11 Client = require('./client.js').Client,
12 HttpHandler = require('./httphandler.js').HttpHandler,
13 rehash = require('./rehash.js'),
14 range_check = require('range_check');
15
16
17
18 rehash.on('rehashed', function (files) {
19 Client = require('./client.js').Client;
20 HttpHandler = require('./httphandler.js').HttpHandler;
21 });
22
23
24 // Instance of HttpHandler
25 var http_handler;
26
27
28 var WebListener = function (web_config, transports) {
29 var hs, opts, ws_opts,
30 that = this;
31
32
33 events.EventEmitter.call(this);
34
35 http_handler = new HttpHandler(web_config);
36
37 // Standard options for the socket.io connections
38 ws_opts = {
39 'log level': 0,
40 'log colors': 0
41 };
42
43
44 if (web_config.ssl) {
45 opts = {
46 key: fs.readFileSync(web_config.ssl_key),
47 cert: fs.readFileSync(web_config.ssl_cert)
48 };
49
50 // Do we have an intermediate certificate?
51 if (typeof web_config.ssl_ca !== 'undefined') {
52 // An array of them?
53 if (typeof web_config.ssl_ca.map !== 'undefined') {
54 opts.ca = web_config.ssl_ca.map(function (f) { return fs.readFileSync(f); });
55
56 } else {
57 opts.ca = fs.readFileSync(web_config.ssl_ca);
58 }
59 }
60
61 hs = spdy.createServer(opts, handleHttpRequest);
62
63 // Start socket.io listening on this weblistener
64 this.ws = ws.listen(hs, _.extend({ssl: true}, ws_opts));
65 hs.listen(web_config.port, web_config.address, function () {
66 that.emit('listening');
67 });
68 } else {
69
70 // Start some plain-text server up
71 hs = http.createServer(handleHttpRequest);
72
73 // Start socket.io listening on this weblistener
74 this.ws = ws.listen(hs, _.extend({ssl: false}, ws_opts));
75 hs.listen(web_config.port, web_config.address, function () {
76 that.emit('listening');
77 });
78 }
79
80 hs.on('error', function (err) {
81 that.emit('error', err);
82 });
83
84 this.ws.enable('browser client minification');
85 this.ws.enable('browser client etag');
86 this.ws.set('transports', transports);
87 this.ws.set('resource', (global.config.http_base_path || '') + '/transport');
88
89 this.ws.of('/kiwi').authorization(authoriseConnection)
90 .on('connection', function () {
91 newConnection.apply(that, arguments);
92 }
93 );
94 this.ws.of('/kiwi').on('error', console.log);
95 };
96 util.inherits(WebListener, events.EventEmitter);
97
98
99
100 function handleHttpRequest(request, response) {
101 var uri = url.parse(request.url, true);
102
103 // If this isn't a socket.io request, pass it onto the http handler
104 if (uri.pathname.substr(0, 10) !== '/socket.io') {
105 http_handler.serve(request, response);
106 }
107 }
108
109
110 /**
111 * Get the reverse DNS entry for this connection.
112 * Used later on for webirc, etc functionality
113 */
114 function authoriseConnection(handshakeData, callback) {
115 var address = handshakeData.address.address;
116
117 // If a forwarded-for header is found, switch the source address
118 if (handshakeData.headers[global.config.http_proxy_ip_header || 'x-forwarded-for']) {
119 // Check we're connecting from a whitelisted proxy
120 if (!global.config.http_proxies || !range_check.in_range(address, global.config.http_proxies)) {
121 console.log('Unlisted proxy:', address);
122 callback(null, false);
123 return;
124 }
125
126 // We're sent from a whitelisted proxy, replace the hosts
127 address = handshakeData.headers['x-forwarded-for'];
128 }
129
130 handshakeData.real_address = address;
131
132 // If enabled, don't go over the connection limit
133 if (global.config.max_client_conns && global.config.max_client_conns > 0) {
134 if (global.clients.numOnAddress(address) + 1 > global.config.max_client_conns) {
135 return callback(null, false);
136 }
137 }
138
139
140 try {
141 dns.reverse(address, function (err, domains) {
142 if (err || domains.length === 0) {
143 handshakeData.revdns = address;
144 } else {
145 handshakeData.revdns = _.first(domains) || address;
146 }
147
148 // All is well, authorise the connection
149 callback(null, true);
150 });
151 } catch (err) {
152 handshakeData.revdns = address;
153 callback(null, true);
154 }
155 }
156
157 function newConnection(websocket) {
158 var client, that = this;
159 client = new Client(websocket);
160 client.on('dispose', function () {
161 that.emit('client_dispose', this);
162 });
163 this.emit('connection', client);
164 }
165
166
167
168
169
170 module.exports = WebListener;