X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=server%2Fweblistener.js;h=0a61821585829a21eeff35c9eefc73863eac893a;hb=a885d1c9104a578a6667b230841e3466abd53fdb;hp=3ab24370abd52d5318a0927ad4cfca5b38bfa550;hpb=64dae534bf6e67818b8666ac85b71af0762af1e0;p=KiwiIRC.git diff --git a/server/weblistener.js b/server/weblistener.js index 3ab2437..0a61821 100644 --- a/server/weblistener.js +++ b/server/weblistener.js @@ -1,16 +1,20 @@ -var ws = require('socket.io'), - events = require('events'), - http = require('http'), - https = require('https'), - util = require('util'), - fs = require('fs'), - dns = require('dns'), - url = require('url'), - _ = require('lodash'), - Client = require('./client.js').Client, - HttpHandler = require('./httphandler.js').HttpHandler, - rehash = require('./rehash.js'), - range_check = require('range_check'); +var engine = require('engine.io'), + WebsocketRpc = require('./websocketrpc.js'), + events = require('events'), + http = require('http'), + https = require('https'), + util = require('util'), + fs = require('fs'), + dns = require('dns'), + url = require('url'), + _ = require('lodash'), + spdy = require('spdy'), + ipaddr = require('ipaddr.js'), + winston = require('winston'), + Client = require('./client.js').Client, + HttpHandler = require('./httphandler.js').HttpHandler, + rehash = require('./rehash.js'), + Stats = require('./stats.js'); @@ -24,80 +28,126 @@ rehash.on('rehashed', function (files) { var http_handler; -var WebListener = function (web_config, transports) { - var hs, opts, ws_opts, +var WebListener = module.exports = function (web_config) { + var hs, opts, that = this; events.EventEmitter.call(this); - - http_handler = new HttpHandler(web_config); - - // Standard options for the socket.io connections - ws_opts = { - 'log level': 0, - 'log colors': 0 - }; + http_handler = new HttpHandler(web_config); if (web_config.ssl) { opts = { - key: fs.readFileSync(__dirname + '/' + web_config.ssl_key), - cert: fs.readFileSync(__dirname + '/' + web_config.ssl_cert) + key: fs.readFileSync(web_config.ssl_key), + cert: fs.readFileSync(web_config.ssl_cert) }; // Do we have an intermediate certificate? if (typeof web_config.ssl_ca !== 'undefined') { - opts.ca = fs.readFileSync(__dirname + '/' + web_config.ssl_ca); + // An array of them? + if (typeof web_config.ssl_ca.map !== 'undefined') { + opts.ca = web_config.ssl_ca.map(function (f) { return fs.readFileSync(f); }); + + } else { + opts.ca = fs.readFileSync(web_config.ssl_ca); + } } + hs = spdy.createServer(opts); - hs = https.createServer(opts, handleHttpRequest); - - // Start socket.io listening on this weblistener - this.ws = ws.listen(hs, _.extend({ssl: true}, ws_opts)); hs.listen(web_config.port, web_config.address, function () { that.emit('listening'); }); - - console.log('Listening on ' + web_config.address + ':' + web_config.port.toString() + ' with SSL'); } else { // Start some plain-text server up - hs = http.createServer(handleHttpRequest); + hs = http.createServer(); - // Start socket.io listening on this weblistener - this.ws = ws.listen(hs, _.extend({ssl: false}, ws_opts)); hs.listen(web_config.port, web_config.address, function () { that.emit('listening'); }); - - console.log('Listening on ' + web_config.address + ':' + web_config.port.toString() + ' without SSL'); } - - this.ws.enable('browser client minification'); - this.ws.enable('browser client etag'); - this.ws.set('transports', transports); - this.ws.set('resource', (global.config.http_base_path || '') + '/transport'); - - this.ws.of('/kiwi').authorization(authoriseConnection) - .on('connection', function () { - newConnection.apply(that, arguments); + + hs.on('error', function (err) { + that.emit('error', err); + }); + + this.ws = new engine.Server(); + + hs.on('upgrade', function(req, socket, head){ + // engine.io can sometimes "loose" the clients remote address. Keep note of it + req.meta = { + remote_address: req.connection.remoteAddress + }; + + that.ws.handleUpgrade(req, socket, head); + }); + + hs.on('request', function(req, res){ + var base_path = (global.config.http_base_path || ''), + transport_url; + + // Trim off any trailing slashes + if (base_path.substr(base_path.length - 1) === '/') { + base_path = base_path.substr(0, base_path.length - 1); + } + transport_url = base_path + '/transport'; + + Stats.incr('http.request'); + + // engine.io can sometimes "loose" the clients remote address. Keep note of it + req.meta = { + remote_address: req.connection.remoteAddress + }; + + // If the request is for our transport, pass it onto engine.io + if (req.url.toLowerCase().indexOf(transport_url.toLowerCase()) === 0) { + that.ws.handleRequest(req, res); + } else { + http_handler.serve(req, res); } - ); - this.ws.of('/kiwi').on('error', console.log); + + + }); + + this.ws.on('connection', function(socket) { + Stats.incr('http.websocket'); + + initialiseSocket(socket, function(err, authorised) { + var client; + + if (!authorised) { + socket.close(); + return; + } + + client = new Client(socket, {server_config: web_config}); + client.on('dispose', function () { + that.emit('client_dispose', this); + }); + + that.emit('connection', client); + + // Call any modules listening for new clients + global.modules.emit('client created', {client: client}); + }); + }); }; util.inherits(WebListener, events.EventEmitter); -function handleHttpRequest(request, response) { - var uri = url.parse(request.url, true); - - // If this isn't a socket.io request, pass it onto the http handler - if (uri.pathname.substr(0, 10) !== '/socket.io') { - http_handler.serve(request, response); +function rangeCheck(addr, range) { + var i, ranges, parts; + ranges = (!_.isArray(range)) ? [range] : range; + for (i = 0; i < ranges.length; i++) { + parts = ranges[i].split('/'); + if (ipaddr.process(addr).match(ipaddr.process(parts[0]), parts[1])) { + return true; + } } + return false; } @@ -105,54 +155,68 @@ function handleHttpRequest(request, response) { * Get the reverse DNS entry for this connection. * Used later on for webirc, etc functionality */ -function authoriseConnection(handshakeData, callback) { - var address = handshakeData.address.address; +function initialiseSocket(socket, callback) { + var request = socket.request, + address = request.meta.remote_address, + revdns; + + // Key/val data stored to the socket to be read later on + // May also be synced to a redis DB to lookup clients + socket.meta = socket.request.meta; // If a forwarded-for header is found, switch the source address - if (handshakeData.headers[global.config.http_proxy_ip_header || 'x-forwarded-for']) { + if (request.headers[global.config.http_proxy_ip_header || 'x-forwarded-for']) { // Check we're connecting from a whitelisted proxy - if (!global.config.http_proxies || !range_check.in_range(address, global.config.http_proxies)) { - console.log('Unlisted proxy:', address); + if (!global.config.http_proxies || !rangeCheck(address, global.config.http_proxies)) { + winston.info('Unlisted proxy: %s', address); callback(null, false); return; } // We're sent from a whitelisted proxy, replace the hosts - address = handshakeData.headers['x-forwarded-for']; + address = request.headers[global.config.http_proxy_ip_header || 'x-forwarded-for']; } - handshakeData.real_address = address; - + socket.meta.real_address = address; + // If enabled, don't go over the connection limit if (global.config.max_client_conns && global.config.max_client_conns > 0) { if (global.clients.numOnAddress(address) + 1 > global.config.max_client_conns) { return callback(null, false); } } - - dns.reverse(address, function (err, domains) { - if (err || domains.length === 0) { - handshakeData.revdns = address; - } else { - handshakeData.revdns = _.first(domains) || address; - } - - // All is well, authorise the connection - callback(null, true); - }); -} - -function newConnection(websocket) { - var client, that = this; - client = new Client(websocket); - client.on('destroy', function () { - that.emit('destroy', this); - }); - this.emit('connection', client); -} - - + try { + dns.reverse(address, function (err, domains) { + if (!err && domains.length > 0) { + revdns = _.first(domains); + } + + if (!revdns) { + // No reverse DNS found, use the IP + socket.meta.revdns = address; + callback(null, true); + + } else { + // Make sure the reverse DNS matches the A record to use the hostname.. + dns.lookup(revdns, function (err, ip_address, family) { + if (!err && ip_address == address) { + // A record matches PTR, perfectly valid hostname + socket.meta.revdns = revdns; + } else { + // A record does not match the PTR, invalid hostname + socket.meta.revdns = address; + } + + // We have all the info we need, proceed with the connection + callback(null, true); + }); + } + }); -module.exports = WebListener; \ No newline at end of file + } catch (err) { + socket.meta.revdns = address; + callback(null, true); + } +}