Log client startup errors to console
[KiwiIRC.git] / server / weblistener.js
1 var engine = require('engine.io'),
2 WebsocketRpc = require('./websocketrpc.js'),
3 events = require('events'),
4 http = require('http'),
5 https = require('https'),
6 util = require('util'),
7 fs = require('fs'),
8 dns = require('dns'),
9 url = require('url'),
10 _ = require('lodash'),
11 spdy = require('spdy'),
12 ipaddr = require('ipaddr.js'),
13 winston = require('winston'),
14 Client = require('./client.js').Client,
15 HttpHandler = require('./httphandler.js').HttpHandler,
16 rehash = require('./rehash.js'),
17 Stats = require('./stats.js');
18
19
20
21 rehash.on('rehashed', function (files) {
22 Client = require('./client.js').Client;
23 HttpHandler = require('./httphandler.js').HttpHandler;
24 });
25
26
27 // Instance of HttpHandler
28 var http_handler;
29
30
31 var WebListener = module.exports = function (web_config) {
32 var hs, opts,
33 that = this;
34
35
36 events.EventEmitter.call(this);
37
38 http_handler = new HttpHandler(web_config);
39
40 if (web_config.ssl) {
41 opts = {
42 key: fs.readFileSync(web_config.ssl_key),
43 cert: fs.readFileSync(web_config.ssl_cert)
44 };
45
46 // Do we have an intermediate certificate?
47 if (typeof web_config.ssl_ca !== 'undefined') {
48 // An array of them?
49 if (typeof web_config.ssl_ca.map !== 'undefined') {
50 opts.ca = web_config.ssl_ca.map(function (f) { return fs.readFileSync(f); });
51
52 } else {
53 opts.ca = fs.readFileSync(web_config.ssl_ca);
54 }
55 }
56
57 hs = spdy.createServer(opts);
58
59 hs.listen(web_config.port, web_config.address, function () {
60 that.emit('listening');
61 });
62 } else {
63
64 // Start some plain-text server up
65 hs = http.createServer();
66
67 hs.listen(web_config.port, web_config.address, function () {
68 that.emit('listening');
69 });
70 }
71
72 hs.on('error', function (err) {
73 that.emit('error', err);
74 });
75
76 this.ws = new engine.Server();
77
78 hs.on('upgrade', function(req, socket, head){
79 // engine.io can sometimes "loose" the clients remote address. Keep note of it
80 req.meta = {
81 remote_address: req.connection.remoteAddress
82 };
83
84 that.ws.handleUpgrade(req, socket, head);
85 });
86
87 hs.on('request', function(req, res){
88 var base_path = (global.config.http_base_path || ''),
89 transport_url;
90
91 // Trim off any trailing slashes
92 if (base_path.substr(base_path.length - 1) === '/') {
93 base_path = base_path.substr(0, base_path.length - 1);
94 }
95 transport_url = base_path + '/transport';
96
97 Stats.incr('http.request');
98
99 // engine.io can sometimes "loose" the clients remote address. Keep note of it
100 req.meta = {
101 remote_address: req.connection.remoteAddress
102 };
103
104 // If the request is for our transport, pass it onto engine.io
105 if (req.url.toLowerCase().indexOf(transport_url.toLowerCase()) === 0) {
106 that.ws.handleRequest(req, res);
107 } else {
108 http_handler.serve(req, res);
109 }
110
111
112 });
113
114 this.ws.on('connection', function(socket) {
115 Stats.incr('http.websocket');
116
117 initialiseSocket(socket, function(err, authorised) {
118 var client;
119
120 if (!authorised) {
121 socket.close();
122 return;
123 }
124
125 client = new Client(socket, {server_config: web_config});
126 client.on('dispose', function () {
127 that.emit('client_dispose', this);
128 });
129
130 that.emit('connection', client);
131
132 // Call any modules listening for new clients
133 global.modules.emit('client created', {client: client});
134 });
135 });
136 };
137 util.inherits(WebListener, events.EventEmitter);
138
139
140
141 function rangeCheck(addr, range) {
142 var i, ranges, parts;
143 ranges = (!_.isArray(range)) ? [range] : range;
144 for (i = 0; i < ranges.length; i++) {
145 parts = ranges[i].split('/');
146 if (ipaddr.process(addr).match(ipaddr.process(parts[0]), parts[1])) {
147 return true;
148 }
149 }
150 return false;
151 }
152
153
154 /**
155 * Get the reverse DNS entry for this connection.
156 * Used later on for webirc, etc functionality
157 */
158 function initialiseSocket(socket, callback) {
159 var request = socket.request,
160 address = request.meta.remote_address,
161 revdns;
162
163 // Key/val data stored to the socket to be read later on
164 // May also be synced to a redis DB to lookup clients
165 socket.meta = socket.request.meta;
166
167 // If a forwarded-for header is found, switch the source address
168 if (request.headers[global.config.http_proxy_ip_header || 'x-forwarded-for']) {
169 // Check we're connecting from a whitelisted proxy
170 if (!global.config.http_proxies || !rangeCheck(address, global.config.http_proxies)) {
171 winston.info('Unlisted proxy: %s', address);
172 callback(null, false);
173 return;
174 }
175
176 // We're sent from a whitelisted proxy, replace the hosts
177 address = request.headers[global.config.http_proxy_ip_header || 'x-forwarded-for'];
178 }
179
180 socket.meta.real_address = address;
181
182 // If enabled, don't go over the connection limit
183 if (global.config.max_client_conns && global.config.max_client_conns > 0) {
184 if (global.clients.numOnAddress(address) + 1 > global.config.max_client_conns) {
185 return callback(null, false);
186 }
187 }
188
189
190 try {
191 dns.reverse(address, function (err, domains) {
192 if (!err && domains.length > 0) {
193 revdns = _.first(domains);
194 }
195
196 if (!revdns) {
197 // No reverse DNS found, use the IP
198 socket.meta.revdns = address;
199 callback(null, true);
200
201 } else {
202 // Make sure the reverse DNS matches the A record to use the hostname..
203 dns.lookup(revdns, function (err, ip_address, family) {
204 if (!err && ip_address == address) {
205 // A record matches PTR, perfectly valid hostname
206 socket.meta.revdns = revdns;
207 } else {
208 // A record does not match the PTR, invalid hostname
209 socket.meta.revdns = address;
210 }
211
212 // We have all the info we need, proceed with the connection
213 callback(null, true);
214 });
215 }
216 });
217
218 } catch (err) {
219 socket.meta.revdns = address;
220 callback(null, true);
221 }
222 }