46 lines
3.6 KiB
Python
46 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Prepare isolated Classic authentication, preserving ENDAMI's SRP credentials.
|
|
Does not stop Wrath or alter any Wrath account. Secrets are never printed.
|
|
"""
|
|
import argparse,base64,hashlib,json,os,pathlib,re,secrets,subprocess
|
|
|
|
def main(root):
|
|
credentials=json.loads((root/'database-credentials.json').read_text())
|
|
def query(container,password,sql):
|
|
result=subprocess.run(['docker','exec','-i','-e','MYSQL_PWD',container,'mysql','-uroot','-N','-B'],input=sql,text=True,capture_output=True,env=dict(os.environ,MYSQL_PWD=password))
|
|
if result.returncode:raise RuntimeError('Authentication database operation failed')
|
|
return result.stdout.strip()
|
|
def classic(sql):return query('azerothai-classic-db',credentials['root'],sql)
|
|
account_file=root/'classic-gm-credentials.json'
|
|
if account_file.exists():gm=json.loads(account_file.read_text())
|
|
else:
|
|
gm={'username':'AZCLASSICGM','password':secrets.token_hex(8)}
|
|
account_file.write_text(json.dumps(gm));account_file.chmod(0o600)
|
|
salt=secrets.token_bytes(32)
|
|
digest=hashlib.sha1((gm['username'].upper()+':'+gm['password'].upper()).encode()).digest()
|
|
exponent=int.from_bytes(hashlib.sha1(salt+digest).digest(),'little')
|
|
verifier=pow(7,exponent,int('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7',16))
|
|
assert re.fullmatch('[A-Z0-9]+',gm['username'])
|
|
classic(f"INSERT INTO classicrealmd.account(username,s,v,gmlevel) VALUES ('{gm['username']}','{salt[::-1].hex()}','{verifier:X}',3) ON DUPLICATE KEY UPDATE gmlevel=3,s=VALUES(s),v=VALUES(v);")
|
|
# Disable upstream sample logins without deleting accounts or characters.
|
|
classic("UPDATE classicrealmd.account SET locked=1,lockedIp='disabled' WHERE id IN (1,2,3,4) AND username IN ('ADMINISTRATOR','GAMEMASTER','MODERATOR','PLAYER');")
|
|
config=json.loads(subprocess.check_output(['docker','inspect','ac-database']))[0]
|
|
env=dict(v.split('=',1) for v in config['Config']['Env'] if '=' in v)
|
|
password=env.get('MYSQL_ROOT_PASSWORD') or env.get('MARIADB_ROOT_PASSWORD')
|
|
row=query('ac-database',password,"SELECT a.username,HEX(a.salt),HEX(a.verifier),MAX(x.gmlevel) FROM acore_auth.account a JOIN acore_auth.account_access x ON x.id=a.id WHERE a.username='ENDAMI' GROUP BY a.id;").split('\t')
|
|
if len(row)!=4 or row[0]!='ENDAMI':raise RuntimeError('Expected existing human GM account was not found')
|
|
salt=bytes.fromhex(row[1])[::-1].hex();verifier=bytes.fromhex(row[2])[::-1].hex()
|
|
classic(f"INSERT INTO classicrealmd.account(username,s,v,gmlevel) VALUES ('ENDAMI','{salt}','{verifier}',3) ON DUPLICATE KEY UPDATE s=VALUES(s),v=VALUES(v),gmlevel=3;")
|
|
classic("UPDATE classicrealmd.realmlist SET name='AzerothAI Classic',address='10.0.0.125',port=8086,realmflags=0,allowedSecurityLevel=0 WHERE id=1;")
|
|
text=(root/'run/etc/realmd.conf.dist').read_text()
|
|
values={'LoginDatabaseInfo':'"azerothai-classic-db;3306;'+credentials['user']+';'+credentials['password']+';classicrealmd"','RealmServerPort':'3724','BindIP':'"0.0.0.0"','LogsDir':'"/work/run/logs"'}
|
|
for key,value in values.items():
|
|
pattern=r'^'+re.escape(key)+r'\s*=.*$';line=key+' = '+value
|
|
text=re.sub(pattern,lambda _:line,text,flags=re.M) if re.search(pattern,text,re.M) else text+'\n'+line+'\n'
|
|
path=root/'run/etc/realmd.conf';path.write_text(text);path.chmod(0o600)
|
|
print('Classic GM credentials prepared; ENDAMI password verifier preserved; sample logins disabled.')
|
|
|
|
if __name__=='__main__':
|
|
parser=argparse.ArgumentParser();parser.add_argument('--root',type=pathlib.Path,required=True)
|
|
main(parser.parse_args().root.resolve())
|