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 ()
0 commit comments