2013-12-12 23:09:49 +00:00
var sio = require ( "socket.io" ) ;
2014-08-19 05:46:30 +00:00
var cookieParser = require ( "cookie-parser" ) ( ) ;
2013-12-12 23:09:49 +00:00
var Logger = require ( "../logger" ) ;
var db = require ( "../database" ) ;
var User = require ( "../user" ) ;
var Server = require ( "../server" ) ;
2014-01-22 23:11:26 +00:00
var Config = require ( "../config" ) ;
2013-12-12 23:09:49 +00:00
var $util = require ( "../utilities" ) ;
2014-05-21 02:30:14 +00:00
var Flags = require ( "../flags" ) ;
var Account = require ( "../account" ) ;
var typecheck = require ( "json-typecheck" ) ;
var net = require ( "net" ) ;
var util = require ( "../utilities" ) ;
2014-08-15 02:42:13 +00:00
var crypto = require ( "crypto" ) ;
var isTorExit = require ( "../tor" ) . isTorExit ;
2013-12-12 23:09:49 +00:00
var CONNECT _RATE = {
burst : 5 ,
sustained : 0.1
} ;
var ipThrottle = { } ;
// Keep track of number of connections per IP
var ipCount = { } ;
/ * *
* Called before an incoming socket . io connection is accepted .
* /
2014-08-20 03:57:28 +00:00
function handleAuth ( socket , accept ) {
var data = socket . request ;
socket . user = false ;
2013-12-12 23:09:49 +00:00
if ( data . headers . cookie ) {
2014-08-19 05:46:30 +00:00
cookieParser ( data , null , function ( ) {
var auth = data . cookies . auth ;
db . users . verifyAuth ( auth , function ( err , user ) {
if ( ! err ) {
2014-08-20 03:57:28 +00:00
socket . user = {
2014-08-19 05:46:30 +00:00
name : user . name ,
global _rank : user . global _rank
} ;
}
accept ( null , true ) ;
} ) ;
2013-12-12 23:09:49 +00:00
} ) ;
} else {
accept ( null , true ) ;
}
}
2014-08-15 02:42:13 +00:00
function throttleIP ( sock ) {
var ip = sock . _realip ;
2013-12-12 23:09:49 +00:00
if ( ! ( ip in ipThrottle ) ) {
ipThrottle [ ip ] = $util . newRateLimiter ( ) ;
}
if ( ipThrottle [ ip ] . throttle ( CONNECT _RATE ) ) {
Logger . syslog . log ( "WARN: IP throttled: " + ip ) ;
sock . emit ( "kick" , {
reason : "Your IP address is connecting too quickly. Please " +
"wait 10 seconds before joining again."
} ) ;
2014-08-15 02:42:13 +00:00
return true ;
2013-12-12 23:09:49 +00:00
}
2014-08-15 02:42:13 +00:00
return false ;
}
function ipLimitReached ( sock ) {
var ip = sock . _realip ;
2013-12-12 23:09:49 +00:00
sock . on ( "disconnect" , function ( ) {
ipCount [ ip ] -- ;
2014-04-11 02:54:46 +00:00
if ( ipCount [ ip ] === 0 ) {
/* Clear out unnecessary counters to save memory */
delete ipCount [ ip ] ;
}
2013-12-12 23:09:49 +00:00
} ) ;
if ( ! ( ip in ipCount ) ) {
ipCount [ ip ] = 0 ;
}
ipCount [ ip ] ++ ;
2014-01-22 23:11:26 +00:00
if ( ipCount [ ip ] > Config . get ( "io.ip-connection-limit" ) ) {
2013-12-12 23:09:49 +00:00
sock . emit ( "kick" , {
reason : "Too many connections from your IP address"
} ) ;
sock . disconnect ( true ) ;
return ;
}
2014-08-15 02:42:13 +00:00
}
2013-12-12 23:09:49 +00:00
2014-08-15 02:42:13 +00:00
function addTypecheckedFunctions ( sock ) {
2014-05-21 02:30:14 +00:00
sock . typecheckedOn = function ( msg , template , cb ) {
sock . on ( msg , function ( data ) {
typecheck ( data , template , function ( err , data ) {
if ( err ) {
sock . emit ( "errorMsg" , {
msg : "Unexpected error for message " + msg + ": " + err . message
} ) ;
} else {
cb ( data ) ;
}
} ) ;
} ) ;
} ;
sock . typecheckedOnce = function ( msg , template , cb ) {
sock . once ( msg , function ( data ) {
typecheck ( data , template , function ( err , data ) {
if ( err ) {
sock . emit ( "errorMsg" , {
msg : "Unexpected error for message " + msg + ": " + err . message
} ) ;
} else {
cb ( data ) ;
}
} ) ;
} ) ;
} ;
2014-08-15 02:42:13 +00:00
}
/ * *
* Called after a connection is accepted
* /
function handleConnection ( sock ) {
2014-08-20 16:44:37 +00:00
var ip = sock . request . connection . remoteAddress ;
2014-08-20 17:09:38 +00:00
if ( ! ip ) {
socket . emit ( "kick" , {
reason : "Your IP address could not be determined from the socket connection. See https://github.com/Automattic/socket.io/issues/1387#issuecomment-48425088 for details"
} ) ;
return ;
}
2014-08-15 02:42:13 +00:00
if ( net . isIPv6 ( ip ) ) {
ip = util . expandIPv6 ( ip ) ;
}
sock . _realip = ip ;
sock . _displayip = $util . cloakIP ( ip ) ;
if ( isTorExit ( ip ) ) {
sock . _isUsingTor = true ;
}
var srv = Server . getServer ( ) ;
2014-08-15 03:02:58 +00:00
if ( throttleIP ( sock ) ) {
2014-08-15 02:42:13 +00:00
return ;
}
// Check for global ban on the IP
if ( db . isGlobalIPBanned ( ip ) ) {
Logger . syslog . log ( "Rejecting " + ip + " - global banned" ) ;
sock . emit ( "kick" , { reason : "Your IP is globally banned." } ) ;
sock . disconnect ( true ) ;
return ;
}
if ( ipLimitReached ( sock ) ) {
return ;
}
Logger . syslog . log ( "Accepted socket from " + ip ) ;
addTypecheckedFunctions ( sock ) ;
2014-05-21 02:30:14 +00:00
2013-12-12 23:09:49 +00:00
var user = new User ( sock ) ;
2014-08-20 03:57:28 +00:00
if ( sock . user ) {
2014-05-21 02:30:14 +00:00
user . setFlag ( Flags . U _REGISTERED ) ;
user . clearFlag ( Flags . U _READY ) ;
2014-08-20 03:57:28 +00:00
user . refreshAccount ( { name : sock . user . name } ,
2014-05-21 02:30:14 +00:00
function ( err , account ) {
if ( err ) {
user . clearFlag ( Flags . U _REGISTERED ) ;
user . setFlag ( Flags . U _READY ) ;
return ;
}
2014-08-15 02:42:13 +00:00
2014-05-21 02:30:14 +00:00
user . socket . emit ( "login" , {
success : true ,
name : user . getName ( ) ,
guest : false
} ) ;
db . recordVisit ( ip , user . getName ( ) ) ;
user . socket . emit ( "rank" , user . account . effectiveRank ) ;
user . setFlag ( Flags . U _LOGGED _IN ) ;
user . emit ( "login" , account ) ;
Logger . syslog . log ( ip + " logged in as " + user . getName ( ) ) ;
user . setFlag ( Flags . U _READY ) ;
2013-12-25 21:18:21 +00:00
} ) ;
2014-01-19 02:18:00 +00:00
} else {
user . socket . emit ( "rank" , - 1 ) ;
2014-05-21 02:30:14 +00:00
user . setFlag ( Flags . U _READY ) ;
2013-12-12 23:09:49 +00:00
}
}
module . exports = {
init : function ( srv ) {
2014-08-20 03:57:28 +00:00
var bound = { } ;
var io = sio . instance = sio ( ) ;
io . use ( handleAuth ) ;
io . on ( "connection" , handleConnection ) ;
2014-04-11 05:14:52 +00:00
Config . get ( "listen" ) . forEach ( function ( bind ) {
if ( ! bind . io ) {
return ;
}
var id = bind . ip + ":" + bind . port ;
2014-08-20 03:57:28 +00:00
if ( id in bound ) {
2014-04-11 05:14:52 +00:00
Logger . syslog . log ( "[WARN] Ignoring duplicate listen address " + id ) ;
return ;
}
2013-12-12 23:09:49 +00:00
2014-04-11 05:14:52 +00:00
if ( id in srv . servers ) {
2014-08-20 03:57:28 +00:00
io . attach ( srv . servers [ id ] ) ;
2014-04-11 05:14:52 +00:00
} else {
2014-08-20 03:57:28 +00:00
io . attach ( require ( "http" ) . createServer ( ) . listen ( bind . port , bind . ip ) ) ;
2014-04-11 05:14:52 +00:00
}
2014-05-21 02:30:14 +00:00
2014-08-20 03:57:28 +00:00
bound [ id ] = null ;
2014-04-11 05:14:52 +00:00
} ) ;
2013-12-12 23:09:49 +00:00
}
} ;
2014-04-11 02:54:46 +00:00
/* Clean out old rate limiters */
setInterval ( function ( ) {
for ( var ip in ipThrottle ) {
if ( ipThrottle [ ip ] . lastTime < Date . now ( ) - 60 * 1000 ) {
var obj = ipThrottle [ ip ] ;
/* Not strictly necessary, but seems to help the GC out a bit */
for ( var key in obj ) {
delete obj [ key ] ;
}
delete ipThrottle [ ip ] ;
}
}
if ( Config . get ( "aggressive-gc" ) && global && global . gc ) {
global . gc ( ) ;
}
} , 5 * 60 * 1000 ) ;