.BANCHO. pep.py modules

This commit is contained in:
Nyo
2016-05-18 19:12:46 +02:00
parent 993079d2dd
commit 9325578377
74 changed files with 212 additions and 212 deletions

107
helpers/configHelper.py Normal file
View File

@@ -0,0 +1,107 @@
import os
import configparser
class config:
"""
config.ini object
config -- list with ini data
default -- if true, we have generated a default config.ini
"""
config = configparser.ConfigParser()
fileName = "" # config filename
default = True
# Check if config.ini exists and load/generate it
def __init__(self, __file):
"""
Initialize a config object
__file -- filename
"""
self.fileName = __file
if os.path.isfile(self.fileName):
# config.ini found, load it
self.config.read(self.fileName)
self.default = False
else:
# config.ini not found, generate a default one
self.generateDefaultConfig()
self.default = True
# Check if config.ini has all needed the keys
def checkConfig(self):
"""
Check if this config has the required keys
return -- True if valid, False if not
"""
try:
# Try to get all the required keys
self.config.get("db","host")
self.config.get("db","username")
self.config.get("db","password")
self.config.get("db","database")
self.config.get("db","pingtime")
self.config.get("server","server")
self.config.get("server","host")
self.config.get("server","port")
self.config.get("server","localizeusers")
self.config.get("server","outputpackets")
self.config.get("server","outputrequesttime")
self.config.get("server","timeouttime")
self.config.get("server","timeoutlooptime")
if self.config["server"]["server"] == "flask":
# Flask only config
self.config.get("flask","threaded")
self.config.get("flask","debug")
self.config.get("flask","logger")
self.config.get("ci","key")
return True
except:
return False
# Generate a default config.ini
def generateDefaultConfig(self):
"""Open and set default keys for that config file"""
# Open config.ini in write mode
f = open(self.fileName, "w")
# Set keys to config object
self.config.add_section("db")
self.config.set("db", "host", "localhost")
self.config.set("db", "username", "root")
self.config.set("db", "password", "")
self.config.set("db", "database", "ripple")
self.config.set("db", "pingtime", "600")
self.config.add_section("server")
self.config.set("server", "server", "tornado")
self.config.set("server", "host", "0.0.0.0")
self.config.set("server", "port", "5001")
self.config.set("server", "localizeusers", "1")
self.config.set("server", "outputpackets", "0")
self.config.set("server", "outputrequesttime", "0")
self.config.set("server", "timeoutlooptime", "100")
self.config.set("server", "timeouttime", "100")
self.config.add_section("flask")
self.config.set("flask", "threaded", "1")
self.config.set("flask", "debug", "0")
self.config.set("flask", "logger", "0")
self.config.add_section("ci")
self.config.set("ci", "key", "changeme")
# Write ini to file and close
self.config.write(f)
f.close()

View File

@@ -1,7 +1,7 @@
"""Some console related functions"""
from constants import bcolors
import glob
from objects import glob
def printServerStartHeader(asciiArt):
"""Print server start header with optional ascii art

282
helpers/countryHelper.py Normal file
View File

@@ -0,0 +1,282 @@
"""Contains all country codes with their osu numeric code"""
countryCodes = {
"LV": 132,
"AD": 3,
"LT": 130,
"KM": 116,
"QA": 182,
"VA": 0,
"PK": 173,
"KI": 115,
"SS": 0,
"KH": 114,
"NZ": 166,
"TO": 215,
"KZ": 122,
"GA": 76,
"BW": 35,
"AX": 247,
"GE": 79,
"UA": 222,
"CR": 50,
"AE": 0,
"NE": 157,
"ZA": 240,
"SK": 196,
"BV": 34,
"SH": 0,
"PT": 179,
"SC": 189,
"CO": 49,
"GP": 86,
"GY": 93,
"CM": 47,
"TJ": 211,
"AF": 5,
"IE": 101,
"AL": 8,
"BG": 24,
"JO": 110,
"MU": 149,
"PM": 0,
"LA": 0,
"IO": 104,
"KY": 121,
"SA": 187,
"KN": 0,
"OM": 167,
"CY": 54,
"BQ": 0,
"BT": 33,
"WS": 236,
"ES": 67,
"LR": 128,
"RW": 186,
"AQ": 12,
"PW": 180,
"JE": 250,
"TN": 214,
"ZW": 243,
"JP": 111,
"BB": 20,
"VN": 233,
"HN": 96,
"KP": 0,
"WF": 235,
"EC": 62,
"HU": 99,
"GF": 80,
"GQ": 87,
"TW": 220,
"MC": 135,
"BE": 22,
"PN": 176,
"SZ": 205,
"CZ": 55,
"LY": 0,
"IN": 103,
"FM": 0,
"PY": 181,
"PH": 172,
"MN": 142,
"GG": 248,
"CC": 39,
"ME": 242,
"DO": 60,
"KR": 0,
"PL": 174,
"MT": 148,
"MM": 141,
"AW": 17,
"MV": 150,
"BD": 21,
"NR": 164,
"AT": 15,
"GW": 92,
"FR": 74,
"LI": 126,
"CF": 41,
"DZ": 61,
"MA": 134,
"VG": 0,
"NC": 156,
"IQ": 105,
"BN": 0,
"BF": 23,
"BO": 30,
"GB": 77,
"CU": 51,
"LU": 131,
"YT": 238,
"NO": 162,
"SM": 198,
"GL": 83,
"IS": 107,
"AO": 11,
"MH": 138,
"SE": 191,
"ZM": 241,
"FJ": 70,
"SL": 197,
"CH": 43,
"RU": 0,
"CW": 0,
"CX": 53,
"TF": 208,
"NL": 161,
"AU": 16,
"FI": 69,
"MS": 147,
"GH": 81,
"BY": 36,
"IL": 102,
"VC": 0,
"NG": 159,
"HT": 98,
"LS": 129,
"MR": 146,
"YE": 237,
"MP": 144,
"SX": 0,
"RE": 183,
"RO": 184,
"NP": 163,
"CG": 0,
"FO": 73,
"CI": 0,
"TH": 210,
"HK": 94,
"TK": 212,
"XK": 0,
"DM": 59,
"LC": 0,
"ID": 100,
"MG": 137,
"JM": 109,
"IT": 108,
"CA": 38,
"TZ": 221,
"GI": 82,
"KG": 113,
"NU": 165,
"TV": 219,
"LB": 124,
"SY": 0,
"PR": 177,
"NI": 160,
"KE": 112,
"MO": 0,
"SR": 201,
"VI": 0,
"SV": 203,
"HM": 0,
"CD": 0,
"BI": 26,
"BM": 28,
"MW": 151,
"TM": 213,
"GT": 90,
"AG": 0,
"UM": 0,
"US": 225,
"AR": 13,
"DJ": 57,
"KW": 120,
"MY": 153,
"FK": 71,
"EG": 64,
"BA": 0,
"CN": 48,
"GN": 85,
"PS": 178,
"SO": 200,
"IM": 249,
"GS": 0,
"BR": 31,
"GM": 84,
"PF": 170,
"PA": 168,
"PG": 171,
"BH": 25,
"TG": 209,
"GU": 91,
"CK": 45,
"MF": 252,
"VE": 230,
"CL": 46,
"TR": 217,
"UG": 223,
"GD": 78,
"TT": 218,
"TL": 0,
"MD": 0,
"MK": 0,
"ST": 202,
"CV": 52,
"MQ": 145,
"GR": 88,
"HR": 97,
"BZ": 37,
"UZ": 227,
"DK": 58,
"SN": 199,
"ET": 68,
"VU": 234,
"ER": 66,
"BJ": 27,
"LK": 127,
"NA": 155,
"AS": 14,
"SG": 192,
"PE": 169,
"IR": 0,
"MX": 152,
"TD": 207,
"AZ": 18,
"AM": 9,
"BL": 0,
"SJ": 195,
"SB": 188,
"NF": 158,
"RS": 239,
"DE": 56,
"EH": 65,
"EE": 63,
"SD": 190,
"ML": 140,
"TC": 206,
"MZ": 154,
"BS": 32,
"UY": 226,
"SI": 194,
"AI": 7
}
def getCountryID(code):
"""
Get country ID for osu client
code -- country name abbreviation (eg: US)
return -- country code int
"""
if code in countryCodes:
return countryCodes[code]
else:
return 0
def getCountryLetters(code):
"""
Get country letters from osu country ID
code -- country code int
return -- country name (2 letters) (XX if code not found)
"""
for key, value in countryCodes.items():
if value == code:
return key
return "XX"

302
helpers/cryptHelper.py Normal file
View File

@@ -0,0 +1,302 @@
# Huge thanks to Cairnarvon
# https://gist.github.com/Cairnarvon/5075687
# Initial permutation
IP = (
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7,
)
# Final permutation, FP = IP^(-1)
FP = (
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25,
)
# Permuted-choice 1 from the key bits to yield C and D.
# Note that bits 8,16... are left out: They are intended for a parity check.
PC1_C = (
57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
)
PC1_D = (
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4,
)
# Permuted-choice 2, to pick out the bits from the CD array that generate the
# key schedule.
PC2_C = (
14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
)
PC2_D = (
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32,
)
# The C and D arrays are used to calculate the key schedule.
C = [0] * 28
D = [0] * 28
# The key schedule. Generated from the key.
KS = [[0] * 48 for _ in range(16)]
# The E bit-selection table.
E = [0] * 48
e2 = (
32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1,
)
# S-boxes.
S = (
(
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13
),
(
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
),
(
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
),
(
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
),
(
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
),
(
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
),
(
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
),
(
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
)
)
# P is a permutation on the selected combination of the current L and key.
P = (
16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25,
)
# The combination of the key and the input, before selection.
preS = [0] * 48
def __setkey(key):
"""
Set up the key schedule from the encryption key.
"""
global C, D, KS, E
shifts = (1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1)
# First, generate C and D by permuting the key. The lower order bit of each
# 8-bit char is not used, so C and D are only 28 bits apiece.
for i in range(28):
C[i] = key[PC1_C[i] - 1]
D[i] = key[PC1_D[i] - 1]
for i in range(16):
# rotate
for k in range(shifts[i]):
temp = C[0]
for j in range(27):
C[j] = C[j + 1]
C[27] = temp
temp = D[0]
for j in range(27):
D[j] = D[j + 1]
D[27] = temp
# get Ki. Note C and D are concatenated
for j in range(24):
KS[i][j] = C[PC2_C[j] - 1]
KS[i][j + 24] = D[PC2_D[j] - 28 - 1]
# load E with the initial E bit selections
for i in range(48):
E[i] = e2[i]
def __encrypt(block):
global preS
left, right = [], [] # block in two halves
f = [0] * 32
# First, permute the bits in the input
for j in range(32):
left.append(block[IP[j] - 1])
for j in range(32, 64):
right.append(block[IP[j] - 1])
# Perform an encryption operation 16 times.
for i in range(16):
# Save the right array, which will be the new left.
old = right[:]
# Expand right to 48 bits using the E selector and exclusive-or with
# the current key bits.
for j in range(48):
preS[j] = right[E[j] - 1] ^ KS[i][j]
# The pre-select bits are now considered in 8 groups of 6 bits each.
# The 8 selection functions map these 6-bit quantities into 4-bit
# quantities and the results are permuted to make an f(R, K).
# The indexing into the selection functions is peculiar; it could be
# simplified by rewriting the tables.
for j in range(8):
temp = 6 * j
k = S[j][(preS[temp + 0] << 5) +
(preS[temp + 1] << 3) +
(preS[temp + 2] << 2) +
(preS[temp + 3] << 1) +
(preS[temp + 4] << 0) +
(preS[temp + 5] << 4)]
temp = 4 * j
f[temp + 0] = (k >> 3) & 1
f[temp + 1] = (k >> 2) & 1
f[temp + 2] = (k >> 1) & 1
f[temp + 3] = (k >> 0) & 1
# The new right is left ^ f(R, K).
# The f here has to be permuted first, though.
for j in range(32):
right[j] = left[j] ^ f[P[j] - 1]
# Finally the new left (the original right) is copied back.
left = old
# The output left and right are reversed.
left, right = right, left
# The final output gets the inverse permutation of the very original
for j in range(64):
i = FP[j]
if i < 33:
block[j] = left[i - 1]
else:
block[j] = right[i - 33]
return block
def crypt(pw, salt):
iobuf = []
# break pw into 64 bits
block = []
for c in pw:
c = ord(c)
for j in range(7):
block.append((c >> (6 - j)) & 1)
block.append(0)
block += [0] * (64 - len(block))
# set key based on pw
__setkey(block)
for i in range(2):
# store salt at beginning of results
iobuf.append(salt[i])
c = ord(salt[i])
if c > ord('Z'):
c -= 6
if c > ord('9'):
c -= 7
c -= ord('.')
# use salt to effect the E-bit selection
for j in range(6):
if (c >> j) & 1:
E[6 * i + j], E[6 * i + j + 24] = E[6 * i + j + 24], E[6 * i + j]
# call DES encryption 25 times using pw as key and initial data = 0
block = [0] * 66
for i in range(25):
block = __encrypt(block)
# format encrypted block for standard crypt(3) output
for i in range(11):
c = 0
for j in range(6):
c <<= 1
c |= block[6 * i + j]
c += ord('.')
if c > ord('9'):
c += 7
if c > ord('Z'):
c += 6
iobuf.append(chr(c))
return ''.join(iobuf)

138
helpers/databaseHelper.py Normal file
View File

@@ -0,0 +1,138 @@
import pymysql
from constants import bcolors
from helpers import consoleHelper
import threading
from objects import glob
class db:
"""A MySQL database connection"""
connection = None
disconnected = False
pingTime = 600
def __init__(self, __host, __username, __password, __database, __pingTime = 600):
"""
Connect to MySQL database
__host -- MySQL host name
__username -- MySQL username
__password -- MySQL password
__database -- MySQL database name
__pingTime -- MySQL database ping time (default: 600)
"""
self.connection = pymysql.connect(host=__host, user=__username, password=__password, db=__database, cursorclass=pymysql.cursors.DictCursor, autocommit=True)
self.pingTime = __pingTime
self.pingLoop()
def bindParams(self, __query, __params):
"""
Replace every ? with the respective **escaped** parameter in array
__query -- query with ?s
__params -- array with params
return -- new query
"""
for i in __params:
escaped = self.connection.escape(i)
__query = __query.replace("?", str(escaped), 1)
return __query
def execute(self, __query, __params = None):
"""
Execute a SQL query
__query -- query, can contain ?s
__params -- array with params. Optional
"""
with self.connection.cursor() as cursor:
try:
# Bind params if needed
if __params != None:
__query = self.bindParams(__query, __params)
# Execute the query
cursor.execute(__query)
finally:
# Close this connection
cursor.close()
def fetch(self, __query, __params = None, __all = False):
"""
Fetch the first (or all) element(s) of SQL query result
__query -- query, can contain ?s
__params -- array with params. Optional
__all -- if true, will fetch all values. Same as fetchAll
return -- dictionary with result data or False if failed
"""
with self.connection.cursor() as cursor:
try:
# Bind params if needed
if __params != None:
__query = self.bindParams(__query, __params)
# Execute the query with binded params
cursor.execute(__query)
# Get first result and return it
if __all == False:
return cursor.fetchone()
else:
return cursor.fetchall()
finally:
# Close this connection
cursor.close()
def fetchAll(self, __query, __params = None):
"""
Fetch the all elements of SQL query result
__query -- query, can contain ?s
__params -- array with params. Optional
return -- dictionary with result data
"""
return self.fetch(__query, __params, True)
def pingLoop(self):
"""
Pings MySQL server. We need to ping/execute a query at least once every 8 hours
or the connection will die.
If called once, will recall after 30 minutes and so on, forever
CALL THIS FUNCTION ONLY ONCE!
"""
# Default loop time
time = self.pingTime
# Make sure the connection is alive
try:
# Try to ping and reconnect if not connected
self.connection.ping()
if self.disconnected == True:
# If we were disconnected, set disconnected to false and print message
self.disconnected = False
consoleHelper.printColored("> Reconnected to MySQL server!", bcolors.GREEN)
except:
# Can't ping MySQL server. Show error and call loop in 5 seconds
consoleHelper.printColored("[!] CRITICAL!! MySQL connection died! Make sure your MySQL server is running! Checking again in 5 seconds...", bcolors.RED)
self.disconnected = True
time = 5
# Schedule a new check (endless loop)
threading.Timer(time, self.pingLoop).start()

View File

@@ -0,0 +1,22 @@
"""Some functions that don't fit in any other file"""
def stringToBool(s):
"""
Convert a string (True/true/1) to bool
s -- string/int value
return -- True/False
"""
return (s == "True" or s== "true" or s == "1" or s == 1)
def hexString(s):
"""
Output s' bytes in HEX
s -- string
return -- string with hex value
"""
return ":".join("{:02x}".format(ord(c)) for c in s)

48
helpers/locationHelper.py Normal file
View File

@@ -0,0 +1,48 @@
import urllib.request
import json
from helpers import consoleHelper
from constants import bcolors
# API URL
URL = "http://ip.zxq.co/"
def getCountry(ip):
"""
Get country from IP address
ip -- IP Address
return -- Country code (2 letters)
"""
# Default value, sent if API is memeing
country = "XX"
try:
# Try to get country from Pikolo Aul's Go-Sanic ip API
result = json.loads(urllib.request.urlopen("{}/{}".format(URL, ip), timeout=3).read().decode())["country"]
return result.upper()
except:
consoleHelper.printColored("[!] Error in get country", bcolors.RED)
return "XX"
def getLocation(ip):
"""
Get latitude and longitude from IP address
ip -- IP address
return -- [latitude, longitude]
"""
# Default value, sent if API is memeing
data = [0,0]
try:
# Try to get position from Pikolo Aul's Go-Sanic ip API
result = json.loads(urllib.request.urlopen("{}/{}".format(URL, ip), timeout=3).read().decode())["loc"].split(",")
return [float(result[0]), float(result[1])]
except:
consoleHelper.printColored("[!] Error in get position", bcolors.RED)
return [0,0]

249
helpers/packetHelper.py Normal file
View File

@@ -0,0 +1,249 @@
import struct
from constants import dataTypes
def uleb128Encode(num):
"""
Encode int -> uleb128
num -- int to encode
return -- bytearray with encoded number
"""
arr = bytearray()
length = 0
if num == 0:
return bytearray(b"\x00")
while num > 0:
arr.append(num & 127)
num = num >> 7
if num != 0:
arr[length] = arr[length] | 128
length+=1
return arr
def uleb128Decode(num):
"""
Decode uleb128 -> int
num -- encoded uleb128
return -- list. [total, length]
"""
shift = 0
arr = [0,0] #total, length
while True:
b = num[arr[1]]
arr[1]+=1
arr[0] = arr[0] | (int(b & 127) << shift)
if b & 128 == 0:
break
shift += 7
return arr
def unpackData(__data, __dataType):
"""
Unpacks data according to dataType
__data -- bytes array to unpack
__dataType -- data type. See dataTypes.py
return -- unpacked bytes
"""
# Get right pack Type
if __dataType == dataTypes.uInt16:
unpackType = "<H"
elif __dataType == dataTypes.sInt16:
unpackType = "<h"
elif __dataType == dataTypes.uInt32:
unpackType = "<L"
elif __dataType == dataTypes.sInt32:
unpackType = "<l"
elif __dataType == dataTypes.uInt64:
unpackType = "<Q"
elif __dataType == dataTypes.sInt64:
unpackType = "<q"
elif __dataType == dataTypes.string:
unpackType = "<s"
elif __dataType == dataTypes.ffloat:
unpackType = "<f"
else:
unpackType = "<B"
# Unpack
return struct.unpack(unpackType, bytes(__data))[0]
def packData(__data, __dataType):
"""
Packs data according to dataType
data -- bytes to pack
dataType -- data type. See dataTypes.py
return -- packed bytes
"""
data = bytes() # data to return
pack = True # if True, use pack. False only with strings
# Get right pack Type
if __dataType == dataTypes.bbytes:
# Bytes, do not use pack, do manually
pack = False
data = __data
elif __dataType == dataTypes.string:
# String, do not use pack, do manually
pack = False
if len(__data) == 0:
# Empty string
data += b"\x00"
else:
# Non empty string
data += b"\x0B"
data += uleb128Encode(len(__data))
data += str.encode(__data, "latin_1")
elif __dataType == dataTypes.uInt16:
packType = "<H"
elif __dataType == dataTypes.sInt16:
packType = "<h"
elif __dataType == dataTypes.uInt32:
packType = "<L"
elif __dataType == dataTypes.sInt32:
packType = "<l"
elif __dataType == dataTypes.uInt64:
packType = "<Q"
elif __dataType == dataTypes.sInt64:
packType = "<q"
elif __dataType == dataTypes.string:
packType = "<s"
elif __dataType == dataTypes.ffloat:
packType = "<f"
else:
packType = "<B"
# Pack if needed
if pack == True:
data += struct.pack(packType, __data)
return data
# TODO: Wat dangerous
def buildPacket(__packet, __packetData = []):
"""
Build a packet
packet -- packet id (int)
packetData -- list [[data, dataType], [data, dataType], ...]
return -- packet bytes
"""
# Set some variables
packetData = bytes()
packetLength = 0
packetBytes = bytes()
# Pack packet data
for i in __packetData:
packetData += packData(i[0], i[1])
# Set packet length
packetLength = len(packetData)
# Return packet as bytes
packetBytes += struct.pack("<h", __packet) # packet id (int16)
packetBytes += bytes(b"\x00") # unused byte
packetBytes += struct.pack("<l", packetLength) # packet lenght (iint32)
packetBytes += packetData # packet data
return packetBytes
def readPacketID(__stream):
"""
Read packetID from __stream (0-1 bytes)
__stream -- data stream
return -- packet ID (int)
"""
return unpackData(__stream[0:2], dataTypes.uInt16)
def readPacketLength(__stream):
"""
Read packet length from __stream (3-4-5-6 bytes)
__stream -- data stream
return -- packet length (int)
"""
return unpackData(__stream[3:7], dataTypes.uInt32)
def readPacketData(__stream, __structure = [], __hasFirstBytes = True):
"""
Read packet data from __stream according to __structure
__stream -- data stream
__structure -- [[name, dataType], [name, dataType], ...]
__hasFirstBytes -- if True, __stream has packetID and length bytes.
if False, __stream has only packetData.
Optional. Default: True
return -- dictionary. key: name, value: read data
"""
# Read packet ID (first 2 bytes)
data = {}
# Skip packet ID and packet length if needed
if __hasFirstBytes == True:
end = 7
start = 7
else:
end = 0
start = 0
# Read packet
for i in __structure:
start = end
unpack = True
if i[1] == dataTypes.string:
# String, don't unpack
unpack = False
# Check empty string
if __stream[start] == 0:
# Empty string
data[i[0]] = ""
end = start+1
else:
# Non empty string
# Read length and calculate end
length = uleb128Decode(__stream[start+1:])
end = start+length[0]+length[1]+1
# Read bytes
data[i[0]] = ''.join(chr(j) for j in __stream[start+1+length[1]:end])
elif i[1] == dataTypes.byte:
end = start+1
elif i[1] == dataTypes.uInt16 or i[1] == dataTypes.sInt16:
end = start+2
elif i[1] == dataTypes.uInt32 or i[1] == dataTypes.sInt32:
end = start+4
elif i[1] == dataTypes.uInt64 or i[1] == dataTypes.sInt64:
end = start+8
# Unpack if needed
if unpack == True:
data[i[0]] = unpackData(__stream[start:end], i[1])
return data

36
helpers/passwordHelper.py Normal file
View File

@@ -0,0 +1,36 @@
from helpers import cryptHelper
import base64
import bcrypt
def checkOldPassword(password, salt, rightPassword):
"""
Check if password+salt corresponds to rightPassword
password -- input password
salt -- password's salt
rightPassword -- right password
return -- bool
"""
return (rightPassword == cryptHelper.crypt(password, "$2y$"+str(base64.b64decode(salt))))
def checkNewPassword(password, dbPassword):
"""
Check if a password (version 2) is right.
password -- input password
dbPassword -- the password in the database
return -- bool
"""
password = password.encode("utf8")
dbPassword = dbPassword.encode("utf8")
return bcrypt.hashpw(password, dbPassword) == dbPassword
def genBcrypt(password):
"""
Bcrypts a password.
password -- the password to hash.
return -- bytestring
"""
return bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt(10, b'2a'))

47
helpers/responseHelper.py Normal file
View File

@@ -0,0 +1,47 @@
import flask
import gzip
def generateResponse(token, data = None):
"""
Return a flask response with required headers for osu! client, token and gzip compressed data
token -- user token
data -- plain response body
return -- flask response
"""
resp = flask.Response(gzip.compress(data, 6))
resp.headers['cho-token'] = token
resp.headers['cho-protocol'] = '19'
resp.headers['Keep-Alive'] = 'timeout=5, max=100'
resp.headers['Connection'] = 'keep-alive'
resp.headers['Content-Type'] = 'text/html; charset=UTF-8'
resp.headers['Vary'] = 'Accept-Encoding'
resp.headers['Content-Encoding'] = 'gzip'
return resp
def HTMLResponse():
"""Return HTML bancho meme response"""
html = "<html><head><title>MA MAURO ESISTE?</title><style type='text/css'>body{width:30%}</style></head><body><pre>"
html += " _ __<br>"
html += " (_) / /<br>"
html += " ______ __ ____ ____ / /____<br>"
html += " / ___/ / _ \\/ _ \\/ / _ \\<br>"
html += " / / / / /_) / /_) / / ____/<br>"
html += "/__/ /__/ .___/ .___/__/ \\_____/<br>"
html += " / / / /<br>"
html += " /__/ /__/<br>"
html += "<b>PYTHON > ALL VERSION</b><br><br>"
html += "<marquee style='white-space:pre;'><br>"
html += " .. o .<br>"
html += " o.o o . o<br>"
html += " oo...<br>"
html += " __[]__<br>"
html += " phwr--> _\\:D/_/o_o_o_|__ <span style=\"font-family: 'Comic Sans MS'; font-size: 8pt;\">u wot m8</span><br>"
html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/<br>"
html += " \\ . .. .. . /<br>"
html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>"
html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.</pre></body></html>"
return html

91
helpers/systemHelper.py Normal file
View File

@@ -0,0 +1,91 @@
from objects import glob
from constants import serverPackets
import psutil
import os
import sys
from helpers import consoleHelper
from constants import bcolors
import threading
import signal
def runningUnderUnix():
"""
Get if the server is running under UNIX or NT
return --- True if running under UNIX, otherwise False
"""
return True if os.name == "posix" else False
def scheduleShutdown(sendRestartTime, restart, message = ""):
"""
Schedule a server shutdown/restart
sendRestartTime -- time (seconds) to wait before sending server restart packets to every client
restart -- if True, server will restart. if False, server will shudown
message -- if set, send that message to every client to warn about the shutdown/restart
"""
# Console output
consoleHelper.printColored("[!] Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+20), bcolors.PINK)
consoleHelper.printColored("[!] Sending server restart packets in {} seconds...".format(sendRestartTime), bcolors.PINK)
# Send notification if set
if message != "":
glob.tokens.enqueueAll(serverPackets.notification(message))
# Schedule server restart packet
threading.Timer(sendRestartTime, glob.tokens.enqueueAll, [serverPackets.banchoRestart(50000)]).start()
glob.restarting = True
# Restart/shutdown
if restart:
action = restartServer
else:
action = shutdownServer
# Schedule actual server shutdown/restart 20 seconds after server restart packet, so everyone gets it
threading.Timer(sendRestartTime+20, action).start()
def restartServer():
"""Restart pep.py script"""
print("> Restarting pep.py...")
os.execv(sys.executable, [sys.executable] + sys.argv)
def shutdownServer():
"""Shutdown pep.py"""
print("> Shutting down pep.py...")
sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
os.kill(os.getpid(), sig)
def getSystemInfo():
"""
Get a dictionary with some system/server info
return -- ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"]
"""
data = {}
# Get if server is running under unix/nt
data["unix"] = runningUnderUnix()
# General stats
data["connectedUsers"] = len(glob.tokens.tokens)
data["webServer"] = glob.conf.config["server"]["server"]
data["cpuUsage"] = psutil.cpu_percent()
data["totalMemory"] = "{0:.2f}".format(psutil.virtual_memory()[0]/1074000000)
data["usedMemory"] = "{0:.2f}".format(psutil.virtual_memory()[3]/1074000000)
# Unix only stats
if data["unix"] == True:
data["loadAverage"] = os.getloadavg()
else:
data["loadAverage"] = (0,0,0)
return data

299
helpers/userHelper.py Normal file
View File

@@ -0,0 +1,299 @@
from helpers import passwordHelper
from constants import gameModes
from helpers import generalFunctions
from objects import glob
def getID(username):
"""
Get username's user ID
db -- database connection
username -- user
return -- user id or False
"""
# Get user ID from db
userID = glob.db.fetch("SELECT id FROM users WHERE username = ?", [username])
# Make sure the query returned something
if userID == None:
return False
# Return user ID
return userID["id"]
def checkLogin(userID, password):
"""
Check userID's login with specified password
db -- database connection
userID -- user id
password -- plain md5 password
return -- True or False
"""
# Get password data
passwordData = glob.db.fetch("SELECT password_md5, salt, password_version FROM users WHERE id = ?", [userID])
# Make sure the query returned something
if passwordData == None:
return False
# Return valid/invalid based on the password version.
if passwordData["password_version"] == 2:
return passwordHelper.checkNewPassword(password, passwordData["password_md5"])
if passwordData["password_version"] == 1:
ok = passwordHelper.checkOldPassword(password, passwordData["salt"], passwordData["password_md5"])
if not ok: return False
newpass = passwordHelper.genBcrypt(password)
glob.db.execute("UPDATE users SET password_md5=?, salt='', password_version='2' WHERE id = ?", [newpass, userID])
def exists(userID):
"""
Check if userID exists
userID -- user ID to check
return -- bool
"""
result = glob.db.fetch("SELECT id FROM users WHERE id = ?", [userID])
if result == None:
return False
else:
return True
def getAllowed(userID):
"""
Get allowed status for userID
db -- database connection
userID -- user ID
return -- allowed int
"""
return glob.db.fetch("SELECT allowed FROM users WHERE id = ?", [userID])["allowed"]
def getRankPrivileges(userID):
"""
This returns rank **(PRIVILEGES)**, not game rank (like #1337)
If you want to get that rank, user getUserGameRank instead
"""
return glob.db.fetch("SELECT rank FROM users WHERE id = ?", [userID])["rank"]
def getSilenceEnd(userID):
"""
Get userID's **ABSOLUTE** silence end UNIX time
Remember to subtract time.time() to get the actual silence time
userID -- userID
return -- UNIX time
"""
return glob.db.fetch("SELECT silence_end FROM users WHERE id = ?", [userID])["silence_end"]
def silence(userID, silenceEndTime, silenceReason):
"""
Set userID's **ABSOLUTE** silence end UNIX time
Remember to add time.time() to the silence length
userID -- userID
silenceEndtime -- UNIX time when the silence ends
silenceReason -- Silence reason shown on website
"""
glob.db.execute("UPDATE users SET silence_end = ?, silence_reason = ? WHERE id = ?", [silenceEndTime, silenceReason, userID])
def getRankedScore(userID, gameMode):
"""
Get userID's ranked score relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- ranked score
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT ranked_score_"+modeForDB+" FROM users_stats WHERE id = ?", [userID])["ranked_score_"+modeForDB]
def getTotalScore(userID, gameMode):
"""
Get userID's total score relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- total score
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT total_score_"+modeForDB+" FROM users_stats WHERE id = ?", [userID])["total_score_"+modeForDB]
def getAccuracy(userID, gameMode):
"""
Get userID's average accuracy relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- accuracy
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT avg_accuracy_"+modeForDB+" FROM users_stats WHERE id = ?", [userID])["avg_accuracy_"+modeForDB]
def getGameRank(userID, gameMode):
"""
Get userID's **in-game rank** (eg: #1337) relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- game rank
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
result = glob.db.fetch("SELECT position FROM leaderboard_"+modeForDB+" WHERE user = ?", [userID])
if result == None:
return 0
else:
return result["position"]
def getPlaycount(userID, gameMode):
"""
Get userID's playcount relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- playcount
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT playcount_"+modeForDB+" FROM users_stats WHERE id = ?", [userID])["playcount_"+modeForDB]
def getUsername(userID):
"""
Get userID's username
userID -- userID
return -- username
"""
return glob.db.fetch("SELECT username FROM users WHERE id = ?", [userID])["username"]
def getFriendList(userID):
"""
Get userID's friendlist
userID -- userID
return -- list with friends userIDs. [0] if no friends.
"""
# Get friends from db
friends = glob.db.fetchAll("SELECT user2 FROM users_relationships WHERE user1 = ?", [userID])
if friends == None or len(friends) == 0:
# We have no friends, return 0 list
return [0]
else:
# Get only friends
friends = [i["user2"] for i in friends]
# Return friend IDs
return friends
def addFriend(userID, friendID):
"""
Add friendID to userID's friend list
userID -- user
friendID -- new friend
"""
# Make sure we aren't adding us to our friends
if userID == friendID:
return
# check user isn't already a friend of ours
if glob.db.fetch("SELECT id FROM users_relationships WHERE user1 = ? AND user2 = ?", [userID, friendID]) != None:
return
# Set new value
glob.db.execute("INSERT INTO users_relationships (user1, user2) VALUES (?, ?)", [userID, friendID])
def removeFriend(userID, friendID):
"""
Remove friendID from userID's friend list
userID -- user
friendID -- old friend
"""
# Delete user relationship. We don't need to check if the relationship was there, because who gives a shit,
# if they were not friends and they don't want to be anymore, be it. ¯\_(ツ)_/¯
glob.db.execute("DELETE FROM users_relationships WHERE user1 = ? AND user2 = ?", [userID, friendID])
def getCountry(userID):
"""
Get userID's country **(two letters)**.
Use countryHelper.getCountryID with what that function returns
to get osu! country ID relative to that user
userID -- user
return -- country code (two letters)
"""
return glob.db.fetch("SELECT country FROM users_stats WHERE id = ?", [userID])["country"]
def getPP(userID, gameMode):
"""
Get userID's PP relative to gameMode
userID -- user
return -- PP
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT pp_{} FROM users_stats WHERE id = ?".format(modeForDB), [userID])["pp_{}".format(modeForDB)]
def setAllowed(userID, allowed):
"""
Set userID's allowed status
userID -- user
allowed -- allowed status. 1: normal, 0: banned
"""
glob.db.execute("UPDATE users SET allowed = ? WHERE id = ?", [allowed, userID])
def setCountry(userID, country):
"""
Set userID's country (two letters)
userID -- userID
country -- country letters
"""
glob.db.execute("UPDATE users_stats SET country = ? WHERE id = ?", [country, userID])
def getShowCountry(userID):
"""
Get userID's show country status
userID -- userID
return -- True if country is shown, False if it's hidden
"""
country = glob.db.fetch("SELECT show_country FROM users_stats WHERE id = ?", [userID])
if country == None:
return False
return generalFunctions.stringToBool(country)