2013-12-12 17:09:49 -06:00
var sio = require ( "socket.io" ) ;
2014-08-19 00:46:30 -05:00
var cookieParser = require ( "cookie-parser" ) ( ) ;
2013-12-12 17:09:49 -06:00
var Logger = require ( "../logger" ) ;
var db = require ( "../database" ) ;
var User = require ( "../user" ) ;
var Server = require ( "../server" ) ;
2014-01-22 17:11:26 -06:00
var Config = require ( "../config" ) ;
2013-12-12 17:09:49 -06:00
var $util = require ( "../utilities" ) ;
2014-05-20 19:30:14 -07:00
var Flags = require ( "../flags" ) ;
var Account = require ( "../account" ) ;
var typecheck = require ( "json-typecheck" ) ;
var net = require ( "net" ) ;
var util = require ( "../utilities" ) ;
2014-08-14 21:42:13 -05:00
var crypto = require ( "crypto" ) ;
var isTorExit = require ( "../tor" ) . isTorExit ;
2013-12-12 17:09:49 -06: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-19 22:57:28 -05:00
function handleAuth ( socket , accept ) {
var data = socket . request ;
socket . user = false ;
2013-12-12 17:09:49 -06:00
if ( data . headers . cookie ) {
2014-08-19 00:46:30 -05:00
cookieParser ( data , null , function ( ) {
var auth = data . cookies . auth ;
db . users . verifyAuth ( auth , function ( err , user ) {
if ( ! err ) {
2014-08-19 22:57:28 -05:00
socket . user = {
2014-08-19 00:46:30 -05:00
name : user . name ,
global _rank : user . global _rank
} ;
}
accept ( null , true ) ;
} ) ;
2013-12-12 17:09:49 -06:00
} ) ;
} else {
accept ( null , true ) ;
}
}
2014-08-14 21:42:13 -05:00
function throttleIP ( sock ) {
var ip = sock . _realip ;
2013-12-12 17:09:49 -06: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-14 21:42:13 -05:00
return true ;
2013-12-12 17:09:49 -06:00
}
2014-08-14 21:42:13 -05:00
return false ;
}
function ipLimitReached ( sock ) {
var ip = sock . _realip ;
2013-12-12 17:09:49 -06:00
sock . on ( "disconnect" , function ( ) {
ipCount [ ip ] -- ;
2014-04-10 21:54:46 -05:00
if ( ipCount [ ip ] === 0 ) {
/* Clear out unnecessary counters to save memory */
delete ipCount [ ip ] ;
}
2013-12-12 17:09:49 -06:00
} ) ;
if ( ! ( ip in ipCount ) ) {
ipCount [ ip ] = 0 ;
}
ipCount [ ip ] ++ ;
2014-01-22 17:11:26 -06:00
if ( ipCount [ ip ] > Config . get ( "io.ip-connection-limit" ) ) {
2013-12-12 17:09:49 -06:00
sock . emit ( "kick" , {
reason : "Too many connections from your IP address"
} ) ;
sock . disconnect ( true ) ;
return ;
}
2014-08-14 21:42:13 -05:00
}
2013-12-12 17:09:49 -06:00
2014-08-14 21:42:13 -05:00
function addTypecheckedFunctions ( sock ) {
2014-05-20 19:30:14 -07: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-14 21:42:13 -05:00
}
/ * *
* Called after a connection is accepted
* /
function handleConnection ( sock ) {
2014-08-20 11:44:37 -05:00
var ip = sock . request . connection . remoteAddress ;
2014-08-20 12:09:38 -05:00
if ( ! ip ) {
2014-08-20 12:11:46 -05:00
sock . emit ( "kick" , {
2014-08-20 12:09:38 -05:00
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-14 21:42:13 -05: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-14 22:02:58 -05:00
if ( throttleIP ( sock ) ) {
2014-08-14 21:42:13 -05: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-20 19:30:14 -07:00
2013-12-12 17:09:49 -06:00
var user = new User ( sock ) ;
2014-08-19 22:57:28 -05:00
if ( sock . user ) {
2014-05-20 19:30:14 -07:00
user . setFlag ( Flags . U _REGISTERED ) ;
user . clearFlag ( Flags . U _READY ) ;
2014-08-19 22:57:28 -05:00
user . refreshAccount ( { name : sock . user . name } ,
2014-05-20 19:30:14 -07:00
function ( err , account ) {
if ( err ) {
user . clearFlag ( Flags . U _REGISTERED ) ;
user . setFlag ( Flags . U _READY ) ;
return ;
}
2014-08-14 21:42:13 -05:00
2014-05-20 19:30:14 -07: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 16:18:21 -05:00
} ) ;
2014-01-18 20:18:00 -06:00
} else {
user . socket . emit ( "rank" , - 1 ) ;
2014-05-20 19:30:14 -07:00
user . setFlag ( Flags . U _READY ) ;
2013-12-12 17:09:49 -06:00
}
}
module . exports = {
init : function ( srv ) {
2014-08-19 22:57:28 -05:00
var bound = { } ;
var io = sio . instance = sio ( ) ;
io . use ( handleAuth ) ;
io . on ( "connection" , handleConnection ) ;
2014-04-11 00:14:52 -05:00
Config . get ( "listen" ) . forEach ( function ( bind ) {
if ( ! bind . io ) {
return ;
}
var id = bind . ip + ":" + bind . port ;
2014-08-19 22:57:28 -05:00
if ( id in bound ) {
2014-04-11 00:14:52 -05:00
Logger . syslog . log ( "[WARN] Ignoring duplicate listen address " + id ) ;
return ;
}
2013-12-12 17:09:49 -06:00
2014-04-11 00:14:52 -05:00
if ( id in srv . servers ) {
2014-08-19 22:57:28 -05:00
io . attach ( srv . servers [ id ] ) ;
2014-04-11 00:14:52 -05:00
} else {
2014-08-19 22:57:28 -05:00
io . attach ( require ( "http" ) . createServer ( ) . listen ( bind . port , bind . ip ) ) ;
2014-04-11 00:14:52 -05:00
}
2014-05-20 19:30:14 -07:00
2014-08-19 22:57:28 -05:00
bound [ id ] = null ;
2014-04-11 00:14:52 -05:00
} ) ;
2013-12-12 17:09:49 -06:00
}
} ;
2014-04-10 21:54:46 -05: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 ) ;