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 # Python artifacts
*.pyc *.pyc
*.pyo
# Batch # Batch
*.bat *.bat
@ -15,3 +16,6 @@
# Git # Git
*.rej *.rej
# Local config
dependencies/config/local.prc

View file

@ -34,6 +34,7 @@ dclass Account {
string LAST_LOGIN db; string LAST_LOGIN db;
string ACCOUNT_ID db; string ACCOUNT_ID db;
uint16 ACCESS_LEVEL db; uint16 ACCESS_LEVEL db;
uint64 LAST_LOGIN_TS db;
}; };
struct BarrierData { 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 pandac.PandaModules import *
from otp.avatar import Avatar, DistributedAvatar from otp.avatar import Avatar, DistributedAvatar
from otp.avatar.DistributedPlayer import DistributedPlayer 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.OtpDoGlobals import *
from otp.distributed.TelemetryLimiter import TelemetryLimiter from otp.distributed.TelemetryLimiter import TelemetryLimiter
from otp.otpbase import OTPGlobals, OTPLocalizer from otp.otpbase import OTPGlobals, OTPLocalizer
@ -218,50 +218,154 @@ class OTPClientRepository(ClientRepositoryBase):
self.dclassesByName = {} self.dclassesByName = {}
self.dclassesByNumber = {} self.dclassesByNumber = {}
self.hashVal = 0 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] dcFileNames = [dcFileNames]
if dcFileNames is not None: dcImports = {}
for dcFileName in dcFileNames: if dcFileNames == None:
if isinstance(dcFileName, StringStream): try:
readResult = dcFile.read(dcFileName, 'DC stream') # For Nirai
else: readResult = dcFile.read(dcStream, '__dc__')
readResult = dcFile.read(dcFileName) del __builtin__.dcStream
if not readResult:
self.notify.error('Could not read DC file.')
else:
dcFile.readAll()
self.hashVal = DCClassImports.hashVal except NameError:
for i in xrange(dcFile.getNumClasses()): 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) dclass = dcFile.getClass(i)
number = dclass.getNumber() number = dclass.getNumber()
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() className = dclass.getName()
classDef = DCClassImports.dcImports.get(className) classDef = dcImports.get(className)
if classDef is None: if classDef is None:
self.notify.debug('No class definition for %s.' % className) self.notify.debug("No class definition for %s." % (className))
else: else:
if type(classDef) == types.ModuleType: if type(classDef) == types.ModuleType:
if not hasattr(classDef, className): 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 continue
classDef = getattr(classDef, className) 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: else:
dclass.setClassDef(classDef) dclass.setClassDef(classDef)
self.dclassesByName[className] = dclass self.dclassesByName[className] = dclass
if number >= 0: if number >= 0:
self.dclassesByNumber[number] = dclass 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): def getGameDoId(self):
return self.GameGlobalsId return self.GameGlobalsId

View file

@ -35,6 +35,9 @@ args = parser.parse_args()
for prc in args.config: for prc in args.config:
loadPrcFile(prc) loadPrcFile(prc)
if os.path.isfile('dependencies/config/local.prc'):
loadPrcFile('dependencies/config/local.prc')
localconfig = '' localconfig = ''
if args.base_channel: localconfig += 'air-base-channel %s\n' % args.base_channel 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 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/general.prc')
loadPrcFile('dependencies/config/release/dev.prc') loadPrcFile('dependencies/config/release/dev.prc')
if os.path.isfile('dependencies/config/local.prc'):
loadPrcFile('dependencies/config/local.prc')
defaultText = "" defaultText = ""
def __inject_wx(_): def __inject_wx(_):

View file

@ -1,45 +1,74 @@
import anydbm
import base64
from direct.directnotify.DirectNotifyGlobal import directNotify from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
from direct.distributed.PyDatagram import * from direct.distributed.PyDatagram import *
from direct.fsm.FSM import FSM 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.ai.MagicWordGlobal import *
from otp.distributed import OtpDoGlobals from otp.distributed import OtpDoGlobals
from toontown.makeatoon.NameGenerator import NameGenerator from toontown.makeatoon.NameGenerator import NameGenerator
from toontown.toon.ToonDNA import ToonDNA from toontown.toon.ToonDNA import ToonDNA
from toontown.toonbase import TTLocalizer from toontown.toonbase import TTLocalizer
from toontown.uberdog import NameJudgeBlacklist from toontown.uberdog import NameJudgeBlacklist
from pandac.PandaModules import *
# Import from PyCrypto only if we are using a database that requires it. This import hashlib, hmac, json
# allows local hosted and developer builds of the game to run without it: import anydbm, math, os
accountDBType = simbase.config.GetString('accountdb-type', 'developer') 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': 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 if len(accountServerSecret) < 16:
# developer server: rejectConfig('the secret is too small! Make it 16+ bytes', retarded=False)
minAccessLevel = simbase.config.GetInt('min-access-level', 100)
accountServerEndpoint = simbase.config.GetString( secretLength = len(accountServerSecret)
'account-server-endpoint', 'http://tigercat1.me/tmpremote/api/') ideal = entropyIdeal(secretLength) / 2
accountServerSecret = simbase.config.GetString( entropy = entropy(accountServerSecret)
'account-server-secret', '9sj6816aj1hs795j') 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() hashSize = len(hashAlgo('').digest())
http.setVerifySsl(0)
minAccessLevel = config.GetInt('min-access-level', 100)
def executeHttpRequest(url, **extras): 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) request = urllib2.Request('http://tigercat1.me/tmpremote/api/' + url)
timestamp = str(int(time.time())) timestamp = str(int(time.time()))
signature = hashlib.sha256(timestamp + accountServerSecret + "h*^ahJGHA017JI&A&*uyhU07") signature = hashlib.sha256(timestamp + accountServerSecret + "h*^ahJGHA017JI&A&*uyhU07")
@ -53,10 +82,10 @@ def executeHttpRequest(url, **extras):
except: except:
return None return None
notify = directNotify.newCategory('ClientServicesManagerUD') notify = directNotify.newCategory('ClientServicesManagerUD')
def executeHttpRequestAndLog(url, **extras): def executeHttpRequestAndLog(url, **extras):
# SEE ABOVE
response = executeHttpRequest(url, extras) response = executeHttpRequest(url, extras)
if response is None: if response is None:
@ -93,20 +122,17 @@ def judgeName(name):
# --- ACCOUNT DATABASES --- # --- ACCOUNT DATABASES ---
# These classes make up the available account databases for Toontown Stride. # 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 # DeveloperAccountDB is a special database that accepts a username, and assigns
# each user with 700 access automatically upon login. # each user with 700 access automatically upon login.
class AccountDB: class AccountDB:
notify = directNotify.newCategory('AccountDB') notify = directNotify.newCategory('AccountDB')
def __init__(self, csm): def __init__(self, csm):
self.csm = csm self.csm = csm
filename = simbase.config.GetString( filename = config.GetString('account-bridge-filename', 'account-bridge.db')
'account-bridge-filename', 'account-bridge.db') filename = os.path.join('dependencies', filename)
filename = os.path.join("dependencies", filename)
self.dbm = anydbm.open(filename, 'c') self.dbm = anydbm.open(filename, 'c')
@ -119,8 +145,20 @@ class AccountDB:
def removeNameRequest(self, avId): def removeNameRequest(self, avId):
pass pass
def lookup(self, username, callback): def lookup(self, data, callback):
pass # Inheritors should override this. 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): def storeAccountID(self, userId, accountId, callback):
self.dbm[str(userId)] = str(accountId) # anydbm only allows strings. 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)) self.notify.warning('Unable to associate user %s with account %d!' % (userId, accountId))
callback(False) callback(False)
class DeveloperAccountDB(AccountDB): class DeveloperAccountDB(AccountDB):
notify = directNotify.newCategory('DeveloperAccountDB') notify = directNotify.newCategory('DeveloperAccountDB')
def lookup(self, username, callback): def lookup(self, userId, callback):
# Let's check if this user's ID is in your account database bridge: return AccountDB.lookup(self, {'userId': userId,
if str(username) not in self.dbm: 'accessLevel': 700,
# Nope. Let's associate them with a brand new Account object! We 'notAfter': 0},
# will assign them with 700 access just because they are a callback)
# 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
class RemoteAccountDB(AccountDB): 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') notify = directNotify.newCategory('RemoteAccountDB')
def addNameRequest(self, avId, name): def addNameRequest(self, avId, name):
@ -171,105 +196,43 @@ class RemoteAccountDB(AccountDB):
return executeHttpRequest('names/remove', ID=str(avId)) return executeHttpRequest('names/remove', ID=str(avId))
def lookup(self, token, callback): 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: try:
token = base64.b64decode(token) token = token.decode('base64')
except TypeError: hash, token = token[:hashSize], token[hashSize:]
self.notify.warning('Could not decode the provided token!')
response = {
'success': False,
'reason': "Can't decode this token."
}
callback(response)
return response
# Ensure this token is a valid size: correctHash = hashAlgo(token + accountServerSecret).digest()
if (not token) or ((len(token) % 16) != 0): if len(hash) != len(correctHash):
self.notify.warning('Invalid token length!') raise ValueError('invalid hash')
response = {
'success': False,
'reason': 'Invalid token length.'
}
callback(response)
return response
# Next, decrypt the token using AES-128 in CBC mode: value = 0
accountServerSecret = simbase.config.GetString( for x, y in zip(hash[::-1], correctHash):
'account-server-secret', '9sj6816aj1hs795j') value |= ord(x) ^ ord(y)
# Ensure that our secret is the correct size: if value:
if len(accountServerSecret) > AES.block_size: raise ValueError('invalid hash')
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'
# Take the initialization vector off the front of the token: token = json.loads(token.decode('base64')[::-1].decode('rot13'))
iv = token[:AES.block_size]
# Truncate the token to get our cipher text: except:
cipherText = token[AES.block_size:] resp = {'success': False}
callback(resp)
# Decrypt! return resp
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
return AccountDB.lookup(self, token, callback)
# --- FSMs --- # --- FSMs ---
class OperationFSM(FSM): class OperationFSM(FSM):
@ -294,7 +257,6 @@ class OperationFSM(FSM):
else: else:
del self.csm.account2fsm[self.target] del self.csm.account2fsm[self.target]
class LoginAccountFSM(OperationFSM): class LoginAccountFSM(OperationFSM):
notify = directNotify.newCategory('LoginAccountFSM') notify = directNotify.newCategory('LoginAccountFSM')
TARGET_CONNECTION = True TARGET_CONNECTION = True
@ -315,6 +277,7 @@ class LoginAccountFSM(OperationFSM):
self.userId = result.get('userId', 0) self.userId = result.get('userId', 0)
self.accountId = result.get('accountId', 0) self.accountId = result.get('accountId', 0)
self.accessLevel = result.get('accessLevel', 0) self.accessLevel = result.get('accessLevel', 0)
self.notAfter = result.get('notAfter', 0)
if self.accountId: if self.accountId:
self.demand('RetrieveAccount') self.demand('RetrieveAccount')
else: else:
@ -330,6 +293,12 @@ class LoginAccountFSM(OperationFSM):
return return
self.account = fields 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') self.demand('SetAccount')
def enterCreateAccount(self): def enterCreateAccount(self):
@ -339,6 +308,7 @@ class LoginAccountFSM(OperationFSM):
'ACCOUNT_AV_SET_DEL': [], 'ACCOUNT_AV_SET_DEL': [],
'CREATED': time.ctime(), 'CREATED': time.ctime(),
'LAST_LOGIN': time.ctime(), 'LAST_LOGIN': time.ctime(),
'LAST_LOGIN_TS': time.time(),
'ACCOUNT_ID': str(self.userId), 'ACCOUNT_ID': str(self.userId),
'ACCESS_LEVEL': self.accessLevel 'ACCESS_LEVEL': self.accessLevel
} }
@ -449,6 +419,7 @@ class LoginAccountFSM(OperationFSM):
self.accountId, self.accountId,
self.csm.air.dclassesByName['AccountUD'], self.csm.air.dclassesByName['AccountUD'],
{'LAST_LOGIN': time.ctime(), {'LAST_LOGIN': time.ctime(),
'LAST_LOGIN_TS': time.time(),
'ACCOUNT_ID': str(self.userId)}) 'ACCOUNT_ID': str(self.userId)})
# We're done. # We're done.
@ -547,7 +518,6 @@ class CreateAvatarFSM(OperationFSM):
self.csm.sendUpdateToAccountId(self.target, 'createAvatarResp', [self.avId]) self.csm.sendUpdateToAccountId(self.target, 'createAvatarResp', [self.avId])
self.demand('Off') self.demand('Off')
class AvatarOperationFSM(OperationFSM): class AvatarOperationFSM(OperationFSM):
POST_ACCOUNT_STATE = 'Off' # This needs to be overridden. POST_ACCOUNT_STATE = 'Off' # This needs to be overridden.
@ -570,7 +540,6 @@ class AvatarOperationFSM(OperationFSM):
self.demand(self.POST_ACCOUNT_STATE) self.demand(self.POST_ACCOUNT_STATE)
class GetAvatarsFSM(AvatarOperationFSM): class GetAvatarsFSM(AvatarOperationFSM):
notify = directNotify.newCategory('GetAvatarsFSM') notify = directNotify.newCategory('GetAvatarsFSM')
POST_ACCOUNT_STATE = 'QueryAvatars' POST_ACCOUNT_STATE = 'QueryAvatars'
@ -807,7 +776,6 @@ class SetNamePatternFSM(AvatarOperationFSM):
self.csm.sendUpdateToAccountId(self.target, 'setNamePatternResp', [self.avId, 1]) self.csm.sendUpdateToAccountId(self.target, 'setNamePatternResp', [self.avId, 1])
self.demand('Off') self.demand('Off')
class AcknowledgeNameFSM(AvatarOperationFSM): class AcknowledgeNameFSM(AvatarOperationFSM):
notify = directNotify.newCategory('AcknowledgeNameFSM') notify = directNotify.newCategory('AcknowledgeNameFSM')
POST_ACCOUNT_STATE = 'GetTargetAvatar' POST_ACCOUNT_STATE = 'GetTargetAvatar'
@ -863,7 +831,6 @@ class AcknowledgeNameFSM(AvatarOperationFSM):
self.csm.sendUpdateToAccountId(self.target, 'acknowledgeAvatarNameResp', []) self.csm.sendUpdateToAccountId(self.target, 'acknowledgeAvatarNameResp', [])
self.demand('Off') self.demand('Off')
class LoadAvatarFSM(AvatarOperationFSM): class LoadAvatarFSM(AvatarOperationFSM):
notify = directNotify.newCategory('LoadAvatarFSM') notify = directNotify.newCategory('LoadAvatarFSM')
POST_ACCOUNT_STATE = 'GetTargetAvatar' POST_ACCOUNT_STATE = 'GetTargetAvatar'
@ -1007,7 +974,6 @@ class UnloadAvatarFSM(OperationFSM):
self.csm.air.writeServerEvent('avatarUnload', self.avId) self.csm.air.writeServerEvent('avatarUnload', self.avId)
self.demand('Off') self.demand('Off')
# --- CLIENT SERVICES MANAGER UBERDOG --- # --- CLIENT SERVICES MANAGER UBERDOG ---
class ClientServicesManagerUD(DistributedObjectGlobalUD): class ClientServicesManagerUD(DistributedObjectGlobalUD):
notify = directNotify.newCategory('ClientServicesManagerUD') notify = directNotify.newCategory('ClientServicesManagerUD')

View file

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