Skip to content

Commit 77946e5

Browse files
v1.0
0 parents  commit 77946e5

12 files changed

Lines changed: 1084 additions & 0 deletions

File tree

IPQualityScore/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.pyc
2+
__pycache__

IPQualityScore/BinaryOption.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
class BinaryOption:
2+
def __init__(self):
3+
self.__data = None
4+
5+
def SetData(self, bit):
6+
self.__data = bit
7+
8+
def Has(self, value):
9+
return (self.__data & value)
10+
11+
def Set(self, value):
12+
self.__data = self.__data | value
13+
14+
@staticmethod
15+
def Create(value):
16+
result = BinaryOption()
17+
result.SetData(value)
18+
return result
19+
20+
IPV4MAP = 0B0001
21+
IPV6MAP = 1 << 1
22+
TREEDATA = 1 << 2
23+
STRINGDATA = 1 << 3
24+
SMALLINTDATA = 1 << 4
25+
INTDATA = 1 << 5
26+
FLOATDATA = 1 << 6
27+
BINARYDATA = 1 << 7
28+
ISPROXY = 1 << 0
29+
ISVPN = 1 << 1
30+
ISTOR = 1 << 2
31+
ISCRAWLER = 1 << 3
32+
ISBOT = 1 << 4
33+
RECENTABUSE = 1 << 5
34+
ISBLACKLISTED = 1 << 6
35+
ISPRIVATE = 1 << 7
36+
ISMOBILE = 1 << 0
37+
HASOPENPORTS = 1 << 1
38+
ISHOSTINGPROVIDER = 1 << 2
39+
ACTIVEVPN = 1 << 3
40+
ACTIVETOR = 1 << 4
41+
PUBLICACCESSPOINT = 1 << 5
42+
RESERVEDONE = 1 << 6
43+
RESERVEDTWO = 1 << 7
44+
RESERVEDTHREE = 1 << 0
45+
RESERVEDFOUR = 1 << 1
46+
RESERVEDFIVE = 1 << 2
47+
CONNECTIONTYPEONE = 1 << 3
48+
CONNECTIONTYPETWO = 1 << 4
49+
CONNECTIONTYPETHREE = 1 << 5
50+
ABUSEVELOCITYONE = 1 << 6
51+
ABUSEVELOCITYTWO = 1 << 7

IPQualityScore/Columns.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from IPQualityScore.BinaryOption import BinaryOption
2+
3+
class Column:
4+
5+
def __init__(self):
6+
self.__Name = None
7+
self.__RawValue = None
8+
self.__Type = None
9+
self.__Name = None
10+
self.__Value = None
11+
12+
@staticmethod
13+
def Create(name, stype, value= None):
14+
C = Column()
15+
C.Name(name)
16+
C.Type(stype)
17+
C.Value(value)
18+
19+
return C
20+
21+
def Name(self, value = None):
22+
if value != None:
23+
self.__Name = value
24+
return self.__Name
25+
26+
def Type(self, value:BinaryOption = None):
27+
if value != None:
28+
self.__Type = value
29+
return self.__Type
30+
31+
def Value(self, value = None):
32+
if value != None:
33+
self.__Value = value
34+
return self.__Value

IPQualityScore/DBReader.py

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
2+
import os
3+
from binascii import hexlify
4+
from socket import error, inet_aton, inet_pton, socket
5+
from socket import AF_INET
6+
from socket import AF_INET6
7+
from struct import unpack, unpack_from, error as struck_err
8+
from IPQualityScore.Columns import Column
9+
from IPQualityScore.Exceptions import FileReaderException
10+
from IPQualityScore.Exceptions import IPNotFoundException
11+
from IPQualityScore.BinaryOption import BinaryOption
12+
from IPQualityScore.IPQSRecord import IPQSRecord
13+
import IPQualityScore.Utilities as Utilities
14+
15+
class DBReader:
16+
READER_VERSION = 1
17+
BASE_TREE_BYTES = 5
18+
TREE_BYTE_WIDTH = 4
19+
BASE_HEADER_BYTES = 11
20+
BINARY_DATA_HEADER = "<BBB"
21+
NONBINARY_DATA_HEADER = "<B"
22+
COLUNN_HEADER = "<P23xB" #"x%s/a23name/Cvalue" null 23null-padded_string UnsigedChar <x23xB
23+
HEADERS = "<BB3BHL"
24+
25+
handler = None
26+
tree_start = None
27+
tree_end = None
28+
record_bytes = None
29+
columns = []
30+
ipv6 = False
31+
32+
def __init__(self, filename:str):
33+
if not os.path.isfile(filename):
34+
raise FileReaderException('Invalid or non existant file name specified. Please check the file and try again')
35+
36+
try:
37+
self.handler = open(filename,'rb')
38+
except IOError:
39+
raise FileReaderException('Invalid or non existant file name specified. Please check the file and try again')
40+
41+
self.SetupHeaders()
42+
self.SetupColumns()
43+
self.SetupTreeHeaders()
44+
45+
def Fetch(self, ip:str):
46+
if not self.IsIPv4(ip) and not self.IsIPv6(ip):
47+
raise FileReaderException("Attemtped to look up invalid IP address. Aborting.")
48+
49+
if self.ipv6 and not self.IsIPv6(ip):
50+
raise FileReaderException("Attemtped to look up IPv4 using IPv6 database file. Aborting.")
51+
52+
if self.ipv6 == False and not self.IsIPv4(ip):
53+
raise FileReaderException("Attemtped to look up IPv6 using IPv4 database file. Aborting.")
54+
55+
v_literal = self.IP2Literal(ip)
56+
position = 0
57+
58+
file_position = self.tree_start + self.BASE_TREE_BYTES
59+
while True:
60+
if len(v_literal) <= position:
61+
raise IPNotFoundException("Invalid or nonexistant IP address specified for lookup. (EID: 8)")
62+
63+
next = 0
64+
try:
65+
if v_literal[position] == "0":
66+
pos = self.ReadAt(file_position, self.TREE_BYTE_WIDTH)
67+
if len(pos) == 4:
68+
next = unpack("<L", pos)[0]
69+
else:
70+
pos = self.ReadAt(file_position + 4, self.TREE_BYTE_WIDTH)
71+
if len(pos) == 4:
72+
next = unpack("<L", pos)[0]
73+
74+
except Exception:
75+
raise IPNotFoundException("Invalid or nonexistant IP address specified for lookup. (EID: 9)")
76+
77+
try:
78+
if next == 0:
79+
if v_literal[position] == "0":
80+
pos = self.ReadAt(file_position + 4, self.TREE_BYTE_WIDTH)
81+
if len(pos) == 4:
82+
next = unpack("<L", pos)[0]
83+
84+
if next == 0:
85+
raise IPNotFoundException()
86+
else:
87+
pos = self.ReadAt(file_position, self.TREE_BYTE_WIDTH)
88+
if len(pos) == 4:
89+
next = unpack("<L", pos)[0]
90+
91+
if next == 0:
92+
raise IPNotFoundException()
93+
except Exception:
94+
raise IPNotFoundException("Invalid or nonexistant IP address specified for lookup. (EID: 10)")
95+
96+
file_position = next
97+
if(file_position < self.tree_end):
98+
position += 1
99+
continue
100+
try:
101+
raw = self.ReadAt(file_position, self.record_bytes)
102+
except Exception:
103+
raise IPNotFoundException("Invalid or nonexistant IP address specified for lookup. (EID: 11)")
104+
105+
try:
106+
return self.CreateRecord(raw)
107+
except Exception:
108+
raise IPNotFoundException("Invalid or nonexistant IP address specified for lookup. (EID: 12)")
109+
def GetColumns(self):
110+
return self.columns
111+
112+
def Read(self, blenght):
113+
try:
114+
data = self.handler.read(blenght)
115+
except IOError:
116+
raise IPNotFoundException("Unknown file format. Please check the file's integrity. EID 13")
117+
return data
118+
119+
def ReadAt(self, position, b):
120+
self.handler.seek(position)
121+
d = self.handler.read(b)
122+
if d == False:
123+
raise IPNotFoundException("Unknown file format. Please check the file's integrity. EID 14")
124+
return d
125+
126+
def SetupHeaders(self):
127+
try:
128+
headers ={}
129+
headers["file_type"], headers["version"], headers["header_bytes1"], headers["header_bytes2"], headers["header_bytes3"], headers["record_bytes"], headers["file_bytes"] = unpack(self.HEADERS, self.Read(self.BASE_HEADER_BYTES))
130+
except Exception:
131+
raise FileReaderException("Invalid file format, unable to read first 11 bytes. EID 1")
132+
if headers["file_type"] == None:
133+
raise FileReaderException("Invalid file format, unable to read first 11 bytes. EID 1")
134+
135+
file_type = BinaryOption.Create(headers["file_type"])
136+
if file_type.Has(BinaryOption.IPV4MAP):
137+
self.valid = True
138+
self.ipv6 = False
139+
140+
if file_type.Has(BinaryOption.IPV6MAP):
141+
self.valid = True
142+
self.ipv6 = True
143+
144+
if file_type.Has(BinaryOption.BINARYDATA):
145+
self.binary_data = True
146+
147+
if(self.valid == False):
148+
raise FileReaderException("Invalid file format, invalid first byte. EID 1.")
149+
150+
if headers['version'] != DBReader.READER_VERSION:
151+
raise FileReaderException("Invalid file version, EID 2.")
152+
153+
154+
self.tree_start = Utilities.uVarInt([headers['header_bytes1'], headers['header_bytes2'], headers['header_bytes3']])
155+
if self.tree_start == 0:
156+
raise FileReaderException("Invalid file format, invalid header bytes. EID 2")
157+
158+
if headers['record_bytes'] == 0:
159+
raise FileReaderException("Invalid file format, invalid record size. EID 3")
160+
161+
self.record_bytes = headers['record_bytes']
162+
163+
if headers['file_bytes'] == 0 :
164+
raise FileReaderException("Invalid file format, invalid file size. EID 3")
165+
166+
def SetupColumns(self):
167+
length = self.tree_start - self.BASE_HEADER_BYTES
168+
column_data = self.Read(length)
169+
values = {}
170+
for i in range(0, int(length/24)):
171+
s = i * 24
172+
values['name'], values['value'] = unpack_from("<{0}x23sB".format(i*24), column_data)
173+
BO = BinaryOption.Create(values['value'])
174+
C = Column.Create(values['name'].decode("utf-8").rstrip('\x00'), BO)
175+
self.columns.append(C)
176+
if len(self.columns) == 0:
177+
raise FileReaderException("File does not appear to be valid, no column data found. EID: 5")
178+
179+
def SetupTreeHeaders(self):
180+
tree = {}
181+
tree['header'], tree['tree_bytes'] = unpack("<BL", self.Read(5))
182+
183+
if tree['tree_bytes'] == 0 or BinaryOption.Create(tree['header']).Has(BinaryOption.TREEDATA) == False:
184+
raise FileReaderException("File does not appear to be valid, bad binary tree. EID: 6")
185+
186+
self.tree_end = tree['tree_bytes'] + self.tree_start
187+
188+
def IP2Literal(self,ip):
189+
result = ""
190+
if(self.ipv6):
191+
for block in self.Expand(ip).split(":"):
192+
result += bin(int(block,16))[2:]
193+
else:
194+
for block in ip.split("."):
195+
result += bin(int(block,10))[2:].zfill(8)
196+
197+
return result
198+
199+
def CreateRecord(self,raw):
200+
bdata = {}
201+
record = IPQSRecord()
202+
current_byte = 0
203+
bdata['one'], bdata["two"], bdata["three"] = unpack("{0}B".format(len(raw)), raw)[:3]
204+
if self.binary_data:
205+
record.ParseFirstByte(BinaryOption.Create(bdata['one']))
206+
record.ParseSecondByte(BinaryOption.Create(bdata['two']))
207+
208+
third = BinaryOption.Create(bdata['three'])
209+
record.ConnectionTypeRaw(third)
210+
record.AbuseVelocityRaw(third)
211+
current_byte = 3
212+
else:
213+
first = BinaryOption.Create(bdata['one'])
214+
record.ConnectionTypeRaw(first)
215+
record.AbuseVelocityRaw(first)
216+
current_byte = 1
217+
218+
for column in self.columns:
219+
value = ""
220+
if column.Name() == "ASN":
221+
value = unpack("<L", raw[current_byte:current_byte+4])[0]
222+
record.ASN(int(value))
223+
current_byte += 4
224+
elif column.Name() == "Latitude":
225+
value = unpack("<f", raw[current_byte:current_byte+4])[0]
226+
record.Latitude(float(value))
227+
current_byte += 4
228+
elif column.Name() == "Longitude":
229+
value = unpack("<f", raw[current_byte:current_byte+4])[0]
230+
record.Longitude(float(value))
231+
current_byte += 4
232+
elif column.Name() == "ZeroFraudScore":
233+
value = unpack("B", raw[current_byte:current_byte+1])[0]
234+
record.SetFraudScore(0, int(value))
235+
current_byte += 1
236+
elif column.Name() == "OneFraudScore":
237+
value = unpack("B", raw[current_byte:current_byte+1])[0]
238+
record.ASN(float(value))
239+
current_byte += 1
240+
else:
241+
if (column.Type()).Has(BinaryOption.STRINGDATA):
242+
value = self.GetRangedStringValue(unpack("<L", raw[current_byte:current_byte+4])[0])
243+
if type(value) == tuple:
244+
value = value[0].decode('utf-8')
245+
current_byte += 4
246+
try:
247+
m = getattr(record, column.Name())
248+
m(value)
249+
except AttributeError:
250+
pass
251+
252+
record.AddColumns(Column.Create(column.Name(),column.Type(), value))
253+
254+
return record
255+
def GetRangedStringValue(self, position):
256+
b = unpack("<B", self.ReadAt(position, 1))[0]
257+
return unpack(("<{0}s".format(b)), self.Read(b))
258+
259+
def Expand(self, ip):
260+
l_hex = list(hexlify(inet_pton(AF_INET6, ip)).decode("utf-8"))
261+
return''.join(l + ':' * (n % 4 == 3) for n, l in enumerate(l_hex))[:-1]
262+
263+
def IsIPv4(self, ip:str):
264+
try:
265+
inet_pton(AF_INET, ip)
266+
return True
267+
except error:
268+
return False
269+
270+
def IsIPv6(self, ip:str):
271+
try:
272+
inet_pton(AF_INET6, ip)
273+
return True
274+
except error:
275+
return False
276+
def __del__(self):
277+
self.handler.close()

IPQualityScore/Exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class FileReaderException(Exception):
2+
pass
3+
4+
class IPNotFoundException(Exception):
5+
pass

0 commit comments

Comments
 (0)