CSM: new tokens + holocaust dc imports

This commit is contained in:
loblao 2015-05-31 17:08:46 -03:00
parent 1303af5a13
commit 0b2107c04d
10 changed files with 275 additions and 294 deletions

4
.gitignore vendored
View file

@ -1,5 +1,6 @@
# Python artifacts
*.pyc
*.pyo
# Batch
*.bat
@ -15,3 +16,6 @@
# Git
*.rej
# Local config
dependencies/config/local.prc

View file

@ -34,6 +34,7 @@ dclass Account {
string LAST_LOGIN db;
string ACCOUNT_ID db;
uint16 ACCESS_LEVEL db;
uint64 LAST_LOGIN_TS db;
};
struct BarrierData {

View file

@ -1,5 +0,0 @@
cd ../../../
"dependencies/panda/python/ppython.exe" "dev/tools/dcimports/parse_dcimports.py" -o "otp/distributed/DCClassImports.py" "dependencies/astron/dclass/stride.dc"
pause

View file

@ -1,50 +0,0 @@
import argparse
from pandac.PandaModules import *
parser = argparse.ArgumentParser()
parser.add_argument('--output', '-o', default='DCClassImports.py',
help='The filename of the generated Python module.')
parser.add_argument('filenames', nargs='+', default=['otp.dc', 'toon.dc'],
help='The DC class files to be included in the generated Python module.')
args = parser.parse_args()
dcFile = DCFile()
for filename in args.filenames:
dcFile.read(Filename.fromOsSpecific(filename))
dcImports = {}
for n in xrange(dcFile.getNumImportModules()):
moduleName = dcFile.getImportModule(n)[:].split('/', 1)[0]
if moduleName not in dcImports:
dcImports[moduleName] = []
importSymbols = []
for i in xrange(dcFile.getNumImportSymbols(n)):
symbolName = dcFile.getImportSymbol(n, i).split('/', 1)[0]
importSymbols.append(symbolName)
dcImports[moduleName].extend(importSymbols)
data = '''\
# This file was generated by the parse_dclass.py utility.
from pandac.PandaModules import *
hashVal = %r
''' % dcFile.getHash()
for moduleName, importSymbols in dcImports.items():
data += 'from %s import %s\n' % (moduleName, ', '.join(importSymbols))
data += '''
dcImports = locals().copy()
'''
print 'Writing %s...' % args.output
with open(args.output, 'w') as f:
f.write(data)
print 'Done writing %s.' % args.output

View file

@ -1,48 +0,0 @@
# This file was generated by the parse_dclass.py utility.
from pandac.PandaModules import *
hashVal = 3796631281L
from toontown.coghq import DistributedCashbotBossSafe, DistributedCashbotBossCrane, DistributedBattleFactory, DistributedCashbotBossTreasure, DistributedCogHQDoor, DistributedCogHQExteriorDoor, DistributedSellbotHQDoor, DistributedFactoryElevatorExt, DistributedMintElevatorExt, DistributedLawOfficeElevatorExt, DistributedLawOfficeElevatorInt, LobbyManager, DistributedMegaCorp, DistributedFactory, DistributedLawOffice, DistributedLawOfficeFloor, DistributedLift, DistributedDoorEntity, DistributedSwitch, DistributedButton, DistributedTrigger, DistributedCrushableEntity, DistributedCrusherEntity, DistributedStomper, DistributedStomperPair, DistributedLaserField, DistributedGolfGreenGame, DistributedSecurityCamera, DistributedMover, DistributedElevatorMarker, DistributedBarrelBase, DistributedGagBarrel, DistributedBeanBarrel, DistributedHealBarrel, DistributedGrid, ActiveCell, DirectionalCell, CrusherCell, DistributedCrate, DistributedSinkingPlatform, BattleBlocker, DistributedMint, DistributedMintRoom, DistributedMintBattle, DistributedStage, DistributedStageRoom, DistributedStageBattle, DistributedLawbotBossGavel, DistributedLawbotCannon, DistributedLawbotChair, DistributedCogKart, DistributedCountryClub, DistributedCountryClubRoom, DistributedMoleField, DistributedCountryClubBattle, DistributedMaze, DistributedFoodBelt, DistributedBanquetTable, DistributedGolfSpot
from toontown.golf import DistributedPhysicsWorld, DistributedGolfHole, DistributedGolfCourse
from toontown.building import DistributedAnimatedProp, DistributedTrophyMgr, DistributedBuilding, DistributedBuildingQueryMgr, DistributedToonInterior, DistributedToonHallInterior, DistributedSuitInterior, DistributedHQInterior, DistributedGagshopInterior, DistributedPetshopInterior, DistributedKartShopInterior, DistributedDoor, DistributedKnockKnockDoor, DistributedElevator, DistributedElevatorFSM, DistributedElevatorExt, DistributedElevatorInt, DistributedElevatorFloor, DistributedBossElevator, DistributedVPElevator, DistributedCFOElevator, DistributedCJElevator, DistributedBBElevator, DistributedBoardingParty, DistributedTutorialInterior, DistributedClubElevator
from toontown.uberdog.DistributedPartyManager import DistributedPartyManager
from otp.friends import FriendManager
from otp.level import DistributedLevel, DistributedEntity, DistributedInteractiveEntity
from toontown.shtiker import DeleteManager, PurchaseManager, NewbiePurchaseManager
from toontown.groups import GroupManager
from toontown.uberdog.ClientServicesManager import ClientServicesManager
from toontown.ai import WelcomeValleyManager, NewsManager, DistributedAprilToonsMgr, DistributedBlackCatMgr, DistributedReportMgr, DistributedPolarPlaceEffectMgr, DistributedGreenToonEffectMgr, DistributedResistanceEmoteMgr, DistributedScavengerHuntTarget, DistributedTrickOrTreatTarget, DistributedWinterCarolingTarget, DistributedJorElCam
from otp.chat import ChatAgent
from toontown.parties.GlobalPartyManager import GlobalPartyManager
from toontown.racing.DistributedStartingBlock import DistributedViewingBlock
from toontown.suit import DistributedSuitPlanner, DistributedSuitBase, DistributedSuit, DistributedTutorialSuit, DistributedFactorySuit, DistributedMintSuit, DistributedStageSuit, DistributedSellbotBoss, DistributedCashbotBoss, DistributedCashbotBossGoon, DistributedGoon, DistributedGridGoon, DistributedLawbotBoss, DistributedLawbotBossSuit, DistributedBossbotBoss
from toontown.distributed import ToontownDistrict, ToontownDistrictStats, DistributedTimer
from toontown.effects import DistributedFireworkShow
from toontown.safezone import DistributedTrolley, DistributedPillow, DistributedPartyGate, DistributedBoat, DistributedButterfly, DistributedMMPiano, DistributedDGFlower, DistributedFishingSpot, SafeZoneManager, DistributedTreasure, DistributedGolfKart, DistributedPicnicBasket, DistributedPicnicTable, DistributedChineseCheckers, DistributedCheckers, DistributedFindFour
from toontown.fishing import DistributedFishingPond, DistributedFishingTarget, DistributedPondBingoManager
from toontown.minigame import DistributedMinigame, DistributedMinigameTemplate, DistributedRaceGame, DistributedCannonGame, DistributedPatternGame, DistributedRingGame, DistributedTagGame, DistributedMazeGame, DistributedTugOfWarGame, DistributedCatchGame, DistributedDivingGame, DistributedTargetGame, DistributedVineGame, DistributedIceGame, DistributedCogThiefGame, DistributedTwoDGame
from toontown.racing import DistributedVehicle, DistributedStartingBlock, DistributedRace, DistributedKartPad, DistributedRacePad, DistributedViewPad, DistributedStartingBlock, DistributedLeaderBoard, DistributedGag, DistributedProjectile
from toontown.catalog import CatalogManager, AccountDate
from toontown.parties import DistributedParty, DistributedPartyActivity, DistributedPartyTeamActivity, DistributedPartyCannon, DistributedPartyCannonActivity, DistributedPartyCatchActivity, DistributedPartyWinterCatchActivity, DistributedPartyCogActivity, DistributedPartyWinterCogActivity, DistributedPartyFireworksActivity, DistributedPartyDanceActivityBase, DistributedPartyDanceActivity, DistributedPartyDance20Activity, DistributedPartyValentineDanceActivity, DistributedPartyValentineDance20Activity, DistributedPartyTrampolineActivity, DistributedPartyValentineTrampolineActivity, DistributedPartyVictoryTrampolineActivity, DistributedPartyWinterTrampolineActivity, DistributedPartyTugOfWarActivity, DistributedPartyJukeboxActivityBase, DistributedPartyJukeboxActivity, DistributedPartyJukebox40Activity, DistributedPartyValentineJukeboxActivity, DistributedPartyValentineJukebox40Activity
from toontown.pets.DistributedPet import *
from toontown.friends import TTSFriendsManager
from toontown.cogdominium import DistributedCogdoInterior, DistributedCogdoBattleBldg, DistributedCogdoElevatorExt, DistributedCogdoElevatorInt, DistributedCogdoBarrel, DistCogdoGame, DistCogdoLevelGame, DistCogdoBoardroomGame, DistCogdoCraneGame, DistCogdoMazeGame, DistCogdoFlyingGame, DistCogdoCrane, DistCogdoCraneMoneyBag, DistCogdoCraneCog
from toontown.uberdog.GlobalLobbyManager import GlobalLobbyManager
from toontown.uberdog.ARGManager import ARGManager
from otp.distributed import Account, DistributedDistrict, DistributedDirectory
from toontown.estate import DistributedCannon, DistributedTarget, EstateManager, DistributedEstate, DistributedHouse, DistributedHouseInterior, DistributedGarden, DistributedHouseDoor, DistributedMailbox, DistributedFurnitureManager, DistributedFurnitureItem, DistributedBank, DistributedCloset, DistributedTrunk, DistributedPhone, DistributedFireworksCannon, DistributedLawnDecor, DistributedGardenPlot, DistributedGardenBox, DistributedFlower, DistributedGagTree, DistributedStatuary, DistributedToonStatuary, DistributedChangingStatuary, DistributedAnimatedStatuary, DistributedPlantBase, DistributedLawnDecor
from toontown.uberdog.DistributedLobbyManager import DistributedLobbyManager
from toontown.toon import DistributedToon, DistributedNPCToonBase, DistributedNPCToon, DistributedSmartNPC, DistributedNPCSpecialQuestGiver, DistributedNPCFlippyInToonHall, DistributedNPCScientist, DistributedNPCClerk, DistributedNPCTailor, DistributedNPCBlocker, DistributedNPCFisherman, DistributedNPCPartyPerson, DistributedNPCPetclerk, DistributedNPCKartClerk, DistributedNPCGlove, DistributedNPCLaffRestock
from toontown.tutorial import DistributedBattleTutorial, TutorialManager
from toontown.pets import DistributedPetProxy
from toontown.coderedemption.TTCodeRedemptionMgr import TTCodeRedemptionMgr
from direct.distributed import DistributedObject, DistributedNode, DistributedSmoothNode, DistributedCartesianGrid, DistributedCamera, DistributedObjectGlobal
from otp.ai import TimeManager, MagicWordManager
from otp.avatar import DistributedAvatar, DistributedPlayer, AvatarHandle
from toontown.battle import DistributedBattleBase, DistributedBattle, DistributedBattleBldg, DistributedBattleFinal, DistributedBattleWaiters, DistributedBattleDiners
dcImports = locals().copy()

View file

@ -11,7 +11,7 @@ from direct.task import Task
from pandac.PandaModules import *
from otp.avatar import Avatar, DistributedAvatar
from otp.avatar.DistributedPlayer import DistributedPlayer
from otp.distributed import DCClassImports, OtpDoGlobals
from otp.distributed import OtpDoGlobals
from otp.distributed.OtpDoGlobals import *
from otp.distributed.TelemetryLimiter import TelemetryLimiter
from otp.otpbase import OTPGlobals, OTPLocalizer
@ -218,50 +218,154 @@ class OTPClientRepository(ClientRepositoryBase):
self.dclassesByName = {}
self.dclassesByNumber = {}
self.hashVal = 0
try:
dcStream
except:
pass
else:
self.notify.info('Detected DC file stream, reading it...')
dcFileNames = [dcStream]
if isinstance(dcFileNames, str):
if isinstance(dcFileNames, types.StringTypes):
# If we were given a single string, make it a list.
dcFileNames = [dcFileNames]
if dcFileNames is not None:
for dcFileName in dcFileNames:
if isinstance(dcFileName, StringStream):
readResult = dcFile.read(dcFileName, 'DC stream')
else:
readResult = dcFile.read(dcFileName)
if not readResult:
self.notify.error('Could not read DC file.')
else:
dcFile.readAll()
dcImports = {}
if dcFileNames == None:
try:
# For Nirai
readResult = dcFile.read(dcStream, '__dc__')
del __builtin__.dcStream
self.hashVal = DCClassImports.hashVal
for i in xrange(dcFile.getNumClasses()):
except NameError:
readResult = dcFile.readAll()
if not readResult:
self.notify.error("Could not read dc file.")
else:
searchPath = getModelPath().getValue()
for dcFileName in dcFileNames:
pathname = Filename(dcFileName)
vfs.resolveFilename(pathname, searchPath)
readResult = dcFile.read(pathname)
if not readResult:
self.notify.error("Could not read dc file: %s" % (pathname))
self.hashVal = dcFile.getHash()
# Now import all of the modules required by the DC file.
for n in range(dcFile.getNumImportModules()):
moduleName = dcFile.getImportModule(n)[:]
# Maybe the module name is represented as "moduleName/AI".
suffix = moduleName.split('/')
moduleName = suffix[0]
suffix=suffix[1:]
if self.dcSuffix in suffix:
moduleName += self.dcSuffix
elif self.dcSuffix == 'UD' and 'AI' in suffix: #HACK:
moduleName += 'AI'
importSymbols = []
for i in range(dcFile.getNumImportSymbols(n)):
symbolName = dcFile.getImportSymbol(n, i)
# Maybe the symbol name is represented as "symbolName/AI".
suffix = symbolName.split('/')
symbolName = suffix[0]
suffix=suffix[1:]
if self.dcSuffix in suffix:
symbolName += self.dcSuffix
elif self.dcSuffix == 'UD' and 'AI' in suffix: #HACK:
symbolName += 'AI'
importSymbols.append(symbolName)
self.importModule(dcImports, moduleName, importSymbols)
# Now get the class definition for the classes named in the DC
# file.
for i in range(dcFile.getNumClasses()):
dclass = dcFile.getClass(i)
number = dclass.getNumber()
className = dclass.getName()
classDef = DCClassImports.dcImports.get(className)
className = dclass.getName() + self.dcSuffix
# Does the class have a definition defined in the newly
# imported namespace?
classDef = dcImports.get(className)
if classDef is None and self.dcSuffix == 'UD': #HACK:
className = dclass.getName() + 'AI'
classDef = dcImports.get(className)
# Also try it without the dcSuffix.
if classDef == None:
className = dclass.getName()
classDef = dcImports.get(className)
if classDef is None:
self.notify.debug('No class definition for %s.' % className)
self.notify.debug("No class definition for %s." % (className))
else:
if type(classDef) == types.ModuleType:
if not hasattr(classDef, className):
self.notify.warning('Module %s does not define class %s.' % (className, className))
self.notify.warning("Module %s does not define class %s." % (className, className))
continue
classDef = getattr(classDef, className)
if (type(classDef) != types.ClassType) and (type(classDef) != types.TypeType):
self.notify.error('Symbol %s is not a class name.' % className)
if type(classDef) != types.ClassType and type(classDef) != types.TypeType:
self.notify.error("Symbol %s is not a class name." % (className))
else:
dclass.setClassDef(classDef)
self.dclassesByName[className] = dclass
if number >= 0:
self.dclassesByNumber[number] = dclass
# Owner Views
if self.hasOwnerView():
ownerDcSuffix = self.dcSuffix + 'OV'
# dict of class names (without 'OV') that have owner views
ownerImportSymbols = {}
# Now import all of the modules required by the DC file.
for n in range(dcFile.getNumImportModules()):
moduleName = dcFile.getImportModule(n)
# Maybe the module name is represented as "moduleName/AI".
suffix = moduleName.split('/')
moduleName = suffix[0]
suffix=suffix[1:]
if ownerDcSuffix in suffix:
moduleName = moduleName + ownerDcSuffix
importSymbols = []
for i in range(dcFile.getNumImportSymbols(n)):
symbolName = dcFile.getImportSymbol(n, i)
# Check for the OV suffix
suffix = symbolName.split('/')
symbolName = suffix[0]
suffix=suffix[1:]
if ownerDcSuffix in suffix:
symbolName += ownerDcSuffix
importSymbols.append(symbolName)
ownerImportSymbols[symbolName] = None
self.importModule(dcImports, moduleName, importSymbols)
# Now get the class definition for the owner classes named
# in the DC file.
for i in range(dcFile.getNumClasses()):
dclass = dcFile.getClass(i)
if ((dclass.getName()+ownerDcSuffix) in ownerImportSymbols):
number = dclass.getNumber()
className = dclass.getName() + ownerDcSuffix
# Does the class have a definition defined in the newly
# imported namespace?
classDef = dcImports.get(className)
if classDef is None:
self.notify.error("No class definition for %s." % className)
else:
if type(classDef) == types.ModuleType:
if not hasattr(classDef, className):
self.notify.error("Module %s does not define class %s." % (className, className))
classDef = getattr(classDef, className)
dclass.setOwnerClassDef(classDef)
self.dclassesByName[className] = dclass
def getGameDoId(self):
return self.GameGlobalsId

View file

@ -35,6 +35,9 @@ args = parser.parse_args()
for prc in args.config:
loadPrcFile(prc)
if os.path.isfile('dependencies/config/local.prc'):
loadPrcFile('dependencies/config/local.prc')
localconfig = ''
if args.base_channel: localconfig += 'air-base-channel %s\n' % args.base_channel
if args.max_channels: localconfig += 'air-channel-allocation %s\n' % args.max_channels

View file

@ -32,6 +32,9 @@ if __debug__:
loadPrcFile('dependencies/config/general.prc')
loadPrcFile('dependencies/config/release/dev.prc')
if os.path.isfile('dependencies/config/local.prc'):
loadPrcFile('dependencies/config/local.prc')
defaultText = ""
def __inject_wx(_):

View file

@ -1,45 +1,74 @@
import anydbm
import base64
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
from direct.distributed.PyDatagram import *
from direct.fsm.FSM import FSM
import hashlib
import hmac
import json
from pandac.PandaModules import *
import time
import urllib2
import os
from otp.ai.MagicWordGlobal import *
from otp.distributed import OtpDoGlobals
from toontown.makeatoon.NameGenerator import NameGenerator
from toontown.toon.ToonDNA import ToonDNA
from toontown.toonbase import TTLocalizer
from toontown.uberdog import NameJudgeBlacklist
from pandac.PandaModules import *
# Import from PyCrypto only if we are using a database that requires it. This
# allows local hosted and developer builds of the game to run without it:
accountDBType = simbase.config.GetString('accountdb-type', 'developer')
import hashlib, hmac, json
import anydbm, math, os
import urllib2, time
def rejectConfig(issue, securityIssue=True, retarded=True):
print
print
print 'Lemme get this straight....'
print 'You are trying to use remote account database type...'
print 'However,', issue + '!!!!'
if securityIssue:
print 'Do you want this server to get hacked?'
if retarded:
print '"Either down\'s or autism"\n - JohnnyDaPirate, 2015'
print 'Go fix that!'
exit()
def entropy(string):
prob = [float(string.count(c)) / len(string) for c in dict.fromkeys(list(string))]
entropy = -sum([p * math.log(p) / math.log(2.0) for p in prob])
return entropy
def entropyIdeal(length):
prob = 1.0 / length
return -length * prob * math.log(prob) / math.log(2.0)
accountDBType = config.GetString('accountdb-type', 'developer')
accountServerSecret = config.GetString('account-server-secret', 'dev')
accountServerHashAlgo = config.GetString('account-server-hash-algo', 'sha512')
if accountDBType == 'remote':
from Crypto.Cipher import AES
if accountServerSecret == 'dev':
rejectConfig('you have not changed the secret in config/local.prc')
# Sometimes we'll want to force a specific access level, such as on the
# developer server:
minAccessLevel = simbase.config.GetInt('min-access-level', 100)
if len(accountServerSecret) < 16:
rejectConfig('the secret is too small! Make it 16+ bytes', retarded=False)
accountServerEndpoint = simbase.config.GetString(
'account-server-endpoint', 'http://tigercat1.me/tmpremote/api/')
accountServerSecret = simbase.config.GetString(
'account-server-secret', '9sj6816aj1hs795j')
secretLength = len(accountServerSecret)
ideal = entropyIdeal(secretLength) / 2
entropy = entropy(accountServerSecret)
if entropy < ideal:
rejectConfig('the secret entropy is too low! For %d bytes,'
' it should be %d. Currently it is %d' % (secretLength, ideal, entropy),
retarded=False)
hashAlgo = getattr(hashlib, accountServerHashAlgo, None)
if not hashAlgo:
rejectConfig('%s is not a valid hash algo' % accountServerHashAlgo, securityIssue=False)
http = HTTPClient()
http.setVerifySsl(0)
hashSize = len(hashAlgo('').digest())
minAccessLevel = config.GetInt('min-access-level', 100)
def executeHttpRequest(url, **extras):
# TO DO: THIS IS QUITE DISGUSTING
# INSTEAD OF USING THE SAME SECRET, WE SHOULD HAVE AN API KEY EXCLUSIVE TO THAT
# MOVE THIS TO ToontownInternalRepository (this might be interesting for AI)
request = urllib2.Request('http://tigercat1.me/tmpremote/api/' + url)
timestamp = str(int(time.time()))
signature = hashlib.sha256(timestamp + accountServerSecret + "h*^ahJGHA017JI&A&*uyhU07")
@ -53,10 +82,10 @@ def executeHttpRequest(url, **extras):
except:
return None
notify = directNotify.newCategory('ClientServicesManagerUD')
def executeHttpRequestAndLog(url, **extras):
# SEE ABOVE
response = executeHttpRequest(url, extras)
if response is None:
@ -93,20 +122,17 @@ def judgeName(name):
# --- ACCOUNT DATABASES ---
# These classes make up the available account databases for Toontown Stride.
# Databases with login tokens use the PyCrypto module for decrypting them.
# DeveloperAccountDB is a special database that accepts a username, and assigns
# each user with 700 access automatically upon login.
class AccountDB:
notify = directNotify.newCategory('AccountDB')
def __init__(self, csm):
self.csm = csm
filename = simbase.config.GetString(
'account-bridge-filename', 'account-bridge.db')
filename = os.path.join("dependencies", filename)
filename = config.GetString('account-bridge-filename', 'account-bridge.db')
filename = os.path.join('dependencies', filename)
self.dbm = anydbm.open(filename, 'c')
@ -119,8 +145,20 @@ class AccountDB:
def removeNameRequest(self, avId):
pass
def lookup(self, username, callback):
pass # Inheritors should override this.
def lookup(self, data, callback):
userId = data['userId']
data['success'] = True
data['accessLevel'] = max(data['accessLevel'], minAccessLevel)
if str(userId) not in self.dbm:
data['accountId'] = 0
else:
data['accountId'] = int(self.dbm[str(userId)])
callback(data)
return data
def storeAccountID(self, userId, accountId, callback):
self.dbm[str(userId)] = str(accountId) # anydbm only allows strings.
@ -131,33 +169,20 @@ class AccountDB:
self.notify.warning('Unable to associate user %s with account %d!' % (userId, accountId))
callback(False)
class DeveloperAccountDB(AccountDB):
notify = directNotify.newCategory('DeveloperAccountDB')
def lookup(self, username, callback):
# Let's check if this user's ID is in your account database bridge:
if str(username) not in self.dbm:
# Nope. Let's associate them with a brand new Account object! We
# will assign them with 700 access just because they are a
# developer:
response = {
'success': True,
'userId': username,
'accountId': 0,
'accessLevel': max(700, minAccessLevel)
}
else:
# We have an account already, let's return what we've got:
response = {
'success': True,
'userId': username,
'accountId': int(self.dbm[str(username)]),
}
callback(response)
return response
def lookup(self, userId, callback):
return AccountDB.lookup(self, {'userId': userId,
'accessLevel': 700,
'notAfter': 0},
callback)
class RemoteAccountDB(AccountDB):
# TO DO FOR NAMES:
# CURRENTLY IT MAKES n REQUESTS FOR EACH AVATAR
# IN THE FUTURE, MAKE ONLY 1 REQUEST
# WHICH RETURNS ALL PENDING AVS
notify = directNotify.newCategory('RemoteAccountDB')
def addNameRequest(self, avId, name):
@ -171,105 +196,43 @@ class RemoteAccountDB(AccountDB):
return executeHttpRequest('names/remove', ID=str(avId))
def lookup(self, token, callback):
# First, base64 decode the token:
'''
Token format:
The token is obfuscated a bit, but nothing too hard to read.
Most of the security is based on the hash.
I. Data contained in a token:
A json-encoded dict, which contains timestamp, userid and extra info
II. Token format
X = BASE64(ROT13(DATA)[::-1])
H = HASH(X)[::-1]
Token = BASE64(H + X)
'''
try:
token = base64.b64decode(token)
except TypeError:
self.notify.warning('Could not decode the provided token!')
response = {
'success': False,
'reason': "Can't decode this token."
}
callback(response)
return response
token = token.decode('base64')
hash, token = token[:hashSize], token[hashSize:]
# Ensure this token is a valid size:
if (not token) or ((len(token) % 16) != 0):
self.notify.warning('Invalid token length!')
response = {
'success': False,
'reason': 'Invalid token length.'
}
callback(response)
return response
correctHash = hashAlgo(token + accountServerSecret).digest()
if len(hash) != len(correctHash):
raise ValueError('invalid hash')
# Next, decrypt the token using AES-128 in CBC mode:
accountServerSecret = simbase.config.GetString(
'account-server-secret', '9sj6816aj1hs795j')
value = 0
for x, y in zip(hash[::-1], correctHash):
value |= ord(x) ^ ord(y)
# Ensure that our secret is the correct size:
if len(accountServerSecret) > AES.block_size:
self.notify.warning('account-server-secret is too big!')
accountServerSecret = accountServerSecret[:AES.block_size]
elif len(accountServerSecret) < AES.block_size:
self.notify.warning('account-server-secret is too small!')
accountServerSecret += '\x80'
while len(accountServerSecret) < AES.block_size:
accountServerSecret += '\x00'
if value:
raise ValueError('invalid hash')
# Take the initialization vector off the front of the token:
iv = token[:AES.block_size]
token = json.loads(token.decode('base64')[::-1].decode('rot13'))
# Truncate the token to get our cipher text:
cipherText = token[AES.block_size:]
# Decrypt!
cipher = AES.new(accountServerSecret, mode=AES.MODE_CBC, IV=iv)
try:
token = json.loads(cipher.decrypt(cipherText).replace('\x00', ''))
if ('timestamp' not in token) or (not isinstance(token['timestamp'], int)):
raise ValueError
if ('userid' not in token) or (not isinstance(token['userid'], int)):
raise ValueError
if ('accesslevel' not in token) or (not isinstance(token['accesslevel'], int)):
raise ValueError
except ValueError, e:
print e
self.notify.warning('Invalid token.')
response = {
'success': False,
'reason': 'Invalid token.'
}
callback(response)
return response
# Next, check if this token has expired:
expiration = simbase.config.GetInt('account-token-expiration', 1800)
tokenDelta = int(time.time()) - token['timestamp']
if tokenDelta > expiration:
response = {
'success': False,
'reason': 'This token has expired.'
}
callback(response)
return response
# This token is valid. That's all we need to know. Next, let's check if
# this user's ID is in your account database bridge:
if str(token['userid']) not in self.dbm:
# Nope. Let's associate them with a brand new Account object!
response = {
'success': True,
'userId': token['userid'],
'accountId': 0,
'accessLevel': max(int(token['accesslevel']), minAccessLevel)
}
callback(response)
return response
else:
# Yep. Let's return their account ID and access level!
response = {
'success': True,
'userId': token['userid'],
'accountId': int(self.dbm[str(token['userid'])]),
'accessLevel': max(int(token['accesslevel']), minAccessLevel)
}
callback(response)
return response
except:
resp = {'success': False}
callback(resp)
return resp
return AccountDB.lookup(self, token, callback)
# --- FSMs ---
class OperationFSM(FSM):
@ -294,7 +257,6 @@ class OperationFSM(FSM):
else:
del self.csm.account2fsm[self.target]
class LoginAccountFSM(OperationFSM):
notify = directNotify.newCategory('LoginAccountFSM')
TARGET_CONNECTION = True
@ -315,6 +277,7 @@ class LoginAccountFSM(OperationFSM):
self.userId = result.get('userId', 0)
self.accountId = result.get('accountId', 0)
self.accessLevel = result.get('accessLevel', 0)
self.notAfter = result.get('notAfter', 0)
if self.accountId:
self.demand('RetrieveAccount')
else:
@ -330,6 +293,12 @@ class LoginAccountFSM(OperationFSM):
return
self.account = fields
if self.notAfter:
if self.account.get('LAST_LOGIN_TS', 0) > self.notAfter:
self.notify.debug('Rejecting old token: %d, notAfter=%d' % (self.account.get('LAST_LOGIN_TS', 0), self.notAfter))
return self.__handleLookup({'success': False})
self.demand('SetAccount')
def enterCreateAccount(self):
@ -339,6 +308,7 @@ class LoginAccountFSM(OperationFSM):
'ACCOUNT_AV_SET_DEL': [],
'CREATED': time.ctime(),
'LAST_LOGIN': time.ctime(),
'LAST_LOGIN_TS': time.time(),
'ACCOUNT_ID': str(self.userId),
'ACCESS_LEVEL': self.accessLevel
}
@ -403,7 +373,7 @@ class LoginAccountFSM(OperationFSM):
datagram.addChannel(self.csm.GetAccountConnectionChannel(self.accountId))
self.csm.air.send(datagram)
# Subscribe to any "staff" channels that the account has access to.
# Subscribe to any "staff" channels that the account has access to.
access = self.account.get('ADMIN_ACCESS', 0)
if access >= 200:
# Subscribe to the moderator channel.
@ -449,6 +419,7 @@ class LoginAccountFSM(OperationFSM):
self.accountId,
self.csm.air.dclassesByName['AccountUD'],
{'LAST_LOGIN': time.ctime(),
'LAST_LOGIN_TS': time.time(),
'ACCOUNT_ID': str(self.userId)})
# We're done.
@ -547,7 +518,6 @@ class CreateAvatarFSM(OperationFSM):
self.csm.sendUpdateToAccountId(self.target, 'createAvatarResp', [self.avId])
self.demand('Off')
class AvatarOperationFSM(OperationFSM):
POST_ACCOUNT_STATE = 'Off' # This needs to be overridden.
@ -570,7 +540,6 @@ class AvatarOperationFSM(OperationFSM):
self.demand(self.POST_ACCOUNT_STATE)
class GetAvatarsFSM(AvatarOperationFSM):
notify = directNotify.newCategory('GetAvatarsFSM')
POST_ACCOUNT_STATE = 'QueryAvatars'
@ -807,7 +776,6 @@ class SetNamePatternFSM(AvatarOperationFSM):
self.csm.sendUpdateToAccountId(self.target, 'setNamePatternResp', [self.avId, 1])
self.demand('Off')
class AcknowledgeNameFSM(AvatarOperationFSM):
notify = directNotify.newCategory('AcknowledgeNameFSM')
POST_ACCOUNT_STATE = 'GetTargetAvatar'
@ -863,7 +831,6 @@ class AcknowledgeNameFSM(AvatarOperationFSM):
self.csm.sendUpdateToAccountId(self.target, 'acknowledgeAvatarNameResp', [])
self.demand('Off')
class LoadAvatarFSM(AvatarOperationFSM):
notify = directNotify.newCategory('LoadAvatarFSM')
POST_ACCOUNT_STATE = 'GetTargetAvatar'
@ -1007,7 +974,6 @@ class UnloadAvatarFSM(OperationFSM):
self.csm.air.writeServerEvent('avatarUnload', self.avId)
self.demand('Off')
# --- CLIENT SERVICES MANAGER UBERDOG ---
class ClientServicesManagerUD(DistributedObjectGlobalUD):
notify = directNotify.newCategory('ClientServicesManagerUD')

View file

@ -33,6 +33,9 @@ args = parser.parse_args()
for prc in args.config:
loadPrcFile(prc)
if os.path.isfile('dependencies/config/local.prc'):
loadPrcFile('dependencies/config/local.prc')
localconfig = ''
if args.base_channel:
localconfig += 'air-base-channel %s\n' % args.base_channel