[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [flashproxy/master] email-poller: configure user data (imap_host, email, password) in the same file
commit a310c1d0f893ebd18a128a9d92684b3cf51d70a4
Author: Ximin Luo <infinity0@xxxxxxx>
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@xxxxxxxxx topsecret11!one
+#flashproxyreg.a@xxxxxxxxx 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, "")
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits