[tor-commits] [flashproxy/master] email-poller: configure user data (imap_host, email, password) in the same file

infinity0 at torproject.org infinity0 at torproject.org
Thu Nov 21 13:18:46 UTC 2013


commit a310c1d0f893ebd18a128a9d92684b3cf51d70a4
Author: Ximin Luo <infinity0 at gmx.com>
Date:   Mon Nov 4 20:49:58 2013 +0000

    email-poller: configure user data (imap_host, email, password) in the same file
    - means we don't depend on initscripts for user config, and easier to explain
    - breaks backwards compatibility, but since the email was hardcoded we only had one user anyway
    - smarter default IMAP host; and resolve DNS by default - previously one had to use an IP address
    - add a nameOk option to parse_addr_spec as certificate verification requires imap host to be a name
---
 facilitator/Makefile.am                      |    7 ++--
 facilitator/default/facilitator-email-poller |    8 -----
 facilitator/examples/reg-email.pass          |   10 ++++++
 facilitator/fac.py                           |   11 +++---
 facilitator/facilitator-email-poller         |   50 ++++++++++++++------------
 facilitator/facilitator-test.py              |    5 +++
 6 files changed, 51 insertions(+), 40 deletions(-)

diff --git a/facilitator/Makefile.am b/facilitator/Makefile.am
index 6d4325b..6a3cdfc 100644
--- a/facilitator/Makefile.am
+++ b/facilitator/Makefile.am
@@ -20,7 +20,7 @@ dist_initconf_DATA = default/facilitator default/facilitator-email-poller defaul
 endif
 
 dist_doc_DATA = doc/appengine-howto.txt doc/facilitator-howto.txt doc/gmail-howto.txt README
-dist_example_DATA = examples/fp-facilitator
+dist_example_DATA = examples/fp-facilitator examples/reg-email.pass
 dist_appengine_DATA = appengine/app.yaml appengine/config.go appengine/fp-reg.go appengine/README
 appengineconf_DATA = appengine/config.go
 
@@ -87,10 +87,7 @@ install-secrets:
 	  openssl rsa -pubout > $(pkgconfdir)/reg-daemon.pub; }
 	test -f $(pkgconfdir)/reg-email.pass || { \
 	  install -m 600 /dev/null $(pkgconfdir)/reg-email.pass && \
-	  echo >> $(pkgconfdir)/reg-email.pass \
-	  "Replace this file's contents with your Gmail app-specific password;" && \
-	  echo >> $(pkgconfdir)/reg-email.pass \
-	  "see gmail-howto.txt in this package's documentation for details."; }
+	  cat $(exampledir)/reg-email.pass > $(pkgconfdir)/reg-email.pass; }
 
 remove-secrets:
 	rm -f $(pkgconfdir)/reg-*
diff --git a/facilitator/default/facilitator-email-poller b/facilitator/default/facilitator-email-poller
index 42f4dc6..b71503f 100644
--- a/facilitator/default/facilitator-email-poller
+++ b/facilitator/default/facilitator-email-poller
@@ -5,11 +5,3 @@ RUN_DAEMON="no"
 # This may be useful for debugging or diagnosing functional problems, but
 # should be avoided in a high-risk environment.
 #UNSAFE_LOGGING="yes"
-
-# Replace this with the email address for your facilitator.
-# You should also edit the reg-email.pass file as needed.
-FACILITATOR_EMAIL_ADDR="invalid"
-
-# Set the host:port for the remote IMAP service to contact
-# If not set, uses the default (imap.gmail.com:993).
-#IMAPADDR="imap.gmail.com:993"
diff --git a/facilitator/examples/reg-email.pass b/facilitator/examples/reg-email.pass
new file mode 100644
index 0000000..6f34e06
--- /dev/null
+++ b/facilitator/examples/reg-email.pass
@@ -0,0 +1,10 @@
+# This file should contain "[<imap_host>] <email> <password>" on a single line,
+# separated by whitespace. If <imap_host> is omitted, it defaults to
+# imap.(<email> domain):993.
+#
+# If your email provider supports it, we advise you to use an app-specific
+# password rather than your account password; see gmail-howto.txt in this
+# package's documentation for details on how to do this for a Google account.
+#
+#imap.gmail.com:993 flashproxyreg.a at gmail.com topsecret11!one
+#flashproxyreg.a at gmail.com passwords with spaces are ok too
diff --git a/facilitator/fac.py b/facilitator/fac.py
index 70d482d..695cc29 100644
--- a/facilitator/fac.py
+++ b/facilitator/fac.py
@@ -46,7 +46,7 @@ def catch_epipe(fn):
                 raise
     return ret
 
-def parse_addr_spec(spec, defhost = None, defport = None, resolve = False):
+def parse_addr_spec(spec, defhost = None, defport = None, resolve = False, nameOk = False):
     """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")
@@ -67,9 +67,9 @@ def parse_addr_spec(spec, defhost = None, defport = None, resolve = False):
     >>> parse_addr_spec("", defhost="192.168.0.1", defport=9999)
     ('192.168.0.1', 9999)
 
-    If resolve is true, then the host in the specification or the defhost may be
-    a domain name, which will be resolved. If resolve is false, then the host
-    must be a numeric IPv4 or IPv6 address.
+    If nameOk is true, then the host in the specification or the defhost may be
+    a domain name. Otherwise, it must be a numeric IPv4 or IPv6 address.
+    If resolve is true, this implies nameOk, and the host will be resolved.
 
     IPv6 addresses must be enclosed in square brackets."""
     host = None
@@ -107,6 +107,9 @@ def parse_addr_spec(spec, defhost = None, defport = None, resolve = False):
     # done only if resolve is true; otherwise the address must be numeric.
     if resolve:
         flags = 0
+    elif nameOk:
+        # don't pass through the getaddrinfo numeric check, just return directly
+        return host, int(port)
     else:
         flags = socket.AI_NUMERICHOST
     try:
diff --git a/facilitator/facilitator-email-poller b/facilitator/facilitator-email-poller
index 32dc2d4..d4e1e16 100755
--- a/facilitator/facilitator-email-poller
+++ b/facilitator/facilitator-email-poller
@@ -21,7 +21,6 @@ import fac
 from hashlib import sha1
 from M2Crypto import SSL, X509
 
-DEFAULT_IMAP_HOST = "imap.gmail.com"
 DEFAULT_IMAP_PORT = 993
 DEFAULT_LOG_FILENAME = "facilitator-email-poller.log"
 
@@ -74,8 +73,6 @@ PUBKEY_SHA1 = (
 LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
 
 class options(object):
-    email_addr = None
-    imap_addr = None
     password_filename = None
     log_filename = DEFAULT_LOG_FILENAME
     log_file = sys.stdout
@@ -132,18 +129,18 @@ them, and forwards the registrations to the facilitator.
 
   -d, --debug               don't daemonize, log to stdout.
       --disable-pin         don't check server public key against a known pin.
-  -e, --email=ADDRESS       log in as ADDRESS
   -h, --help                show this help.
-  -i, --imap=HOST[:PORT]    use the given IMAP server (default "%(imap_addr)s").
       --imaplib-debug       show raw IMAP messages (will include email password).
   -l, --log FILENAME        write log to FILENAME (default \"%(log)s\").
-  -p, --pass=PASSFILE       use the email password contained in PASSFILE.
+  -p, --pass=PASSFILE       use the email/password contained in PASSFILE. This file
+                            should contain "[<imap_host>] <email> <password>" on a
+                            single line, separated by whitespace. If <imap_host> is
+                            omitted, it defaults to imap.(<email> domain):993.
       --pidfile FILENAME    write PID to FILENAME after daemonizing.
       --privdrop-user USER  switch UID and GID to those of USER.
       --unsafe-logging      don't scrub email password and IP addresses from logs.\
 """ % {
     "progname": sys.argv[0],
-    "imap_addr": fac.format_addr((DEFAULT_IMAP_HOST, DEFAULT_IMAP_PORT)),
     "log": DEFAULT_LOG_FILENAME,
 }
 
@@ -158,9 +155,6 @@ def log(msg):
     print >> options.log_file, (u"%s %s" % (time.strftime(LOG_DATE_FORMAT), msg)).encode("UTF-8")
     options.log_file.flush()
 
-options.email_addr = None
-options.imap_addr = (DEFAULT_IMAP_HOST, DEFAULT_IMAP_PORT)
-
 opts, args = getopt.gnu_getopt(sys.argv[1:], "de:hi:l:p:", [
     "debug",
     "disable-pin",
@@ -180,13 +174,9 @@ for o, a in opts:
         options.log_filename = None
     elif o == "--disable-pin":
         options.use_certificate_pin = False
-    elif o == "-e" or o == "--email":
-        options.email_addr = a
     elif o == "-h" or o == "--help":
         usage()
         sys.exit()
-    elif o == "-i" or o == "--imap":
-        options.imap_addr = fac.parse_addr_spec(a, DEFAULT_IMAP_HOST, DEFAULT_IMAP_PORT)
     if o == "--imaplib-debug":
         options.imaplib_debug = True
     elif o == "-l" or o == "--log":
@@ -204,11 +194,6 @@ if len(args) != 0:
     usage(sys.stderr)
     sys.exit(1)
 
-# Check the email
-if not options.email_addr or '@' not in options.email_addr:
-    print >> sys.stderr, "The --email option is required and must be an email address."
-    sys.exit(1)
-
 # Load the email password.
 if options.password_filename is None:
     print >> sys.stderr, "The --pass option is required."
@@ -225,7 +210,26 @@ try:
         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)
-    email_password = password_file.read().strip()
+    for line in password_file.readlines():
+        line = line.strip("\n")
+        if not line or line.startswith('#'): continue
+        # we do this stricter regex match because passwords might have spaces in
+        res = re.match(r"(?:(\S+)\s)?(\S+@\S+)\s(.+)", line)
+        if not res:
+            raise ValueError("could not find email or password: %s" % line)
+        (imap_addr_spec, email_addr, email_password) = res.groups()
+        default_imap_host = "imap.%s" % (email_addr.split('@', 1)[1])
+        imap_addr = fac.parse_addr_spec(
+            imap_addr_spec or "", default_imap_host, DEFAULT_IMAP_PORT, nameOk=True)
+        break
+    else:
+        raise ValueError("no email line found")
+except Exception, e:
+    print >> sys.stderr, """\
+Failed to parse password file "%s": %s.
+Syntax is [<imap_host>] <email> <password>.
+""" % (options.password_filename, str(e))
+    sys.exit(1)
 finally:
     password_file.close()
 
@@ -375,7 +379,7 @@ def imap_login():
     try:
         ca_certs_file.write(CA_CERTS)
         ca_certs_file.flush()
-        imap = IMAP4_SSL_REQUIRED(options.imap_addr[0], options.imap_addr[1],
+        imap = IMAP4_SSL_REQUIRED(imap_addr[0], imap_addr[1],
             None, ca_certs_file.name)
     finally:
         ca_certs_file.close()
@@ -393,8 +397,8 @@ def imap_login():
             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))
 
-    log(u"logging in as %s" % options.email_addr)
-    imap.login(options.email_addr, email_password)
+    log(u"logging in as %s" % email_addr)
+    imap.login(email_addr, email_password)
 
     return imap
 
diff --git a/facilitator/facilitator-test.py b/facilitator/facilitator-test.py
index e5ed843..e00ea5e 100755
--- a/facilitator/facilitator-test.py
+++ b/facilitator/facilitator-test.py
@@ -156,6 +156,11 @@ class ParseAddrSpecTest(unittest.TestCase):
         """Test that parse_addr_spec does not do DNS resolution by default."""
         self.assertRaises(ValueError, fac.parse_addr_spec, "example.com")
 
+    def test_noresolve_nameok(self):
+        """Test that nameok passes through a domain name without resolving it."""
+        self.assertEqual(fac.parse_addr_spec("example.com:8888", defhost="other.com", defport=9999, nameOk=True), ("example.com", 8888))
+        self.assertEqual(fac.parse_addr_spec("", defhost="other.com", defport=9999, nameOk=True), ("other.com", 9999))
+
 class ParseTransactionTest(unittest.TestCase):
     def test_empty_string(self):
         self.assertRaises(ValueError, fac.parse_transaction, "")





More information about the tor-commits mailing list