[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [flashproxy/master] Add facilitator IMAP Gmail poller.
commit d046f5b6bf9815a37107262168a054d8e793a2d1
Author: David Fifield <david@xxxxxxxxxxxxxxx>
Date: Wed Sep 19 13:09:49 2012 -0700
Add facilitator IMAP Gmail poller.
---
facilitator/facilitator-email-poller | 234 ++++++++++++++++++++++++++++++++++
1 files changed, 234 insertions(+), 0 deletions(-)
diff --git a/facilitator/facilitator-email-poller b/facilitator/facilitator-email-poller
new file mode 100755
index 0000000..c9d9a84
--- /dev/null
+++ b/facilitator/facilitator-email-poller
@@ -0,0 +1,234 @@
+#!/usr/bin/env python
+
+import email
+import getopt
+import imaplib
+import os
+import socket
+import ssl
+import stat
+import sys
+import tempfile
+import time
+
+import fac
+
+from hashlib import sha1
+from M2Crypto import BIO, RSA, X509
+
+DEFAULT_IMAP_HOST = "imap.gmail.com"
+DEFAULT_IMAP_PORT = 993
+DEFAULT_EMAIL_ADDRESS = "hoddwee@xxxxxxxxx"
+
+POLL_INTERVAL = 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 key. See
+# http://www.imperialviolet.org/2011/05/04/pinning.html for the reason behind
+# hashing the public key, not the entire certificate.
+PUBKEY_SHA1 = "5d97e1ec007e48c1f36e736e652eeaf2184697c3".decode("hex")
+
+class options(object):
+ email_addr = None
+ imap_addr = None
+ key_filename = None
+ password_filename = None
+ debug = False
+
+def usage(f = sys.stdout):
+ print >> f, """\
+Usage: %(progname)s --key=KEYFILE --pass=PASSFILE
+Facilitator-side helper for the facilitator-reg-email rendezvous. Polls
+an IMAP server for email messages with client registrations, deletes
+them, and forwards the registrations to the facilitator.
+
+ -d, --debug enable debugging output (Python imaplib messages);
+ beware that the login password will be shown.
+ -e, --email=ADDRESS log in as ADDRESS (default "%(email_addr)s").
+ -h, --help show this help.
+ -i, --imap=HOST[:PORT] use the given IMAP server (default "%(imap_addr)s").
+ -k, --key=KEYFILE read a facilitator private key from KEYFILE.
+ -p, --pass=PASSFILE use the email password contained in PASSFILE.\
+""" % {
+ "progname": sys.argv[0],
+ "email_addr": DEFAULT_EMAIL_ADDRESS,
+ "imap_addr": fac.format_addr((DEFAULT_IMAP_HOST, DEFAULT_IMAP_PORT)),
+}
+
+options.email_addr = DEFAULT_EMAIL_ADDRESS
+options.imap_addr = (DEFAULT_IMAP_HOST, DEFAULT_IMAP_PORT)
+
+opts, args = getopt.gnu_getopt(sys.argv[1:], "de:hi:k:p:", ["debug", "email=", "help", "imap=", "key=", "pass="])
+for o, a in opts:
+ if o == "-d" or o == "--debug":
+ options.debug = True
+ 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)
+ elif o == "-k" or o == "--key":
+ options.key_filename = a
+ elif o == "-p" or o == "--pass":
+ options.password_filename = a
+
+if len(args) != 0:
+ usage(sys.stderr)
+ sys.exit(1)
+
+# 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
+
+# Load the email password.
+if options.password_filename is None:
+ print >> sys.stderr, "The --pass option is required."
+ sys.exit(1)
+try:
+ password_file = open(options.password_filename)
+except OSError, e:
+ print >> sys.stderr, """\
+Failed to open password file "%s": %s.\
+""" % (options.password_filename, str(e))
+ sys.exit(1)
+else:
+ if not 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)
+ email_password = password_file.read().strip()
+finally:
+ password_file.close()
+
+# Load the private key specific to this registration method.
+if options.key_filename is None:
+ print >> sys.stderr, "The --key option is required."
+ sys.exit(1)
+try:
+ key_file = open(options.key_filename)
+except OSError, e:
+ print >> sys.stderr, """\
+Failed to open private key file "%s": %s.\
+""" % (options.key_filename, str(e))
+ sys.exit(1)
+else:
+ if not 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)
+ rsa = RSA.load_key_string(key_file.read())
+finally:
+ key_file.close()
+
+class IMAP4_SSL_REQUIRED(imaplib.IMAP4_SSL):
+ """A subclass of of IMAP4_SSL that uses ssl_version=ssl.PROTOCOL_TLSv1 and
+ cert_reqs=ssl.CERT_REQUIRED."""
+ def open(self, host = "", port = imaplib.IMAP4_SSL_PORT):
+ self.host = host
+ self.port = port
+ self.sock = socket.create_connection((host, port))
+ self.sslobj = ssl.wrap_socket(self.sock, ssl_version=ssl.PROTOCOL_TLSv1,
+ cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.certfile)
+ self.file = self.sslobj.makefile('rb')
+
+def find_client_addr(body):
+ """Find and parse the first client line of the form
+ client=...
+ Returns None if no client line was found.
+ """
+ for line in body.splitlines():
+ if line.startswith("client="):
+ _, client_spec = line.split("=", 1)
+ return fac.parse_addr_spec(client_spec)
+ return None
+
+def handle_message(msg):
+ ciphertext = msg.get_payload().decode("base64")
+ plaintext = rsa.private_decrypt(ciphertext, RSA.pkcs1_oaep_padding)
+ client_addr = find_client_addr(plaintext)
+ fac.put_reg(FACILITATOR_ADDR, client_addr)
+
+def imap_loop(imap):
+ while True:
+ typ, data = imap.select()
+ exists = int(data[0])
+
+ for i in range(exists):
+ # Grab and delete message 1 on each iteration; remaining messages
+ # shift down so the next message we process is also message 1.
+ typ, data = imap.fetch(1, "(BODY[])")
+ if data[0] is None:
+ break
+
+ try:
+ msg = email.message_from_string(data[0][1])
+ handle_message(msg)
+ except Exception, e:
+ print >> sys.stderr, "Error processing message, deleting anyway:", e
+
+ imap.store(1, "+FLAGS", "\\Deleted")
+ imap.expunge()
+
+ time.sleep(POLL_INTERVAL)
+
+if options.debug:
+ imaplib.Debug = 4
+
+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(options.imap_addr[0], options.imap_addr[1],
+ None, ca_certs_file.name)
+finally:
+ ca_certs_file.close()
+
+# Check that the public key is what we expect.
+cert_der = imap.ssl().getpeercert(binary_form=True)
+cert = X509.load_cert_string(cert_der, format=X509.FORMAT_DER)
+pubkey_der = cert.get_pubkey().as_der()
+pubkey_digest = sha1(pubkey_der).digest()
+
+if pubkey_digest != PUBKEY_SHA1:
+ raise ValueError("Public key does not match pin: got %s but expected %s" %
+ (pubkey_digest.encode("hex"), PUBKEY_SHA1.encode("hex")))
+
+imap.login(options.email_addr, email_password)
+
+imap_loop(imap)
+
+imap.close()
+imap.logout()
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits