tor-commits
Threads by month
- ----- 2025 -----
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
October 2012
- 20 participants
- 1288 discussions

[translation/orbot_completed] Update translations for orbot_completed
by translation@torproject.org 07 Oct '12
by translation@torproject.org 07 Oct '12
07 Oct '12
commit 4f1cf73d6038038aea008258d8d8c5478bac38f5
Author: Translation commit bot <translation(a)torproject.org>
Date: Sun Oct 7 16:15:07 2012 +0000
Update translations for orbot_completed
---
values-hu/strings.xml | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
diff --git a/values-hu/strings.xml b/values-hu/strings.xml
index 68f3339..67a4883 100644
--- a/values-hu/strings.xml
+++ b/values-hu/strings.xml
@@ -25,6 +25,8 @@
<string name="menu_stop">Leállítás</string>
<string name="menu_about">Névjegy</string>
<string name="menu_wizard">Varázsló</string>
+ <string name="main_layout_download">Letöltés (sebesség/összes)</string>
+ <string name="main_layout_upload">Feltöltés (sebesség/összes)</string>
<string name="button_help">Súgó</string>
<string name="button_close">Bezár</string>
<string name="button_about">Névjegy</string>
@@ -72,6 +74,9 @@
<string name="wizard_tips_msg">Javasoljuk, hogy töltsön le és használjon olyan alkalmazásokat, amelyek tudják, hogyna kell közvetlenül kapcsolódni az Orbot-hoz. Kattintson az gombokra alább a telepítéshez.</string>
<string name="wizard_tips_otrchat">Gibberbot - Biztonságos azonnali üzenetküldő kliens Android-ra</string>
<string name="wizard_tips_proxy">Proxy beállítások - Tanulja meg, hogyan állíthatja be alkalmazásait, hogy együttműködjenek az Orbot-tal</string>
+ <string name="wizard_tips_duckgo">Duckduckgo keresőgép alkalmazás</string>
+ <string name="wizard_tips_firefox">Firefox Proxy Mobile kiegészítővel (külön telepítés utána)</string>
+ <string name="wizard_tips_twitter">A Twitter támogatja a \"localhost:8118\" http proxyt</string>
<string name="wizard_proxy_help_info">Proxy beállítások</string>
<string name="wizard_proxy_help_msg">Ha az Android alkalmazás, amit használ képes a HTTP vagy SOCKS proxy-k használatára, akkor be tudja állítani, hogy csatlakozzon az Orbot-hoz és használj a Tor-t.\n\n\n A host beállítás 127.0.0.1 vagy \"localhost\". A HTTP-hez a port értéke 8118. A SOCKS esetében a proxy port értéke 9050. SOCKS4A vagy SOCKS5 proxy javasolt használnia, ha lehetséges.\n \n\n\n További információkat talál a proxyzásról Android készülékne a FAQ szekcióban az alábbi oldalon: http://tinyurl.com/proxyandroid\n </string>
<string name="wizard_final">Az Orbot kész!</string>
1
0

07 Oct '12
commit d9e52a3d79f5df6553205468772aa17e41fafb59
Author: Translation commit bot <translation(a)torproject.org>
Date: Sun Oct 7 16:15:06 2012 +0000
Update translations for orbot
---
values-hu/strings.xml | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
diff --git a/values-hu/strings.xml b/values-hu/strings.xml
index 68f3339..67a4883 100644
--- a/values-hu/strings.xml
+++ b/values-hu/strings.xml
@@ -25,6 +25,8 @@
<string name="menu_stop">Leállítás</string>
<string name="menu_about">Névjegy</string>
<string name="menu_wizard">Varázsló</string>
+ <string name="main_layout_download">Letöltés (sebesség/összes)</string>
+ <string name="main_layout_upload">Feltöltés (sebesség/összes)</string>
<string name="button_help">Súgó</string>
<string name="button_close">Bezár</string>
<string name="button_about">Névjegy</string>
@@ -72,6 +74,9 @@
<string name="wizard_tips_msg">Javasoljuk, hogy töltsön le és használjon olyan alkalmazásokat, amelyek tudják, hogyna kell közvetlenül kapcsolódni az Orbot-hoz. Kattintson az gombokra alább a telepítéshez.</string>
<string name="wizard_tips_otrchat">Gibberbot - Biztonságos azonnali üzenetküldő kliens Android-ra</string>
<string name="wizard_tips_proxy">Proxy beállítások - Tanulja meg, hogyan állíthatja be alkalmazásait, hogy együttműködjenek az Orbot-tal</string>
+ <string name="wizard_tips_duckgo">Duckduckgo keresőgép alkalmazás</string>
+ <string name="wizard_tips_firefox">Firefox Proxy Mobile kiegészítővel (külön telepítés utána)</string>
+ <string name="wizard_tips_twitter">A Twitter támogatja a \"localhost:8118\" http proxyt</string>
<string name="wizard_proxy_help_info">Proxy beállítások</string>
<string name="wizard_proxy_help_msg">Ha az Android alkalmazás, amit használ képes a HTTP vagy SOCKS proxy-k használatára, akkor be tudja állítani, hogy csatlakozzon az Orbot-hoz és használj a Tor-t.\n\n\n A host beállítás 127.0.0.1 vagy \"localhost\". A HTTP-hez a port értéke 8118. A SOCKS esetében a proxy port értéke 9050. SOCKS4A vagy SOCKS5 proxy javasolt használnia, ha lehetséges.\n \n\n\n További információkat talál a proxyzásról Android készülékne a FAQ szekcióban az alábbi oldalon: http://tinyurl.com/proxyandroid\n </string>
<string name="wizard_final">Az Orbot kész!</string>
1
0

[ooni-probe/master] Add text file about the current status of tests in this directory.
by art@torproject.org 07 Oct '12
by art@torproject.org 07 Oct '12
07 Oct '12
commit 1d68063f23d8c5780051a83c1ea9258ad7c3bcad
Author: Arturo Filastò <arturo(a)filasto.net>
Date: Sun Oct 7 16:11:58 2012 +0000
Add text file about the current status of tests in this directory.
---
ooni/plugins/TESTS_ARE_MOVING.txt | 8 ++++++++
1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/ooni/plugins/TESTS_ARE_MOVING.txt b/ooni/plugins/TESTS_ARE_MOVING.txt
new file mode 100644
index 0000000..f4c0084
--- /dev/null
+++ b/ooni/plugins/TESTS_ARE_MOVING.txt
@@ -0,0 +1,8 @@
+7/10/2012
+
+All new tests will be moved to the directory /nettests/.
+
+Tests that are in this directory are either here for historical reasons or have
+not yet been properly tested and fully supporting the new API.
+
+A.
1
0
commit 2af04501f054c41179ffe7466670c32a0085f1da
Author: Arturo Filastò <arturo(a)filasto.net>
Date: Sun Oct 7 16:10:03 2012 +0000
Restructure the nettest directory.
---
nettests/captiveportal.py | 620 -----------------------------------
nettests/core/captiveportal.py | 620 +++++++++++++++++++++++++++++++++++
nettests/core/http_host.py | 35 ++
nettests/example_httpt.py | 39 ---
nettests/example_scapyt.py | 15 -
nettests/examples/example_httpt.py | 39 +++
nettests/examples/example_myip.py | 17 +
nettests/examples/example_scapyt.py | 15 +
nettests/http_host.py | 35 --
nettests/myip.py | 17 -
10 files changed, 726 insertions(+), 726 deletions(-)
diff --git a/nettests/captiveportal.py b/nettests/captiveportal.py
deleted file mode 100644
index 6eed04d..0000000
--- a/nettests/captiveportal.py
+++ /dev/null
@@ -1,620 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- captiveportal
- *************
-
- This test is a collection of tests to detect the presence of a
- captive portal. Code is taken, in part, from the old ooni-probe,
- which was written by Jacob Appelbaum and Arturo Filastò.
-
- :copyright: (c) 2012 Isis Lovecruft
- :license: see LICENSE for more details
-"""
-import base64
-import os
-import random
-import re
-import string
-import urllib2
-from urlparse import urlparse
-
-from ooni import nettest
-from ooni.templates import httpt
-from ooni.utils import log
-
-try:
- from dns import resolver
-except ImportError:
- print "The dnspython module was not found. https://crate.io/packages/dnspython/"
- resolver = None
-
-__plugoo__ = "captiveportal"
-__desc__ = "Captive portal detection test"
-
-optParameters = [['asset', 'a', None, 'Asset file'],
- ['experiment-url', 'e', 'http://google.com/', 'Experiment URL'],
- ['user-agent', 'u', random.choice(httpt.useragents),
- 'User agent for HTTP requests']
- ]
-
-class CaptivePortal(nettest.TestCase):
- """
- Compares content and status codes of HTTP responses, and attempts
- to determine if content has been altered.
- """
-
- name = "captivep"
- description = "Captive Portal Test"
- requirements = None
-
- def http_fetch(self, url, headers={}):
- """
- Parses an HTTP url, fetches it, and returns a urllib2 response
- object.
- """
- url = urlparse(url).geturl()
- request = urllib2.Request(url, None, headers)
- response = urllib2.urlopen(request)
- response_headers = dict(response.headers)
- return response, response_headers
-
- def http_content_match_fuzzy_opt(self, experimental_url, control_result,
- headers=None, fuzzy=False):
- """
- Makes an HTTP request on port 80 for experimental_url, then
- compares the response_content of experimental_url with the
- control_result. Optionally, if the fuzzy parameter is set to
- True, the response_content is compared with a regex of the
- control_result. If the response_content from the
- experimental_url and the control_result match, returns True
- with the HTTP status code and headers; False, status code, and
- headers if otherwise.
- """
-
- if headers is None:
- default_ua = self.local_options['user-agent']
- headers = {'User-Agent': default_ua}
-
- response, response_headers = self.http_fetch(experimental_url, headers)
- response_content = response.read()
- response_code = response.code
- if response_content is None:
- log.warn("HTTP connection appears to have failed.")
- return False, False, False
-
- if fuzzy:
- pattern = re.compile(control_result)
- match = pattern.search(response_content)
- log.msg("Fuzzy HTTP content comparison for experiment URL")
- log.msg("'%s'" % experimental_url)
- if not match:
- log.msg("does not match!")
- return False, response_code, response_headers
- else:
- log.msg("and the expected control result yielded a match.")
- return True, response_code, response_headers
- else:
- if str(response_content) != str(control_result):
- log.msg("HTTP content comparison of experiment URL")
- log.msg("'%s'" % experimental_url)
- log.msg("and the expected control result do not match.")
- return False, response_code, response_headers
- else:
- return True, response_code, response_headers
-
- def http_status_code_match(self, experiment_code, control_code):
- """
- Compare two HTTP status codes, returns True if they match.
- """
- return int(experiment_code) == int(control_code)
-
- def http_status_code_no_match(self, experiment_code, control_code):
- """
- Compare two HTTP status codes, returns True if they do not match.
- """
- return int(experiment_code) != int(control_code)
-
- def dns_resolve(self, hostname, nameserver=None):
- """
- Resolves hostname(s) though nameserver to corresponding
- address(es). hostname may be either a single hostname string,
- or a list of strings. If nameserver is not given, use local
- DNS resolver, and if that fails try using 8.8.8.8.
- """
- if not resolver:
- log.msg("dnspython is not installed.\
- Cannot perform DNS Resolve test")
- return []
- if isinstance(hostname, str):
- hostname = [hostname]
-
- if nameserver is not None:
- res = resolver.Resolver(configure=False)
- res.nameservers = [nameserver]
- else:
- res = resolver.Resolver()
-
- response = []
- answer = None
-
- for hn in hostname:
- try:
- answer = res.query(hn)
- except resolver.NoNameservers:
- res.nameservers = ['8.8.8.8']
- try:
- answer = res.query(hn)
- except resolver.NXDOMAIN:
- log.msg("DNS resolution for %s returned NXDOMAIN" % hn)
- response.append('NXDOMAIN')
- except resolver.NXDOMAIN:
- log.msg("DNS resolution for %s returned NXDOMAIN" % hn)
- response.append('NXDOMAIN')
- finally:
- if not answer:
- return response
- for addr in answer:
- response.append(addr.address)
- return response
-
- def dns_resolve_match(self, experiment_hostname, control_address):
- """
- Resolve experiment_hostname, and check to see that it returns
- an experiment_address which matches the control_address. If
- they match, returns True and experiment_address; otherwise
- returns False and experiment_address.
- """
- experiment_address = self.dns_resolve(experiment_hostname)
- if not experiment_address:
- log.debug("dns_resolve() for %s failed" % experiment_hostname)
- return None, experiment_address
-
- if len(set(experiment_address) & set([control_address])) > 0:
- return True, experiment_address
- else:
- log.msg("DNS comparison of control '%s' does not" % control_address)
- log.msg("match experiment response '%s'" % experiment_address)
- return False, experiment_address
-
- def get_auth_nameservers(self, hostname):
- """
- Many CPs set a nameserver to be used. Let's query that
- nameserver for the authoritative nameservers of hostname.
-
- The equivalent of:
- $ dig +short NS ooni.nu
- """
- if not resolver:
- log.msg("dnspython not installed.")
- log.msg("Cannot perform test.")
- return []
-
- res = resolver.Resolver()
- answer = res.query(hostname, 'NS')
- auth_nameservers = []
- for auth in answer:
- auth_nameservers.append(auth.to_text())
- return auth_nameservers
-
- def hostname_to_0x20(self, hostname):
- """
- MaKEs yOur HOsTnaME lOoK LiKE THis.
-
- For more information, see:
- D. Dagon, et. al. "Increased DNS Forgery Resistance
- Through 0x20-Bit Encoding". Proc. CSS, 2008.
- """
- hostname_0x20 = ''
- for char in hostname:
- l33t = random.choice(['caps', 'nocaps'])
- if l33t == 'caps':
- hostname_0x20 += char.capitalize()
- else:
- hostname_0x20 += char.lower()
- return hostname_0x20
-
- def check_0x20_to_auth_ns(self, hostname, sample_size=None):
- """
- Resolve a 0x20 DNS request for hostname over hostname's
- authoritative nameserver(s), and check to make sure that
- the capitalization in the 0x20 request matches that of the
- response. Also, check the serial numbers of the SOA (Start
- of Authority) records on the authoritative nameservers to
- make sure that they match.
-
- If sample_size is given, a random sample equal to that number
- of authoritative nameservers will be queried; default is 5.
- """
- log.msg("")
- log.msg("Testing random capitalization of DNS queries...")
- log.msg("Testing that Start of Authority serial numbers match...")
-
- auth_nameservers = self.get_auth_nameservers(hostname)
-
- if sample_size is None:
- sample_size = 5
- resolved_auth_ns = random.sample(self.dns_resolve(auth_nameservers),
- sample_size)
-
- querynames = []
- answernames = []
- serials = []
-
- # Even when gevent monkey patching is on, the requests here
- # are sent without being 0x20'd, so we need to 0x20 them.
- hostname = self.hostname_to_0x20(hostname)
-
- for auth_ns in resolved_auth_ns:
- res = resolver.Resolver(configure=False)
- res.nameservers = [auth_ns]
- try:
- answer = res.query(hostname, 'SOA')
- except resolver.Timeout:
- continue
- querynames.append(answer.qname.to_text())
- answernames.append(answer.rrset.name.to_text())
- for soa in answer:
- serials.append(str(soa.serial))
-
- if len(set(querynames).intersection(answernames)) == 1:
- log.msg("Capitalization in DNS queries and responses match.")
- name_match = True
- else:
- log.msg("The random capitalization '%s' used in" % hostname)
- log.msg("DNS queries to that hostname's authoritative")
- log.msg("nameservers does not match the capitalization in")
- log.msg("the response.")
- name_match = False
-
- if len(set(serials)) == 1:
- log.msg("Start of Authority serial numbers all match.")
- serial_match = True
- else:
- log.msg("Some SOA serial numbers did not match the rest!")
- serial_match = False
-
- ret = name_match, serial_match, querynames, answernames, serials
-
- if name_match and serial_match:
- log.msg("Your DNS queries do not appear to be tampered.")
- return ret
- elif name_match or serial_match:
- log.msg("Something is tampering with your DNS queries.")
- return ret
- elif not name_match and not serial_match:
- log.msg("Your DNS queries are definitely being tampered with.")
- return ret
-
- def get_random_url_safe_string(self, length):
- """
- Returns a random url-safe string of specified length, where
- 0 < length <= 256. The returned string will always start with
- an alphabetic character.
- """
- if (length <= 0):
- length = 1
- elif (length > 256):
- length = 256
-
- random_ascii = base64.urlsafe_b64encode(os.urandom(int(length)))
-
- while not random_ascii[:1].isalpha():
- random_ascii = base64.urlsafe_b64encode(os.urandom(int(length)))
-
- three_quarters = int((len(random_ascii)) * (3.0/4.0))
- random_string = random_ascii[:three_quarters]
- return random_string
-
- def get_random_hostname(self, length=None):
- """
- Returns a random hostname with SLD of specified length. If
- length is unspecified, length=32 is used.
-
- These *should* all resolve to NXDOMAIN. If they actually
- resolve to a box that isn't part of a captive portal that
- would be rather interesting.
- """
- if length is None:
- length = 32
-
- random_sld = self.get_random_url_safe_string(length)
-
- # if it doesn't start with a letter, chuck it.
- while not random_sld[:1].isalpha():
- random_sld = self.get_random_url_safe_string(length)
-
- tld_list = ['.com', '.net', '.org', '.info', '.test', '.invalid']
- random_tld = urllib2.random.choice(tld_list)
- random_hostname = random_sld + random_tld
- return random_hostname
-
- def compare_random_hostnames(self, hostname_count=None, hostname_length=None):
- """
- Get hostname_count number of random hostnames with SLD length
- of hostname_length, and then attempt DNS resolution. If no
- arguments are given, default to three hostnames of 32 bytes
- each. These random hostnames *should* resolve to NXDOMAIN,
- except in the case where a user is presented with a captive
- portal and remains unauthenticated, in which case the captive
- portal may return the address of the authentication page.
-
- If the cardinality of the intersection of the set of resolved
- random hostnames and the single element control set
- (['NXDOMAIN']) are equal to one, then DNS properly resolved.
-
- Returns true if only NXDOMAINs were returned, otherwise returns
- False with the relative complement of the control set in the
- response set.
- """
- if hostname_count is None:
- hostname_count = 3
-
- log.msg("Generating random hostnames...")
- log.msg("Resolving DNS for %d random hostnames..." % hostname_count)
-
- control = ['NXDOMAIN']
- responses = []
-
- for x in range(hostname_count):
- random_hostname = self.get_random_hostname(hostname_length)
- response_match, response_address = self.dns_resolve_match(random_hostname,
- control[0])
- for address in response_address:
- if response_match is False:
- log.msg("Strangely, DNS resolution of the random hostname")
- log.msg("%s actually points to %s"
- % (random_hostname, response_address))
- responses = responses + [address]
- else:
- responses = responses + [address]
-
- intersection = set(responses) & set(control)
- relative_complement = set(responses) - set(control)
- r = set(responses)
-
- if len(intersection) == 1:
- log.msg("All %d random hostnames properly resolved to NXDOMAIN."
- % hostname_count)
- return True, relative_complement
- elif (len(intersection) == 1) and (len(r) > 1):
- log.msg("Something odd happened. Some random hostnames correctly")
- log.msg("resolved to NXDOMAIN, but several others resolved to")
- log.msg("to the following addresses: %s" % relative_complement)
- return False, relative_complement
- elif (len(intersection) == 0) and (len(r) == 1):
- log.msg("All random hostnames resolved to the IP address ")
- log.msg("'%s', which is indicative of a captive portal." % r)
- return False, relative_complement
- else:
- log.debug("Apparently, pigs are flying on your network, 'cause a")
- log.debug("bunch of hostnames made from 32-byte random strings")
- log.debug("just magically resolved to a bunch of random addresses.")
- log.debug("That is definitely highly improbable. In fact, my napkin")
- log.debug("tells me that the probability of just one of those")
- log.debug("hostnames resolving to an address is 1.68e-59, making")
- log.debug("it nearly twice as unlikely as an MD5 hash collision.")
- log.debug("Either someone is seriously messing with your network,")
- log.debug("or else you are witnessing the impossible. %s" % r)
- return False, relative_complement
-
- def google_dns_cp_test(self):
- """
- Google Chrome resolves three 10-byte random hostnames.
- """
- subtest = "Google Chrome DNS-based"
-
- log.msg("")
- log.msg("Running the Google Chrome DNS-based captive portal test...")
-
- gmatch, google_dns_result = self.compare_random_hostnames(3, 10)
-
- if gmatch:
- log.msg("Google Chrome DNS-based captive portal test did not")
- log.msg("detect a captive portal.")
- return google_dns_result
- else:
- log.msg("Google Chrome DNS-based captive portal test believes")
- log.msg("you are in a captive portal, or else something very")
- log.msg("odd is happening with your DNS.")
- return google_dns_result
-
- def ms_dns_cp_test(self):
- """
- Microsoft "phones home" to a server which will always resolve
- to the same address.
- """
- subtest = "Microsoft NCSI DNS-based"
-
- log.msg("")
- log.msg("Running the Microsoft NCSI DNS-based captive portal")
- log.msg("test...")
-
- msmatch, ms_dns_result = self.dns_resolve_match("dns.msftncsi.com",
- "131.107.255.255")
- if msmatch:
- log.msg("Microsoft NCSI DNS-based captive portal test did not")
- log.msg("detect a captive portal.")
- return ms_dns_result
- else:
- log.msg("Microsoft NCSI DNS-based captive portal test ")
- log.msg("believes you are in a captive portal.")
- return ms_dns_result
-
- def run_vendor_dns_tests(self):
- """
- Run the vendor DNS tests.
- """
- report = {}
- report['google_dns_cp'] = self.google_dns_cp_test()
- report['ms_dns_cp'] = self.ms_dns_cp_test()
-
- return report
-
- def run_vendor_tests(self, *a, **kw):
- """
- These are several vendor tests used to detect the presence of
- a captive portal. Each test compares HTTP status code and
- content to the control results and has its own User-Agent
- string, in order to emulate the test as it would occur on the
- device it was intended for. Vendor tests are defined in the
- format:
- [exp_url, ctrl_result, ctrl_code, ua, test_name]
- """
-
- vendor_tests = [['http://www.apple.com/library/test/success.html',
- 'Success',
- '200',
- 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3',
- 'Apple HTTP Captive Portal'],
- ['http://tools.ietf.org/html/draft-nottingham-http-portal-02',
- '428 Network Authentication Required',
- '428',
- 'Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0',
- 'W3 Captive Portal'],
- ['http://www.msftncsi.com/ncsi.txt',
- 'Microsoft NCSI',
- '200',
- 'Microsoft NCSI',
- 'MS HTTP Captive Portal',]]
-
- cm = self.http_content_match_fuzzy_opt
- sm = self.http_status_code_match
- snm = self.http_status_code_no_match
-
- def compare_content(status_func, fuzzy, experiment_url, control_result,
- control_code, headers, test_name):
- log.msg("")
- log.msg("Running the %s test..." % test_name)
-
- content_match, experiment_code, experiment_headers = cm(experiment_url,
- control_result,
- headers, fuzzy)
- status_match = status_func(experiment_code, control_code)
-
- if status_match and content_match:
- log.msg("The %s test was unable to detect" % test_name)
- log.msg("a captive portal.")
- return True
- else:
- log.msg("The %s test shows that your network" % test_name)
- log.msg("is filtered.")
- return False
-
- result = []
- for vt in vendor_tests:
- report = {}
- report['vt'] = vt
-
- experiment_url = vt[0]
- control_result = vt[1]
- control_code = vt[2]
- headers = {'User-Agent': vt[3]}
- test_name = vt[4]
-
- args = (experiment_url, control_result, control_code, headers, test_name)
-
- if test_name == "MS HTTP Captive Portal":
- report['result'] = compare_content(sm, False, *args)
-
- elif test_name == "Apple HTTP Captive Portal":
- report['result'] = compare_content(sm, True, *args)
-
- elif test_name == "W3 Captive Portal":
- report['result'] = compare_content(snm, True, *args)
-
- else:
- log.warn("Ooni is trying to run an undefined CP vendor test.")
- result.append(report)
- return result
-
- def control(self, experiment_result, args):
- """
- Compares the content and status code of the HTTP response for
- experiment_url with the control_result and control_code
- respectively. If the status codes match, but the experimental
- content and control_result do not match, fuzzy matching is enabled
- to determine if the control_result is at least included somewhere
- in the experimental content. Returns True if matches are found,
- and False if otherwise.
- """
- # XXX put this back to being parametrized
- #experiment_url = self.local_options['experiment-url']
- experiment_url = 'http://google.com/'
- control_result = 'XX'
- control_code = 200
- ua = self.local_options['user-agent']
-
- cm = self.http_content_match_fuzzy_opt
- sm = self.http_status_code_match
- snm = self.http_status_code_no_match
-
- log.msg("Running test for '%s'..." % experiment_url)
- content_match, experiment_code, experiment_headers = cm(experiment_url,
- control_result)
- status_match = sm(experiment_code, control_code)
- if status_match and content_match:
- log.msg("The test for '%s'" % experiment_url)
- log.msg("was unable to detect a captive portal.")
-
- self.report['result'] = True
-
- elif status_match and not content_match:
- log.msg("Retrying '%s' with fuzzy match enabled."
- % experiment_url)
- fuzzy_match, experiment_code, experiment_headers = cm(experiment_url,
- control_result,
- fuzzy=True)
- if fuzzy_match:
- self.report['result'] = True
- else:
- log.msg("Found modified content on '%s'," % experiment_url)
- log.msg("which could indicate a captive portal.")
-
- self.report['result'] = False
- else:
- log.msg("The content comparison test for ")
- log.msg("'%s'" % experiment_url)
- log.msg("shows that your HTTP traffic is filtered.")
-
- self.report['result'] = False
-
- def test_captive_portal(self):
- """
- Runs the CaptivePortal(Test).
-
- CONFIG OPTIONS
- --------------
-
- If "do_captive_portal_vendor_tests" is set to "true", then vendor
- specific captive portal HTTP-based tests will be run.
-
- If "do_captive_portal_dns_tests" is set to "true", then vendor
- specific captive portal DNS-based tests will be run.
-
- If "check_dns_requests" is set to "true", then Ooni-probe will
- attempt to check that your DNS requests are not being tampered with
- by a captive portal.
-
- If "captive_portal" = "yourfilename.txt", then user-specified tests
- will be run.
-
- Any combination of the above tests can be run.
- """
-
- log.msg("")
- log.msg("Running vendor tests...")
- self.report['vendor_tests'] = self.run_vendor_tests()
-
- log.msg("")
- log.msg("Running vendor DNS-based tests...")
- self.report['vendor_dns_tests'] = self.run_vendor_dns_tests()
-
- log.msg("")
- log.msg("Checking that DNS requests are not being tampered...")
- self.report['check0x20'] = self.check_0x20_to_auth_ns('ooni.nu')
-
- log.msg("")
- log.msg("Captive portal test finished!")
-
- self.control(self.report)
-
diff --git a/nettests/core/captiveportal.py b/nettests/core/captiveportal.py
new file mode 100644
index 0000000..6eed04d
--- /dev/null
+++ b/nettests/core/captiveportal.py
@@ -0,0 +1,620 @@
+# -*- coding: utf-8 -*-
+"""
+ captiveportal
+ *************
+
+ This test is a collection of tests to detect the presence of a
+ captive portal. Code is taken, in part, from the old ooni-probe,
+ which was written by Jacob Appelbaum and Arturo Filastò.
+
+ :copyright: (c) 2012 Isis Lovecruft
+ :license: see LICENSE for more details
+"""
+import base64
+import os
+import random
+import re
+import string
+import urllib2
+from urlparse import urlparse
+
+from ooni import nettest
+from ooni.templates import httpt
+from ooni.utils import log
+
+try:
+ from dns import resolver
+except ImportError:
+ print "The dnspython module was not found. https://crate.io/packages/dnspython/"
+ resolver = None
+
+__plugoo__ = "captiveportal"
+__desc__ = "Captive portal detection test"
+
+optParameters = [['asset', 'a', None, 'Asset file'],
+ ['experiment-url', 'e', 'http://google.com/', 'Experiment URL'],
+ ['user-agent', 'u', random.choice(httpt.useragents),
+ 'User agent for HTTP requests']
+ ]
+
+class CaptivePortal(nettest.TestCase):
+ """
+ Compares content and status codes of HTTP responses, and attempts
+ to determine if content has been altered.
+ """
+
+ name = "captivep"
+ description = "Captive Portal Test"
+ requirements = None
+
+ def http_fetch(self, url, headers={}):
+ """
+ Parses an HTTP url, fetches it, and returns a urllib2 response
+ object.
+ """
+ url = urlparse(url).geturl()
+ request = urllib2.Request(url, None, headers)
+ response = urllib2.urlopen(request)
+ response_headers = dict(response.headers)
+ return response, response_headers
+
+ def http_content_match_fuzzy_opt(self, experimental_url, control_result,
+ headers=None, fuzzy=False):
+ """
+ Makes an HTTP request on port 80 for experimental_url, then
+ compares the response_content of experimental_url with the
+ control_result. Optionally, if the fuzzy parameter is set to
+ True, the response_content is compared with a regex of the
+ control_result. If the response_content from the
+ experimental_url and the control_result match, returns True
+ with the HTTP status code and headers; False, status code, and
+ headers if otherwise.
+ """
+
+ if headers is None:
+ default_ua = self.local_options['user-agent']
+ headers = {'User-Agent': default_ua}
+
+ response, response_headers = self.http_fetch(experimental_url, headers)
+ response_content = response.read()
+ response_code = response.code
+ if response_content is None:
+ log.warn("HTTP connection appears to have failed.")
+ return False, False, False
+
+ if fuzzy:
+ pattern = re.compile(control_result)
+ match = pattern.search(response_content)
+ log.msg("Fuzzy HTTP content comparison for experiment URL")
+ log.msg("'%s'" % experimental_url)
+ if not match:
+ log.msg("does not match!")
+ return False, response_code, response_headers
+ else:
+ log.msg("and the expected control result yielded a match.")
+ return True, response_code, response_headers
+ else:
+ if str(response_content) != str(control_result):
+ log.msg("HTTP content comparison of experiment URL")
+ log.msg("'%s'" % experimental_url)
+ log.msg("and the expected control result do not match.")
+ return False, response_code, response_headers
+ else:
+ return True, response_code, response_headers
+
+ def http_status_code_match(self, experiment_code, control_code):
+ """
+ Compare two HTTP status codes, returns True if they match.
+ """
+ return int(experiment_code) == int(control_code)
+
+ def http_status_code_no_match(self, experiment_code, control_code):
+ """
+ Compare two HTTP status codes, returns True if they do not match.
+ """
+ return int(experiment_code) != int(control_code)
+
+ def dns_resolve(self, hostname, nameserver=None):
+ """
+ Resolves hostname(s) though nameserver to corresponding
+ address(es). hostname may be either a single hostname string,
+ or a list of strings. If nameserver is not given, use local
+ DNS resolver, and if that fails try using 8.8.8.8.
+ """
+ if not resolver:
+ log.msg("dnspython is not installed.\
+ Cannot perform DNS Resolve test")
+ return []
+ if isinstance(hostname, str):
+ hostname = [hostname]
+
+ if nameserver is not None:
+ res = resolver.Resolver(configure=False)
+ res.nameservers = [nameserver]
+ else:
+ res = resolver.Resolver()
+
+ response = []
+ answer = None
+
+ for hn in hostname:
+ try:
+ answer = res.query(hn)
+ except resolver.NoNameservers:
+ res.nameservers = ['8.8.8.8']
+ try:
+ answer = res.query(hn)
+ except resolver.NXDOMAIN:
+ log.msg("DNS resolution for %s returned NXDOMAIN" % hn)
+ response.append('NXDOMAIN')
+ except resolver.NXDOMAIN:
+ log.msg("DNS resolution for %s returned NXDOMAIN" % hn)
+ response.append('NXDOMAIN')
+ finally:
+ if not answer:
+ return response
+ for addr in answer:
+ response.append(addr.address)
+ return response
+
+ def dns_resolve_match(self, experiment_hostname, control_address):
+ """
+ Resolve experiment_hostname, and check to see that it returns
+ an experiment_address which matches the control_address. If
+ they match, returns True and experiment_address; otherwise
+ returns False and experiment_address.
+ """
+ experiment_address = self.dns_resolve(experiment_hostname)
+ if not experiment_address:
+ log.debug("dns_resolve() for %s failed" % experiment_hostname)
+ return None, experiment_address
+
+ if len(set(experiment_address) & set([control_address])) > 0:
+ return True, experiment_address
+ else:
+ log.msg("DNS comparison of control '%s' does not" % control_address)
+ log.msg("match experiment response '%s'" % experiment_address)
+ return False, experiment_address
+
+ def get_auth_nameservers(self, hostname):
+ """
+ Many CPs set a nameserver to be used. Let's query that
+ nameserver for the authoritative nameservers of hostname.
+
+ The equivalent of:
+ $ dig +short NS ooni.nu
+ """
+ if not resolver:
+ log.msg("dnspython not installed.")
+ log.msg("Cannot perform test.")
+ return []
+
+ res = resolver.Resolver()
+ answer = res.query(hostname, 'NS')
+ auth_nameservers = []
+ for auth in answer:
+ auth_nameservers.append(auth.to_text())
+ return auth_nameservers
+
+ def hostname_to_0x20(self, hostname):
+ """
+ MaKEs yOur HOsTnaME lOoK LiKE THis.
+
+ For more information, see:
+ D. Dagon, et. al. "Increased DNS Forgery Resistance
+ Through 0x20-Bit Encoding". Proc. CSS, 2008.
+ """
+ hostname_0x20 = ''
+ for char in hostname:
+ l33t = random.choice(['caps', 'nocaps'])
+ if l33t == 'caps':
+ hostname_0x20 += char.capitalize()
+ else:
+ hostname_0x20 += char.lower()
+ return hostname_0x20
+
+ def check_0x20_to_auth_ns(self, hostname, sample_size=None):
+ """
+ Resolve a 0x20 DNS request for hostname over hostname's
+ authoritative nameserver(s), and check to make sure that
+ the capitalization in the 0x20 request matches that of the
+ response. Also, check the serial numbers of the SOA (Start
+ of Authority) records on the authoritative nameservers to
+ make sure that they match.
+
+ If sample_size is given, a random sample equal to that number
+ of authoritative nameservers will be queried; default is 5.
+ """
+ log.msg("")
+ log.msg("Testing random capitalization of DNS queries...")
+ log.msg("Testing that Start of Authority serial numbers match...")
+
+ auth_nameservers = self.get_auth_nameservers(hostname)
+
+ if sample_size is None:
+ sample_size = 5
+ resolved_auth_ns = random.sample(self.dns_resolve(auth_nameservers),
+ sample_size)
+
+ querynames = []
+ answernames = []
+ serials = []
+
+ # Even when gevent monkey patching is on, the requests here
+ # are sent without being 0x20'd, so we need to 0x20 them.
+ hostname = self.hostname_to_0x20(hostname)
+
+ for auth_ns in resolved_auth_ns:
+ res = resolver.Resolver(configure=False)
+ res.nameservers = [auth_ns]
+ try:
+ answer = res.query(hostname, 'SOA')
+ except resolver.Timeout:
+ continue
+ querynames.append(answer.qname.to_text())
+ answernames.append(answer.rrset.name.to_text())
+ for soa in answer:
+ serials.append(str(soa.serial))
+
+ if len(set(querynames).intersection(answernames)) == 1:
+ log.msg("Capitalization in DNS queries and responses match.")
+ name_match = True
+ else:
+ log.msg("The random capitalization '%s' used in" % hostname)
+ log.msg("DNS queries to that hostname's authoritative")
+ log.msg("nameservers does not match the capitalization in")
+ log.msg("the response.")
+ name_match = False
+
+ if len(set(serials)) == 1:
+ log.msg("Start of Authority serial numbers all match.")
+ serial_match = True
+ else:
+ log.msg("Some SOA serial numbers did not match the rest!")
+ serial_match = False
+
+ ret = name_match, serial_match, querynames, answernames, serials
+
+ if name_match and serial_match:
+ log.msg("Your DNS queries do not appear to be tampered.")
+ return ret
+ elif name_match or serial_match:
+ log.msg("Something is tampering with your DNS queries.")
+ return ret
+ elif not name_match and not serial_match:
+ log.msg("Your DNS queries are definitely being tampered with.")
+ return ret
+
+ def get_random_url_safe_string(self, length):
+ """
+ Returns a random url-safe string of specified length, where
+ 0 < length <= 256. The returned string will always start with
+ an alphabetic character.
+ """
+ if (length <= 0):
+ length = 1
+ elif (length > 256):
+ length = 256
+
+ random_ascii = base64.urlsafe_b64encode(os.urandom(int(length)))
+
+ while not random_ascii[:1].isalpha():
+ random_ascii = base64.urlsafe_b64encode(os.urandom(int(length)))
+
+ three_quarters = int((len(random_ascii)) * (3.0/4.0))
+ random_string = random_ascii[:three_quarters]
+ return random_string
+
+ def get_random_hostname(self, length=None):
+ """
+ Returns a random hostname with SLD of specified length. If
+ length is unspecified, length=32 is used.
+
+ These *should* all resolve to NXDOMAIN. If they actually
+ resolve to a box that isn't part of a captive portal that
+ would be rather interesting.
+ """
+ if length is None:
+ length = 32
+
+ random_sld = self.get_random_url_safe_string(length)
+
+ # if it doesn't start with a letter, chuck it.
+ while not random_sld[:1].isalpha():
+ random_sld = self.get_random_url_safe_string(length)
+
+ tld_list = ['.com', '.net', '.org', '.info', '.test', '.invalid']
+ random_tld = urllib2.random.choice(tld_list)
+ random_hostname = random_sld + random_tld
+ return random_hostname
+
+ def compare_random_hostnames(self, hostname_count=None, hostname_length=None):
+ """
+ Get hostname_count number of random hostnames with SLD length
+ of hostname_length, and then attempt DNS resolution. If no
+ arguments are given, default to three hostnames of 32 bytes
+ each. These random hostnames *should* resolve to NXDOMAIN,
+ except in the case where a user is presented with a captive
+ portal and remains unauthenticated, in which case the captive
+ portal may return the address of the authentication page.
+
+ If the cardinality of the intersection of the set of resolved
+ random hostnames and the single element control set
+ (['NXDOMAIN']) are equal to one, then DNS properly resolved.
+
+ Returns true if only NXDOMAINs were returned, otherwise returns
+ False with the relative complement of the control set in the
+ response set.
+ """
+ if hostname_count is None:
+ hostname_count = 3
+
+ log.msg("Generating random hostnames...")
+ log.msg("Resolving DNS for %d random hostnames..." % hostname_count)
+
+ control = ['NXDOMAIN']
+ responses = []
+
+ for x in range(hostname_count):
+ random_hostname = self.get_random_hostname(hostname_length)
+ response_match, response_address = self.dns_resolve_match(random_hostname,
+ control[0])
+ for address in response_address:
+ if response_match is False:
+ log.msg("Strangely, DNS resolution of the random hostname")
+ log.msg("%s actually points to %s"
+ % (random_hostname, response_address))
+ responses = responses + [address]
+ else:
+ responses = responses + [address]
+
+ intersection = set(responses) & set(control)
+ relative_complement = set(responses) - set(control)
+ r = set(responses)
+
+ if len(intersection) == 1:
+ log.msg("All %d random hostnames properly resolved to NXDOMAIN."
+ % hostname_count)
+ return True, relative_complement
+ elif (len(intersection) == 1) and (len(r) > 1):
+ log.msg("Something odd happened. Some random hostnames correctly")
+ log.msg("resolved to NXDOMAIN, but several others resolved to")
+ log.msg("to the following addresses: %s" % relative_complement)
+ return False, relative_complement
+ elif (len(intersection) == 0) and (len(r) == 1):
+ log.msg("All random hostnames resolved to the IP address ")
+ log.msg("'%s', which is indicative of a captive portal." % r)
+ return False, relative_complement
+ else:
+ log.debug("Apparently, pigs are flying on your network, 'cause a")
+ log.debug("bunch of hostnames made from 32-byte random strings")
+ log.debug("just magically resolved to a bunch of random addresses.")
+ log.debug("That is definitely highly improbable. In fact, my napkin")
+ log.debug("tells me that the probability of just one of those")
+ log.debug("hostnames resolving to an address is 1.68e-59, making")
+ log.debug("it nearly twice as unlikely as an MD5 hash collision.")
+ log.debug("Either someone is seriously messing with your network,")
+ log.debug("or else you are witnessing the impossible. %s" % r)
+ return False, relative_complement
+
+ def google_dns_cp_test(self):
+ """
+ Google Chrome resolves three 10-byte random hostnames.
+ """
+ subtest = "Google Chrome DNS-based"
+
+ log.msg("")
+ log.msg("Running the Google Chrome DNS-based captive portal test...")
+
+ gmatch, google_dns_result = self.compare_random_hostnames(3, 10)
+
+ if gmatch:
+ log.msg("Google Chrome DNS-based captive portal test did not")
+ log.msg("detect a captive portal.")
+ return google_dns_result
+ else:
+ log.msg("Google Chrome DNS-based captive portal test believes")
+ log.msg("you are in a captive portal, or else something very")
+ log.msg("odd is happening with your DNS.")
+ return google_dns_result
+
+ def ms_dns_cp_test(self):
+ """
+ Microsoft "phones home" to a server which will always resolve
+ to the same address.
+ """
+ subtest = "Microsoft NCSI DNS-based"
+
+ log.msg("")
+ log.msg("Running the Microsoft NCSI DNS-based captive portal")
+ log.msg("test...")
+
+ msmatch, ms_dns_result = self.dns_resolve_match("dns.msftncsi.com",
+ "131.107.255.255")
+ if msmatch:
+ log.msg("Microsoft NCSI DNS-based captive portal test did not")
+ log.msg("detect a captive portal.")
+ return ms_dns_result
+ else:
+ log.msg("Microsoft NCSI DNS-based captive portal test ")
+ log.msg("believes you are in a captive portal.")
+ return ms_dns_result
+
+ def run_vendor_dns_tests(self):
+ """
+ Run the vendor DNS tests.
+ """
+ report = {}
+ report['google_dns_cp'] = self.google_dns_cp_test()
+ report['ms_dns_cp'] = self.ms_dns_cp_test()
+
+ return report
+
+ def run_vendor_tests(self, *a, **kw):
+ """
+ These are several vendor tests used to detect the presence of
+ a captive portal. Each test compares HTTP status code and
+ content to the control results and has its own User-Agent
+ string, in order to emulate the test as it would occur on the
+ device it was intended for. Vendor tests are defined in the
+ format:
+ [exp_url, ctrl_result, ctrl_code, ua, test_name]
+ """
+
+ vendor_tests = [['http://www.apple.com/library/test/success.html',
+ 'Success',
+ '200',
+ 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3',
+ 'Apple HTTP Captive Portal'],
+ ['http://tools.ietf.org/html/draft-nottingham-http-portal-02',
+ '428 Network Authentication Required',
+ '428',
+ 'Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0',
+ 'W3 Captive Portal'],
+ ['http://www.msftncsi.com/ncsi.txt',
+ 'Microsoft NCSI',
+ '200',
+ 'Microsoft NCSI',
+ 'MS HTTP Captive Portal',]]
+
+ cm = self.http_content_match_fuzzy_opt
+ sm = self.http_status_code_match
+ snm = self.http_status_code_no_match
+
+ def compare_content(status_func, fuzzy, experiment_url, control_result,
+ control_code, headers, test_name):
+ log.msg("")
+ log.msg("Running the %s test..." % test_name)
+
+ content_match, experiment_code, experiment_headers = cm(experiment_url,
+ control_result,
+ headers, fuzzy)
+ status_match = status_func(experiment_code, control_code)
+
+ if status_match and content_match:
+ log.msg("The %s test was unable to detect" % test_name)
+ log.msg("a captive portal.")
+ return True
+ else:
+ log.msg("The %s test shows that your network" % test_name)
+ log.msg("is filtered.")
+ return False
+
+ result = []
+ for vt in vendor_tests:
+ report = {}
+ report['vt'] = vt
+
+ experiment_url = vt[0]
+ control_result = vt[1]
+ control_code = vt[2]
+ headers = {'User-Agent': vt[3]}
+ test_name = vt[4]
+
+ args = (experiment_url, control_result, control_code, headers, test_name)
+
+ if test_name == "MS HTTP Captive Portal":
+ report['result'] = compare_content(sm, False, *args)
+
+ elif test_name == "Apple HTTP Captive Portal":
+ report['result'] = compare_content(sm, True, *args)
+
+ elif test_name == "W3 Captive Portal":
+ report['result'] = compare_content(snm, True, *args)
+
+ else:
+ log.warn("Ooni is trying to run an undefined CP vendor test.")
+ result.append(report)
+ return result
+
+ def control(self, experiment_result, args):
+ """
+ Compares the content and status code of the HTTP response for
+ experiment_url with the control_result and control_code
+ respectively. If the status codes match, but the experimental
+ content and control_result do not match, fuzzy matching is enabled
+ to determine if the control_result is at least included somewhere
+ in the experimental content. Returns True if matches are found,
+ and False if otherwise.
+ """
+ # XXX put this back to being parametrized
+ #experiment_url = self.local_options['experiment-url']
+ experiment_url = 'http://google.com/'
+ control_result = 'XX'
+ control_code = 200
+ ua = self.local_options['user-agent']
+
+ cm = self.http_content_match_fuzzy_opt
+ sm = self.http_status_code_match
+ snm = self.http_status_code_no_match
+
+ log.msg("Running test for '%s'..." % experiment_url)
+ content_match, experiment_code, experiment_headers = cm(experiment_url,
+ control_result)
+ status_match = sm(experiment_code, control_code)
+ if status_match and content_match:
+ log.msg("The test for '%s'" % experiment_url)
+ log.msg("was unable to detect a captive portal.")
+
+ self.report['result'] = True
+
+ elif status_match and not content_match:
+ log.msg("Retrying '%s' with fuzzy match enabled."
+ % experiment_url)
+ fuzzy_match, experiment_code, experiment_headers = cm(experiment_url,
+ control_result,
+ fuzzy=True)
+ if fuzzy_match:
+ self.report['result'] = True
+ else:
+ log.msg("Found modified content on '%s'," % experiment_url)
+ log.msg("which could indicate a captive portal.")
+
+ self.report['result'] = False
+ else:
+ log.msg("The content comparison test for ")
+ log.msg("'%s'" % experiment_url)
+ log.msg("shows that your HTTP traffic is filtered.")
+
+ self.report['result'] = False
+
+ def test_captive_portal(self):
+ """
+ Runs the CaptivePortal(Test).
+
+ CONFIG OPTIONS
+ --------------
+
+ If "do_captive_portal_vendor_tests" is set to "true", then vendor
+ specific captive portal HTTP-based tests will be run.
+
+ If "do_captive_portal_dns_tests" is set to "true", then vendor
+ specific captive portal DNS-based tests will be run.
+
+ If "check_dns_requests" is set to "true", then Ooni-probe will
+ attempt to check that your DNS requests are not being tampered with
+ by a captive portal.
+
+ If "captive_portal" = "yourfilename.txt", then user-specified tests
+ will be run.
+
+ Any combination of the above tests can be run.
+ """
+
+ log.msg("")
+ log.msg("Running vendor tests...")
+ self.report['vendor_tests'] = self.run_vendor_tests()
+
+ log.msg("")
+ log.msg("Running vendor DNS-based tests...")
+ self.report['vendor_dns_tests'] = self.run_vendor_dns_tests()
+
+ log.msg("")
+ log.msg("Checking that DNS requests are not being tampered...")
+ self.report['check0x20'] = self.check_0x20_to_auth_ns('ooni.nu')
+
+ log.msg("")
+ log.msg("Captive portal test finished!")
+
+ self.control(self.report)
+
diff --git a/nettests/core/http_host.py b/nettests/core/http_host.py
new file mode 100644
index 0000000..2228c52
--- /dev/null
+++ b/nettests/core/http_host.py
@@ -0,0 +1,35 @@
+# -*- encoding: utf-8 -*-
+#
+# HTTP Host Test
+# **************
+#
+# for more details see:
+# https://trac.torproject.org/projects/tor/wiki/doc/OONI/Tests/HTTPHost
+#
+# :authors: Arturo Filastò
+# :licence: see LICENSE
+
+from ooni.templates import httpt
+
+class HTTPHost(httpt.HTTPTest):
+ name = "HTTP Host"
+ author = "Arturo Filastò"
+ version = 0.1
+
+ optParameters = [['url', 'u', 'http://torproject.org/', 'Test single site'],
+ ['backend', 'b', 'http://ooni.nu/test/', 'Test backend to use'],
+ ]
+
+ inputFile = ['urls', 'f', None, 'Urls file']
+
+ def test_send_host_header(self):
+ headers = {}
+ headers["Host"] = [self.input]
+ return self.doRequest(self.localOptions['backend'], headers=headers)
+
+ def processResponseBody(self, body):
+ if 'not censored' in body:
+ self.report['trans_http_proxy'] = False
+ else:
+ self.report['trans_http_proxy'] = True
+
diff --git a/nettests/example_httpt.py b/nettests/example_httpt.py
deleted file mode 100644
index b113b23..0000000
--- a/nettests/example_httpt.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- encoding: utf-8 -*-
-#
-# :authors: Arturo Filastò
-# :licence: see LICENSE
-
-from ooni.templates import httpt
-class ExampleHTTP(httpt.HTTPTest):
- name = "Example HTTP Test"
- author = "Arturo Filastò"
- version = 0.1
-
- inputs = ['http://google.com/', 'http://wikileaks.org/',
- 'http://torproject.org/']
-
- def test_http(self):
- if self.input:
- url = self.input
- return self.doRequest(url)
- else:
- raise Exception("No input specified")
-
- def processResponseBody(self, body):
- # XXX here shall go your logic
- # for processing the body
- if 'blocked' in body:
- self.report['censored'] = True
- else:
- self.report['censored'] = False
-
- def processResponseHeaders(self, headers):
- # XXX place in here all the logic for handling the processing of HTTP
- # Headers.
- if headers.hasHeader('location'):
- self.report['redirect'] = True
-
- server = headers.getRawHeaders("Server")
- if server:
- self.report['http_server'] = str(server.pop())
-
diff --git a/nettests/example_scapyt.py b/nettests/example_scapyt.py
deleted file mode 100644
index 1cc8054..0000000
--- a/nettests/example_scapyt.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- encoding: utf-8 -*-
-#
-# :authors: Arturo Filastò
-# :licence: see LICENSE
-
-from ooni.templates import scapyt
-from scapy.all import *
-class ExampleScapy(scapyt.ScapyTest):
- name = "Example Scapy Test"
- author = "Arturo Filastò"
- version = 0.1
-
- inputs = [IP(dst="8.8.8.8")/TCP(dport=31337),
- IP(dst="ooni.nu")/TCP(dport=31337)]
-
diff --git a/nettests/examples/example_httpt.py b/nettests/examples/example_httpt.py
new file mode 100644
index 0000000..b113b23
--- /dev/null
+++ b/nettests/examples/example_httpt.py
@@ -0,0 +1,39 @@
+# -*- encoding: utf-8 -*-
+#
+# :authors: Arturo Filastò
+# :licence: see LICENSE
+
+from ooni.templates import httpt
+class ExampleHTTP(httpt.HTTPTest):
+ name = "Example HTTP Test"
+ author = "Arturo Filastò"
+ version = 0.1
+
+ inputs = ['http://google.com/', 'http://wikileaks.org/',
+ 'http://torproject.org/']
+
+ def test_http(self):
+ if self.input:
+ url = self.input
+ return self.doRequest(url)
+ else:
+ raise Exception("No input specified")
+
+ def processResponseBody(self, body):
+ # XXX here shall go your logic
+ # for processing the body
+ if 'blocked' in body:
+ self.report['censored'] = True
+ else:
+ self.report['censored'] = False
+
+ def processResponseHeaders(self, headers):
+ # XXX place in here all the logic for handling the processing of HTTP
+ # Headers.
+ if headers.hasHeader('location'):
+ self.report['redirect'] = True
+
+ server = headers.getRawHeaders("Server")
+ if server:
+ self.report['http_server'] = str(server.pop())
+
diff --git a/nettests/examples/example_myip.py b/nettests/examples/example_myip.py
new file mode 100644
index 0000000..40a4849
--- /dev/null
+++ b/nettests/examples/example_myip.py
@@ -0,0 +1,17 @@
+# -*- encoding: utf-8 -*-
+#
+# :authors: Arturo Filastò
+# :licence: see LICENSE
+
+from ooni.templates import httpt
+class MyIP(httpt.HTTPTest):
+ inputs = ['https://check.torproject.org']
+ def processResponseBody(self, body):
+ import re
+ regexp = "Your IP address appears to be: <b>(.+?)<\/b>"
+ match = re.search(regexp, body)
+ try:
+ self.report['myip'] = match.group(1)
+ except:
+ self.report['myip'] = None
+
diff --git a/nettests/examples/example_scapyt.py b/nettests/examples/example_scapyt.py
new file mode 100644
index 0000000..1cc8054
--- /dev/null
+++ b/nettests/examples/example_scapyt.py
@@ -0,0 +1,15 @@
+# -*- encoding: utf-8 -*-
+#
+# :authors: Arturo Filastò
+# :licence: see LICENSE
+
+from ooni.templates import scapyt
+from scapy.all import *
+class ExampleScapy(scapyt.ScapyTest):
+ name = "Example Scapy Test"
+ author = "Arturo Filastò"
+ version = 0.1
+
+ inputs = [IP(dst="8.8.8.8")/TCP(dport=31337),
+ IP(dst="ooni.nu")/TCP(dport=31337)]
+
diff --git a/nettests/http_host.py b/nettests/http_host.py
deleted file mode 100644
index 2228c52..0000000
--- a/nettests/http_host.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- encoding: utf-8 -*-
-#
-# HTTP Host Test
-# **************
-#
-# for more details see:
-# https://trac.torproject.org/projects/tor/wiki/doc/OONI/Tests/HTTPHost
-#
-# :authors: Arturo Filastò
-# :licence: see LICENSE
-
-from ooni.templates import httpt
-
-class HTTPHost(httpt.HTTPTest):
- name = "HTTP Host"
- author = "Arturo Filastò"
- version = 0.1
-
- optParameters = [['url', 'u', 'http://torproject.org/', 'Test single site'],
- ['backend', 'b', 'http://ooni.nu/test/', 'Test backend to use'],
- ]
-
- inputFile = ['urls', 'f', None, 'Urls file']
-
- def test_send_host_header(self):
- headers = {}
- headers["Host"] = [self.input]
- return self.doRequest(self.localOptions['backend'], headers=headers)
-
- def processResponseBody(self, body):
- if 'not censored' in body:
- self.report['trans_http_proxy'] = False
- else:
- self.report['trans_http_proxy'] = True
-
diff --git a/nettests/myip.py b/nettests/myip.py
deleted file mode 100644
index 40a4849..0000000
--- a/nettests/myip.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- encoding: utf-8 -*-
-#
-# :authors: Arturo Filastò
-# :licence: see LICENSE
-
-from ooni.templates import httpt
-class MyIP(httpt.HTTPTest):
- inputs = ['https://check.torproject.org']
- def processResponseBody(self, body):
- import re
- regexp = "Your IP address appears to be: <b>(.+?)<\/b>"
- match = re.search(regexp, body)
- try:
- self.report['myip'] = match.group(1)
- except:
- self.report['myip'] = None
-
1
0
commit cff756fd2b2120f54562f44152c7a6736207cc07
Author: Arturo Filastò <arturo(a)filasto.net>
Date: Sun Oct 7 16:07:26 2012 +0000
Avoid logging to stdout twice.
(Since we now use trial as a base for test running we no longer
have to attach logging to stdout, because trial does it for us!)
---
ooni/runner.py | 1 -
ooni/utils/log.py | 6 +-----
2 files changed, 1 insertions(+), 6 deletions(-)
diff --git a/ooni/runner.py b/ooni/runner.py
index 81272eb..ebeb4c5 100644
--- a/ooni/runner.py
+++ b/ooni/runner.py
@@ -208,7 +208,6 @@ class ORunner(object):
result.done()
def run(self):
- #log.startLogging(sys.stdout)
log.start()
self.reporterFactory.options = self.options
diff --git a/ooni/utils/log.py b/ooni/utils/log.py
index b5e1363..eef50d8 100644
--- a/ooni/utils/log.py
+++ b/ooni/utils/log.py
@@ -73,7 +73,7 @@ class OONILogObserver(log.FileLogObserver):
util.untilConcludes(self.write, timeStr + " " + msgStr)
util.untilConcludes(self.flush)
-def start(logfile=None, logstdout=True, verbosity=None):
+def start(logfile=None, verbosity=None):
if log.defaultObserver:
verbosity = _get_log_level(verbosity)
@@ -81,10 +81,6 @@ def start(logfile=None, logstdout=True, verbosity=None):
file = open(logfile, 'a') if logfile else stderr
OONILogObserver(file, "info").start()
- if logstdout is True:
- observerooni = OONILogObserver(stdout, verbosity)
- log.addObserver(observerooni.emit)
-
log.msg("Starting OONI...")
def debug(message, level="debug", **kw):
1
0

[ooni-probe/master] Implement geodata lookups to be included in every report.
by art@torproject.org 07 Oct '12
by art@torproject.org 07 Oct '12
07 Oct '12
commit 0452fa3e05e8d0d01dc2c135d166927653dd0cdd
Author: Arturo Filastò <arturo(a)filasto.net>
Date: Sun Oct 7 16:06:30 2012 +0000
Implement geodata lookups to be included in every report.
---
ooni/inputunit.py | 13 +++++++++++++
ooni/nettest.py | 18 +++++++++++++++++-
ooni/reporter.py | 41 +++++++++++++++++++++++++++++++----------
ooni/runner.py | 16 +++++++++++-----
ooni/utils/geodata.py | 7 ++++---
5 files changed, 76 insertions(+), 19 deletions(-)
diff --git a/ooni/inputunit.py b/ooni/inputunit.py
index 69507b4..157f038 100644
--- a/ooni/inputunit.py
+++ b/ooni/inputunit.py
@@ -1,3 +1,16 @@
+from twisted.trial import unittest
+
+class PatchedPyUnitResultAdapter(unittest.PyUnitResultAdapter):
+ def __init__(self, original):
+ """
+ Here we patch PyUnitResultAdapter to support our reporterFactory to
+ properly write headers to reports.
+ """
+ self.original = original
+ self.reporterFactory = original.reporterFactory
+
+unittest.PyUnitResultAdapter = PatchedPyUnitResultAdapter
+
class InputUnitFactory(object):
"""
This is a factory that takes the size of input units to be generated a set
diff --git a/ooni/nettest.py b/ooni/nettest.py
index 89dd279..835a5a0 100644
--- a/ooni/nettest.py
+++ b/ooni/nettest.py
@@ -1,6 +1,7 @@
import itertools
from twisted.python import log
from twisted.trial import unittest, itrial
+from twisted.internet import defer
pyunit = __import__('unittest')
@@ -71,12 +72,27 @@ class TestCase(unittest.TestCase):
inputs = [None]
inputFile = None
-
report = {}
report['errors'] = []
optParameters = None
+ def deferSetUp(self, ignored, result):
+ """
+ If we have the reporterFactory set we need to write the header. If such
+ method is not present we will only run the test skipping header
+ writing.
+ """
+ if result.reporterFactory.firstrun:
+ print "Running both!!"
+ d1 = result.reporterFactory.writeHeader()
+ d2 = unittest.TestCase.deferSetUp(self, ignored, result)
+ dl = defer.DeferredList([d1, d2])
+ return dl
+ else:
+ print "Only one :P"
+ return unittest.TestCase.deferSetUp(self, ignored, result)
+
def inputProcessor(self, fp):
for x in fp.readlines():
yield x.strip()
diff --git a/ooni/reporter.py b/ooni/reporter.py
index 86c1fc1..b335738 100644
--- a/ooni/reporter.py
+++ b/ooni/reporter.py
@@ -6,7 +6,8 @@ import itertools
from datetime import datetime
from twisted.python.util import OrderedDict, untilConcludes
from twisted.trial import unittest, reporter, runner
-from ooni.utils import date
+from twisted.internet import defer
+from ooni.utils import date, log, geodata
try:
from scapy.all import packet
@@ -24,6 +25,8 @@ class OReporter(pyunit.TestResult):
This is an extension of the unittest TestResult. It adds support for
reporting to yaml format.
"""
+ reporterFactory = None
+
def __init__(self, stream=sys.stdout, tbformat='default', realtime=False,
publisher=None, testSuite=None):
super(OReporter, self).__init__()
@@ -62,6 +65,8 @@ class ReporterFactory(OReporter):
This is a reporter factory. It emits new instances of Reports. It is also
responsible for writing the OONI Report headers.
"""
+ firstrun = True
+
def __init__(self, stream=sys.stdout, tbformat='default', realtime=False,
publisher=None, testSuite=None):
super(ReporterFactory, self).__init__(stream=stream,
@@ -70,25 +75,41 @@ class ReporterFactory(OReporter):
self._testSuite = testSuite
self._reporters = []
- def writeHeader(self, options, geodata={}):
+ @defer.inlineCallbacks
+ def writeHeader(self):
+ self.firstrun = False
+ options = self.options
self._writeln("###########################################")
self._writeln("# OONI Probe Report for %s test" % options['name'])
self._writeln("# %s" % date.pretty_date())
self._writeln("###########################################")
- address = {'asn': 'unknown',
- 'ip': 'unknown'}
- if 'ip' in geodata:
- address['ip'] = geodata['ip']
+ client_geodata = {}
+ log.msg("Running geo IP lookup via check.torproject.org")
+
+ client_ip = yield geodata.myIP()
+ try:
+ import txtorcon
+ client_location = txtorcon.util.NetLocation(client_ip)
+ except:
+ log.err("txtorcon is not installed. Geolocation lookup is not"\
+ "supported")
- if 'asn' in geodata:
- address['asn'] = geodata['asn']
+ client_geodata['ip'] = client_ip
+ client_geodata['asn'] = client_location.asn
+ client_geodata['city'] = client_location.city
+ client_geodata['countrycode'] = client_location.countrycode
test_details = {'startTime': repr(date.now()),
- 'probeASN': address['asn'],
+ 'probeASN': client_geodata['asn'],
+ 'probeCC': client_geodata['countrycode'],
+ 'probeIP': client_geodata['ip'],
+ 'probeLocation': {'city': client_geodata['city'],
+ 'countrycode':
+ client_geodata['countrycode']},
'testName': options['name'],
'testVersion': options['version'],
- 'probeIP': address['ip']}
+ }
self.writeYamlLine(test_details)
self._writeln('')
diff --git a/ooni/runner.py b/ooni/runner.py
index a753d02..81272eb 100644
--- a/ooni/runner.py
+++ b/ooni/runner.py
@@ -16,7 +16,7 @@ from ooni.reporter import ReporterFactory
from ooni.inputunit import InputUnitFactory
from ooni.nettest import InputTestSuite
from ooni import nettest
-from ooni.utils import log
+from ooni.utils import log, geodata
from ooni.plugoo import tests as oonitests
def isTestCase(thing):
@@ -97,13 +97,16 @@ def processTest(obj, config):
optParameters = obj.optParameters
inputFile = obj.inputFile
- Options.optParameters.append(inputFile)
+ if inputFile:
+ Options.optParameters.append(inputFile)
options = Options()
options.parseOptions(config['subArgs'])
obj.localOptions = options
- obj.inputFile = options[inputFile[0]]
+
+ if inputFile:
+ obj.inputFile = options[inputFile[0]]
return obj
@@ -186,7 +189,10 @@ class ORunner(object):
def runWithInputUnit(self, inputUnit):
idx = 0
result = self.reporterFactory.create()
+
for input in inputUnit:
+ result.reporterFactory = self.reporterFactory
+
suite = self.baseSuite(self.cases)
suite.input = input
suite(result, idx)
@@ -204,9 +210,9 @@ class ORunner(object):
def run(self):
#log.startLogging(sys.stdout)
log.start()
- self.reporterFactory.writeHeader(self.options)
+
+ self.reporterFactory.options = self.options
for inputUnit in InputUnitFactory(self.inputs):
self.runWithInputUnit(inputUnit)
-
diff --git a/ooni/utils/geodata.py b/ooni/utils/geodata.py
index 3e5202e..9f782e8 100644
--- a/ooni/utils/geodata.py
+++ b/ooni/utils/geodata.py
@@ -17,11 +17,13 @@ class BodyReceiver(protocol.Protocol):
def myIP():
target_site = 'https://check.torproject.org/'
regexp = "Your IP address appears to be: <b>(.+?)<\/b>"
-
myAgent = Agent(reactor)
+
result = yield myAgent.request('GET', target_site)
+
finished = defer.Deferred()
result.deliverBody(BodyReceiver(finished))
+
body = yield finished
match = re.search(regexp, body)
@@ -30,6 +32,5 @@ def myIP():
except:
myip = "unknown"
- return myip
-
+ defer.returnValue(myip)
1
0

r25840: {website} Adding Compass and Pyoninoo to the project listing Adding a (website/trunk/getinvolved/en)
by Damian Johnson 06 Oct '12
by Damian Johnson 06 Oct '12
06 Oct '12
Author: atagar
Date: 2012-10-06 18:41:41 +0000 (Sat, 06 Oct 2012)
New Revision: 25840
Modified:
website/trunk/getinvolved/en/volunteer.wml
Log:
Adding Compass and Pyoninoo to the project listing
Adding a table entry for Compass and noting Pyonionoo under Onionoo's listing
(seems redundant to list them separately).
Modified: website/trunk/getinvolved/en/volunteer.wml
===================================================================
--- website/trunk/getinvolved/en/volunteer.wml 2012-10-06 01:48:05 UTC (rev 25839)
+++ website/trunk/getinvolved/en/volunteer.wml 2012-10-06 18:41:41 UTC (rev 25840)
@@ -238,6 +238,14 @@
</tr>
<tr class="alt">
+ <td><a href="#project-compass">Compass</a></td>
+ <td>Client Service</td>
+ <td>Python</td>
+ <td>Heavy</td>
+ <td>gsathya, karsten</td>
+ </tr>
+
+ <tr>
<td><a href="#project-weather">Weather</a></td>
<td>Client Service</td>
<td>Python</td>
@@ -245,7 +253,7 @@
<td>kaner</td>
</tr>
- <tr>
+ <tr class="alt">
<td><a href="#project-gettor">GetTor</a></td>
<td>Client Service</td>
<td>Python</td>
@@ -253,7 +261,7 @@
<td>kaner</td>
</tr>
- <tr class="alt">
+ <tr>
<td><a href="#project-torcheck">TorCheck</a></td>
<td>Client Service</td>
<td>Python, Perl</td>
@@ -261,15 +269,15 @@
<td></td>
</tr>
- <tr>
+ <tr class="alt">
<td><a href="#project-onionoo">Onionoo</a></td>
<td>Backend Service</td>
- <td>Java</td>
+ <td>Java, Python</td>
<td>Moderate</td>
<td>karsten, gsathya</td>
</tr>
- <tr class="alt">
+ <tr>
<td><a href="#project-bridgedb">BridgeDB</a></td>
<td>Backend Service</td>
<td>Python</td>
@@ -277,7 +285,7 @@
<td>kaner, nickm</td>
</tr>
- <tr>
+ <tr class="alt">
<td><a href="#project-torflow">TorFlow</a></td>
<td>Backend Service</td>
<td>Python</td>
@@ -285,7 +293,7 @@
<td>aagbsn</td>
</tr>
- <tr class="alt">
+ <tr>
<td>*<a href="#project-torbel">TorBEL</a></td>
<td>Backend Service</td>
<td>Python</td>
@@ -599,6 +607,17 @@
Django counterpart.
</p>
+ <a id="project-compass"></a>
+ <h3><a href="https://compass.torproject.org/">Compass</a> (<a
+ href="https://gitweb.torproject.org/compass.git">code</a>, <a
+ href="https://trac.torproject.org/projects/tor/query?status=accepted&status=assig…">bug
+ tracker</a>)</h3>
+
+ <p>
+ Compass is a web and command line application that filters and
+ aggregates the Tor relays based on various attributes.
+ </p>
+
<a id="project-weather"></a>
<h3><a href="https://trac.torproject.org/projects/tor/wiki/org/roadmaps/Weather">Weather</a> (<a
href="https://gitweb.torproject.org/weather.git">code</a>, <a
@@ -636,7 +655,9 @@
<a id="project-onionoo"></a>
<h3><a href="<page projects/onionoo>">Onionoo</a> (<a
- href="https://gitweb.torproject.org/onionoo.git">code</a>)</h3>
+ href="https://gitweb.torproject.org/onionoo.git">java codebase</a>, <a
+ href="https://gitweb.torproject.org/pyonionoo.git">python
+ codebase</a>)</h3>
<p>
Onionoo is a JSON based protocol to learn information about currently
1
0

[translation/liveusb-creator] Update translations for liveusb-creator
by translation@torproject.org 06 Oct '12
by translation@torproject.org 06 Oct '12
06 Oct '12
commit 1dd1c9480af2fa125a67324061d7a01dae5d5fb0
Author: Translation commit bot <translation(a)torproject.org>
Date: Sat Oct 6 18:15:36 2012 +0000
Update translations for liveusb-creator
---
es/es.po | 12 ++++++------
1 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/es/es.po b/es/es.po
index 73cddfb..2ad04f1 100644
--- a/es/es.po
+++ b/es/es.po
@@ -9,7 +9,7 @@ msgstr ""
"Project-Id-Version: The Tor Project\n"
"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
"POT-Creation-Date: 2012-04-13 14:16+0200\n"
-"PO-Revision-Date: 2012-10-06 17:44+0000\n"
+"PO-Revision-Date: 2012-10-06 17:52+0000\n"
"Last-Translator: Carlos Capote <carloscapote(a)masticable.org>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/torproject/language/es/)\n"
"MIME-Version: 1.0\n"
@@ -90,7 +90,7 @@ msgstr "Instalación completa. Pulsa OK para cerrar este programa."
msgid ""
"The Master Boot Record on your device is blank. Pressing 'Create Live USB' "
"again will reset the MBR on this device."
-msgstr ""
+msgstr "El registro de arranque principal (MBR) de tu dispositivo está en blanco. Al pulsar 'Crear Live USB' de nuevo, se reseteará el MBR de este dispositivo."
#: ../liveusb/gui.py:624
msgid ""
@@ -152,7 +152,7 @@ msgstr "No se puede leer el fichero seleccionado. Por favor, corrige los permiso
msgid ""
"Unable to use the selected file. You may have better luck if you move your "
"ISO to the root of your drive (ie: C:\\)"
-msgstr ""
+msgstr "Imposible utilizar el fichero seleccionado. Puede que tengas más suerte si mueves la ISO a la carpeta raíz de tu unidad (por ejemplo, C:\\)"
#: ../liveusb/gui.py:743
#, python-format
@@ -174,11 +174,11 @@ msgstr "Verificando la suma MD5 del ISO"
#: ../liveusb/creator.py:137
msgid "ISO MD5 checksum verification failed"
-msgstr ""
+msgstr "Falló la verificación ISO MD5"
#: ../liveusb/creator.py:139
msgid "ISO MD5 checksum passed"
-msgstr ""
+msgstr "Verificación ISO MD5 pasada con éxito"
#: ../liveusb/creator.py:144
msgid "Extracting live image to USB device..."
@@ -276,7 +276,7 @@ msgstr ""
#: ../liveusb/creator.py:786 ../liveusb/creator.py:886
msgid "Unknown filesystem. Your device may need to be reformatted."
-msgstr ""
+msgstr "Sistema de ficheros no conocido. Debe reformatearse el dispositivo."
#: ../liveusb/creator.py:789
#, python-format
1
0

[translation/liveusb-creator] Update translations for liveusb-creator
by translation@torproject.org 06 Oct '12
by translation@torproject.org 06 Oct '12
06 Oct '12
commit 3a89752fc14e07ee5fed654cf3fb82249815d4cb
Author: Translation commit bot <translation(a)torproject.org>
Date: Sat Oct 6 17:45:36 2012 +0000
Update translations for liveusb-creator
---
es/es.po | 48 ++++++++++++++++++++++++------------------------
1 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/es/es.po b/es/es.po
index 4f345d4..73cddfb 100644
--- a/es/es.po
+++ b/es/es.po
@@ -9,7 +9,7 @@ msgstr ""
"Project-Id-Version: The Tor Project\n"
"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
"POT-Creation-Date: 2012-04-13 14:16+0200\n"
-"PO-Revision-Date: 2012-10-06 16:38+0000\n"
+"PO-Revision-Date: 2012-10-06 17:44+0000\n"
"Last-Translator: Carlos Capote <carloscapote(a)masticable.org>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/torproject/language/es/)\n"
"MIME-Version: 1.0\n"
@@ -84,7 +84,7 @@ msgstr "¡Instalación completa!"
#: ../liveusb/gui.py:587
msgid "Installation was completed. Press OK to close this program."
-msgstr ""
+msgstr "Instalación completa. Pulsa OK para cerrar este programa."
#: ../liveusb/gui.py:610
msgid ""
@@ -105,15 +105,15 @@ msgstr "No se pudo montar el dispositivo"
#: ../liveusb/gui.py:644
msgid "Warning: All data on the selected drive will be lost."
-msgstr ""
+msgstr "Advertencia: Se perderán todos los datos de la unidad seleccionada."
#: ../liveusb/gui.py:646 ../liveusb/gui.py:663
msgid "Press 'Next' if you wish to continue."
-msgstr ""
+msgstr "Pulsa 'Siguiente' si quieres continuar"
#: ../liveusb/gui.py:648 ../liveusb/gui.py:665
msgid "Next"
-msgstr ""
+msgstr "Siguiente"
#: ../liveusb/gui.py:658
msgid ""
@@ -146,7 +146,7 @@ msgstr "Seleccione ISO Vivo"
msgid ""
"The selected file is unreadable.Please fix its permissions or select another"
" file."
-msgstr ""
+msgstr "No se puede leer el fichero seleccionado. Por favor, corrige los permisos o elige otro fichero."
#: ../liveusb/gui.py:737
msgid ""
@@ -157,7 +157,7 @@ msgstr ""
#: ../liveusb/gui.py:743
#, python-format
msgid "%(filename)s selected"
-msgstr ""
+msgstr "%(filename)s seleccionados"
#: ../liveusb/creator.py:91
msgid "You must run this application as root"
@@ -187,7 +187,7 @@ msgstr "Extrayendo la imágen viva en el dispositivo USB..."
#: ../liveusb/creator.py:151
#, python-format
msgid "Wrote to device at %(speed)d MB/sec"
-msgstr ""
+msgstr "Escrito el dispositivo a %(speed)d MB/sec"
#: ../liveusb/creator.py:181
msgid "Setting up OLPC boot file..."
@@ -198,7 +198,7 @@ msgstr "Configurando un archivo OLPC de arranque..."
msgid ""
"There was a problem executing the following command: `%(command)s`.\n"
"A more detailed error log has been written to '%(filename)s'."
-msgstr ""
+msgstr "Se produjo un error ejecutando el siguiente comando: `%(command)s`.\nUn log más detallado del error ha sido escrito en '%(filename)s'."
#: ../liveusb/creator.py:384
msgid "Verifying SHA1 checksum of LiveCD image..."
@@ -234,12 +234,12 @@ msgstr ""
#: ../liveusb/creator.py:497
msgid "Removing existing Live OS"
-msgstr ""
+msgstr "Eliminando Live OS"
#: ../liveusb/creator.py:506 ../liveusb/creator.py:517
#, python-format
msgid "Unable to chmod %(file)s: %(message)s"
-msgstr ""
+msgstr "No se pudo ejecutar chmod %(file)s: %(message)s"
#: ../liveusb/creator.py:510
#, python-format
@@ -265,7 +265,7 @@ msgstr ""
#: ../liveusb/creator.py:718
#, python-format
msgid "Unable to write on %(device)s, skipping."
-msgstr ""
+msgstr "Imposible escribir en %(device)s, ignorando."
#: ../liveusb/creator.py:737
#, python-format
@@ -302,12 +302,12 @@ msgstr ""
#: ../liveusb/creator.py:823
msgid "No mount points found"
-msgstr ""
+msgstr "No se encontraron puntos de montaje"
#: ../liveusb/creator.py:834
#, python-format
msgid "Unmounting %(device)s"
-msgstr ""
+msgstr "Desmontando %(device)s"
#: ../liveusb/creator.py:846
#, python-format
@@ -321,21 +321,21 @@ msgstr "Verificando sistema de archivo..."
#: ../liveusb/creator.py:907
#, python-format
msgid "Unable to change volume label: %(message)s"
-msgstr ""
+msgstr "Imposible cambiar el nombre del volumen: %(message)s"
#: ../liveusb/creator.py:912 ../liveusb/creator.py:1236
msgid "Installing bootloader..."
-msgstr ""
+msgstr "Instalando bootloader..."
#: ../liveusb/creator.py:935 ../liveusb/creator.py:1254
#, python-format
msgid "Removing %(file)s"
-msgstr ""
+msgstr "Eliminando %(file)s"
#: ../liveusb/creator.py:1017
#, python-format
msgid "%s already bootable"
-msgstr ""
+msgstr "%s ya es de arranque"
#: ../liveusb/creator.py:1037
msgid "Unable to find partition"
@@ -344,12 +344,12 @@ msgstr "No se pudo encontrar la partición"
#: ../liveusb/creator.py:1060
#, python-format
msgid "Formatting %(device)s as FAT32"
-msgstr ""
+msgstr "Formateando %(device)s como FAT32"
#: ../liveusb/creator.py:1109
#, python-format
msgid "Resetting Master Boot Record of %s"
-msgstr ""
+msgstr "Reestableciendo el MBR de %s"
#: ../liveusb/creator.py:1112
msgid ""
@@ -363,19 +363,19 @@ msgstr ""
#: ../liveusb/creator.py:1119 ../liveusb/creator.py:1382
#, python-format
msgid "Calculating the SHA1 of %s"
-msgstr ""
+msgstr "Calculando el SHA1 de %s"
#: ../liveusb/creator.py:1143
msgid "Synchronizing data on disk..."
-msgstr ""
+msgstr "Sincronizando datos en el disco..."
#: ../liveusb/creator.py:1195
msgid "Error probing device"
-msgstr ""
+msgstr "Error probando el dispositivo"
#: ../liveusb/creator.py:1197
msgid "Unable to find any removable device"
-msgstr ""
+msgstr "No se pudo encontrar ningún dispositivo extraíble"
#: ../liveusb/creator.py:1207
msgid ""
1
0

[translation/liveusb-creator] Update translations for liveusb-creator
by translation@torproject.org 06 Oct '12
by translation@torproject.org 06 Oct '12
06 Oct '12
commit 776f24b910208b2b9dfcde7a069804aa282bb41b
Author: Translation commit bot <translation(a)torproject.org>
Date: Sat Oct 6 16:45:36 2012 +0000
Update translations for liveusb-creator
---
es/es.po | 17 +++++++++--------
1 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/es/es.po b/es/es.po
index 626396c..4f345d4 100644
--- a/es/es.po
+++ b/es/es.po
@@ -3,13 +3,14 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
+# Carlos Capote <carloscapote(a)masticable.org>, 2012.
msgid ""
msgstr ""
"Project-Id-Version: The Tor Project\n"
"Report-Msgid-Bugs-To: https://trac.torproject.org/projects/tor\n"
"POT-Creation-Date: 2012-04-13 14:16+0200\n"
-"PO-Revision-Date: 2012-10-03 18:09+0000\n"
-"Last-Translator: Tor Project <tor-assistants(a)torproject.org>\n"
+"PO-Revision-Date: 2012-10-06 16:38+0000\n"
+"Last-Translator: Carlos Capote <carloscapote(a)masticable.org>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/torproject/language/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -30,7 +31,7 @@ msgstr "Descargando %s..."
#: ../liveusb/gui.py:197
#, python-format
msgid "Device is too small: it must be at least %s MiB."
-msgstr ""
+msgstr "El dispositivo es demasiado pequeño: debe ser de, al menos %s MB."
#: ../liveusb/gui.py:226
msgid ""
@@ -41,7 +42,7 @@ msgstr "Error: No se pudo etiquetar u obtener el UUID de su dispositivo. No se p
#: ../liveusb/gui.py:276
#, python-format
msgid "Installation complete! (%s)"
-msgstr ""
+msgstr "¡Instalación completa! (%s)"
#: ../liveusb/gui.py:281
msgid "LiveUSB creation failed!"
@@ -52,11 +53,11 @@ msgid ""
"Warning: This tool needs to be run as an Administrator. To do this, right "
"click on the icon and open the Properties. Under the Compatibility tab, "
"check the \"Run this program as an administrator\" box."
-msgstr ""
+msgstr "Advertencia: Esta herramienta debe ser ejecutada por un Administrador. Para hacerlo, haz clic con el botón derecho en el icono y abre las Propiedades. Bajo la pestaña de Compatibilidad, marca la casilla \"Ejecutar este programa como un administrador\"."
#: ../liveusb/gui.py:413
msgid "Unable to find any USB drive"
-msgstr ""
+msgstr "No se encontró ninguna unidad USB"
#: ../liveusb/gui.py:521
msgid ""
@@ -67,7 +68,7 @@ msgstr "El dispositivo no está montado todavía, por lo que no se puede determi
#: ../liveusb/gui.py:528
#, python-format
msgid "No free space on device %(device)s"
-msgstr ""
+msgstr "No hay espacio libre en el dispositivo %(device)s"
#: ../liveusb/gui.py:533
msgid "Partition is FAT16; Restricting overlay size to 2G"
@@ -79,7 +80,7 @@ msgstr "Almacenamiento Persistente"
#: ../liveusb/gui.py:586
msgid "Installation complete!"
-msgstr ""
+msgstr "¡Instalación completa!"
#: ../liveusb/gui.py:587
msgid "Installation was completed. Press OK to close this program."
1
0