2016-05-17 21:40:34 +00:00
import sys
2016-10-02 20:48:14 +00:00
import time
2016-05-17 21:40:34 +00:00
import traceback
2016-10-02 20:48:14 +00:00
from common . constants import privileges
from common . log import logUtils as log
from common . ripple import userUtils
from constants import exceptions
from constants import serverPackets
2016-07-14 10:37:07 +00:00
from helpers import chatHelper as chat
2016-10-02 20:48:14 +00:00
from helpers import countryHelper
from helpers import locationHelper
from objects import glob
2016-04-19 17:40:59 +00:00
2016-05-31 20:49:30 +00:00
def handle ( tornadoRequest ) :
2016-04-19 17:40:59 +00:00
# Data to return
responseTokenString = " ayy "
responseData = bytes ( )
2016-06-04 10:44:54 +00:00
# Get IP from tornado request
2016-05-31 20:49:30 +00:00
requestIP = tornadoRequest . getRequestIP ( )
2016-04-19 17:40:59 +00:00
2016-07-20 09:59:53 +00:00
# Avoid exceptions
clientData = [ " unknown " , " unknown " , " unknown " , " unknown " , " unknown " ]
osuVersion = " unknown "
2016-04-19 17:40:59 +00:00
# Split POST body so we can get username/password/hardware data
# 2:-3 thing is because requestData has some escape stuff that we don't need
2016-05-31 20:49:30 +00:00
loginData = str ( tornadoRequest . request . body ) [ 2 : - 3 ] . split ( " \\ n " )
2016-04-19 17:40:59 +00:00
try :
# If true, print error to console
err = False
2016-07-14 10:37:07 +00:00
# Make sure loginData is valid
if len ( loginData ) < 3 :
2016-09-13 09:39:39 +00:00
raise exceptions . invalidArgumentsException ( )
2016-07-14 10:37:07 +00:00
2016-07-20 09:59:53 +00:00
# Get HWID, MAC address and more
# Structure (new line = "|", already split)
# [0] osu! version
# [1] plain mac addressed, separated by "."
# [2] mac addresses hash set
# [3] unique ID
# [4] disk ID
splitData = loginData [ 2 ] . split ( " | " )
osuVersion = splitData [ 0 ]
2016-08-01 18:38:26 +00:00
timeOffset = int ( splitData [ 1 ] )
2016-07-20 09:59:53 +00:00
clientData = splitData [ 3 ] . split ( " : " ) [ : 5 ]
if len ( clientData ) < 4 :
raise exceptions . forceUpdateException ( )
2016-04-19 17:40:59 +00:00
# Try to get the ID from username
2016-07-03 18:51:19 +00:00
username = str ( loginData [ 0 ] )
2016-10-02 20:48:14 +00:00
userID = userUtils . getID ( username )
2016-04-19 17:40:59 +00:00
2016-09-02 15:45:10 +00:00
if not userID :
2016-04-19 17:40:59 +00:00
# Invalid username
raise exceptions . loginFailedException ( )
2016-10-02 20:48:14 +00:00
if not userUtils . checkLogin ( userID , loginData [ 1 ] ) :
2016-04-19 17:40:59 +00:00
# Invalid password
raise exceptions . loginFailedException ( )
2016-09-13 09:39:39 +00:00
# Make sure we are not banned or locked
2016-10-02 20:48:14 +00:00
priv = userUtils . getPrivileges ( userID )
if userUtils . isBanned ( userID ) == True and priv & privileges . USER_PENDING_VERIFICATION == 0 :
2016-04-19 17:40:59 +00:00
raise exceptions . loginBannedException ( )
2016-10-02 20:48:14 +00:00
if userUtils . isLocked ( userID ) == True and priv & privileges . USER_PENDING_VERIFICATION == 0 :
2016-09-13 09:39:39 +00:00
raise exceptions . loginLockedException ( )
2016-04-19 17:40:59 +00:00
2016-06-11 16:43:27 +00:00
# 2FA check
2016-10-02 20:48:14 +00:00
if userUtils . check2FA ( userID , requestIP ) :
2016-06-11 16:43:27 +00:00
log . warning ( " Need 2FA check for user {} " . format ( loginData [ 0 ] ) )
raise exceptions . need2FAException ( )
2016-06-10 11:15:42 +00:00
# No login errors!
2016-07-20 09:59:53 +00:00
# Verify this user (if pending activation)
firstLogin = False
2016-10-02 20:48:14 +00:00
if priv & privileges . USER_PENDING_VERIFICATION > 0 or userUtils . hasVerifiedHardware ( userID ) == False :
if userUtils . verifyUser ( userID , clientData ) :
2016-07-20 09:59:53 +00:00
# Valid account
log . info ( " Account {} verified successfully! " . format ( userID ) )
glob . verifiedCache [ str ( userID ) ] = 1
firstLogin = True
else :
# Multiaccount detected
log . info ( " Account {} NOT verified! " . format ( userID ) )
glob . verifiedCache [ str ( userID ) ] = 0
raise exceptions . loginBannedException ( )
2016-07-28 20:23:13 +00:00
# Save HWID in db for multiaccount detection
2016-10-02 20:48:14 +00:00
hwAllowed = userUtils . logHardware ( userID , clientData , firstLogin )
2016-07-28 20:23:13 +00:00
2016-07-20 09:59:53 +00:00
# This is false only if HWID is empty
# if HWID is banned, we get restricted so there's no
# need to deny bancho access
2016-09-02 15:45:10 +00:00
if not hwAllowed :
2016-07-20 09:59:53 +00:00
raise exceptions . haxException ( )
2016-05-29 11:32:11 +00:00
# Log user IP
2016-10-02 20:48:14 +00:00
userUtils . logIP ( userID , requestIP )
2016-05-29 11:32:11 +00:00
2016-04-19 17:40:59 +00:00
# Delete old tokens for that user and generate a new one
2016-10-05 21:28:26 +00:00
isTournament = " tourney " in osuVersion
if not isTournament :
glob . tokens . deleteOldTokens ( userID )
responseToken = glob . tokens . addToken ( userID , requestIP , timeOffset = timeOffset , tournament = isTournament )
2016-04-19 17:40:59 +00:00
responseTokenString = responseToken . token
2016-07-14 10:37:07 +00:00
# Check restricted mode (and eventually send message)
responseToken . checkRestricted ( )
2016-09-25 17:05:31 +00:00
# Send message if donor expires soon
if responseToken . privileges & privileges . USER_DONOR > 0 :
2016-10-02 20:48:14 +00:00
expireDate = userUtils . getDonorExpire ( responseToken . userID )
2016-09-25 17:05:31 +00:00
if expireDate - int ( time . time ( ) ) < = 86400 * 3 :
expireDays = round ( ( expireDate - int ( time . time ( ) ) ) / 86400 )
expireIn = " {} days " . format ( expireDays ) if expireDays > 1 else " less than 24 hours "
responseToken . enqueue ( serverPackets . notification ( " Your donor tag expires in {} ! When your donor tag expires, you won ' t have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don ' t want to lose your donor privileges, you can donate again by clicking on ' Support us ' on Ripple ' s website. " . format ( expireIn ) ) )
2016-06-09 08:43:28 +00:00
# Set silence end UNIX time in token
2016-10-02 20:48:14 +00:00
responseToken . silenceEndTime = userUtils . getSilenceEnd ( userID )
2016-06-09 08:43:28 +00:00
# Get only silence remaining seconds
silenceSeconds = responseToken . getSilenceSecondsLeft ( )
2016-04-19 17:40:59 +00:00
# Get supporter/GMT
userGMT = False
userSupporter = True
2016-10-06 15:41:13 +00:00
userTournament = False
2016-09-02 15:45:10 +00:00
if responseToken . admin :
2016-04-19 17:40:59 +00:00
userGMT = True
2016-10-06 15:41:13 +00:00
if responseToken . privileges & privileges . USER_TOURNAMENT_STAFF > 0 :
userTournament = True
2016-04-19 17:40:59 +00:00
# Server restarting check
2016-09-02 15:45:10 +00:00
if glob . restarting :
2016-04-19 17:40:59 +00:00
raise exceptions . banchoRestartingException ( )
2016-06-09 08:52:39 +00:00
# Send login notification before maintenance message
if glob . banchoConf . config [ " loginNotification " ] != " " :
responseToken . enqueue ( serverPackets . notification ( glob . banchoConf . config [ " loginNotification " ] ) )
2016-04-19 17:40:59 +00:00
# Maintenance check
2016-09-02 15:45:10 +00:00
if glob . banchoConf . config [ " banchoMaintenance " ] :
if not userGMT :
2016-04-19 17:40:59 +00:00
# We are not mod/admin, delete token, send notification and logout
glob . tokens . deleteToken ( responseTokenString )
raise exceptions . banchoMaintenanceException ( )
else :
# We are mod/admin, send warning notification and continue
responseToken . enqueue ( serverPackets . notification ( " Bancho is in maintenance mode. Only mods/admins have full access to the server. \n Type !system maintenance off in chat to turn off maintenance mode. " ) )
# Send all needed login packets
2016-06-09 08:43:28 +00:00
responseToken . enqueue ( serverPackets . silenceEndTime ( silenceSeconds ) )
2016-04-19 17:40:59 +00:00
responseToken . enqueue ( serverPackets . userID ( userID ) )
responseToken . enqueue ( serverPackets . protocolVersion ( ) )
2016-10-06 15:41:13 +00:00
responseToken . enqueue ( serverPackets . userSupporterGMT ( userSupporter , userGMT , userTournament ) )
2016-07-08 13:11:59 +00:00
responseToken . enqueue ( serverPackets . userPanel ( userID , True ) )
responseToken . enqueue ( serverPackets . userStats ( userID , True ) )
2016-04-19 17:40:59 +00:00
# Channel info end (before starting!?! wtf bancho?)
responseToken . enqueue ( serverPackets . channelInfoEnd ( ) )
# Default opened channels
# TODO: Configurable default channels
2016-07-14 10:37:07 +00:00
chat . joinChannel ( token = responseToken , channel = " #osu " )
chat . joinChannel ( token = responseToken , channel = " #announce " )
2016-07-03 18:51:19 +00:00
# Join admin channel if we are an admin
2016-09-02 15:45:10 +00:00
if responseToken . admin :
2016-07-14 10:37:07 +00:00
chat . joinChannel ( token = responseToken , channel = " #admin " )
2016-04-19 17:40:59 +00:00
# Output channels info
for key , value in glob . channels . channels . items ( ) :
2016-07-15 09:46:44 +00:00
if value . publicRead == True and value . hidden == False :
2016-04-19 17:40:59 +00:00
responseToken . enqueue ( serverPackets . channelInfo ( key ) )
2016-06-09 08:43:28 +00:00
# Send friends list
2016-04-19 17:40:59 +00:00
responseToken . enqueue ( serverPackets . friendList ( userID ) )
2016-06-09 08:52:39 +00:00
# Send main menu icon
2016-04-19 17:40:59 +00:00
if glob . banchoConf . config [ " menuIcon " ] != " " :
responseToken . enqueue ( serverPackets . mainMenuIcon ( glob . banchoConf . config [ " menuIcon " ] ) )
# Send online users IDs array
responseToken . enqueue ( serverPackets . onlineUsers ( ) )
# Get location and country from ip.zxq.co or database
2016-09-02 15:45:10 +00:00
if glob . localize :
2016-05-12 17:22:13 +00:00
# Get location and country from IP
2016-11-17 18:13:06 +00:00
latitude , longitude = locationHelper . getLocation ( requestIP )
2016-05-12 17:22:13 +00:00
countryLetters = locationHelper . getCountry ( requestIP )
country = countryHelper . getCountryID ( countryLetters )
2016-04-19 17:40:59 +00:00
else :
# Set location to 0,0 and get country from db
2016-06-04 10:44:54 +00:00
log . warning ( " Location skipped " )
2016-11-17 18:13:06 +00:00
latitude = 0
longitude = 0
2016-05-01 18:39:01 +00:00
countryLetters = " XX "
2016-10-02 20:48:14 +00:00
country = countryHelper . getCountryID ( userUtils . getCountry ( userID ) )
2016-04-19 17:40:59 +00:00
# Set location and country
2016-11-17 18:13:06 +00:00
responseToken . setLocation ( latitude , longitude )
responseToken . country = country
2016-04-19 17:40:59 +00:00
2016-05-01 16:09:35 +00:00
# Set country in db if user has no country (first bancho login)
2016-10-02 20:48:14 +00:00
if userUtils . getCountry ( userID ) == " XX " :
userUtils . setCountry ( userID , countryLetters )
2016-07-08 13:11:59 +00:00
2016-10-05 21:28:26 +00:00
# Send to everyone our userpanel if we are not restricted or tournament
2016-09-02 15:45:10 +00:00
if not responseToken . restricted :
2016-10-01 19:19:03 +00:00
glob . streams . broadcast ( " main " , serverPackets . userPanel ( userID ) )
2016-04-19 17:40:59 +00:00
# Set reponse data to right value and reset our queue
responseData = responseToken . queue
responseToken . resetQueue ( )
except exceptions . loginFailedException :
# Login failed error packet
# (we don't use enqueue because we don't have a token since login has failed)
err = True
responseData + = serverPackets . loginFailed ( )
2016-09-13 09:39:39 +00:00
except exceptions . invalidArgumentsException :
2016-07-14 10:37:07 +00:00
# Invalid POST data
# (we don't use enqueue because we don't have a token since login has failed)
err = True
responseData + = serverPackets . loginFailed ( )
responseData + = serverPackets . notification ( " I see what you ' re doing... " )
2016-04-19 17:40:59 +00:00
except exceptions . loginBannedException :
# Login banned error packet
err = True
responseData + = serverPackets . loginBanned ( )
2016-09-13 09:39:39 +00:00
except exceptions . loginLockedException :
# Login banned error packet
err = True
responseData + = serverPackets . loginLocked ( )
2016-04-19 17:40:59 +00:00
except exceptions . banchoMaintenanceException :
# Bancho is in maintenance mode
2016-07-08 13:11:59 +00:00
responseData = responseToken . queue
2016-04-19 17:40:59 +00:00
responseData + = serverPackets . notification ( " Our bancho server is in maintenance mode. Please try to login again later. " )
2016-06-11 16:43:27 +00:00
responseData + = serverPackets . loginFailed ( )
2016-04-19 17:40:59 +00:00
except exceptions . banchoRestartingException :
# Bancho is restarting
responseData + = serverPackets . notification ( " Bancho is restarting. Try again in a few minutes. " )
2016-06-11 16:43:27 +00:00
responseData + = serverPackets . loginFailed ( )
except exceptions . need2FAException :
# User tried to log in from unknown IP
responseData + = serverPackets . needVerification ( )
2016-07-20 09:59:53 +00:00
except exceptions . haxException :
2016-07-28 20:23:13 +00:00
# Using oldoldold client, we don't have client data. Force update.
2016-07-20 09:59:53 +00:00
# (we don't use enqueue because we don't have a token since login has failed)
err = True
responseData + = serverPackets . forceUpdate ( )
2016-09-13 09:39:39 +00:00
responseData + = serverPackets . notification ( " Hory shitto, your client is TOO old! Nice prehistory! Please turn update it from the settings! " )
2016-07-14 10:37:07 +00:00
except :
log . error ( " Unknown error! \n ``` \n {} \n {} ``` " . format ( sys . exc_info ( ) , traceback . format_exc ( ) ) )
2016-04-19 17:40:59 +00:00
finally :
2016-06-04 10:44:54 +00:00
# Console and discord log
2016-07-14 10:37:07 +00:00
if len ( loginData ) < 3 :
msg = " Invalid bancho login request from ** {} ** (insufficient POST data) " . format ( requestIP )
else :
2016-07-31 09:36:21 +00:00
msg = " Bancho login request from ** {} ** for user ** {} ** ( {} ) " . format ( requestIP , loginData [ 0 ] , " failed " if err == True else " success " )
2016-07-20 09:59:53 +00:00
log . info ( msg , " bunker " )
2016-06-02 17:22:02 +00:00
# Return token string and data
2016-09-02 15:45:10 +00:00
return responseTokenString , responseData