Initial login handlers

This commit is contained in:
Ben 2019-04-27 00:12:42 +01:00
parent 8412f325ff
commit fcff0cd405
6 changed files with 139 additions and 8 deletions

View File

@ -184,7 +184,7 @@ class VersionChkConverter(IConverter):
description = """Used for checking the verChk version number""" description = """Used for checking the verChk version number"""
async def convert(self, ctx): async def convert(self, ctx):
return ctx.argument[0].get('v') return int(ctx.argument[0].get('v'))
class ConnectedPenguinConverter(IConverter): class ConnectedPenguinConverter(IConverter):

View File

@ -9,7 +9,9 @@ class Crypto:
@staticmethod @staticmethod
def hash(undigested): def hash(undigested):
return hashlib.md5(undigested.encode('utf-8')).hexdigest() if type(undigested) == str:
undigested = undigested.encode('utf-8')
return hashlib.md5(undigested).hexdigest()
@staticmethod @staticmethod
def generate_random_key(): def generate_random_key():

View File

@ -66,6 +66,11 @@ class Penguin(db.Model):
RejectionDe = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) RejectionDe = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
RejectionRu = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) RejectionRu = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
@property
def approval(self):
return int('{}{}0{}{}{}{}'.format(self.ApprovalRu * 1, self.ApprovalDe * 1, self.ApprovalEs * 1,
self.ApprovalFr * 1, self.ApprovalPt * 1, self.ApprovalEn * 1), 2)
class ActivationKey(db.Model): class ActivationKey(db.Model):
__tablename__ = 'activation_key' __tablename__ = 'activation_key'

View File

@ -1,14 +1,119 @@
from Houdini import Handlers from Houdini import Handlers
from Houdini.Handlers import XMLPacket from Houdini.Handlers import XMLPacket, Login
from Houdini.Converters import CredentialsConverter, VersionChkConverter from Houdini.Converters import CredentialsConverter
from Houdini.Data.Penguin import Penguin
from Houdini.Data.Buddy import BuddyList
from Houdini.Data.Moderator import Ban
from Houdini.Crypto import Crypto
import asyncio
import bcrypt
import time
import os
import config
from datetime import datetime
@Handlers.handler(XMLPacket('login')) @Handlers.handler(XMLPacket('login'))
async def handle_login(p, credentials: CredentialsConverter): async def handle_login(p, credentials: CredentialsConverter):
loop = asyncio.get_event_loop()
login_timestamp = time.time()
username, password = credentials username, password = credentials
p.logger.info('{}:{} is logging in!'.format(username, password)) p.logger.info('{} is logging in!'.format(username))
data = await Penguin.query.where(Penguin.Username == username).gino.first()
@Handlers.handler(XMLPacket('verChk')) if data is None:
async def handle_version_check(p, version: VersionChkConverter): p.logger.info('{} failed to login: penguin does not exist')
p.logger.info('Version: {}'.format(version)) await p.send_error_and_disconnect(100)
password_correct = await loop.run_in_executor(None, bcrypt.checkpw,
password.encode('utf-8'), data.Password.encode('utf-8'))
ip_addr = p.peer_name[0]
if not password_correct:
p.logger.info('{} failed to login: incorrect password'.format(username))
if ip_addr in p.server.login_attempts:
last_failed_attempt, failure_count = p.server.login_attempts[ip_addr]
failure_count = 1 if login_timestamp - last_failed_attempt >= p.server.server_config['LoginFailureTimer'] \
else failure_count + 1
p.server.login_attempts[ip_addr] = [login_timestamp, failure_count]
if failure_count >= p.server.server_config['LoginFailureLimit']:
return await p.send_error_and_disconnect(150)
else:
p.server.login_attempts[ip_addr] = [login_timestamp, 1]
return await p.send_error_and_disconnect(101)
if ip_addr in p.server.login_attempts:
previous_attempt, failure_count = p.server.login_attempts[ip_addr]
max_attempts_exceeded = failure_count >= p.server.server_config['LoginFailureLimit']
timer_surpassed = (login_timestamp - previous_attempt) > p.server.server_config['LoginFailureTimer']
if max_attempts_exceeded and not timer_surpassed:
return await p.send_error_and_disconnect(150)
else:
del p.server.login_attempts[ip_addr]
if not data.Active:
return await p.send_error_and_disconnect(900)
if data.Permaban:
return await p.send_error_and_disconnect(603)
active_ban = await Ban.query.where(Ban.PenguinID == data.ID and Ban.Expires >= datetime.now()).gino.first()
if active_ban is not None:
hours_left = round((active_ban.Expires - datetime.now()).total_seconds() / 60 / 60)
if hours_left == 0:
return await p.send_error_and_disconnect(602)
else:
await p.send_error_and_disconnect(601, hours_left)
p.logger.info('{} has logged in successfully'.format(username))
random_key = Crypto.generate_random_key()
login_key = Crypto.hash(random_key[::-1])
confirmation_hash = Crypto.hash(os.urandom(24))
await p.server.redis.setex('{}.lkey'.format(data.ID), p.server.server_config['KeyTTL'], login_key)
await p.server.redis.setex('{}.ckey'.format(data.ID), p.server.server_config['KeyTTL'], confirmation_hash)
buddy_worlds = []
world_populations = []
servers_config = config.servers
for server_name, server_config in servers_config.items():
if server_config['World']:
server_population = await p.server.redis.get('{}.population'.format(server_name))
server_population = int(server_population) / (server_config['Capacity'] / 6) if server_population else 0
server_players = await p.server.redis.smembers('{}.players'.format(server_name))
world_populations.append('{},{}'.format(server_config['Id'], server_population))
if not len(server_players) > 0:
p.logger.debug('Skipping buddy iteration for {}'.format(server_name))
continue
buddies = await BuddyList.select('BuddyID').where(BuddyList.PenguinID == data.ID).gino.all()
for buddy_id in buddies:
if str(buddy_id) in server_players:
buddy_worlds.append(server_config['Id'])
break
raw_login_data = '|'.join([str(data.ID), str(data.ID), data.Username, login_key, str(data.approval), '1'])
await p.send_xt('l', raw_login_data, confirmation_hash, 'friendsKey', '|'.join(world_populations), data.Email)
handle_version_check = Login.handle_version_check
handle_random_key = Login.handle_random_key

View File

@ -0,0 +1,17 @@
from Houdini import Handlers
from Houdini.Handlers import XMLPacket
from Houdini.Converters import VersionChkConverter
@Handlers.handler(XMLPacket('verChk'))
async def handle_version_check(p, version: VersionChkConverter):
if not version == 153:
await p.send_xml({'body': {'action': 'apiKO', 'r': '0'}})
await p.close()
else:
await p.send_xml({'body': {'action': 'apiOK', 'r': '0'}})
@Handlers.handler(XMLPacket('rndK'))
async def handle_random_key(p, data):
await p.send_xml({'body': {'action': 'rndK', 'r': '-1'}, 'k': 'houdini'})

View File

@ -68,6 +68,8 @@ class HoudiniFactory:
self.penguins_by_id = {} self.penguins_by_id = {}
self.penguins_by_username = {} self.penguins_by_username = {}
self.login_attempts = {}
self.xt_listeners, self.xml_listeners = {}, {} self.xt_listeners, self.xml_listeners = {}, {}
self.commands = {} self.commands = {}
self.plugins = {} self.plugins = {}