diff --git a/doc/CHANGELOG b/doc/CHANGELOG index b64185f..805eb2c 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -2,9 +2,19 @@ Changelog: Border-Check ============================== + +================= +October 4, 2013: +================= + +- added feature to import traces from XML template +- fixed some LFT subprocess errors on Unix based systems +- more clean non-debug mode +- added 'localhost' filter to target parser + ================= September 27, 2013: ================= -- Public release: Border Check v0.1 +- public release: Border Check v0.1 diff --git a/doc/INSTALL b/doc/INSTALL index 41d0c02..bac6fd3 100644 --- a/doc/INSTALL +++ b/doc/INSTALL @@ -30,7 +30,11 @@ BC runs on OSx and Unix systems. It requires the following libraries/dependencie sudo apt-get install python-pip - and then run pip install lxml and pip install pygeoip + and then run: + + pip install lxml + + pip install pygeoip - biplist: https://pypi.python.org/pypi/biplist/0.5 diff --git a/doc/README b/doc/README index e2da879..df79439 100644 --- a/doc/README +++ b/doc/README @@ -22,6 +22,7 @@ Options: -h, --help show this help message and exit -d, --debug debug mode --xml=EXPORT_XML export traces to xml (ex: --xml foo.xml) + --load=IMPORT_XML import traces to show (ex: --load bar.xml) --bh=BROWSER_HISTORY set browser's history path -b BROWSER set browser manually: F = Firefox / C = Chrome / S = Safari / Ch = Chromium @@ -64,4 +65,8 @@ $ python bc --bh "Library/Safari/History.plist" $ python bc --bh "Library/Safari/History.plist" ------------------- +* Import 'traces' from xml: + +$ python bc --load "mytravel.xml" +------------------- diff --git a/main.py b/main.py index c769f36..fb8aaeb 100755 --- a/main.py +++ b/main.py @@ -23,9 +23,6 @@ from webserver import BorderCheckWebserver from xml_exporter import xml_reporting import webbrowser -# set to emit debug messages about errors (0 = off). -DEBUG = 1 - class bc(object): """ BC main Class @@ -96,17 +93,21 @@ class bc(object): try: return func(*args) except Exception as e: - print("\n[Error] - Something wrong fetching urls. Aborting..."), "\n" - if DEBUG: + if not options.debug: + print("[Error] - Something wrong happens!. Try to run again with --debug option to see more detailed information about the error on a Traceback output."), "\n" + else: + print("[Error] - Something wrong happens!. You have the reason at the end of the Traceback. If you don't understand what's happen, try to contact with project contributors."), "\n" + if options.debug == 1: traceback.print_exc() - sys.exit(2) + print "" # \n after traceback ouput + sys.exit(2) def check_root(self): """ Check root permissions """ if not os.geteuid()==0: - sys.exit("\nOnly root can run this script...\n") + sys.exit("Warning: Only root can launch traceroutes. (Try: 'sudo ./bc')\n") def check_browser(self): """ @@ -384,7 +385,10 @@ class bc(object): print "Can't get Safari version information, you'll have to look it up manually \n" else: print "Version:", self.browser_version - print "History:", self.browser_history_path, "\n" + if self.options.import_xml: # history not needed on xml importing + pass + else: + print "History:", self.browser_history_path, "\n" def getURL(self): """ @@ -429,6 +433,9 @@ class bc(object): """ Run an LFT """ + # LFT needs root + root = self.try_running(self.check_root, "\nInternal error checking root permissions.") + #try: if self.operating_system == 'darwin': try: @@ -438,19 +445,19 @@ class bc(object): self.content = a.stdout.read() if self.operating_system == 'linux': - if self.method == '-e': + if self.method == '-e': # tcp probes self.method = '-E' try: - self.content = subprocess.check_output(['lft', '-S', '-n', self.destination_ip]) + self.content = subprocess.check_output(['lft', '-S', '-n', self.method, self.destination_ip]) # support for older python versions (<2.75) that don't support subprocess.check_output except: - a = subprocess.Popen(['lft', '-S', '-n', self.destination_ip], stdout=subprocess.PIPE) + a = subprocess.Popen(['lft', '-S', '-n', self.method, self.destination_ip], stdout=subprocess.PIPE) self.content = a.stdout.read() self.attempts += 1 if self.options.debug == True: print "Tracing:", self.destination_ip, "with method:", self.method, 'attempt:', self.attempts, '\n' self.lft_parse() - + def lft_parse(self): """ Parse the lft to see if it produced any results, if not, run another LFT using a different method @@ -524,7 +531,7 @@ class bc(object): for ip in line: if re.match(r'\d{1,4}\.\dms$', ip): self.timestamp = ip.replace('ms', '') - if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', ip): + if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', ip) or re.match('localhost', ip): pass else: if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$",ip): @@ -639,6 +646,45 @@ class bc(object): os.remove('GeoIPASNum.gz') print "Database: GeoIPASNum \n" + def importXML(self): + """ + Import travels data directly from XML file (no root needed) and launch a web browser on a thread with a map showing them. + """ + try: + xml_results = xml_reporting(self) + xml_imported = xml_results.read_xml_results() # read xml directly from file + except: + print("[Error] - Something wrong importing data from XML file. Aborting..."), "\n" + sys.exit(2) + + # Set the maxmind geo databases + self.geoip = pygeoip.GeoIP('GeoLiteCity.dat') + self.geoasn = pygeoip.GeoIP('GeoIPASNum.dat') + match_ip = xml_imported[0].strip('http://').strip(':8080') + #regex for filtering local network IPs + if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip) or match_ip.startswith('file://') or match_ip.startswith('localhost'): + print '='*45 + "\n", "Target:\n" + '='*45 + "\n" + print "URL:", self.options.import_xml, "\n" + print "Warning: This target is not valid!.\n" + sys.exit(2) + else: + if xml_imported[0].startswith('file://'): + print '='*45 + "\n", "Target:\n" + '='*45 + "\n" + print "URL:", self.options.import_xml, "\n" + print "Warning: This target is not valid!.\n" + sys.exit(2) + else: + print '='*45 + "\n", "Target:\n" + '='*45 + "\n" + print "URL:", self.options.import_xml, "\n" + print "Host:", xml_imported[0], "\n" + os.system('cp -r ' + self.options.import_xml + ' data.xml') # copy XML data provided by user to data.xml template + # start web mode (on a different thread) + try: + webbrowser.open('http://127.0.0.1:8080', new=1) + BorderCheckWebserver(self) + except (KeyboardInterrupt, SystemExit): + sys.exit() + def run(self, opts=None): """ Run BorderCheck @@ -653,77 +699,78 @@ class bc(object): print('='*75) print(str(p.version)) print('='*75) - # root checker - root = self.try_running(self.check_root, "\nInternal error checking root permissions.") # extract browser type and path browser = self.try_running(self.check_browser, "\nInternal error checking browser files path.") # extract url url = self.try_running(self.getURL, "\nInternal error getting urls from browser's database.") # set geoip database geo = self.try_running(self.getGEO, "\nInternal error setting geoIP database.") - # run traceroutes - match_ip = self.url[0].strip('http://').strip(':8080') - #regex for filtering local network IPs - if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip) or match_ip.startswith('file://'): - print '='*45 + "\n", "Target:\n" + '='*45 + "\n" - print "URL:", self.url[0], "\n" - print "Warning: This target is not valid!.\n" - pass + # read from XML or run traceroutes + stay latent mode + if options.import_xml: + import_xml = self.try_running(self.importXML, "\nInternal error importing XML data from file.") else: - if self.url[0].startswith('file://'): + match_ip = self.url[0].strip('http://').strip(':8080') + #regex for filtering local network IPs + if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip) or match_ip.startswith('file://') or match_ip.startswith('localhost'): print '='*45 + "\n", "Target:\n" + '='*45 + "\n" print "URL:", self.url[0], "\n" print "Warning: This target is not valid!.\n" pass else: - traces = self.try_running(self.traces, "\nInternal error tracerouting.") + if self.url[0].startswith('file://'): + print '='*45 + "\n", "Target:\n" + '='*45 + "\n" + print "URL:", self.url[0], "\n" + print "Warning: This target is not valid!.\n" + pass + else: + traces = self.try_running(self.traces, "\nInternal error tracerouting.") # start web mode (on a different thread) - try: - t = threading.Thread(target=BorderCheckWebserver, args=(self, )) - t.daemon = True - t.start() - time.sleep(2) - except (KeyboardInterrupt, SystemExit): - t.join() - sys.exit() - # open same browser of history access on a new tab try: - webbrowser.open('http://127.0.0.1:8080', new=1) - except: - print "Error: Browser is not responding correctly.\n" + t = threading.Thread(target=BorderCheckWebserver, args=(self, )) + t.daemon = True + t.start() + time.sleep(2) + except (KeyboardInterrupt, SystemExit): + t.join() + sys.exit() + # open same browser of history access on a new tab + try: + webbrowser.open('http://127.0.0.1:8080', new=1) + except: + print "Error: Browser is not responding correctly.\n" - print('='*75) - print(str(p.version)) - print('='*75 + "\n") - print "Status: Waiting for new urls ...\n" - print "Type 'Control+C' to exit.\n" - # stay latent waiting for new urls - while True: - url = urlparse(self.getURL()).netloc - #url = url.replace('www.','') - try: - match_ip = url.strip('http://').strip(':8080') - except: - print '='*45 + "\n", "Target:\n" + '='*45 + "\n" - print "URL:", self.url[0], "\n" - pass - if url != self.old_url: - if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip): + print('='*75) + print(str(p.version)) + print('='*75 + "\n") + print "Status: Waiting for new urls ...\n" + print "Type 'Control+C' to exit.\n" + # stay latent waiting for new urls + while True: + url = urlparse(self.getURL()).netloc + #url = url.replace('www.','') + try: + match_ip = url.strip('http://').strip(':8080') + except: + print '='*45 + "\n", "Target:\n" + '='*45 + "\n" + print "URL:", self.url[0], "\n" pass - else: - if self.url[0].startswith('file://'): + if url != self.old_url: + if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip) or match_ip.startswith('localhost'): pass else: - if os.path.exists('data.xml'): # removing xml data to has a new map each time that bc is launched - os.remove('data.xml') - open('data.xml', 'w') # starting a new xml data container in write mode - traces = self.try_running(self.traces, "\nInternal error tracerouting.") - # open same browser of history access on a new tab - # try: - # webbrowser.open('http://127.0.0.1:8080', new=2) # open on same tab? - # except: - # print "Error: Browser is not responding correctly.\n" - time.sleep(5) # free process time or goodbye :-) + if self.url[0].startswith('file://'): + pass + else: + if os.path.exists('data.xml'): # removing xml data to has a new map each time that bc is launched + os.remove('data.xml') + open('data.xml', 'w') # starting a new xml data container in write mode + traces = self.try_running(self.traces, "\nInternal error tracerouting.") + # open same browser of history access on a new tab + # try: + # webbrowser.open('http://127.0.0.1:8080', new=2) # open on same tab? + # except: + # print "Error: Browser is not responding correctly.\n" + time.sleep(5) # free process time or goodbye :-) if __name__ == "__main__": app = bc() diff --git a/options.py b/options.py index 34a7d25..d544609 100644 --- a/options.py +++ b/options.py @@ -15,6 +15,7 @@ class BCOptions(optparse.OptionParser): self.add_option("-d", "--debug", action="store_true", dest="debug", help="debug mode") self.add_option("--xml", action="store", dest="export_xml", help="export traces to xml (ex: --xml foo.xml)") + self.add_option("--load", action="store", dest="import_xml", help="import traces to show (ex: --load bar.xml)") self.add_option("--bh", action="store", dest="browser_history", help="set browser's history path") self.add_option("-b", action="store", dest="browser", help="set browser manually: F = Firefox / C = Chrome / S = Safari / Ch = Chromium") #self.add_option("--proxy", action="store", dest="proxy", help="set proxy server") diff --git a/xml_exporter.py b/xml_exporter.py index 3358fc7..8146498 100755 --- a/xml_exporter.py +++ b/xml_exporter.py @@ -4,8 +4,6 @@ BC (Border-Check) is a tool to retrieve info of traceroute tests over website navigation routes. GPLv3 - 2013 by psy (epsylon@riseup.net) """ -import xml.etree.ElementTree as ET - class xml_reporting(object): """ Print results from a traceroute in an XML fashion @@ -15,6 +13,7 @@ class xml_reporting(object): self.instance = bc def print_xml_results(self, filename): + import xml.etree.ElementTree as ET root = ET.Element("travel") i = 1 for i in self.instance.result_list: @@ -48,3 +47,37 @@ class xml_reporting(object): tree = ET.ElementTree(root) tree.write(filename) + def read_xml_results(self): + from xml.dom.minidom import parseString + file = open(self.instance.options.import_xml,'r') + data = file.read() + file.close() + dom = parseString(data) + travel_tag = dom.getElementsByTagName('travel')[0].toxml() + travel_data = travel_tag.replace('','').replace('','').split('<')[0] + hop_tag = dom.getElementsByTagName('hop')[0].toxml() + hop_data = hop_tag.replace('','').replace('','') + host_tag = dom.getElementsByTagName('host')[0].toxml() + host_data = host_tag.replace('','').replace('','') + hop_ip_tag = dom.getElementsByTagName('hop_ip')[0].toxml() + hop_ip_data = hop_ip_tag.replace('','').replace('','') + longitude_tag = dom.getElementsByTagName('longitude')[0].toxml() + longitude_data = longitude_tag.replace('','').replace('','') + latitude_tag = dom.getElementsByTagName('latitude')[0].toxml() + latitude_data = hop_tag.replace('','').replace('','') + city_tag = dom.getElementsByTagName('city')[0].toxml() + city_data = city_tag.replace('','').replace('','') + country_tag = dom.getElementsByTagName('country')[0].toxml() + country_data = country_tag.replace('','').replace('','') + server_name_tag = dom.getElementsByTagName('server_name')[0].toxml() + server_name_data = server_name_tag.replace('','').replace('','') + asn_tag = dom.getElementsByTagName('asn')[0].toxml() + asn_data = asn_tag.replace('','').replace('','') + timestamp_tag = dom.getElementsByTagName('timestamp')[0].toxml() + timestamp_data = timestamp_tag.replace('','').replace('','') + country_code_tag = dom.getElementsByTagName('country_code')[0].toxml() + country_code_data = country_code_tag.replace('','').replace('','') + meta_tag = dom.getElementsByTagName('meta')[0].toxml() + meta_data = meta_tag.replace('','').replace('','') + + return travel_data, hop_data, hop_ip_data, longitude_data, latitude_data, city_data, country_data, server_name_data, asn_data, timestamp_data, country_code_data, meta_data