tor-commits
Threads by month
- ----- 2025 -----
- June
- 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
November 2013
- 17 participants
- 1171 discussions

[flashproxy/master] make fac.py in flashproxy-common not soft-implicit-depends on facilitator-reg
by infinity0@torproject.org 21 Nov '13
by infinity0@torproject.org 21 Nov '13
21 Nov '13
commit def182a2501070fa5dfcb9793fe977c58b0861ae
Author: Ximin Luo <infinity0(a)gmx.com>
Date: Thu Nov 21 18:57:23 2013 +0000
make fac.py in flashproxy-common not soft-implicit-depends on facilitator-reg
- to do this, we turn put_reg_base64 into a generic run-a-program function
- also push the pad-base64 behaviour directly into facilitator-reg
- fix earlier oversight, and document the fact that facilitator-reg is used
---
facilitator/doc/facilitator-design.txt | 4 ++--
facilitator/facilitator-email-poller | 2 +-
facilitator/facilitator-reg | 7 ++++++-
facilitator/facilitator.cgi | 2 +-
flashproxy/fac.py | 14 ++++----------
5 files changed, 14 insertions(+), 15 deletions(-)
diff --git a/facilitator/doc/facilitator-design.txt b/facilitator/doc/facilitator-design.txt
index 20f9c0a..19d5d47 100644
--- a/facilitator/doc/facilitator-design.txt
+++ b/facilitator/doc/facilitator-design.txt
@@ -5,8 +5,8 @@ pass them to the backend. There are three supported helper rendezvous
methods: HTTP, email, and appspot.
facilitator-reg is a simple program that forwards its standard input to
-a locally running facilitator-reg-daemon process. It is not used by the
-other components, but is useful for debugging and test purposes.
+a locally running facilitator-reg-daemon process. It is used by other
+components as a utility, but is also useful for debugging and testing.
facilitator-reg-daemon accepts connections containing encrypted client
registrations and forwards them to the facilitator. It exists as a
diff --git a/facilitator/facilitator-email-poller b/facilitator/facilitator-email-poller
index 9c35df1..8a5c972 100755
--- a/facilitator/facilitator-email-poller
+++ b/facilitator/facilitator-email-poller
@@ -265,7 +265,7 @@ def message_ok(msg):
def handle_message(msg):
try:
- if fac.put_reg_base64(msg.get_payload()):
+ if fac.put_reg_proc(["facilitator-reg"], msg.get_payload()):
log(u"registered client")
else:
log(u"failed to register client")
diff --git a/facilitator/facilitator-reg b/facilitator/facilitator-reg
index 5346dcc..3a3d196 100755
--- a/facilitator/facilitator-reg
+++ b/facilitator/facilitator-reg
@@ -48,11 +48,16 @@ def main():
s = socket.socket(addrinfo[0], addrinfo[1], addrinfo[2])
s.connect(addrinfo[4])
+ sent = 0
while True:
data = sys.stdin.read(1024)
if data == "":
+ mod = sent % 4
+ if mod != 0:
+ s.sendall((4 - mod) * "=")
break
- s.send(data)
+ s.sendall(data)
+ sent += len(data)
s.shutdown(socket.SHUT_WR)
data = s.recv(1024)
diff --git a/facilitator/facilitator.cgi b/facilitator/facilitator.cgi
index addcaf4..10f4b19 100755
--- a/facilitator/facilitator.cgi
+++ b/facilitator/facilitator.cgi
@@ -23,7 +23,7 @@ def exit_error(status):
def send_url_reg(reg):
# Translate from url-safe base64 alphabet to the standard alphabet.
reg = reg.replace('-', '+').replace('_', '/')
- return fac.put_reg_base64(reg)
+ return fac.put_reg_proc(["facilitator-reg"], reg)
method = os.environ.get("REQUEST_METHOD")
remote_addr = (os.environ.get("REMOTE_ADDR"), None)
diff --git a/flashproxy/fac.py b/flashproxy/fac.py
index 0686f54..25c1f84 100644
--- a/flashproxy/fac.py
+++ b/flashproxy/fac.py
@@ -211,14 +211,8 @@ def get_reg(facilitator_addr, proxy_addr, proxy_transport_list):
else:
raise ValueError("Facilitator response was not \"OK\"")
-def put_reg_base64(b64):
- """Attempt to add a registration by running a facilitator-reg program
- locally."""
- # Padding is optional, but the python base64 functions can't
- # handle lack of padding. Add it here. Assumes correct encoding.
- mod = len(b64) % 4
- if mod != 0:
- b64 += (4 - mod) * "="
- p = subprocess.Popen(["facilitator-reg"], stdin=subprocess.PIPE)
- stdout, stderr = p.communicate(b64)
+def put_reg_proc(args, data):
+ """Attempt to add a registration by running a program."""
+ p = subprocess.Popen(args, stdin=subprocess.PIPE)
+ stdout, stderr = p.communicate(data)
return p.returncode == 0
1
0

[flashproxy/master] add short descriptions to the top of all programs.
by infinity0@torproject.org 21 Nov '13
by infinity0@torproject.org 21 Nov '13
21 Nov '13
commit c228341a056f0380bb5d70d84ed9e0d2f67956bb
Author: Ximin Luo <infinity0(a)gmx.com>
Date: Thu Nov 21 18:16:49 2013 +0000
add short descriptions to the top of all programs.
---
facilitator/facilitator | 3 +++
facilitator/facilitator-email-poller | 3 +++
facilitator/facilitator-reg | 3 +++
facilitator/facilitator-reg-daemon | 3 +++
flashproxy-client | 3 +++
flashproxy-reg-appspot | 1 +
flashproxy-reg-email | 1 +
flashproxy-reg-http | 1 +
flashproxy-reg-url | 1 +
9 files changed, 19 insertions(+)
diff --git a/facilitator/facilitator b/facilitator/facilitator
index 39ffe2a..35d74be 100755
--- a/facilitator/facilitator
+++ b/facilitator/facilitator
@@ -1,4 +1,7 @@
#!/usr/bin/env python
+"""
+The flashproxy facilitator.
+"""
import SocketServer
import getopt
diff --git a/facilitator/facilitator-email-poller b/facilitator/facilitator-email-poller
index f30e1cb..9c35df1 100755
--- a/facilitator/facilitator-email-poller
+++ b/facilitator/facilitator-email-poller
@@ -1,4 +1,7 @@
#!/usr/bin/env python
+"""
+Polls a mailbox for new registrations and forwards them using facilitator-reg.
+"""
import calendar
import datetime
diff --git a/facilitator/facilitator-reg b/facilitator/facilitator-reg
index 67f8bb1..5346dcc 100755
--- a/facilitator/facilitator-reg
+++ b/facilitator/facilitator-reg
@@ -1,4 +1,7 @@
#!/usr/bin/env python
+"""
+Forwards encrypted client registrations to a running facilitator-reg-daemon.
+"""
import getopt
import socket
diff --git a/facilitator/facilitator-reg-daemon b/facilitator/facilitator-reg-daemon
index f5e592f..bba5aab 100755
--- a/facilitator/facilitator-reg-daemon
+++ b/facilitator/facilitator-reg-daemon
@@ -1,4 +1,7 @@
#!/usr/bin/env python
+"""
+Accepts encrypted client registrations and forwards them to the facilitator.
+"""
import SocketServer
import getopt
diff --git a/flashproxy-client b/flashproxy-client
index f2cf04b..ee0b068 100755
--- a/flashproxy-client
+++ b/flashproxy-client
@@ -1,4 +1,7 @@
#!/usr/bin/env python
+"""
+The flashproxy client transport plugin.
+"""
import BaseHTTPServer
import array
diff --git a/flashproxy-reg-appspot b/flashproxy-reg-appspot
index 7738e43..db2fdbd 100755
--- a/flashproxy-reg-appspot
+++ b/flashproxy-reg-appspot
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+"""Register with a facilitator through Google App Engine."""
import errno
import getopt
diff --git a/flashproxy-reg-email b/flashproxy-reg-email
index d286af8..5d38fa5 100755
--- a/flashproxy-reg-email
+++ b/flashproxy-reg-email
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+"""Register with a facilitator using the email method."""
import errno
import getopt
diff --git a/flashproxy-reg-http b/flashproxy-reg-http
index 83b7084..9f85570 100755
--- a/flashproxy-reg-http
+++ b/flashproxy-reg-http
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+"""Register with a facilitator using the HTTP method."""
import getopt
import socket
diff --git a/flashproxy-reg-url b/flashproxy-reg-url
index 0f92d44..4685f28 100755
--- a/flashproxy-reg-url
+++ b/flashproxy-reg-url
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+"""Register with a facilitator using an indirect URL."""
import base64
import getopt
1
0
commit 4b546c6c381e611e1354836b372622a43571699a
Author: Translation commit bot <translation(a)torproject.org>
Date: Thu Nov 21 17:45:08 2013 +0000
Update translations for tsum
---
gl/short-user-manual_gl_noimg.xhtml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/gl/short-user-manual_gl_noimg.xhtml b/gl/short-user-manual_gl_noimg.xhtml
index 91e4b1b..663c9f3 100644
--- a/gl/short-user-manual_gl_noimg.xhtml
+++ b/gl/short-user-manual_gl_noimg.xhtml
@@ -22,12 +22,12 @@
<p><strong>Nota</strong>: Os Tor Browser Bundles para Linux e Mac OS X son moi grandes, e non poderá recibir estes paquetes cunha conta de Gmail, Hotmail ou Yahoo. Se non pode recibir o paquete que quere, envíe un correo-e a help(a)rt.torproject.org e dámoslle unha lista de páxinas espello das que baixalo.</p>
<h3 id="tor-for-smartphones">Tor para teléfonos intelixentes</h3>
<p>Pode obter Tor no dispositivo Android coa instalación do paquete chamado <em>Orbot</em>. Para obter información sobre como descargar e instalar Orbot, consulte a <a href="https://www.torproject.org/docs/android.html.en">páxina do Proxecto Tor</a>.</p>
- <p>We also have experimental packages for <a href="https://www.torproject.org/docs/N900.html.en">Nokia Maemo/N900</a> and <a href="http://sid77.slackware.it/iphone/">Apple iOS</a>.</p>
+ <p>Temos tamén paquetes experimentais para <a href="https://www.torproject.org/docs/N900.html.en">Nokia Maemo/N900</a> e <a href="http://sid77.slackware.it/iphone/">Apple iOS</a>.</p>
<h3 id="how-to-verify-that-you-have-the-right-version">Como comprobar se ten a versión correcta</h3>
<p>Antes de executar o Tor Browser Bundle, ten que asegurarse de que ten a versión correcta.</p>
<p>O software que recibiu vai acompañado por un arquivo co mesmo nome do paquete e a extensión <strong>.asc</strong>. Este ficheiro asc. é unha sinatura GPG, e permitirá que comprobe que o ficheiro que baixou é exactamente o pretendido.</p>
<p>Antes de que poida comprobar a sinatura, terá que baixar e instalar o GnuPG:</p>
- <p><strong>Windows</strong>: <a href="http://gpg4win.org/download.html">http://gpg4win.org/download.html</a><br/><strong>Mac OS X</strong>: <a href="http://macgpg.sourceforge.net/">http://macgpg.sourceforge.net/</a><br/><strong>Linux</strong>: Most Linux distributions come with GnuPG preinstalled.</p>
+ <p><strong>Windows</strong>: <a href="http://gpg4win.org/download.html">http://gpg4win.org/download.html</a><br/><strong>Mac OS X</strong>: <a href="http://macgpg.sourceforge.net/">http://macgpg.sourceforge.net/</a><br/><strong>Linux</strong>: A maioría das distribucións Linux vén co GnuPG preinstalado.</p>
<p>Teña en conta que pode ter que editar as rutas e as ordes indicadas embaixo para facelo funcionar no seu sistema.</p>
<p>Erinn Clark asina os Tor Browser Bundles con chave 0x63FEE659. Para descargar a clave de Erinn, execute:</p>
<pre>
@@ -55,7 +55,7 @@ sub 2048R/EB399FD7 2003-10-16
<p>A saída debe dicir <em>"Boa sinatura"</em>. Unha sinatura mala significa que o ficheiro pode estar adulterado. Se ve unha sinatura mala, envíe información sobre o lugar de onde descargou o paquete, como comprobou a sinatura, ea saída de GnuPG nun e-mail a help(a)rt.torproject.org.</p>
<p>Despois de ter verificado a sinatura e visto a saída <em>"Boa sinatura"</em>, extraia o ficheiro do paquete. Debería ver un directorio como <strong>tor-browser_en-US</strong>. Dentro deste directorio está outro directorio chamado <strong>Docs</strong>, que contén un ficheiro chamado <strong>changelog</strong>. Asegúrese de que o número de versión na liña superior do ficheiro de changelog corresponde ao número de versión no nome do ficheiro.</p>
<h3 id="how-to-use-the-tor-browser-bundle">Como usar o Tor Browser Bundle</h3>
- <p>After downloading the Tor Browser Bundle and extracting the package, you should have a directory with a few files in it. One of the files is an executable called "Start Tor Browser" (or "start-tor-browser", depending on your operating system).</p>
+ <p>Despois de descargar o Tor Browser Bundle e extraer o paquete, ten que ter un directorio con algúns arquivos nel. Un dos arquivos é un executable chamado "Start Tor Browser" (ou "start-tor-browser", dependendo do seu sistema operativo).</p>
<p>Cando inicie o Tor Browser Bundle, vai ver primeiro iniciarse Vidalia e conectalo á rede Tor. Despois diso, vai ver un navegador, confirmando que está a usar o Tor, amosando <a href="https://check.torproject.org/">https://check.torproject.org/</a>. Xa pode navegar por Internet a través de Tor.</p>
<p>
<em>Teña en conta que é importante que use o navegador que vén co paquete, e non o seu propio navegador.</em>
@@ -123,7 +123,7 @@ sub 2048R/EB399FD7 2003-10-16
<p>Por favor, vexa as <a href="https://www.torproject.org/torbutton/torbutton-faq.html#noflash">Torbutton FAQ</a> para máis información.</p>
<h3 id="i-want-to-use-another-browser">Quero usar outro navegador</h3>
<p>Por razóns de seguridade, recomendamos só navegar usando Tor mediante o Tor Browser Bundle. É tecnicamente posíbel usar o Tor con outros navegadores, pero ao facelo ábrese a posibles ataques.</p>
- <h3 id="why-tor-is-slow">Why Tor is slow</h3>
- <p>Tor can sometimes be a bit slower than your normal Internet connection. After all, your traffic is sent through many different countries, sometimes across oceans around the world!</p>
+ <h3 id="why-tor-is-slow">Por que Tor é lento</h3>
+ <p>Tor ás veces pode ser un pouco máis lento que a súa conexión a Internet normal. Despois de todo, o tráfico é enviado a través de moitos países diferentes, por veces arredor do mundo!</p>
</body>
</html>
1
0
commit ad34de2a4b7079721fe2d4f99feff9c8194519a1
Author: Translation commit bot <translation(a)torproject.org>
Date: Thu Nov 21 17:15:09 2013 +0000
Update translations for tsum
---
gl/short-user-manual_gl_noimg.xhtml | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/gl/short-user-manual_gl_noimg.xhtml b/gl/short-user-manual_gl_noimg.xhtml
index 228dc96..91e4b1b 100644
--- a/gl/short-user-manual_gl_noimg.xhtml
+++ b/gl/short-user-manual_gl_noimg.xhtml
@@ -117,12 +117,12 @@ sub 2048R/EB399FD7 2003-10-16
<p><strong>Vidalia caeu, pero deixou Tor en execución</strong>: Se a caixa de diálogo que pide un contrasinal de control ten un botón de Reset, pode facer clic no botón e Vidalia reiniciará o Tor cun novo contrasinal de control aleatorio. Se non ve o botón, ou se Vidalia é incapaz de reiniciar Tor, use o seu xestor de tarefas ou procesos e finalice o proceso de Tor. Entón use Vidalia para reiniciar Tor.</p>
<p>Para máis información, consulte as <a href="https://torproject.org/docs/faq.html#VidaliaPassword">FAQ</a> na páxina do Proxecto Tor.</p>
<h3 id="flash-does-not-work">Flash non funciona</h3>
- <p>For security reasons, Flash, Java, and other plugins are currently disabled for Tor. Plugins operate independently from Firefox and can perform activity on your computer that ruins your anonymity.</p>
- <p>Most YouTube videos work with HTML5, and it is possible to view these videos over Tor. You need to join the <a href="https://www.youtube.com/html5">HTML5 trial</a> on the YouTube website before you can use the HTML5 player.</p>
- <p>Note that the browser will not remember that you joined the trial once you close it, so you will need to re-join the trial the next time you run the Tor Browser Bundle.</p>
- <p>Please see the <a href="https://www.torproject.org/torbutton/torbutton-faq.html#noflash">Torbutton FAQ</a> for more information.</p>
- <h3 id="i-want-to-use-another-browser">I want to use another browser</h3>
- <p>For security reasons, we recommend that you only browse the web through Tor using the Tor Browser Bundle. It is technically possible to use Tor with other browsers, but by doing so you open yourself up to potential attacks.</p>
+ <p>Por razóns de seguridade, Flash, Java e outros complementos están desactivados no Tor. Os complementos son independentes do Firefox e poden realizar actividades no seu ordenador que arruínen o anonimato.</p>
+ <p>A maioría dos vídeos de YouTube traballan con HTML5, e se poden ver en Tor. Ten que unirse ao <a href="https://www.youtube.com/html5">HTML5 trial</a> na páxina do YouTube antes de que poida utilizar o reprodutor HTML5.</p>
+ <p>Nótese que o navegador cando o peche non vai lembrar que se xuntou ao HTML5 trial, entón vai ter para volver a entrar nel a próxima vez que execute o Tor Browser Bundle.</p>
+ <p>Por favor, vexa as <a href="https://www.torproject.org/torbutton/torbutton-faq.html#noflash">Torbutton FAQ</a> para máis información.</p>
+ <h3 id="i-want-to-use-another-browser">Quero usar outro navegador</h3>
+ <p>Por razóns de seguridade, recomendamos só navegar usando Tor mediante o Tor Browser Bundle. É tecnicamente posíbel usar o Tor con outros navegadores, pero ao facelo ábrese a posibles ataques.</p>
<h3 id="why-tor-is-slow">Why Tor is slow</h3>
<p>Tor can sometimes be a bit slower than your normal Internet connection. After all, your traffic is sent through many different countries, sometimes across oceans around the world!</p>
</body>
1
0

[flashproxy/master] document and add tests for the None/"" default behaviour of parse_addr_spec
by infinity0@torproject.org 21 Nov '13
by infinity0@torproject.org 21 Nov '13
21 Nov '13
commit 8d5eeb8bd9f589451d95d6e6a2ba3f2697c8cab0
Author: Ximin Luo <infinity0(a)gmx.com>
Date: Thu Nov 21 16:56:14 2013 +0000
document and add tests for the None/"" default behaviour of parse_addr_spec
---
flashproxy/test/test_util.py | 8 ++++++++
flashproxy/util.py | 14 ++++++++++++--
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/flashproxy/test/test_util.py b/flashproxy/test/test_util.py
index af4c2e6..935dd1f 100644
--- a/flashproxy/test/test_util.py
+++ b/flashproxy/test/test_util.py
@@ -27,6 +27,14 @@ class ParseAddrSpecTest(unittest.TestCase):
self.assertEqual(parse_addr_spec(":", defhost="1234::1", defport=9999), ("1234::1", 9999))
self.assertEqual(parse_addr_spec("", defhost="1234::1", defport=9999), ("1234::1", 9999))
+ def test_empty_defaults(self):
+ self.assertEqual(parse_addr_spec("192.168.0.2:8888"), ("192.168.0.2", 8888))
+ self.assertEqual(parse_addr_spec("", defhost="", defport=0), ("", 0))
+ self.assertEqual(parse_addr_spec(":8888", defhost=""), ("", 8888))
+ self.assertRaises(ValueError, parse_addr_spec, ":8888")
+ self.assertEqual(parse_addr_spec("192.168.0.2", defport=0), ("192.168.0.2", 0))
+ self.assertRaises(ValueError, parse_addr_spec, "192.168.0.2")
+
def test_canonical_ip_noresolve(self):
"""Test that canonical_ip does not do DNS resolution by default."""
self.assertRaises(ValueError, canonical_ip, *parse_addr_spec("example.com:80"))
diff --git a/flashproxy/util.py b/flashproxy/util.py
index b069bf7..a53bdad 100644
--- a/flashproxy/util.py
+++ b/flashproxy/util.py
@@ -4,11 +4,15 @@ import socket
def parse_addr_spec(spec, defhost = None, defport = None):
"""Parse a host:port specification and return a 2-tuple ("host", port) as
understood by the Python socket functions.
+
>>> parse_addr_spec("192.168.0.1:9999")
('192.168.0.1', 9999)
- If defhost or defport are given, those parts of the specification may be
- omitted; if so, they will be filled in with defaults.
+ If defhost or defport are given and not None, the respective parts of the
+ specification may be omitted, and will be filled in with the defaults.
+ If defhost or defport are omitted or None, the respective parts of the
+ specification must be given, or else a ValueError will be raised.
+
>>> parse_addr_spec("192.168.0.2:8888", defhost="192.168.0.1", defport=9999)
('192.168.0.2', 8888)
>>> parse_addr_spec(":8888", defhost="192.168.0.1", defport=9999)
@@ -21,6 +25,12 @@ def parse_addr_spec(spec, defhost = None, defport = None):
('192.168.0.1', 9999)
>>> parse_addr_spec("", defhost="192.168.0.1", defport=9999)
('192.168.0.1', 9999)
+ >>> parse_addr_spec(":")
+ Traceback (most recent call last):
+ [..]
+ ValueError: Bad address specification ":"
+ >>> parse_addr_spec(":", "", 0)
+ ('', 0)
IPv6 addresses must be enclosed in square brackets."""
host = None
1
0

[flashproxy/master] use common certificate pinning code in facilitator-email-poller
by infinity0@torproject.org 21 Nov '13
by infinity0@torproject.org 21 Nov '13
21 Nov '13
commit 871e4c394dbe81efbe350185624d9ac81fcd5133
Author: Ximin Luo <infinity0(a)gmx.com>
Date: Wed Nov 13 17:28:53 2013 +0000
use common certificate pinning code in facilitator-email-poller
---
facilitator/facilitator-email-poller | 63 +++-------------------------------
1 file changed, 5 insertions(+), 58 deletions(-)
diff --git a/facilitator/facilitator-email-poller b/facilitator/facilitator-email-poller
index 0eef115..f30e1cb 100755
--- a/facilitator/facilitator-email-poller
+++ b/facilitator/facilitator-email-poller
@@ -17,6 +17,7 @@ import tempfile
import time
from flashproxy import fac
+from flashproxy import keys
from flashproxy import proc
from flashproxy.util import parse_addr_spec
@@ -36,45 +37,6 @@ REGISTRATION_AGE_LIMIT = 30 * 60
FACILITATOR_ADDR = ("127.0.0.1", 9002)
-# We trust no other CA certificate than this.
-#
-# To find the certificate to copy here,
-# $ strace openssl s_client -connect imap.gmail.com:993 -verify 10 -CApath /etc/ssl/certs 2>&1 | grep /etc/ssl/certs
-# stat("/etc/ssl/certs/XXXXXXXX.0", {st_mode=S_IFREG|0644, st_size=YYYY, ...}) = 0
-CA_CERTS = """\
-subject=/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
-issuer=/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
------BEGIN CERTIFICATE-----
-MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
-UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
-dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
-MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
-dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
-AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
-BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
-cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
-AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
-MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
-aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
-ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
-IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
-MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
-A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
-7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
-1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
------END CERTIFICATE-----
-"""
-# SHA-1 digest of expected public keys. Any of these is valid. See
-# http://www.imperialviolet.org/2011/05/04/pinning.html for the reason behind
-# hashing the public key, not the entire certificate.
-PUBKEY_SHA1 = (
- # https://src.chromium.org/viewvc/chrome/trunk/src/net/http/transport_securit…
- # kSPKIHash_Google1024
- "\x40\xc5\x40\x1d\x6f\x8c\xba\xf0\x8b\x00\xed\xef\xb1\xee\x87\xd0\x05\xb3\xb9\xcd",
- # kSPKIHash_GoogleG2
- "\x43\xda\xd6\x30\xee\x53\xf8\xa9\x80\xca\x6e\xfd\x85\xf4\x6a\xa3\x79\x90\xe0\xea",
-)
-
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
class options(object):
@@ -379,27 +341,12 @@ def imap_loop(imap):
def imap_login():
"""Make an IMAP connection, check the certificate and public key, and log in."""
- ca_certs_file = tempfile.NamedTemporaryFile(prefix="facilitator-email-poller-", suffix=".crt", delete=True)
- try:
- ca_certs_file.write(CA_CERTS)
- ca_certs_file.flush()
- imap = IMAP4_SSL_REQUIRED(imap_addr[0], imap_addr[1],
- None, ca_certs_file.name)
- finally:
- ca_certs_file.close()
+ with keys.temp_cert(keys.PIN_GOOGLE_CA_CERT) as ca_certs_file:
+ imap = IMAP4_SSL_REQUIRED(
+ imap_addr[0], imap_addr[1], None, ca_certs_file.name)
if options.use_certificate_pin:
- found = []
- for cert in imap.ssl().get_peer_cert_chain():
- pubkey_der = cert.get_pubkey().as_der()
- pubkey_digest = sha1(pubkey_der).digest()
- if pubkey_digest in PUBKEY_SHA1:
- break
- found.append(pubkey_digest)
- else:
- found = "(" + ", ".join(x.encode("hex") for x in found) + ")"
- expected = "(" + ", ".join(x.encode("hex") for x in PUBKEY_SHA1) + ")"
- raise ValueError("Public key does not match pin: got %s but expected any of %s" % (found, expected))
+ keys.check_certificate_pin(imap.ssl(), keys.PIN_GOOGLE_PUBKEY_SHA1)
log(u"logging in as %s" % email_addr)
imap.login(email_addr, email_password)
1
0

[flashproxy/master] move fac.py and associated tests to flashproxy-common
by infinity0@torproject.org 21 Nov '13
by infinity0@torproject.org 21 Nov '13
21 Nov '13
commit 5d3ae963fa6b514c28e5ad37e60a165d82526831
Author: Ximin Luo <infinity0(a)gmx.com>
Date: Wed Nov 13 16:34:31 2013 +0000
move fac.py and associated tests to flashproxy-common
- use the facilitator's parse_addr_spec, and fix client uses to set defhost = ""
- the old client side accepted an empty host when defhost = None.
- the facilitator side requires setting defhost = "" to accept an empty host
- behaviour is otherwise equivalent
---
Makefile | 2 +-
facilitator/HACKING | 26 ++
facilitator/INSTALL | 15 +-
facilitator/Makefile.am | 10 +-
facilitator/doc/facilitator-design.txt | 3 -
facilitator/fac.py | 404 --------------------------------
facilitator/facilitator | 22 +-
facilitator/facilitator-email-poller | 10 +-
facilitator/facilitator-reg-daemon | 16 +-
facilitator/facilitator-test.py | 143 +----------
facilitator/facilitator.cgi | 2 +-
flashproxy-client | 12 +-
flashproxy-reg-appspot | 2 +-
flashproxy-reg-email | 2 +-
flashproxy-reg-url | 2 +-
flashproxy/fac.py | 224 ++++++++++++++++++
flashproxy/proc.py | 47 ++++
flashproxy/reg.py | 31 +++
flashproxy/test/test_fac.py | 93 ++++++++
flashproxy/test/test_reg.py | 23 ++
flashproxy/test/test_util.py | 35 +++
flashproxy/util.py | 92 ++++++--
22 files changed, 618 insertions(+), 598 deletions(-)
diff --git a/Makefile b/Makefile
index 9e8d2dd..93287f6 100644
--- a/Makefile
+++ b/Makefile
@@ -84,7 +84,7 @@ test-full: test
cd facilitator && \
{ test -x ./config.status && ./config.status || \
{ test -x ./configure || ./autogen.sh; } && ./configure; } \
- && make && make check
+ && make && PYTHONPATH=.. make check
cd proxy && make test
force-dist:
diff --git a/facilitator/HACKING b/facilitator/HACKING
new file mode 100644
index 0000000..f102880
--- /dev/null
+++ b/facilitator/HACKING
@@ -0,0 +1,26 @@
+== Running from source checkout
+
+In order to run the code directly from a source checkout, you must make sure it
+can find the flashproxy module, located in the top-level directory of the
+source checkout, which is probably the parent directory. You have two options:
+
+1. Install it in "development mode", see [1]
+
+ flashproxy# python setup-common.py develop
+
+This process is reversible too:
+
+ flashproxy# python setup-common.py develop --uninstall
+
+The disadvantage is that other programs (such as a system-installed flashproxy,
+or other checkouts in another directory) will see this development copy, rather
+than a more appropriate copy.
+
+2. Export PYTHONPATH when you need to run
+
+ $ export PYTHONPATH=..
+ $ make check
+
+The disadvantage is that you need to do this every shell session.
+
+[1] http://pythonhosted.org/distribute/setuptools.html#development-mode
diff --git a/facilitator/INSTALL b/facilitator/INSTALL
index 6325e53..7716ccc 100644
--- a/facilitator/INSTALL
+++ b/facilitator/INSTALL
@@ -1,12 +1,21 @@
Install the dependencies.
- $ apt-get install make openssl python-m2crypto
- $ apt-get install automake autoconf # if running from git
- $ apt-get install apache2
+ # apt-get install make openssl python-m2crypto
+ # apt-get install automake autoconf # if running from git
+
+ # apt-get install apache2
You may use a different webserver, but currently we only provide an apache2 site
config example, so you will need to adapt this to the correct syntax.
+ # apt-get install flashproxy-common
+
+If your distro does not have flashproxy-common, you can install it
+directly from the top-level source directory:
+
+ flashproxy# python setup-common.py install --record install.log \
+ --single-version-externally-managed
+
Configure and install.
$ ./autogen.sh # if running from git or ./configure doesn't otherwise exist
diff --git a/facilitator/Makefile.am b/facilitator/Makefile.am
index 2d315b9..435956d 100644
--- a/facilitator/Makefile.am
+++ b/facilitator/Makefile.am
@@ -2,9 +2,7 @@
fpfacilitatoruser = @fpfacilitatoruser@
initconfdir = @initconfdir@
-# TODO(infinity0): switch this to @cgibindir@ once we replace fac.py with
-# flashproxy-common, so that we install facilitator.cgi in the right place
-cgibindir = @bindir@
+cgibindir = @cgibindir@
# unfortunately sysvinit does not support having initscripts in /usr/local/etc
# yet, so we have to hard code a path here. :(
@@ -16,7 +14,7 @@ appengineconfdir = $(pkgconfdir)/reg-appspot
# automake PLVs
-dist_bin_SCRIPTS = facilitator facilitator-email-poller facilitator-reg-daemon facilitator-reg fac.py
+dist_bin_SCRIPTS = facilitator facilitator-email-poller facilitator-reg-daemon facilitator-reg
dist_cgibin_SCRIPTS = facilitator.cgi
if DO_INITSCRIPTS
initscript_SCRIPTS = init.d/facilitator init.d/facilitator-email-poller init.d/facilitator-reg-daemon
@@ -29,13 +27,13 @@ pkgconf_DATA = examples/facilitator-relays
dist_appengine_DATA = appengine/app.yaml appengine/config.go appengine/fp-reg.go
appengineconf_DATA = appengine/config.go
CLEANFILES = examples/fp-facilitator.conf
-EXTRA_DIST = examples/fp-facilitator.conf.in $(TESTS)
+EXTRA_DIST = examples/fp-facilitator.conf.in HACKING $(TESTS)
TESTS = facilitator-test.py
# see http://www.gnu.org/software/automake/manual/html_node/Parallel-Test-Harness…
TEST_EXTENSIONS = .py
PY_LOG_COMPILER = $(PYTHON)
-AM_TESTS_ENVIRONMENT = PYTHONPATH='$(srcdir)'; export PYTHONPATH;
+AM_TESTS_ENVIRONMENT = PYTHONPATH='$(srcdir):$(PYTHONPATH)'; export PYTHONPATH;
AM_PY_LOG_FLAGS =
# AC_CONFIG_FILES doesn't fully-expand directory variables
diff --git a/facilitator/doc/facilitator-design.txt b/facilitator/doc/facilitator-design.txt
index 0d84da3..20f9c0a 100644
--- a/facilitator/doc/facilitator-design.txt
+++ b/facilitator/doc/facilitator-design.txt
@@ -39,6 +39,3 @@ the HTTP method, either yours or that of another facilitator. It takes
advantage of the fact that a censor cannot distinguish between a TLS
connection to appspot.com or google.com, since the IPs are the same,
and it is highly unlikely that anyone will try to block the latter.
-
-fac.py is a Python module containing code common to the various
-facilitator programs.
diff --git a/facilitator/fac.py b/facilitator/fac.py
deleted file mode 100644
index 00c103c..0000000
--- a/facilitator/fac.py
+++ /dev/null
@@ -1,404 +0,0 @@
-import errno
-import os
-import re
-import socket
-import stat
-import subprocess
-import pwd
-import urlparse
-from collections import namedtuple
-
-DEFAULT_CLIENT_TRANSPORT = "websocket"
-
-# Return true iff the given fd is readable, writable, and executable only by its
-# owner.
-def check_perms(fd):
- mode = os.fstat(fd)[0]
- return (mode & (stat.S_IRWXG | stat.S_IRWXO)) == 0
-
-# Drop privileges by switching ID to that of the given user.
-# http://stackoverflow.com/questions/2699907/dropping-root-permissions-in-pyt…
-# https://www.securecoding.cert.org/confluence/display/seccode/POS36-C.+Obser…
-# https://www.securecoding.cert.org/confluence/display/seccode/POS37-C.+Ensur…
-def drop_privs(username):
- uid = pwd.getpwnam(username).pw_uid
- gid = pwd.getpwnam(username).pw_gid
- os.setgroups([])
- os.setgid(gid)
- os.setuid(uid)
- try:
- os.setuid(0)
- except OSError:
- pass
- else:
- raise AssertionError("setuid(0) succeeded after attempting to drop privileges")
-
-# A decorator to ignore "broken pipe" errors.
-def catch_epipe(fn):
- def ret(self, *args):
- try:
- return fn(self, *args)
- except socket.error, e:
- try:
- err_num = e.errno
- except AttributeError:
- # Before Python 2.6, exception can be a pair.
- err_num, errstr = e
- except:
- raise
- if err_num != errno.EPIPE:
- raise
- return ret
-
-def parse_addr_spec(spec, defhost = None, defport = None):
- """Parse a host:port specification and return a 2-tuple ("host", port) as
- understood by the Python socket functions.
- >>> parse_addr_spec("192.168.0.1:9999")
- ('192.168.0.1', 9999)
-
- If defhost or defport are given, those parts of the specification may be
- omitted; if so, they will be filled in with defaults.
- >>> parse_addr_spec("192.168.0.2:8888", defhost="192.168.0.1", defport=9999)
- ('192.168.0.2', 8888)
- >>> parse_addr_spec(":8888", defhost="192.168.0.1", defport=9999)
- ('192.168.0.1', 8888)
- >>> parse_addr_spec("192.168.0.2", defhost="192.168.0.1", defport=9999)
- ('192.168.0.2', 9999)
- >>> parse_addr_spec("192.168.0.2:", defhost="192.168.0.1", defport=9999)
- ('192.168.0.2', 9999)
- >>> parse_addr_spec(":", defhost="192.168.0.1", defport=9999)
- ('192.168.0.1', 9999)
- >>> parse_addr_spec("", defhost="192.168.0.1", defport=9999)
- ('192.168.0.1', 9999)
- IPv6 addresses must be enclosed in square brackets."""
- host = None
- port = None
- af = 0
- m = None
- # IPv6 syntax.
- if not m:
- m = re.match(ur'^\[(.+)\]:(\d*)$', spec)
- if m:
- host, port = m.groups()
- af = socket.AF_INET6
- if not m:
- m = re.match(ur'^\[(.+)\]$', spec)
- if m:
- host, = m.groups()
- af = socket.AF_INET6
- # IPv4/hostname/port-only syntax.
- if not m:
- try:
- host, port = spec.split(":", 1)
- except ValueError:
- host = spec
- if re.match(ur'^[\d.]+$', host):
- af = socket.AF_INET
- else:
- af = 0
- host = host or defhost
- port = port or defport
- if host is None or port is None:
- raise ValueError("Bad address specification \"%s\"" % spec)
- return host, int(port)
-
-def resolve_to_ip(host, port, af=0, gai_flags=0):
- """Resolves a host string to an IP address in canonical format.
-
- Note: in many cases this is not necessary since the consumer of the address
- can probably accept host names directly.
-
- :param: host string to resolve; may be a DNS name or an IP address.
- :param: port of the host
- :param: af address family, default unspecified. set to socket.AF_INET or
- socket.AF_INET6 to force IPv4 or IPv6 name resolution.
- :returns: (IP address in canonical format, port)
- """
- # Forward-resolve the name into an addrinfo struct. Real DNS resolution is
- # done only if resolve is true; otherwise the address must be numeric.
- try:
- addrs = socket.getaddrinfo(host, port, af, 0, 0, gai_flags)
- except socket.gaierror, e:
- raise ValueError("Bad host or port: \"%s\" \"%s\": %s" % (host, port, str(e)))
- if not addrs:
- raise ValueError("Bad host or port: \"%s\" \"%s\"" % (host, port))
-
- # Convert the result of socket.getaddrinfo (which is a 2-tuple for IPv4 and
- # a 4-tuple for IPv6) into a (host, port) 2-tuple.
- host, port = socket.getnameinfo(addrs[0][4], socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
- return host, int(port)
-
-def canonical_ip(host, port, af=0):
- """Convert an IP address to a canonical format. Identical to resolve_to_ip,
- except that the host param must already be an IP address."""
- return resolve_to_ip(host, port, af, gai_flags=socket.AI_NUMERICHOST)
-
-def format_addr(addr):
- host, port = addr
- host_str = u""
- port_str = u""
- if host is not None:
- # Numeric IPv6 address?
- try:
- addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST)
- af = addrs[0][0]
- except socket.gaierror, e:
- af = 0
- if af == socket.AF_INET6:
- host_str = u"[%s]" % host
- else:
- host_str = u"%s" % host
- if port is not None:
- if not (0 < port <= 65535):
- raise ValueError("port must be between 1 and 65535 (is %d)" % port)
- port_str = u":%d" % port
-
- if not host_str and not port_str:
- raise ValueError("host and port may not both be None")
- return u"%s%s" % (host_str, port_str)
-
-def read_client_registrations(body, defhost=None, defport=None):
- """Yield client registrations (as Endpoints) from an encoded registration
- message body. The message format is one registration per line, with each
- line being encoded as application/x-www-form-urlencoded. The key "client" is
- required and contains the client address and port (perhaps filled in by
- defhost and defport). The key "client-transport" is optional and defaults to
- "websocket".
- Example:
- client=1.2.3.4:9000&client-transport=websocket
- client=1.2.3.4:9090&client-transport=obfs3|websocket
- """
- for line in body.splitlines():
- qs = urlparse.parse_qs(line, keep_blank_values=True, strict_parsing=True)
- # Get the unique value associated with the given key in qs. If the key
- # is absent or appears more than once, raise ValueError.
- def get_unique(key, default=None):
- try:
- vals = qs[key]
- except KeyError:
- if default is None:
- raise ValueError("missing %r key" % key)
- vals = (default,)
- if len(vals) != 1:
- raise ValueError("more than one %r key" % key)
- return vals[0]
- addr = parse_addr_spec(get_unique("client"), defhost, defport)
- transport = get_unique("client-transport", DEFAULT_CLIENT_TRANSPORT)
- yield Endpoint(addr, transport)
-
-
-class Transport(namedtuple("Transport", "inner outer")):
- @classmethod
- def parse(cls, transport):
- if isinstance(transport, cls):
- return transport
- elif type(transport) == str:
- if "|" in transport:
- inner, outer = transport.rsplit("|", 1)
- else:
- inner, outer = "", transport
- return cls(inner, outer)
- else:
- raise ValueError("could not parse transport: %s" % transport)
-
- def __init__(self, inner, outer):
- if not outer:
- raise ValueError("outer (proxy) part of transport must be non-empty: %s" % str(self))
-
- def __str__(self):
- return "%s|%s" % (self.inner, self.outer) if self.inner else self.outer
-
-
-class Endpoint(namedtuple("Endpoint", "addr transport")):
- @classmethod
- def parse(cls, spec, transport, defhost = None, defport = None):
- host, port = parse_addr_spec(spec, defhost, defport)
- return cls((host, port), Transport.parse(transport))
-
-
-def skip_space(pos, line):
- """Skip a (possibly empty) sequence of space characters (the ASCII character
- '\x20' exactly). Returns a pair (pos, num_skipped)."""
- begin = pos
- while pos < len(line) and line[pos] == "\x20":
- pos += 1
- return pos, pos - begin
-
-TOKEN_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
-def get_token(pos, line):
- begin = pos
- while pos < len(line) and line[pos] in TOKEN_CHARS:
- pos += 1
- if begin == pos:
- raise ValueError("No token found at position %d" % pos)
- return pos, line[begin:pos]
-
-def get_quoted_string(pos, line):
- chars = []
- if not (pos < len(line) and line[pos] == '"'):
- raise ValueError("Expected '\"' at beginning of quoted string.")
- pos += 1
- while pos < len(line) and line[pos] != '"':
- if line[pos] == '\\':
- pos += 1
- if not (pos < len(line)):
- raise ValueError("End of line after backslash in quoted string")
- chars.append(line[pos])
- pos += 1
- if not (pos < len(line) and line[pos] == '"'):
- raise ValueError("Expected '\"' at end of quoted string.")
- pos += 1
- return pos, "".join(chars)
-
-def parse_transaction(line):
- """A transaction is a command followed by zero or more key-value pairs. Like so:
- COMMAND KEY="VALUE" KEY="\"ESCAPED\" VALUE"
- Values must be quoted. Any byte value may be escaped with a backslash.
- Returns a pair: (COMMAND, ((KEY1, VALUE1), (KEY2, VALUE2), ...)).
- """
- pos = 0
- pos, skipped = skip_space(pos, line)
- pos, command = get_token(pos, line)
-
- pairs = []
- while True:
- pos, skipped = skip_space(pos, line)
- if not (pos < len(line)):
- break
- if skipped == 0:
- raise ValueError("Expected space before key-value pair")
- pos, key = get_token(pos, line)
- if not (pos < len(line) and line[pos] == '='):
- raise ValueError("No '=' found after key")
- pos += 1
- pos, value = get_quoted_string(pos, line)
- pairs.append((key, value))
- return command, tuple(pairs)
-
-def param_first(key, params):
- """Search 'params' for 'key' and return the first value that
- occurs. If 'key' was not found, return None."""
- for k, v in params:
- if key == k:
- return v
- return None
-
-def param_getlist(key, params):
- """Search 'params' for 'key' and return a list with its values. If
- 'key' did not appear in 'params', return the empty list."""
- result = []
- for k, v in params:
- if key == k:
- result.append(v)
- return result
-
-def quote_string(s):
- chars = []
- for c in s:
- if c == "\\":
- c = "\\\\"
- elif c == "\"":
- c = "\\\""
- chars.append(c)
- return "\"" + "".join(chars) + "\""
-
-def render_transaction(command, *params):
- parts = [command]
- for key, value in params:
- parts.append("%s=%s" % (key, quote_string(value)))
- return " ".join(parts)
-
-def fac_socket(facilitator_addr):
- return socket.create_connection(facilitator_addr, 1.0).makefile()
-
-def transact(f, command, *params):
- transaction = render_transaction(command, *params)
- print >> f, transaction
- f.flush()
- line = f.readline()
- if not (len(line) > 0 and line[-1] == '\n'):
- raise ValueError("No newline at end of string returned by facilitator")
- return parse_transaction(line[:-1])
-
-def put_reg(facilitator_addr, client_addr, transport):
- """Send a registration to the facilitator using a one-time socket. Returns
- true iff the command was successful. transport is a transport string such as
- "websocket" or "obfs3|websocket"."""
- f = fac_socket(facilitator_addr)
- params = [("CLIENT", format_addr(client_addr))]
- params.append(("TRANSPORT", transport))
- try:
- command, params = transact(f, "PUT", *params)
- finally:
- f.close()
- return command == "OK"
-
-def get_reg(facilitator_addr, proxy_addr, proxy_transport_list):
- """
- Get a client registration for proxy proxy_addr from the
- facilitator at facilitator_addr using a one-time
- socket. proxy_transport_list is a list containing the transport names that
- the flashproxy supports.
-
- Returns a dict with keys "client", "client-transport", "relay",
- and "relay-transport" if successful, or a dict with the key "client"
- mapped to the value "" if there are no registrations available for
- proxy_addr. Raises an exception otherwise."""
- f = fac_socket(facilitator_addr)
-
- # Form a list (in transact() format) with the transports that we
- # should send to the facilitator. Then pass that list to the
- # transact() function.
- # For example, PROXY-TRANSPORT=obfs2 PROXY-TRANSPORT=obfs3.
- transports = [("PROXY-TRANSPORT", tp) for tp in proxy_transport_list]
-
- try:
- command, params = transact(f, "GET", ("FROM", format_addr(proxy_addr)), *transports)
- finally:
- f.close()
- response = {}
- check_back_in = param_first("CHECK-BACK-IN", params)
- if check_back_in is not None:
- try:
- float(check_back_in)
- except ValueError:
- raise ValueError("Facilitator returned non-numeric polling interval.")
- response["check-back-in"] = check_back_in
- if command == "NONE":
- response["client"] = ""
- return response
- elif command == "OK":
- client_spec = param_first("CLIENT", params)
- client_transport = param_first("CLIENT-TRANSPORT", params)
- relay_spec = param_first("RELAY", params)
- relay_transport = param_first("RELAY-TRANSPORT", params)
- if not client_spec:
- raise ValueError("Facilitator did not return CLIENT")
- if not client_transport:
- raise ValueError("Facilitator did not return CLIENT-TRANSPORT")
- if not relay_spec:
- raise ValueError("Facilitator did not return RELAY")
- if not relay_transport:
- raise ValueError("Facilitator did not return RELAY-TRANSPORT")
- # Check the syntax returned by the facilitator.
- client = parse_addr_spec(client_spec)
- relay = parse_addr_spec(relay_spec)
- response["client"] = format_addr(client)
- response["client-transport"] = client_transport
- response["relay"] = format_addr(relay)
- response["relay-transport"] = relay_transport
- return response
- else:
- raise ValueError("Facilitator response was not \"OK\"")
-
-def put_reg_base64(b64):
- """Attempt to add a registration by running a facilitator-reg program
- locally."""
- # Padding is optional, but the python base64 functions can't
- # handle lack of padding. Add it here. Assumes correct encoding.
- mod = len(b64) % 4
- if mod != 0:
- b64 += (4 - mod) * "="
- p = subprocess.Popen(["facilitator-reg"], stdin=subprocess.PIPE)
- stdout, stderr = p.communicate(b64)
- return p.returncode == 0
diff --git a/facilitator/facilitator b/facilitator/facilitator
index a2dd56a..39ffe2a 100755
--- a/facilitator/facilitator
+++ b/facilitator/facilitator
@@ -9,8 +9,10 @@ import threading
import time
from collections import defaultdict
-import fac
-from fac import Transport, Endpoint
+from flashproxy import fac
+from flashproxy import proc
+from flashproxy.reg import Transport, Endpoint
+from flashproxy.util import parse_addr_spec, format_addr, canonical_ip
LISTEN_ADDRESS = "127.0.0.1"
DEFAULT_LISTEN_PORT = 9002
@@ -241,7 +243,7 @@ class Handler(SocketServer.StreamRequestHandler):
if buflen >= READLINE_MAX_LENGTH:
raise socket.error("readline: refusing to buffer %d bytes (last read was %d bytes)" % (buflen, len(data)))
- @fac.catch_epipe
+ @proc.catch_epipe
def handle(self):
num_lines = 0
while True:
@@ -290,7 +292,7 @@ class Handler(SocketServer.StreamRequestHandler):
if proxy_spec is None:
return self.error(u"GET missing FROM param")
try:
- proxy_addr = fac.canonical_ip(*fac.parse_addr_spec(proxy_spec, defport=0))
+ proxy_addr = canonical_ip(*parse_addr_spec(proxy_spec, defport=0))
except ValueError, e:
return self.error(u"syntax error in proxy address %s: %s" % (safe_str(repr(proxy_spec)), safe_str(repr(str(e)))))
@@ -309,9 +311,9 @@ class Handler(SocketServer.StreamRequestHandler):
log(u"proxy (%s) gets client '%s' (supported transports: %s) (num relays: %s) (remaining regs: %d/%d)" %
(safe_str(repr(proxy_spec)), safe_str(repr(client_reg.addr)), transport_list, num_relays(), num_unhandled_regs(), num_regs()))
print >> self.wfile, fac.render_transaction("OK",
- ("CLIENT", fac.format_addr(client_reg.addr)),
+ ("CLIENT", format_addr(client_reg.addr)),
("CLIENT-TRANSPORT", client_reg.transport.outer),
- ("RELAY", fac.format_addr(relay_reg.addr)),
+ ("RELAY", format_addr(relay_reg.addr)),
("RELAY-TRANSPORT", relay_reg.transport.outer),
("CHECK-BACK-IN", str(check_back_in)))
else:
@@ -356,7 +358,7 @@ class Handler(SocketServer.StreamRequestHandler):
self.send_ok()
return True
- finish = fac.catch_epipe(SocketServer.StreamRequestHandler.finish)
+ finish = proc.catch_epipe(SocketServer.StreamRequestHandler.finish)
class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
allow_reuse_address = True
@@ -422,7 +424,7 @@ def parse_relay_file(servers, fp):
transport_spec, addr_spec = line.strip().split()
except ValueError, e:
raise ValueError("Wrong line format: %s." % repr(line))
- addr = fac.parse_addr_spec(addr_spec, defport=DEFAULT_RELAY_PORT)
+ addr = parse_addr_spec(addr_spec, defport=DEFAULT_RELAY_PORT)
transport = Transport.parse(transport_spec)
if transport.outer not in options.outer_transports:
raise ValueError(u"Unrecognized transport: %s" % transport)
@@ -495,7 +497,7 @@ obfs2|websocket 1.4.6.1:4123\
server = Server(addrinfo[4], Handler)
- log(u"start on %s" % fac.format_addr(addrinfo[4]))
+ log(u"start on %s" % format_addr(addrinfo[4]))
log(u"using IPv4 relays %s" % str(RELAYS[socket.AF_INET]._endpoints))
log(u"using IPv6 relays %s" % str(RELAYS[socket.AF_INET6]._endpoints))
@@ -512,7 +514,7 @@ obfs2|websocket 1.4.6.1:4123\
if options.privdrop_username is not None:
log(u"dropping privileges to those of user %s" % options.privdrop_username)
try:
- fac.drop_privs(options.privdrop_username)
+ proc.drop_privs(options.privdrop_username)
except BaseException, e:
print >> sys.stderr, "Can't drop privileges:", str(e)
sys.exit(1)
diff --git a/facilitator/facilitator-email-poller b/facilitator/facilitator-email-poller
index 2efceec..0eef115 100755
--- a/facilitator/facilitator-email-poller
+++ b/facilitator/facilitator-email-poller
@@ -16,7 +16,9 @@ import sys
import tempfile
import time
-import fac
+from flashproxy import fac
+from flashproxy import proc
+from flashproxy.util import parse_addr_spec
from hashlib import sha1
from M2Crypto import SSL
@@ -209,7 +211,7 @@ Failed to open password file "%s": %s.\
""" % (options.password_filename, str(e))
sys.exit(1)
try:
- if not fac.check_perms(password_file.fileno()):
+ if not proc.check_perms(password_file.fileno()):
print >> sys.stderr, "Refusing to run with group- or world-readable password file. Try"
print >> sys.stderr, "\tchmod 600 %s" % options.password_filename
sys.exit(1)
@@ -221,7 +223,7 @@ try:
if not res:
raise ValueError("could not find email or password on line %s" % (lineno0+1))
(imap_addr_spec, email_addr, email_password) = res.groups()
- imap_addr = fac.parse_addr_spec(
+ imap_addr = parse_addr_spec(
imap_addr_spec or "", DEFAULT_IMAP_HOST, DEFAULT_IMAP_PORT)
break
else:
@@ -255,7 +257,7 @@ if options.daemonize:
if options.privdrop_username is not None:
log(u"dropping privileges to those of user %s" % options.privdrop_username)
try:
- fac.drop_privs(options.privdrop_username)
+ proc.drop_privs(options.privdrop_username)
except BaseException, e:
print >> sys.stderr, "Can't drop privileges:", str(e)
sys.exit(1)
diff --git a/facilitator/facilitator-reg-daemon b/facilitator/facilitator-reg-daemon
index cab7403..f5e592f 100755
--- a/facilitator/facilitator-reg-daemon
+++ b/facilitator/facilitator-reg-daemon
@@ -8,7 +8,9 @@ import sys
import threading
import time
-import fac
+from flashproxy import fac
+from flashproxy import proc
+from flashproxy.util import format_addr
from M2Crypto import RSA
@@ -98,7 +100,7 @@ class Handler(SocketServer.StreamRequestHandler):
raise socket.error("refusing to buffer %d bytes (last read was %d bytes)" % (buflen, len(data)))
return self.buffer
- @fac.catch_epipe
+ @proc.catch_epipe
def handle(self):
try:
b64_ciphertext = self.read_input()
@@ -109,7 +111,7 @@ class Handler(SocketServer.StreamRequestHandler):
ciphertext = b64_ciphertext.decode("base64")
plaintext = rsa.private_decrypt(ciphertext, RSA.pkcs1_oaep_padding)
for client_reg in fac.read_client_registrations(plaintext):
- log(u"registering %s" % safe_str(fac.format_addr(client_reg.addr)))
+ log(u"registering %s" % safe_str(format_addr(client_reg.addr)))
if not fac.put_reg(FACILITATOR_ADDR, client_reg.addr, client_reg.transport):
print >> self.wfile, "FAIL"
break
@@ -120,7 +122,7 @@ class Handler(SocketServer.StreamRequestHandler):
print >> self.wfile, "FAIL"
raise
- finish = fac.catch_epipe(SocketServer.StreamRequestHandler.finish)
+ finish = proc.catch_epipe(SocketServer.StreamRequestHandler.finish)
class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
allow_reuse_address = True
@@ -164,7 +166,7 @@ def main():
print >> sys.stderr, "Failed to open private key file \"%s\": %s." % (options.key_filename, str(e))
sys.exit(1)
try:
- if not fac.check_perms(key_file.fileno()):
+ if not proc.check_perms(key_file.fileno()):
print >> sys.stderr, "Refusing to run with group- or world-readable private key file. Try"
print >> sys.stderr, "\tchmod 600 %s" % options.key_filename
sys.exit(1)
@@ -183,7 +185,7 @@ def main():
server = Server(addrinfo[4], Handler)
- log(u"start on %s" % fac.format_addr(addrinfo[4]))
+ log(u"start on %s" % format_addr(addrinfo[4]))
if options.daemonize:
log(u"daemonizing")
@@ -198,7 +200,7 @@ def main():
if options.privdrop_username is not None:
log(u"dropping privileges to those of user %s" % options.privdrop_username)
try:
- fac.drop_privs(options.privdrop_username)
+ proc.drop_privs(options.privdrop_username)
except BaseException, e:
print >> sys.stderr, "Can't drop privileges:", str(e)
sys.exit(1)
diff --git a/facilitator/facilitator-test.py b/facilitator/facilitator-test.py
index d63e0b5..fd4ac88 100755
--- a/facilitator/facilitator-test.py
+++ b/facilitator/facilitator-test.py
@@ -9,8 +9,9 @@ import sys
import time
import unittest
-import fac
-from fac import Transport, Endpoint
+from flashproxy import fac
+from flashproxy.reg import Transport, Endpoint
+from flashproxy.util import format_addr
# Import the facilitator program as a module.
import imp
@@ -178,19 +179,6 @@ class EndpointsTest(unittest.TestCase):
class FacilitatorTest(unittest.TestCase):
- def test_transport_parse(self):
- self.assertEquals(Transport.parse("a"), Transport("", "a"))
- self.assertEquals(Transport.parse("|a"), Transport("", "a"))
- self.assertEquals(Transport.parse("a|b|c"), Transport("a|b","c"))
- self.assertEquals(Transport.parse(Transport("a|b","c")), Transport("a|b","c"))
- self.assertRaises(ValueError, Transport, "", "")
- self.assertRaises(ValueError, Transport, "a", "")
- self.assertRaises(ValueError, Transport.parse, "")
- self.assertRaises(ValueError, Transport.parse, "|")
- self.assertRaises(ValueError, Transport.parse, "a|")
- self.assertRaises(ValueError, Transport.parse, ["a"])
- self.assertRaises(ValueError, Transport.parse, [Transport("a", "b")])
-
def test_parse_relay_file(self):
fp = StringIO()
fp.write("websocket 0.0.1.0:1\n")
@@ -201,6 +189,7 @@ class FacilitatorTest(unittest.TestCase):
parse_relay_file(servers, fp)
self.assertEquals(servers[af]._endpoints, {('0.0.1.0', 1): Transport('', 'websocket')})
+
class FacilitatorProcTest(unittest.TestCase):
IPV4_CLIENT_ADDR = ("1.1.1.1", 9000)
IPV6_CLIENT_ADDR = ("[11::11]", 9000)
@@ -214,8 +203,8 @@ class FacilitatorProcTest(unittest.TestCase):
def setUp(self):
self.relay_file = tempfile.NamedTemporaryFile()
- self.relay_file.write("%s %s\n" % (RELAY_TP, fac.format_addr(self.IPV4_RELAY_ADDR)))
- self.relay_file.write("%s %s\n" % (RELAY_TP, fac.format_addr(self.IPV6_RELAY_ADDR)))
+ self.relay_file.write("%s %s\n" % (RELAY_TP, format_addr(self.IPV4_RELAY_ADDR)))
+ self.relay_file.write("%s %s\n" % (RELAY_TP, format_addr(self.IPV6_RELAY_ADDR)))
self.relay_file.flush()
self.relay_file.seek(0)
fn = os.path.join(os.path.dirname(__file__), "./facilitator")
@@ -261,7 +250,7 @@ class FacilitatorProcTest(unittest.TestCase):
fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR, CLIENT_TP)
fac.put_reg(FACILITATOR_ADDR, self.IPV6_CLIENT_ADDR, CLIENT_TP)
reg = fac.get_reg(FACILITATOR_ADDR, self.IPV4_PROXY_ADDR, PROXY_TPS)
- self.assertEqual(reg["client"], fac.format_addr(self.IPV4_CLIENT_ADDR))
+ self.assertEqual(reg["client"], format_addr(self.IPV4_CLIENT_ADDR))
def test_af_v4_v6(self):
"""Test that IPv4 proxies do not get IPv6 clients."""
@@ -280,15 +269,15 @@ class FacilitatorProcTest(unittest.TestCase):
fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR, CLIENT_TP)
fac.put_reg(FACILITATOR_ADDR, self.IPV6_CLIENT_ADDR, CLIENT_TP)
reg = fac.get_reg(FACILITATOR_ADDR, self.IPV6_PROXY_ADDR, PROXY_TPS)
- self.assertEqual(reg["client"], fac.format_addr(self.IPV6_CLIENT_ADDR))
+ self.assertEqual(reg["client"], format_addr(self.IPV6_CLIENT_ADDR))
def test_fields(self):
"""Test that facilitator responses contain all the required fields."""
fac.put_reg(FACILITATOR_ADDR, self.IPV4_CLIENT_ADDR, CLIENT_TP)
reg = fac.get_reg(FACILITATOR_ADDR, self.IPV4_PROXY_ADDR, PROXY_TPS)
- self.assertEqual(reg["client"], fac.format_addr(self.IPV4_CLIENT_ADDR))
+ self.assertEqual(reg["client"], format_addr(self.IPV4_CLIENT_ADDR))
self.assertEqual(reg["client-transport"], CLIENT_TP)
- self.assertEqual(reg["relay"], fac.format_addr(self.IPV4_RELAY_ADDR))
+ self.assertEqual(reg["relay"], format_addr(self.IPV4_RELAY_ADDR))
self.assertEqual(reg["relay-transport"], RELAY_TP)
self.assertGreater(int(reg["check-back-in"]), 0)
@@ -323,117 +312,5 @@ class FacilitatorProcTest(unittest.TestCase):
# """Test that the facilitator rejects hostnames."""
# self.fail()
-class ParseAddrSpecTest(unittest.TestCase):
- def test_ipv4(self):
- self.assertEqual(fac.parse_addr_spec("192.168.0.1:9999"), ("192.168.0.1", 9999))
-
- def test_ipv6(self):
- self.assertEqual(fac.parse_addr_spec("[12::34]:9999"), ("12::34", 9999))
-
- def test_defhost_defport_ipv4(self):
- self.assertEqual(fac.parse_addr_spec("192.168.0.2:8888", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 8888))
- self.assertEqual(fac.parse_addr_spec("192.168.0.2:", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 9999))
- self.assertEqual(fac.parse_addr_spec("192.168.0.2", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 9999))
- self.assertEqual(fac.parse_addr_spec(":8888", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 8888))
- self.assertEqual(fac.parse_addr_spec(":", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 9999))
- self.assertEqual(fac.parse_addr_spec("", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 9999))
-
- def test_defhost_defport_ipv6(self):
- self.assertEqual(fac.parse_addr_spec("[1234::2]:8888", defhost="1234::1", defport=9999), ("1234::2", 8888))
- self.assertEqual(fac.parse_addr_spec("[1234::2]:", defhost="1234::1", defport=9999), ("1234::2", 9999))
- self.assertEqual(fac.parse_addr_spec("[1234::2]", defhost="1234::1", defport=9999), ("1234::2", 9999))
- self.assertEqual(fac.parse_addr_spec(":8888", defhost="1234::1", defport=9999), ("1234::1", 8888))
- self.assertEqual(fac.parse_addr_spec(":", defhost="1234::1", defport=9999), ("1234::1", 9999))
- self.assertEqual(fac.parse_addr_spec("", defhost="1234::1", defport=9999), ("1234::1", 9999))
-
- def test_canonical_ip_noresolve(self):
- """Test that canonical_ip does not do DNS resolution by default."""
- self.assertRaises(ValueError, fac.canonical_ip, *fac.parse_addr_spec("example.com:80"))
-
-class ParseTransactionTest(unittest.TestCase):
- def test_empty_string(self):
- self.assertRaises(ValueError, fac.parse_transaction, "")
-
- def test_correct(self):
- self.assertEqual(fac.parse_transaction("COMMAND"), ("COMMAND", ()))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"\""), ("COMMAND", (("X", ""),)))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"ABC\""), ("COMMAND", (("X", "ABC"),)))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"\\A\\B\\C\""), ("COMMAND", (("X", "ABC"),)))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"\\\\\\\"\""), ("COMMAND", (("X", "\\\""),)))
- self.assertEqual(fac.parse_transaction("COMMAND X=\"ABC\" Y=\"DEF\""), ("COMMAND", (("X", "ABC"), ("Y", "DEF"))))
- self.assertEqual(fac.parse_transaction("COMMAND KEY-NAME=\"ABC\""), ("COMMAND", (("KEY-NAME", "ABC"),)))
- self.assertEqual(fac.parse_transaction("COMMAND KEY_NAME=\"ABC\""), ("COMMAND", (("KEY_NAME", "ABC"),)))
-
- def test_missing_command(self):
- self.assertRaises(ValueError, fac.parse_transaction, "X=\"ABC\"")
- self.assertRaises(ValueError, fac.parse_transaction, " X=\"ABC\"")
-
- def test_missing_space(self):
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND/X=\"ABC\"")
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\"Y=\"DEF\"")
-
- def test_bad_quotes(self):
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"")
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC")
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\" Y=\"ABC")
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\\")
-
- def test_truncated(self):
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=")
-
- def test_newline(self):
- self.assertRaises(ValueError, fac.parse_transaction, "COMMAND X=\"ABC\" \nY=\"DEF\"")
-
-class ReadClientRegistrationsTest(unittest.TestCase):
- def testSingle(self):
- l = list(fac.read_client_registrations(""))
- self.assertEqual(len(l), 0)
- l = list(fac.read_client_registrations("client=1.2.3.4:1111"))
- self.assertEqual(len(l), 1)
- self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
- l = list(fac.read_client_registrations("client=1.2.3.4:1111\n"))
- self.assertEqual(len(l), 1)
- self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
- l = list(fac.read_client_registrations("foo=bar&client=1.2.3.4:1111&baz=quux"))
- self.assertEqual(len(l), 1)
- self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
- l = list(fac.read_client_registrations("foo=b%3dar&client=1.2.3.4%3a1111"))
- self.assertEqual(len(l), 1)
- self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
- l = list(fac.read_client_registrations("client=%5b1::2%5d:3333"))
- self.assertEqual(len(l), 1)
- self.assertEqual(l[0].addr, ("1::2", 3333))
-
- def testDefaultAddress(self):
- l = list(fac.read_client_registrations("client=:1111&transport=websocket", defhost="1.2.3.4"))
- self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
- l = list(fac.read_client_registrations("client=1.2.3.4:&transport=websocket", defport=1111))
- self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
-
- def testDefaultTransport(self):
- l = list(fac.read_client_registrations("client=1.2.3.4:1111"))
- self.assertEqual(l[0].transport, "websocket")
-
- def testMultiple(self):
- l = list(fac.read_client_registrations("client=1.2.3.4:1111&foo=bar\nfoo=bar&client=5.6.7.8:2222"))
- self.assertEqual(len(l), 2)
- self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
- self.assertEqual(l[1].addr, ("5.6.7.8", 2222))
- l = list(fac.read_client_registrations("client=1.2.3.4:1111&foo=bar\nfoo=bar&client=%5b1::2%5d:3333"))
- self.assertEqual(len(l), 2)
- self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
- self.assertEqual(l[1].addr, ("1::2", 3333))
-
- def testInvalid(self):
- # Missing "client".
- with self.assertRaises(ValueError):
- list(fac.read_client_registrations("foo=bar"))
- # More than one "client".
- with self.assertRaises(ValueError):
- list(fac.read_client_registrations("client=1.2.3.4:1111&foo=bar&client=5.6.7.8:2222"))
- # Single client with bad syntax.
- with self.assertRaises(ValueError):
- list(fac.read_client_registrations("client=1.2.3.4,1111"))
-
if __name__ == "__main__":
unittest.main()
diff --git a/facilitator/facilitator.cgi b/facilitator/facilitator.cgi
index 42a888e..addcaf4 100755
--- a/facilitator/facilitator.cgi
+++ b/facilitator/facilitator.cgi
@@ -6,7 +6,7 @@ import socket
import sys
import urllib
-import fac
+from flashproxy import fac
FACILITATOR_ADDR = ("127.0.0.1", 9002)
diff --git a/flashproxy-client b/flashproxy-client
index e54e554..f2cf04b 100755
--- a/flashproxy-client
+++ b/flashproxy-client
@@ -1129,14 +1129,14 @@ def main():
default_remote_port = DEFAULT_REMOTE_PORT
if len(args) == 0:
- local_addr = (None, default_local_port)
- remote_addr = (None, default_remote_port)
+ local_addr = ("", default_local_port)
+ remote_addr = ("", default_remote_port)
elif len(args) == 1:
- local_addr = parse_addr_spec(args[0], defport=default_local_port)
- remote_addr = (None, default_remote_port)
+ local_addr = parse_addr_spec(args[0], defhost="", defport=default_local_port)
+ remote_addr = ("", default_remote_port)
elif len(args) == 2:
- local_addr = parse_addr_spec(args[0], defport=default_local_port)
- remote_addr = parse_addr_spec(args[1], defport=default_remote_port)
+ local_addr = parse_addr_spec(args[0], defhost="", defport=default_local_port)
+ remote_addr = parse_addr_spec(args[1], defhost="", defport=default_remote_port)
else:
usage(sys.stderr)
sys.exit(1)
diff --git a/flashproxy-reg-appspot b/flashproxy-reg-appspot
index 516fcc9..7738e43 100755
--- a/flashproxy-reg-appspot
+++ b/flashproxy-reg-appspot
@@ -19,7 +19,7 @@ except ImportError:
# Defer the error reporting so that --help works even without M2Crypto.
SSL = None
-DEFAULT_REMOTE_ADDRESS = None
+DEFAULT_REMOTE_ADDRESS = ""
DEFAULT_REMOTE_PORT = 9000
DEFAULT_TRANSPORT = "websocket"
diff --git a/flashproxy-reg-email b/flashproxy-reg-email
index ab8d4cb..d286af8 100755
--- a/flashproxy-reg-email
+++ b/flashproxy-reg-email
@@ -20,7 +20,7 @@ except ImportError:
RSA = None
SSL = None
-DEFAULT_REMOTE_ADDRESS = None
+DEFAULT_REMOTE_ADDRESS = ""
DEFAULT_REMOTE_PORT = 9000
DEFAULT_EMAIL_ADDRESS = "flashproxyreg.a(a)gmail.com"
# dig MX gmail.com
diff --git a/flashproxy-reg-url b/flashproxy-reg-url
index c45bc26..0f92d44 100755
--- a/flashproxy-reg-url
+++ b/flashproxy-reg-url
@@ -15,7 +15,7 @@ except ImportError:
# Defer the error reporting so that --help works even without M2Crypto.
RSA = None
-DEFAULT_REMOTE_ADDRESS = None
+DEFAULT_REMOTE_ADDRESS = ""
DEFAULT_REMOTE_PORT = 9000
DEFAULT_FACILITATOR_URL = "https://fp-facilitator.org/"
DEFAULT_TRANSPORT = "websocket"
diff --git a/flashproxy/fac.py b/flashproxy/fac.py
new file mode 100644
index 0000000..0686f54
--- /dev/null
+++ b/flashproxy/fac.py
@@ -0,0 +1,224 @@
+import socket
+import subprocess
+import urlparse
+
+from flashproxy import reg
+from flashproxy.util import parse_addr_spec, format_addr
+
+DEFAULT_CLIENT_TRANSPORT = "websocket"
+
+def read_client_registrations(body, defhost=None, defport=None):
+ """Yield client registrations (as Endpoints) from an encoded registration
+ message body. The message format is one registration per line, with each
+ line being encoded as application/x-www-form-urlencoded. The key "client" is
+ required and contains the client address and port (perhaps filled in by
+ defhost and defport). The key "client-transport" is optional and defaults to
+ "websocket".
+ Example:
+ client=1.2.3.4:9000&client-transport=websocket
+ client=1.2.3.4:9090&client-transport=obfs3|websocket
+ """
+ for line in body.splitlines():
+ qs = urlparse.parse_qs(line, keep_blank_values=True, strict_parsing=True)
+ # Get the unique value associated with the given key in qs. If the key
+ # is absent or appears more than once, raise ValueError.
+ def get_unique(key, default=None):
+ try:
+ vals = qs[key]
+ except KeyError:
+ if default is None:
+ raise ValueError("missing %r key" % key)
+ vals = (default,)
+ if len(vals) != 1:
+ raise ValueError("more than one %r key" % key)
+ return vals[0]
+ addr = parse_addr_spec(get_unique("client"), defhost, defport)
+ transport = get_unique("client-transport", DEFAULT_CLIENT_TRANSPORT)
+ yield reg.Endpoint(addr, transport)
+
+def skip_space(pos, line):
+ """Skip a (possibly empty) sequence of space characters (the ASCII character
+ '\x20' exactly). Returns a pair (pos, num_skipped)."""
+ begin = pos
+ while pos < len(line) and line[pos] == "\x20":
+ pos += 1
+ return pos, pos - begin
+
+TOKEN_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
+def get_token(pos, line):
+ begin = pos
+ while pos < len(line) and line[pos] in TOKEN_CHARS:
+ pos += 1
+ if begin == pos:
+ raise ValueError("No token found at position %d" % pos)
+ return pos, line[begin:pos]
+
+def get_quoted_string(pos, line):
+ chars = []
+ if not (pos < len(line) and line[pos] == '"'):
+ raise ValueError("Expected '\"' at beginning of quoted string.")
+ pos += 1
+ while pos < len(line) and line[pos] != '"':
+ if line[pos] == '\\':
+ pos += 1
+ if not (pos < len(line)):
+ raise ValueError("End of line after backslash in quoted string")
+ chars.append(line[pos])
+ pos += 1
+ if not (pos < len(line) and line[pos] == '"'):
+ raise ValueError("Expected '\"' at end of quoted string.")
+ pos += 1
+ return pos, "".join(chars)
+
+def parse_transaction(line):
+ """A transaction is a command followed by zero or more key-value pairs. Like so:
+ COMMAND KEY="VALUE" KEY="\"ESCAPED\" VALUE"
+ Values must be quoted. Any byte value may be escaped with a backslash.
+ Returns a pair: (COMMAND, ((KEY1, VALUE1), (KEY2, VALUE2), ...)).
+ """
+ pos = 0
+ pos, skipped = skip_space(pos, line)
+ pos, command = get_token(pos, line)
+
+ pairs = []
+ while True:
+ pos, skipped = skip_space(pos, line)
+ if not (pos < len(line)):
+ break
+ if skipped == 0:
+ raise ValueError("Expected space before key-value pair")
+ pos, key = get_token(pos, line)
+ if not (pos < len(line) and line[pos] == '='):
+ raise ValueError("No '=' found after key")
+ pos += 1
+ pos, value = get_quoted_string(pos, line)
+ pairs.append((key, value))
+ return command, tuple(pairs)
+
+def param_first(key, params):
+ """Search 'params' for 'key' and return the first value that
+ occurs. If 'key' was not found, return None."""
+ for k, v in params:
+ if key == k:
+ return v
+ return None
+
+def param_getlist(key, params):
+ """Search 'params' for 'key' and return a list with its values. If
+ 'key' did not appear in 'params', return the empty list."""
+ result = []
+ for k, v in params:
+ if key == k:
+ result.append(v)
+ return result
+
+def quote_string(s):
+ chars = []
+ for c in s:
+ if c == "\\":
+ c = "\\\\"
+ elif c == "\"":
+ c = "\\\""
+ chars.append(c)
+ return "\"" + "".join(chars) + "\""
+
+def render_transaction(command, *params):
+ parts = [command]
+ for key, value in params:
+ parts.append("%s=%s" % (key, quote_string(value)))
+ return " ".join(parts)
+
+def fac_socket(facilitator_addr):
+ return socket.create_connection(facilitator_addr, 1.0).makefile()
+
+def transact(f, command, *params):
+ transaction = render_transaction(command, *params)
+ print >> f, transaction
+ f.flush()
+ line = f.readline()
+ if not (len(line) > 0 and line[-1] == '\n'):
+ raise ValueError("No newline at end of string returned by facilitator")
+ return parse_transaction(line[:-1])
+
+def put_reg(facilitator_addr, client_addr, transport):
+ """Send a registration to the facilitator using a one-time socket. Returns
+ true iff the command was successful. transport is a transport string such as
+ "websocket" or "obfs3|websocket"."""
+ f = fac_socket(facilitator_addr)
+ params = [("CLIENT", format_addr(client_addr))]
+ params.append(("TRANSPORT", transport))
+ try:
+ command, params = transact(f, "PUT", *params)
+ finally:
+ f.close()
+ return command == "OK"
+
+def get_reg(facilitator_addr, proxy_addr, proxy_transport_list):
+ """
+ Get a client registration for proxy proxy_addr from the
+ facilitator at facilitator_addr using a one-time
+ socket. proxy_transport_list is a list containing the transport names that
+ the flashproxy supports.
+
+ Returns a dict with keys "client", "client-transport", "relay",
+ and "relay-transport" if successful, or a dict with the key "client"
+ mapped to the value "" if there are no registrations available for
+ proxy_addr. Raises an exception otherwise."""
+ f = fac_socket(facilitator_addr)
+
+ # Form a list (in transact() format) with the transports that we
+ # should send to the facilitator. Then pass that list to the
+ # transact() function.
+ # For example, PROXY-TRANSPORT=obfs2 PROXY-TRANSPORT=obfs3.
+ transports = [("PROXY-TRANSPORT", tp) for tp in proxy_transport_list]
+
+ try:
+ command, params = transact(f, "GET", ("FROM", format_addr(proxy_addr)), *transports)
+ finally:
+ f.close()
+ response = {}
+ check_back_in = param_first("CHECK-BACK-IN", params)
+ if check_back_in is not None:
+ try:
+ float(check_back_in)
+ except ValueError:
+ raise ValueError("Facilitator returned non-numeric polling interval.")
+ response["check-back-in"] = check_back_in
+ if command == "NONE":
+ response["client"] = ""
+ return response
+ elif command == "OK":
+ client_spec = param_first("CLIENT", params)
+ client_transport = param_first("CLIENT-TRANSPORT", params)
+ relay_spec = param_first("RELAY", params)
+ relay_transport = param_first("RELAY-TRANSPORT", params)
+ if not client_spec:
+ raise ValueError("Facilitator did not return CLIENT")
+ if not client_transport:
+ raise ValueError("Facilitator did not return CLIENT-TRANSPORT")
+ if not relay_spec:
+ raise ValueError("Facilitator did not return RELAY")
+ if not relay_transport:
+ raise ValueError("Facilitator did not return RELAY-TRANSPORT")
+ # Check the syntax returned by the facilitator.
+ client = parse_addr_spec(client_spec)
+ relay = parse_addr_spec(relay_spec)
+ response["client"] = format_addr(client)
+ response["client-transport"] = client_transport
+ response["relay"] = format_addr(relay)
+ response["relay-transport"] = relay_transport
+ return response
+ else:
+ raise ValueError("Facilitator response was not \"OK\"")
+
+def put_reg_base64(b64):
+ """Attempt to add a registration by running a facilitator-reg program
+ locally."""
+ # Padding is optional, but the python base64 functions can't
+ # handle lack of padding. Add it here. Assumes correct encoding.
+ mod = len(b64) % 4
+ if mod != 0:
+ b64 += (4 - mod) * "="
+ p = subprocess.Popen(["facilitator-reg"], stdin=subprocess.PIPE)
+ stdout, stderr = p.communicate(b64)
+ return p.returncode == 0
diff --git a/flashproxy/proc.py b/flashproxy/proc.py
new file mode 100644
index 0000000..4a008b2
--- /dev/null
+++ b/flashproxy/proc.py
@@ -0,0 +1,47 @@
+import errno
+import os
+import socket
+import stat
+import pwd
+
+DEFAULT_CLIENT_TRANSPORT = "websocket"
+
+# Return true iff the given fd is readable, writable, and executable only by its
+# owner.
+def check_perms(fd):
+ mode = os.fstat(fd)[0]
+ return (mode & (stat.S_IRWXG | stat.S_IRWXO)) == 0
+
+# Drop privileges by switching ID to that of the given user.
+# http://stackoverflow.com/questions/2699907/dropping-root-permissions-in-pyt…
+# https://www.securecoding.cert.org/confluence/display/seccode/POS36-C.+Obser…
+# https://www.securecoding.cert.org/confluence/display/seccode/POS37-C.+Ensur…
+def drop_privs(username):
+ uid = pwd.getpwnam(username).pw_uid
+ gid = pwd.getpwnam(username).pw_gid
+ os.setgroups([])
+ os.setgid(gid)
+ os.setuid(uid)
+ try:
+ os.setuid(0)
+ except OSError:
+ pass
+ else:
+ raise AssertionError("setuid(0) succeeded after attempting to drop privileges")
+
+# A decorator to ignore "broken pipe" errors.
+def catch_epipe(fn):
+ def ret(self, *args):
+ try:
+ return fn(self, *args)
+ except socket.error, e:
+ try:
+ err_num = e.errno
+ except AttributeError:
+ # Before Python 2.6, exception can be a pair.
+ err_num, errstr = e
+ except:
+ raise
+ if err_num != errno.EPIPE:
+ raise
+ return ret
diff --git a/flashproxy/reg.py b/flashproxy/reg.py
new file mode 100644
index 0000000..0551f06
--- /dev/null
+++ b/flashproxy/reg.py
@@ -0,0 +1,31 @@
+from collections import namedtuple
+
+from flashproxy.util import parse_addr_spec
+
+class Transport(namedtuple("Transport", "inner outer")):
+ @classmethod
+ def parse(cls, transport):
+ if isinstance(transport, cls):
+ return transport
+ elif type(transport) == str:
+ if "|" in transport:
+ inner, outer = transport.rsplit("|", 1)
+ else:
+ inner, outer = "", transport
+ return cls(inner, outer)
+ else:
+ raise ValueError("could not parse transport: %s" % transport)
+
+ def __init__(self, inner, outer):
+ if not outer:
+ raise ValueError("outer (proxy) part of transport must be non-empty: %s" % str(self))
+
+ def __str__(self):
+ return "%s|%s" % (self.inner, self.outer) if self.inner else self.outer
+
+
+class Endpoint(namedtuple("Endpoint", "addr transport")):
+ @classmethod
+ def parse(cls, spec, transport, defhost = None, defport = None):
+ host, port = parse_addr_spec(spec, defhost, defport)
+ return cls((host, port), Transport.parse(transport))
diff --git a/flashproxy/test/test_fac.py b/flashproxy/test/test_fac.py
new file mode 100644
index 0000000..e7dfa00
--- /dev/null
+++ b/flashproxy/test/test_fac.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+
+import unittest
+
+from flashproxy.fac import parse_transaction, read_client_registrations
+
+class ParseTransactionTest(unittest.TestCase):
+ def test_empty_string(self):
+ self.assertRaises(ValueError, parse_transaction, "")
+
+ def test_correct(self):
+ self.assertEqual(parse_transaction("COMMAND"), ("COMMAND", ()))
+ self.assertEqual(parse_transaction("COMMAND X=\"\""), ("COMMAND", (("X", ""),)))
+ self.assertEqual(parse_transaction("COMMAND X=\"ABC\""), ("COMMAND", (("X", "ABC"),)))
+ self.assertEqual(parse_transaction("COMMAND X=\"\\A\\B\\C\""), ("COMMAND", (("X", "ABC"),)))
+ self.assertEqual(parse_transaction("COMMAND X=\"\\\\\\\"\""), ("COMMAND", (("X", "\\\""),)))
+ self.assertEqual(parse_transaction("COMMAND X=\"ABC\" Y=\"DEF\""), ("COMMAND", (("X", "ABC"), ("Y", "DEF"))))
+ self.assertEqual(parse_transaction("COMMAND KEY-NAME=\"ABC\""), ("COMMAND", (("KEY-NAME", "ABC"),)))
+ self.assertEqual(parse_transaction("COMMAND KEY_NAME=\"ABC\""), ("COMMAND", (("KEY_NAME", "ABC"),)))
+
+ def test_missing_command(self):
+ self.assertRaises(ValueError, parse_transaction, "X=\"ABC\"")
+ self.assertRaises(ValueError, parse_transaction, " X=\"ABC\"")
+
+ def test_missing_space(self):
+ self.assertRaises(ValueError, parse_transaction, "COMMAND/X=\"ABC\"")
+ self.assertRaises(ValueError, parse_transaction, "COMMAND X=\"ABC\"Y=\"DEF\"")
+
+ def test_bad_quotes(self):
+ self.assertRaises(ValueError, parse_transaction, "COMMAND X=\"")
+ self.assertRaises(ValueError, parse_transaction, "COMMAND X=\"ABC")
+ self.assertRaises(ValueError, parse_transaction, "COMMAND X=\"ABC\" Y=\"ABC")
+ self.assertRaises(ValueError, parse_transaction, "COMMAND X=\"ABC\\")
+
+ def test_truncated(self):
+ self.assertRaises(ValueError, parse_transaction, "COMMAND X=")
+
+ def test_newline(self):
+ self.assertRaises(ValueError, parse_transaction, "COMMAND X=\"ABC\" \nY=\"DEF\"")
+
+class ReadClientRegistrationsTest(unittest.TestCase):
+ def testSingle(self):
+ l = list(read_client_registrations(""))
+ self.assertEqual(len(l), 0)
+ l = list(read_client_registrations("client=1.2.3.4:1111"))
+ self.assertEqual(len(l), 1)
+ self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
+ l = list(read_client_registrations("client=1.2.3.4:1111\n"))
+ self.assertEqual(len(l), 1)
+ self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
+ l = list(read_client_registrations("foo=bar&client=1.2.3.4:1111&baz=quux"))
+ self.assertEqual(len(l), 1)
+ self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
+ l = list(read_client_registrations("foo=b%3dar&client=1.2.3.4%3a1111"))
+ self.assertEqual(len(l), 1)
+ self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
+ l = list(read_client_registrations("client=%5b1::2%5d:3333"))
+ self.assertEqual(len(l), 1)
+ self.assertEqual(l[0].addr, ("1::2", 3333))
+
+ def testDefaultAddress(self):
+ l = list(read_client_registrations("client=:1111&transport=websocket", defhost="1.2.3.4"))
+ self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
+ l = list(read_client_registrations("client=1.2.3.4:&transport=websocket", defport=1111))
+ self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
+
+ def testDefaultTransport(self):
+ l = list(read_client_registrations("client=1.2.3.4:1111"))
+ self.assertEqual(l[0].transport, "websocket")
+
+ def testMultiple(self):
+ l = list(read_client_registrations("client=1.2.3.4:1111&foo=bar\nfoo=bar&client=5.6.7.8:2222"))
+ self.assertEqual(len(l), 2)
+ self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
+ self.assertEqual(l[1].addr, ("5.6.7.8", 2222))
+ l = list(read_client_registrations("client=1.2.3.4:1111&foo=bar\nfoo=bar&client=%5b1::2%5d:3333"))
+ self.assertEqual(len(l), 2)
+ self.assertEqual(l[0].addr, ("1.2.3.4", 1111))
+ self.assertEqual(l[1].addr, ("1::2", 3333))
+
+ def testInvalid(self):
+ # Missing "client".
+ with self.assertRaises(ValueError):
+ list(read_client_registrations("foo=bar"))
+ # More than one "client".
+ with self.assertRaises(ValueError):
+ list(read_client_registrations("client=1.2.3.4:1111&foo=bar&client=5.6.7.8:2222"))
+ # Single client with bad syntax.
+ with self.assertRaises(ValueError):
+ list(read_client_registrations("client=1.2.3.4,1111"))
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/flashproxy/test/test_reg.py b/flashproxy/test/test_reg.py
new file mode 100644
index 0000000..6b0e196
--- /dev/null
+++ b/flashproxy/test/test_reg.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+import unittest
+
+from flashproxy.reg import Transport
+
+class TransportTest(unittest.TestCase):
+
+ def test_transport_parse(self):
+ self.assertEquals(Transport.parse("a"), Transport("", "a"))
+ self.assertEquals(Transport.parse("|a"), Transport("", "a"))
+ self.assertEquals(Transport.parse("a|b|c"), Transport("a|b","c"))
+ self.assertEquals(Transport.parse(Transport("a|b","c")), Transport("a|b","c"))
+ self.assertRaises(ValueError, Transport, "", "")
+ self.assertRaises(ValueError, Transport, "a", "")
+ self.assertRaises(ValueError, Transport.parse, "")
+ self.assertRaises(ValueError, Transport.parse, "|")
+ self.assertRaises(ValueError, Transport.parse, "a|")
+ self.assertRaises(ValueError, Transport.parse, ["a"])
+ self.assertRaises(ValueError, Transport.parse, [Transport("a", "b")])
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/flashproxy/test/test_util.py b/flashproxy/test/test_util.py
new file mode 100644
index 0000000..af4c2e6
--- /dev/null
+++ b/flashproxy/test/test_util.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+import unittest
+
+from flashproxy.util import parse_addr_spec, canonical_ip
+
+class ParseAddrSpecTest(unittest.TestCase):
+ def test_ipv4(self):
+ self.assertEqual(parse_addr_spec("192.168.0.1:9999"), ("192.168.0.1", 9999))
+
+ def test_ipv6(self):
+ self.assertEqual(parse_addr_spec("[12::34]:9999"), ("12::34", 9999))
+
+ def test_defhost_defport_ipv4(self):
+ self.assertEqual(parse_addr_spec("192.168.0.2:8888", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 8888))
+ self.assertEqual(parse_addr_spec("192.168.0.2:", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 9999))
+ self.assertEqual(parse_addr_spec("192.168.0.2", defhost="192.168.0.1", defport=9999), ("192.168.0.2", 9999))
+ self.assertEqual(parse_addr_spec(":8888", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 8888))
+ self.assertEqual(parse_addr_spec(":", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 9999))
+ self.assertEqual(parse_addr_spec("", defhost="192.168.0.1", defport=9999), ("192.168.0.1", 9999))
+
+ def test_defhost_defport_ipv6(self):
+ self.assertEqual(parse_addr_spec("[1234::2]:8888", defhost="1234::1", defport=9999), ("1234::2", 8888))
+ self.assertEqual(parse_addr_spec("[1234::2]:", defhost="1234::1", defport=9999), ("1234::2", 9999))
+ self.assertEqual(parse_addr_spec("[1234::2]", defhost="1234::1", defport=9999), ("1234::2", 9999))
+ self.assertEqual(parse_addr_spec(":8888", defhost="1234::1", defport=9999), ("1234::1", 8888))
+ self.assertEqual(parse_addr_spec(":", defhost="1234::1", defport=9999), ("1234::1", 9999))
+ self.assertEqual(parse_addr_spec("", defhost="1234::1", defport=9999), ("1234::1", 9999))
+
+ def test_canonical_ip_noresolve(self):
+ """Test that canonical_ip does not do DNS resolution by default."""
+ self.assertRaises(ValueError, canonical_ip, *parse_addr_spec("example.com:80"))
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/flashproxy/util.py b/flashproxy/util.py
index 47bd87a..b069bf7 100644
--- a/flashproxy/util.py
+++ b/flashproxy/util.py
@@ -2,6 +2,27 @@ import re
import socket
def parse_addr_spec(spec, defhost = None, defport = None):
+ """Parse a host:port specification and return a 2-tuple ("host", port) as
+ understood by the Python socket functions.
+ >>> parse_addr_spec("192.168.0.1:9999")
+ ('192.168.0.1', 9999)
+
+ If defhost or defport are given, those parts of the specification may be
+ omitted; if so, they will be filled in with defaults.
+ >>> parse_addr_spec("192.168.0.2:8888", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.2', 8888)
+ >>> parse_addr_spec(":8888", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.1', 8888)
+ >>> parse_addr_spec("192.168.0.2", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.2', 9999)
+ >>> parse_addr_spec("192.168.0.2:", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.2', 9999)
+ >>> parse_addr_spec(":", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.1', 9999)
+ >>> parse_addr_spec("", defhost="192.168.0.1", defport=9999)
+ ('192.168.0.1', 9999)
+
+ IPv6 addresses must be enclosed in square brackets."""
host = None
port = None
af = 0
@@ -29,24 +50,61 @@ def parse_addr_spec(spec, defhost = None, defport = None):
af = 0
host = host or defhost
port = port or defport
- if port is not None:
- port = int(port)
- return host, port
+ if host is None or port is None:
+ raise ValueError("Bad address specification \"%s\"" % spec)
+ return host, int(port)
-def format_addr(addr):
- host, port = addr
- if not host:
- return u":%d" % port
- # Numeric IPv6 address?
+def resolve_to_ip(host, port, af=0, gai_flags=0):
+ """Resolves a host string to an IP address in canonical format.
+
+ Note: in many cases this is not necessary since the consumer of the address
+ can probably accept host names directly.
+
+ :param: host string to resolve; may be a DNS name or an IP address.
+ :param: port of the host
+ :param: af address family, default unspecified. set to socket.AF_INET or
+ socket.AF_INET6 to force IPv4 or IPv6 name resolution.
+ :returns: (IP address in canonical format, port)
+ """
+ # Forward-resolve the name into an addrinfo struct. Real DNS resolution is
+ # done only if resolve is true; otherwise the address must be numeric.
try:
- addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST)
- af = addrs[0][0]
+ addrs = socket.getaddrinfo(host, port, af, 0, 0, gai_flags)
except socket.gaierror, e:
- af = 0
- if af == socket.AF_INET6:
- result = u"[%s]" % host
- else:
- result = "%s" % host
+ raise ValueError("Bad host or port: \"%s\" \"%s\": %s" % (host, port, str(e)))
+ if not addrs:
+ raise ValueError("Bad host or port: \"%s\" \"%s\"" % (host, port))
+
+ # Convert the result of socket.getaddrinfo (which is a 2-tuple for IPv4 and
+ # a 4-tuple for IPv6) into a (host, port) 2-tuple.
+ host, port = socket.getnameinfo(addrs[0][4], socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
+ return host, int(port)
+
+def canonical_ip(host, port, af=0):
+ """Convert an IP address to a canonical format. Identical to resolve_to_ip,
+ except that the host param must already be an IP address."""
+ return resolve_to_ip(host, port, af, gai_flags=socket.AI_NUMERICHOST)
+
+def format_addr(addr):
+ host, port = addr
+ host_str = u""
+ port_str = u""
+ if host is not None:
+ # Numeric IPv6 address?
+ try:
+ addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_NUMERICHOST)
+ af = addrs[0][0]
+ except socket.gaierror, e:
+ af = 0
+ if af == socket.AF_INET6:
+ host_str = u"[%s]" % host
+ else:
+ host_str = u"%s" % host
if port is not None:
- result += u":%d" % port
- return result
+ if not (0 < port <= 65535):
+ raise ValueError("port must be between 1 and 65535 (is %d)" % port)
+ port_str = u":%d" % port
+
+ if not host_str and not port_str:
+ raise ValueError("host and port may not both be None")
+ return u"%s%s" % (host_str, port_str)
1
0

[flashproxy/master] more specific rm in remove-{secrets, symlinks} and add remove-daemon-data to purge logs and other data
by infinity0@torproject.org 21 Nov '13
by infinity0@torproject.org 21 Nov '13
21 Nov '13
commit b7aab78ab124884cac44339203721f9609e56548
Author: Ximin Luo <infinity0(a)gmx.com>
Date: Thu Nov 21 14:17:26 2013 +0000
more specific rm in remove-{secrets,symlinks} and add remove-daemon-data to purge logs and other data
- also don't symlink no-longer-existant appengine/README
---
facilitator/Makefile.am | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/facilitator/Makefile.am b/facilitator/Makefile.am
index 10811e8..9e56a19 100644
--- a/facilitator/Makefile.am
+++ b/facilitator/Makefile.am
@@ -60,7 +60,7 @@ pre-install: meta-install-sanity install-user
post-install: meta-install-sanity install-secrets install-symlinks install-daemon
pre-remove: meta-install-sanity remove-daemon remove-symlinks
post-remove: meta-install-sanity
-pre-purge: pre-remove remove-secrets
+pre-purge: pre-remove remove-secrets remove-daemon-data
post-purge: post-remove remove-user
meta-install-sanity:
@@ -74,13 +74,13 @@ install-user:
--system \
--group \
--disabled-password \
- --home ${sysconfdir}/flashproxy \
+ --home ${pkgconfdir} \
--no-create-home \
--shell /bin/false \
${fpfacilitatoruser} || \
useradd \
--system \
- --home ${sysconfdir}/flashproxy \
+ --home ${pkgconfdir} \
-M \
--shell /bin/false \
${fpfacilitatoruser} ; }
@@ -105,15 +105,19 @@ install-secrets:
cat ${exampledir}/reg-email.pass > ${pkgconfdir}/reg-email.pass; }
remove-secrets:
- rm -f ${pkgconfdir}/reg-*
+ for i in reg-daemon.key reg-daemon.pub reg-email.pass; do \
+ rm -f ${pkgconfdir}/$$i; \
+ done
install-symlinks:
- for i in fp-reg.go app.yaml README; do \
+ for i in fp-reg.go app.yaml; do \
$(LN_S) -f ${appenginedir}/$$i ${appengineconfdir}/$$i; \
done
remove-symlinks:
- rm -rf ${appengineconfdir}
+ for i in fp-reg.go app.yaml; do \
+ rm -f ${appengineconfdir}/$$i; \
+ done
# initscripts: assume that if the user wanted to install them, then they also
# wanted to configure them, and that the system supports them. if this isn't the
@@ -141,6 +145,14 @@ if DO_INITSCRIPTS
done
endif
+remove-daemon-data:
+if DO_INITSCRIPTS
+ for i in facilitator facilitator-email-poller facilitator-reg-daemon; do \
+ rm -f ${localstatedir}/log/$$i.log* \
+ rm -f ${localstatedir}/run/$$i.pid \
+ done
+endif
+
.PHONY: pre-install post-install pre-remove post-remove pre-purge post-purge
.PHONY: install-user install-secrets install-symlinks install-daemon
.PHONY: remove-user remove-secrets remove-symlinks remove-daemon
1
0

[flashproxy/master] in extra-install tasks, use ${} rather than $() syntax for easier copying into distro-specific scripts
by infinity0@torproject.org 21 Nov '13
by infinity0@torproject.org 21 Nov '13
21 Nov '13
commit 04ce42a22197f4bfef66fdd1934b6d5ac594c821
Author: Ximin Luo <infinity0(a)gmx.com>
Date: Thu Nov 21 14:06:36 2013 +0000
in extra-install tasks, use ${} rather than $() syntax for easier copying into distro-specific scripts
---
facilitator/Makefile.am | 40 ++++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/facilitator/Makefile.am b/facilitator/Makefile.am
index 435956d..10811e8 100644
--- a/facilitator/Makefile.am
+++ b/facilitator/Makefile.am
@@ -68,52 +68,52 @@ meta-install-sanity:
"don't run {pre,post}-{install,remove} when DESTDIR is set"; false; }
install-user:
- id -u $(fpfacilitatoruser) >/dev/null 2>&1 || { \
+ id -u ${fpfacilitatoruser} >/dev/null 2>&1 || { \
which adduser >/dev/null 2>&1 && \
adduser --quiet \
--system \
--group \
--disabled-password \
- --home $(sysconfdir)/flashproxy \
+ --home ${sysconfdir}/flashproxy \
--no-create-home \
--shell /bin/false \
- $(fpfacilitatoruser) || \
+ ${fpfacilitatoruser} || \
useradd \
--system \
- --home $(sysconfdir)/flashproxy \
+ --home ${sysconfdir}/flashproxy \
-M \
--shell /bin/false \
- $(fpfacilitatoruser) ; }
+ ${fpfacilitatoruser} ; }
remove-user:
: # deluser does actually remove the group as well
- id -u $(fpfacilitatoruser) >/dev/null 2>&1 && { \
+ id -u ${fpfacilitatoruser} >/dev/null 2>&1 && { \
which deluser >/dev/null 2>&1 && \
deluser --quiet \
--system \
- $(fpfacilitatoruser) || \
+ ${fpfacilitatoruser} || \
userdel \
- $(fpfacilitatoruser) ; } || true
+ ${fpfacilitatoruser} ; } || true
install-secrets:
- test -f $(pkgconfdir)/reg-daemon.key || { \
- install -m 600 /dev/null $(pkgconfdir)/reg-daemon.key && \
- openssl genrsa 2048 | tee $(pkgconfdir)/reg-daemon.key | \
- openssl rsa -pubout > $(pkgconfdir)/reg-daemon.pub; }
- test -f $(pkgconfdir)/reg-email.pass || { \
- install -m 600 /dev/null $(pkgconfdir)/reg-email.pass && \
- cat $(exampledir)/reg-email.pass > $(pkgconfdir)/reg-email.pass; }
+ test -f ${pkgconfdir}/reg-daemon.key || { \
+ install -m 600 /dev/null ${pkgconfdir}/reg-daemon.key && \
+ openssl genrsa 2048 | tee ${pkgconfdir}/reg-daemon.key | \
+ openssl rsa -pubout > ${pkgconfdir}/reg-daemon.pub; }
+ test -f ${pkgconfdir}/reg-email.pass || { \
+ install -m 600 /dev/null ${pkgconfdir}/reg-email.pass && \
+ cat ${exampledir}/reg-email.pass > ${pkgconfdir}/reg-email.pass; }
remove-secrets:
- rm -f $(pkgconfdir)/reg-*
+ rm -f ${pkgconfdir}/reg-*
install-symlinks:
for i in fp-reg.go app.yaml README; do \
- $(LN_S) -f $(appenginedir)/$$i $(appengineconfdir)/$$i; \
+ $(LN_S) -f ${appenginedir}/$$i ${appengineconfdir}/$$i; \
done
remove-symlinks:
- rm -rf $(appengineconfdir)
+ rm -rf ${appengineconfdir}
# initscripts: assume that if the user wanted to install them, then they also
# wanted to configure them, and that the system supports them. if this isn't the
@@ -124,8 +124,8 @@ remove-symlinks:
install-daemon:
if DO_INITSCRIPTS
# initscripts use these directories for logs and runtime data
- mkdir -p $(localstatedir)/log
- mkdir -p $(localstatedir)/run
+ mkdir -p ${localstatedir}/log
+ mkdir -p ${localstatedir}/run
for i in facilitator facilitator-email-poller facilitator-reg-daemon; do \
update-rc.d $$i defaults; \
invoke-rc.d $$i start; \
1
0
commit b2afaebd0e473a410d0266fe2bbd6ca765731332
Author: Translation commit bot <translation(a)torproject.org>
Date: Thu Nov 21 16:45:10 2013 +0000
Update translations for tsum
---
gl/short-user-manual_gl_noimg.xhtml | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/gl/short-user-manual_gl_noimg.xhtml b/gl/short-user-manual_gl_noimg.xhtml
index 7ae85ea..228dc96 100644
--- a/gl/short-user-manual_gl_noimg.xhtml
+++ b/gl/short-user-manual_gl_noimg.xhtml
@@ -93,30 +93,30 @@ sub 2048R/EB399FD7 2003-10-16
<h4 id="windows-xp">Windows XP</h4>
<ol style="list-style-type: decimal">
<li>Abrir <em>Meu Computador</em></li>
- <li>Click on <em>Tools</em> and choose <em>Folder Options...</em> in the menu</li>
- <li>Click on the <em>View</em> tab</li>
- <li>Uncheck <em>Hide extensions for known file types</em> and click <em>OK</em></li>
+ <li>Prema en <em>Ferramentas</em> e escolla no menú <em>Opcións de Cartafol ...</em></li>
+ <li>Prema na lapela <em>Ver</em> </li>
+ <li>Desmarque <em>Ocultar as extensións dos tipos de arquivo coñecidos</em> e prema en <em>Aceptar</em></li>
</ol>
<h4 id="windows-vista">Windows Vista</h4>
<ol style="list-style-type: decimal">
- <li>Open <em>Computer</em></li>
- <li>Click on <em>Organize</em> and choose <em>Folder and search options</em> in the menu</li>
- <li>Click on the <em>View</em> tab</li>
- <li>Uncheck <em>Hide extensions for known file types</em> and click <em>OK</em></li>
+ <li>Abrir <em>Ordenador</em></li>
+ <li>Prema <em>Organizar</em> e escolla no menú <em>Cartafol e opcións de busca</em></li>
+ <li>Prema na lapela <em>Ver</em> </li>
+ <li>Desmarque <em>Ocultar as extensións dos tipos de arquivo coñecidos</em> e prema en <em>Aceptar</em></li>
</ol>
<h4 id="windows-7">Windows 7</h4>
<ol style="list-style-type: decimal">
- <li>Open <em>Computer</em></li>
- <li>Click on <em>Organize</em> and choose <em>Folder and search options</em> in the menu</li>
- <li>Click on the <em>View</em> tab</li>
- <li>Uncheck <em>Hide extensions for known file types</em> and click <em>OK</em></li>
+ <li>Abrir <em>Ordenador</em></li>
+ <li>Prema <em>Organizar</em> e escolla no menú <em>Cartafol e opcións de busca</em></li>
+ <li>Prema na lapela <em>Ver</em> </li>
+ <li>Desmarque <em>Ocultar as extensións dos tipos de arquivo coñecidos</em> e prema en <em>Aceptar</em></li>
</ol>
- <h3 id="vidalia-asks-for-a-password">Vidalia asks for a password</h3>
- <p>You should not have to enter a password when starting Vidalia. If you are prompted for one, you are likely affected by one of these problems:</p>
- <p><strong>You are already running Vidalia and Tor</strong>: For example, this situation can happen if you installed the Vidalia bundle and now you're trying to run the Tor Browser Bundle. In that case, you will need to close the old Vidalia and Tor before you can run this one.</p>
- <p><strong>Vidalia crashed, but left Tor running</strong>: If the dialog that prompts you for a control password has a Reset button, you can click the button and Vidalia will restart Tor with a new random control password. If you do not see a Reset button, or if Vidalia is unable to restart Tor for you; go into your process or task manager, and terminate the Tor process. Then use Vidalia to restart Tor.</p>
- <p>For more information, see the <a href="https://torproject.org/docs/faq.html#VidaliaPassword">FAQ</a> on the Tor Project website.</p>
- <h3 id="flash-does-not-work">Flash does not work</h3>
+ <h3 id="vidalia-asks-for-a-password">Vidalia pide un contrasinal</h3>
+ <p>Non debería ter que escribir un contrasinal ao iniciar Vidalia. Se se lle pregunta por un, probablemente ten un destes problemas:</p>
+ <p><strong>Xa está executando Vidalia e Tor</strong>: Por exemplo, esta situación pode ocorrer se instalou o paquete Vidalia e agora está intentando executar o Tor Browser Bundle. Neste caso, vai ter para pechar o antigo Vidalia e Tor antes de executar este.</p>
+ <p><strong>Vidalia caeu, pero deixou Tor en execución</strong>: Se a caixa de diálogo que pide un contrasinal de control ten un botón de Reset, pode facer clic no botón e Vidalia reiniciará o Tor cun novo contrasinal de control aleatorio. Se non ve o botón, ou se Vidalia é incapaz de reiniciar Tor, use o seu xestor de tarefas ou procesos e finalice o proceso de Tor. Entón use Vidalia para reiniciar Tor.</p>
+ <p>Para máis información, consulte as <a href="https://torproject.org/docs/faq.html#VidaliaPassword">FAQ</a> na páxina do Proxecto Tor.</p>
+ <h3 id="flash-does-not-work">Flash non funciona</h3>
<p>For security reasons, Flash, Java, and other plugins are currently disabled for Tor. Plugins operate independently from Firefox and can perform activity on your computer that ruins your anonymity.</p>
<p>Most YouTube videos work with HTML5, and it is possible to view these videos over Tor. You need to join the <a href="https://www.youtube.com/html5">HTML5 trial</a> on the YouTube website before you can use the HTML5 player.</p>
<p>Note that the browser will not remember that you joined the trial once you close it, so you will need to re-join the trial the next time you run the Tor Browser Bundle.</p>
1
0