2 # -*- coding: utf-8 -*-
4 # For the people of Smubworld!
8 from optparse
import OptionParser
10 from math
import ceil
, log
15 __program__
= 'blockfinder'
16 __url__
= 'http://github.com/ioerror/blockfinder/'
17 ___author__
= 'Jacob Appelbaum <jacob@appelbaum.net>, David <db@d1b.org>'
18 __copyright__
= 'Copyright (c) 2010'
19 __license__
= 'See LICENSE for licensing information'
20 __version__
= '3.1415'
28 from future
import antigravity
37 import ipaddr
as Ipaddr
42 class BlockFinderError(Exception):
45 def update_progress_bar(percent_done
, caption
=""):
46 """Write a progress bar to the console"""
47 rows
, columns
= map(int, os
.popen('stty size', 'r').read().split())
48 width
= columns
- 4 - len(caption
)
49 sys
.stdout
.write("[%s>%s] %s\x1b[G" % (
50 "=" * int(percent_done
*width
),
51 "." * (width
- int(percent_done
* width
)),
55 # XXX TODO:allow the use of a proxy
56 # Set up a proper Request object, set the user agent and if desired, a proxy
57 def fetch(url
, useragent
):
58 """ Fetch (with progress meter) and return the contents of a url. """
59 req
= urllib2
.Request(url
)
60 req
.add_header('User-agent', useragent
)
61 #req.set_proxy(host, type)
62 fetcher
= urllib2
.urlopen(req
)
63 length_header
= fetcher
.headers
.get("content-length")
64 if length_header
== None:
65 """ The server did not provide a content-length header. """
67 length
= int(length_header
)
68 print "Fetching ", str (round(float(length
/1024),2)) , " kilobytes"
72 t_delta
= time
.time() - t_start
75 if length_header
!= -1:
76 update_progress_bar( float(len(ret
)) / length
,
77 "%.2f K/s" % (len(ret
) / 1024 / t_delta
) )
78 tmp
= fetcher
.read(1024)
80 if len(ret
) != length
and length_header
!= -1:
81 raise Exception("Expected %s bytes, only received %s" % (
87 def write_to_a_text_file(file_loc
, data
):
88 f
= open(file_loc
, 'w')
92 def calculate_ipv4_subnet(host_count
):
93 """ XXX: this will return incorrect values for anything above a /4
94 e.g. calculate_ipv4_subnet(536870912) will return 2 instead of 3
96 return 32 - int(ceil(log(host_count
,2)))
98 def extract_data_from_gzip_file(gzip_file_loc
, extract_file_loc
):
99 gzip_file
= gzip
.open(gzip_file_loc
, 'rb')
100 gunzipped_file
= open(extract_file_loc
, 'w')
102 gunzipped_data
= gzip_file
.read(1024)
103 if gunzipped_data
== "":
105 gunzipped_file
.writelines(gunzipped_data
)
108 gunzipped_file
.close()
110 def read_data_from_binary_file(fname
):
111 f
= open(fname
, 'rb')
116 class Blockfinder(object):
118 def __init__(self
, cache_dir
, useragent
, verbose
=False):
119 self
.cache_dir
= cache_dir
120 self
.useragent
= useragent
123 self
.verbose
= verbose
125 def create_blockfinder_cache_dir(self
):
126 if not os
.path
.exists(self
.cache_dir
):
128 print "Initializing the cache directory..."
129 os
.mkdir(self
.cache_dir
)
131 def cache_delegation(self
, delegation_url
):
132 """ Attempt to cache the contents of a delegation url in our cache dir. """
134 print "Fetching " + delegation_url
135 delegation
= fetch(delegation_url
, self
.useragent
)
136 tmp
= delegation_url
.split('/')
137 delegation_file
= str(self
.cache_dir
) + str(tmp
[-1])
139 write_to_a_text_file(delegation_file
, delegation
)
145 def cache_is_dated(self
, cached_files
):
146 """ Returns True if the mtime of any files in cache dir is > 24hrs."""
148 os
.stat(self
.cache_dir
)
150 print "\nDid you initialize the cache directory?\n"
152 for file in cached_files
:
153 fstat
= os
.stat(self
.cache_dir
+ file)
154 if (time
.time() - fstat
.st_mtime
) > 86400:
159 def connect_to_database(self
):
160 self
.conn
= sqlite3
.connect(self
.cache_dir
+ "sqlitedb")
161 self
.cursor
= self
.conn
.cursor()
163 def commit_and_close_database(self
):
167 def create_sql_database(self
):
168 """ Creates a new sqlite database.
169 Existing delegation entries are dropped prior to inserting
173 sql_script_comp
.append("""drop table if exists delegations""" )
174 sql_script_comp
.append("""create table delegations(registry text, cc text, start text, value INTEGER, date text, status text, type text)""")
175 sql_script_comp
.append("""create table if not exists lir_record(cc text, start text, value INTEGER, type INTEGER)""")
176 self
.cursor
.executescript(";\n".join(sql_script_comp
) + ";" )
179 def insert_into_sql_database(self
, delegations
):
180 """ inserts delegation information into the sqlite database"""
182 for delegation
in delegations
:
183 for entry
in delegation
:
184 registry
= str(entry
['registry'])
185 if not registry
.isdigit() and str (entry
['cc']) !="*":
186 temp_row
= [entry
['registry'], entry
['cc'], entry
['start'], \
187 entry
['value'], entry
['date'], entry
['status'], entry
['type']]
188 rows
.append(temp_row
)
190 text
= """INSERT INTO delegations (registry, cc, start, value, date, status, type) VALUES (?,?,?,?,?,?,?)"""
191 self
.cursor
.executemany(text
, rows
)
194 def get_total_delegations_from_db(self
):
195 """ Returns the total count of the number of entries in the ipv4, ipv6 and asn table """
196 self
.cursor
.execute("""select count (*) from delegations""")
197 return int (self
.cursor
.fetchone()[0] )
199 def get_possible_match_entries(self
, cc
):
200 """ Get the count of 'possible' matching delegation entries"""
201 self
.cursor
.execute("""select count (*) from delegations where cc=?""", cc
)
202 return int (self
.cursor
.fetchone()[0] )
204 def use_sql_database(self
, request
, cc
):
205 """ Use the sqlite database that is created after fetching delegations
206 to output information for a given request """
208 print "We have %d entries in our delegation cache." % self
.get_total_delegations_from_db()
209 text
= "select start, value from delegations where type=? and cc=?"
211 self
.cursor
.execute(text
, (request
, cc
[0]) )
213 for row
in self
.cursor
:
214 if request
== "ipv4":
216 first
= Ipaddr
.IPv4Address(str(row
[0]))
217 last
= Ipaddr
.IPv4Address(int(first
) + int(row
[1]) - 1)
218 result
+= [str(x
) for x
in Ipaddr
.summarize_address_range(first
, last
)]
220 result
.append(str(row
[0]) + "/" + str(calculate_ipv4_subnet(int(row
[1]))))
221 elif request
== "ipv6":
222 result
.append(str(row
[0]) + "/" + str(int(row
[1])))
224 result
.append(str(int(row
[0])))
227 result
.append("We found %d possible entries in our delegation cache." % self
.get_possible_match_entries(cc
) )
228 self
.cursor
.execute("""select count(*) from delegations where cc=? and type=?""", (cc
[0], request
) )
229 result
.append("We found %d matching entries in our delegation cache." % int (self
.cursor
.fetchone()[0] ) )
232 def get_md5_from_delegation_md5_file(self
, delegation_file
):
233 """ Returns the md5sum from the delegation md5 file
234 if it doesn't exist it returns an empty string"""
237 f
= open(self
.cache_dir
+ delegation_file
+ ".md5", "r")
241 pos
= checksum
.find("=") +2
242 checksum
= str (checksum
[pos
:-1])
247 def verify_delegation_file(self
, delegation_file
):
248 """ compares the delegation file md5sum to that of the provided md5sum
249 returns True if they match otherwise returns False """
251 checksum_of_file
= ""
253 data
= read_data_from_binary_file(self
.cache_dir
+ delegation_file
)
254 checksum_of_file
= str (hashlib
.md5(data
).hexdigest() )
257 checksum
= self
.get_md5_from_delegation_md5_file(delegation_file
)
258 if checksum
!= checksum_of_file
:
260 if checksum
== checksum_of_file
and checksum
!= "":
264 def verify_cache(self
, delegation_files
):
265 """ if in verbose mode prints the result of checking the checksum of the
267 for file in delegation_files
:
269 print "verifying " + file
270 if self
.verify_delegation_file(file):
272 print "the md5 checksum of " + file + " *matches* the provided checksum"
275 print "the md5 checksum of " + file + " does *not* match the provided checksum"
277 def update_delegation_cache(self
, delegation_urls
):
278 """ Fetch multiple delegation urls and cache the contents. """
279 print "Updating delegation cache..."
281 for url
in delegation_urls
.split():
282 self
.cache_delegation(url
+ ".md5")
283 if self
.verify_delegation_file(url
.rpartition('/')[-1]):
286 self
.cache_delegation(url
)
288 def update_lir_delegation_cache(self
, delegation_urls
):
289 """ Fetch multiple LIR delegation urls and cache the contents. """
290 print "Updating LIR delegation cache..."
291 for url
in delegation_urls
.split():
292 self
.cache_delegation(url
)
293 self
.unpack_a_delegation_cache(delegation_urls
, "LIR")
295 def unpack_a_delegation_cache(self
, delegation_urls
, del_type
=""):
296 """ Unpack the fetched LIR delegation files into the blockfinder cache. """
297 # This probably should unlink the gzip'ed file if we care about space...
298 for url
in delegation_urls
.split():
299 gzip_filename
= url
.rpartition('/')[-1]
300 gunziped_filename
= gzip_filename
.rpartition('.')[0]
302 print "Unpacking " + del_type
+ "file " + gzip_filename
+ " into our cache as " + gunziped_filename
303 extract_data_from_gzip_file(self
.cache_dir
+ gzip_filename
, self
.cache_dir
+ gunziped_filename
)
305 def update_geoip_cache(self
, geoip_urls
):
306 """ Fetch country level resolution GeoIP files from a given url and cache
307 the contents. Unpack it if it's compressed. """
308 print "Updating GeoIP cache..."
309 for url
in geoip_urls
.split():
310 self
.cache_delegation(url
)
311 self
.unpack_a_delegation_cache(geoip_urls
, "GeoIP")
313 def load_delegation(self
, delegation_file
):
314 """ Load, parse and store the delegation file contents as a list. """
315 keys
= "registry cc type start value date status"
317 f
= open(delegation_file
, "r")
318 delegations
= [ dict((k
,v
) for k
,v
in zip(keys
.split(), line
.split("|")))
319 for line
in f
.readlines() if not line
.startswith("#")]
325 def load_all_delegations(self
, delegation_urls
):
326 """ Load all delegations into memory. """
328 for url
in delegation_urls
.split():
329 filename
= url
.rpartition('/')[-1]
331 print "Attempting to load delegation file into memory: " + filename
332 delegations
.append(self
.load_delegation(self
.cache_dir
+ filename
))
335 def download_country_code_file(self
):
336 """ Download and save the latest opencountrycode TXT(';' - separated) file """
337 url
= "http://www.iso.org/iso/list-en1-semic-3.txt"
338 text_content
= urllib2
.urlopen(url
).read()
339 write_to_a_text_file(self
.cache_dir
+ "countrycodes.txt", text_content
)
341 def build_country_code_dictionary(self
):
342 """ Return a dictionary mapping country name to the country code"""
344 txt_file
= str(self
.cache_dir
) + "countrycodes.txt"
345 for line
in open(txt_file
, 'r'):
346 line
= line
.replace("\n", "").replace("\r", "")
347 if line
.startswith("This list states the country"):
349 if line
== "" or ";" not in line
:
351 name
, code
= line
.split(";")
352 """ capitalize the individual parts of the country name """
353 name
= ' '.join([part
.capitalize() for part
in name
.split(" ")])
357 def get_name_from_country_code(self
, cc_code
):
358 map_co
= self
.build_country_code_dictionary()
359 country_name
= [(key
, value
) for (key
, value
) in map_co
.items() if value
== cc_code
]
360 if len(country_name
) > 0:
361 return country_name
[0][0]
363 def get_country_code_from_name(self
, country_name
):
364 """ Return the country code for a given country name. """
365 map_co
= self
.build_country_code_dictionary()
366 cc_code
= [map_co
[key
] for key
in map_co
.keys() if key
.upper().startswith(country_name
.upper())]
370 def geoip_lookup(self
, ip_addr
):
371 # This would work with the CVS version of the GeoIP code
372 # However, MaxMind hasn't done a release in a long time.
373 # http://geoip.cvs.sourceforge.net/viewvc/geoip/python/test_v6.py?revision=1.1&view=markup
374 # gi = GeoIP.open(self.cache_dir + "GeoIPv6.dat",GeoIP.GEOIP_STANDARD)
375 # cc = gi.country_code_by_addr_v6(ip_addr)
376 # cc_name = gi.country_name_by_addr_v6(ip_addr)
377 gi
= GeoIP
.open(self
.cache_dir
+ "GeoIP.dat",GeoIP
.GEOIP_STANDARD
)
378 cc
= gi
.country_code_by_addr(ip_addr
)
379 cc_name
= gi
.country_name_by_addr(ip_addr
)
383 def rir_or_lir_lookup_ipv4(self
, ip_addr
, lookup_type
):
384 ipv4arr
= ip_addr
.split('.')
386 if lookup_type
== 'rir':
387 self
.cursor
.execute('select cc, start, value from delegations WHERE type="ipv4" and start LIKE ?', ( ipv4arr
[0] + "." + ipv4arr
[1] + ".%",))
389 self
.cursor
.execute('select * from lir_record WHERE start LIKE ? and type=4', (ipv4arr
[0] + "." + ipv4arr
[1] + ".%",))
390 row
= self
.cursor
.fetchone()
393 if lookup_type
== "rir":
394 self
.cursor
.execute('select cc, start, value from delegations WHERE type="ipv4" and start LIKE ? ', (ipv4arr
[0] + ".%",))
396 self
.cursor
.execute('select * from lir_record WHERE start LIKE ? and type=4', (ipv4arr
[0] + ".%",))
397 row
= self
.cursor
.fetchone()
399 while(row
is not None):
400 if (ip_address_to_dec(row
[1]) <= ip_address_to_dec(ip_addr
) < (ip_address_to_dec(row
[1]) + row
[2])):
401 result
.append(row
[0])
402 result
.append(self
.get_name_from_country_code(row
[0]))
404 row
= self
.cursor
.fetchone()
406 def rir_lookup(self
, ip_addr
):
407 return self
.rir_or_lir_lookup_ipv4(ip_addr
, "rir")
409 def lir_lookup(self
, ip_addr
):
410 return self
.rir_or_lir_lookup_ipv4(ip_addr
, "lir")
412 def asn_lookup(self
, asn
):
413 self
.cursor
.execute('select cc from asn WHERE start LIKE ?', (asn
,))
414 row
= self
.cursor
.fetchone()
416 print "AS country code: %s" % row
[0]
417 print "AS country name: %s" % self
.get_name_from_country_code(row
[0])
419 print "AS%s not found!" % asn
421 def rir_or_lir_lookup_ipv6(self
, ip_addr
, ip_query
, type_q
):
423 self
.cursor
.execute("select cc, start, value from delegations where type='ipv6'and start like ?", (ip_query
,) )
425 self
.cursor
.execute("select cc, start, value from lir_record where type=6 and start like ?", (ip_query
,) )
426 for row
in self
.cursor
:
428 if ip_addr
in IPy
.IP(row
[1] + "/" + str(row
[2])):
430 except ValueError, e
:
435 def lookup_ipv6_address(self
, ip_addr
):
436 print "Reverse lookup for: " + ip_addr
437 split_addr
= ip_addr
.split(":")
438 for i
in ["RIR", "LIR"]:
439 ip_query
= ip_addr
.split(":")[0] + ":" + ip_addr
.split(":")[1] + "%"
440 result
= self
.rir_or_lir_lookup_ipv6(ip_addr
, ip_query
, i
)
442 print i
, "Country Name:", self
.get_name_from_country_code(result
)
444 ip_query
= ip_addr
.split(":")[0] + ":%"
445 result
= self
.rir_or_lir_lookup_ipv6(ip_addr
, ip_query
, i
)
447 print i
, "Country Name:", self
.get_name_from_country_code(result
)
451 def lookup_ip_address(self
, ip_addr
):
452 """ Return the country code and name for a given ip address. Attempts to
453 use GeoIP if available."""
455 ip_addr
= socket
.getaddrinfo(ip_addr
, 80)[0][4][0]
456 if IPy
and IPy
.IP(ip_addr
).version() == 6:
457 self
.lookup_ipv6_address(ip_addr
)
460 if not ipv4_address_valid(ip_addr
):
461 raise BlockFinderError('Invalid ip address!')
463 print "Reverse lookup for: " + ip_addr
465 geoip_cc
, geoip_cc_name
= self
.geoip_lookup(ip_addr
)
466 print "GeoIP country code: " + str(geoip_cc
)
467 print "GeoIP country name: " + str(geoip_cc_name
)
469 rir
= self
.rir_lookup(ip_addr
)
473 print 'RIR country code:', rir
[0]
474 print 'RIR country:', rir
[1]
476 print 'Not found in RIR db'
478 lir
= self
.lir_lookup(ip_addr
)
480 print 'LIR country code:', lir
[0]
481 print 'LIR country :', lir
[1]
484 if geoip_cc
!= rir_cc
:
485 print "It appears that the RIR data conflicts with the GeoIP data"
486 print "The GeoIP data is likely closer to being correct due to " \
487 "sub-delegation issues with LIR databases"
489 def create_or_replace_lir_table_in_db(self
):
490 self
.cursor
.execute("""drop table if exists lir_record """)
491 self
.cursor
.execute("""create table if not exists lir_record(cc text, start text, value INTEGER, type INTEGER)""")
494 def extract_info_from_lir_file_and_insert_into_sqlite(self
, filename
):
498 insert_text
= """insert into lir_record (cc, start, value, type) VALUES (?,?,?,?)"""
500 for line
in open(self
.cache_dir
+ filename
, "r"):
501 line
= line
.replace("\n", "")
504 country
, block
, version
= "", [], ""
505 elif not entry
and "inetnum:" in line
:
507 line
= line
.replace("inetnum:", "").strip()
508 start_ip
, num_ips
= return_first_ip_and_number_in_inetnum(line
)
509 block
= [start_ip
, num_ips
]
515 elif not entry
and "inet6num:" in line
:
517 block
= line
.replace("inet6num:", "").strip().split("/")
523 elif entry
and "country:" in line
:
524 country
= line
.replace("country:", "").strip()
525 data
= (country
, block
[0], block
[1], version
)
526 self
.cursor
.execute(insert_text
, data
)
529 def create_db_and_insert_delegation_into_db(self
, delegation_urls
):
530 self
.create_sql_database()
531 delegations
= self
.load_all_delegations(delegation_urls
)
532 self
.insert_into_sql_database(delegations
)
535 def return_first_ip_and_number_in_inetnum(line
):
536 start_ip
= line
.split("-")[0].strip()
537 end_ip
= line
.split("-")[1].strip()
538 num_ips
= 1 + (ip_address_to_dec(end_ip
) - ip_address_to_dec(start_ip
) )
539 return start_ip
, num_ips
541 def ip_address_to_dec(ip_addr
):
542 ipar
= ip_addr
.split('.')
545 a
[i
] = hex(int(ipar
[i
]))[2:]
546 if(int(ipar
[i
]) <= 15):
547 a
[i
] = """0""" + a
[i
]
549 total
= '0x'+a
[0]+a
[1]+a
[2]+a
[3]
550 decimal
= int(total
,16)
553 def ipv4_address_valid(ip_addr
):
554 ipv4arr
= ip_addr
.split('.')
555 if len(ipv4arr
) == 4:
556 for items
in ipv4arr
:
564 """ Where the magic starts. """
566 parser
= OptionParser()
567 parser
.add_option("-v", "--verbose", action
="store_true", dest
="verbose", \
568 help = "Be verbose", default
=False)
570 parser
.add_option("-c", "--cachedir", action
="store", dest
="cache_dir", \
571 help = "Set the cache directory", default
= str(os
.path
.expanduser('~')) + "/.blockfinder/" )
573 parser
.add_option("-u","--useragent", action
="store", dest
="useragent", \
574 help = "Provide a useragent which will be used when fetching delegation files" , \
575 default
="Mozilla/5.0")
577 parser
.add_option("-4", "--ipv4", action
="store_true", dest
="ipv4", \
578 help = "Search IPv4 allocations")
580 parser
.add_option("-6", "--ipv6", action
="store_true", dest
="ipv6", \
581 help = "Search IPv6 allocations")
583 parser
.add_option("-a", "--asn", action
="store_true", dest
="asn", \
584 help = "Search ASN allocations")
586 parser
.add_option("-t", "--nation-state", action
="store", dest
="cc", \
587 help = "Set the country to search (given as a two-letter code)")
589 parser
.add_option("-n", "--country-name", action
="store", dest
="country_name", \
590 help = "Set country to search (full name)")
592 parser
.add_option("-x", "--hack-the-internet", action
="store_true", dest
="hack_the_internet", \
593 help = "Hack the internet")
595 parser
.add_option("-r", "--reverse-lookup", action
="store", dest
="reverse_host", \
596 help = "Return the county name for the specified IP or hostname")
598 parser
.add_option("-i", "--initialize-delegation", action
="store_true", dest
="init_del", \
599 help = "Initialize or update delegation information")
601 parser
.add_option("-l", "--initialize-lir", action
="store_true", dest
="init_lir", \
602 help = "Initialize or update lir information")
604 parser
.add_option("-d", "--reload-delegation", action
="store_true", dest
="reload_del", \
605 help = "Use existing delegation files to update the database")
607 parser
.add_option("-z", "--reload-lir", action
="store_true", dest
="reload_lir", \
608 help = "Use existing lir files to update the database")
610 (options
, args
) = parser
.parse_args()
612 verbose
= options
.verbose
613 useragent
= options
.useragent
614 cache_dir
= options
.cache_dir
616 block_f
= Blockfinder(cache_dir
, useragent
, verbose
)
619 delegation_urls
= """
620 ftp://ftp.arin.net/pub/stats/arin/delegated-arin-latest
621 ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest
622 ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest
623 ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest
624 ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest
626 geoip_country_urls
= """http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
627 http://geolite.maxmind.com/download/geoip/database/GeoIPv6.dat.gz"""
629 lir_urls
= """ftp://ftp.ripe.net/ripe/dbase/split/ripe.db.inetnum.gz
630 ftp://ftp.ripe.net/ripe/dbase/split/ripe.db.inet6num.gz"""
631 delegation_files
= []
632 for url
in delegation_urls
.split():
633 filename
= url
.rpartition('/')
634 delegation_files
.append(filename
[-1])
636 block_f
.create_blockfinder_cache_dir()
637 block_f
.connect_to_database()
638 if not os
.path
.exists(cache_dir
+ "countrycodes.txt"):
640 block_f
.download_country_code_file()
645 if options
.hack_the_internet
:
646 print "all your bases are belong to us!"
648 if options
.asn
and options
.reverse_host
:
649 block_f
.asn_lookup(options
.reverse_host
)
652 if options
.reverse_host
:
653 block_f
.lookup_ip_address(options
.reverse_host
)
657 requests
.append("ipv4")
659 requests
.append("ipv6")
661 requests
.append("asn")
664 country
= options
.cc
.upper()
665 if options
.country_name
:
666 country
= block_f
.get_country_code_from_name(options
.country_name
)
668 if options
.reload_del
:
669 block_f
.create_db_and_insert_delegation_into_db(delegation_urls
)
675 block_f
.update_geoip_cache(geoip_country_urls
)
676 block_f
.update_delegation_cache(delegation_urls
)
678 block_f
.verify_cache(delegation_files
)
679 block_f
.create_db_and_insert_delegation_into_db(delegation_urls
)
680 if not options
.init_lir
:
682 if options
.init_lir
or options
.reload_lir
:
684 block_f
.update_lir_delegation_cache(lir_urls
)
685 print "Extracting and inserting information from the lir files can take up to 5 minutes"
686 block_f
.create_or_replace_lir_table_in_db()
687 for fname
in "ripe.db.inetnum ripe.db.inet6num".split():
688 block_f
.extract_info_from_lir_file_and_insert_into_sqlite(fname
)
692 print "Nothing to do. Have you requested anything?"
693 print "Example usage: blockfinder -v --ipv4 -t mm"
697 print "It appears your search did not match a country."
699 # Check our cache age and warn if it's aged
700 if block_f
.cache_is_dated(delegation_files
) and verbose
:
701 print "Your delegation cache is older than 24 hours; you probably want to update it."
703 print "Using country code: %s" % country
705 for request
in requests
:
708 print " \n".join(block_f
.use_sql_database(request
, country
))
709 except sqlite3
.Error
, e
:
711 print "Please try reloading the database. (run ./blockfinder -i)."
715 block_f
.cursor
.close()
719 if __name__
== "__main__":