[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [gettor/master] Added SMTP module skeleton. Simple stuff for now.
commit 6b56b53257c2997386f2dfbeb7304556e84bc2c2
Author: ilv <ilv@xxxxxxxxxxxxxxxxxxxxxxxx>
Date: Wed Jul 2 00:44:14 2014 -0400
Added SMTP module skeleton. Simple stuff for now.
---
src/smtp.py | 296 ++++++++++++++++++++++++++++++++++++++
src/smtp/log/all.log | 16 +++
src/smtp/log/debug.log | 14 ++
src/smtp/log/info.log | 3 +
src/smtp/sample/sample-email.eml | 57 ++++++++
src/smtp_demo.py | 20 +++
6 files changed, 406 insertions(+)
diff --git a/src/smtp.cfg b/src/smtp.cfg
new file mode 100644
index 0000000..e69de29
diff --git a/src/smtp.py b/src/smtp.py
new file mode 100644
index 0000000..3435412
--- /dev/null
+++ b/src/smtp.py
@@ -0,0 +1,296 @@
+import os
+import re
+import sys
+import email
+import logging
+import ConfigParser
+
+import gettor
+
+
+class SingleLevelFilter(logging.Filter):
+ """
+ Filter logging levels to create separated logs.
+
+ Public methods:
+ filter(record)
+ """
+
+ def __init__(self, passlevel, reject):
+ """
+ Initialize a new object with level to be filtered.
+
+ If reject value is false, all but the passlevel will be
+ filtered. Useful for logging in separated files.
+ """
+
+ self.passlevel = passlevel
+ self.reject = reject
+
+ def filter(self, record):
+ """
+ Do the actual filtering.
+ """
+ if self.reject:
+ return (record.levelno != self.passlevel)
+ else:
+ return (record.levelno == self.passlevel)
+
+
+class SMTP(object):
+ """
+ Class for the GetTor's SMTP service. Provides an interface to
+ interact with requests received by email.
+ """
+
+ def __init__(self, config):
+ """
+ Creates new object by reading a configuration file.
+
+ Args:
+
+ - config (string): the path of the file that will be used as
+ configuration
+ """
+ logging.basicConfig(format='[%(levelname)s] %(asctime)s - %(message)s',
+ datefmt="%Y-%m-%d %H:%M:%S")
+ logger = logging.getLogger(__name__)
+
+ self.delay = True
+ self.logdir = 'smtp/log/'
+ self.loglevel = 'DEBUG'
+
+ # Better log format
+ string_format = '[%(levelname)7s] %(asctime)s - %(message)s'
+ formatter = logging.Formatter(string_format, '%Y-%m-%d %H:%M:%S')
+
+ # Keep logs separated (and filtered)
+ # all.log depends on level specified on configuration file
+ all_log = logging.FileHandler(os.path.join(self.logdir, 'all.log'),
+ mode='a+')
+ all_log.setLevel(logging.getLevelName(self.loglevel))
+ all_log.setFormatter(formatter)
+
+ debug_log = logging.FileHandler(os.path.join(self.logdir, 'debug.log'),
+ mode='a+')
+ debug_log.setLevel('DEBUG')
+ debug_log.addFilter(SingleLevelFilter(logging.DEBUG, False))
+ debug_log.setFormatter(formatter)
+
+ info_log = logging.FileHandler(os.path.join(self.logdir, 'info.log'),
+ mode='a+')
+ info_log.setLevel('INFO')
+ info_log.addFilter(SingleLevelFilter(logging.INFO, False))
+ info_log.setFormatter(formatter)
+
+ warn_log = logging.FileHandler(os.path.join(self.logdir, 'warn.log'),
+ mode='a+')
+ warn_log.setLevel('WARNING')
+ warn_log.addFilter(SingleLevelFilter(logging.WARNING, False))
+ warn_log.setFormatter(formatter)
+
+ error_log = logging.FileHandler(os.path.join(self.logdir, 'error.log'),
+ mode='a+')
+ error_log.setLevel('ERROR')
+ error_log.addFilter(SingleLevelFilter(logging.ERROR, False))
+ error_log.setFormatter(formatter)
+
+ logger.addHandler(all_log)
+ logger.addHandler(info_log)
+ logger.addHandler(debug_log)
+ logger.addHandler(warn_log)
+ logger.addHandler(error_log)
+
+ self.logger = logger
+ self.logger.setLevel(logging.getLevelName(self.loglevel))
+ logger.debug('Redirecting logging to %s' % self.logdir)
+
+ # Stop logging on stdout from now on
+ logger.propagate = False
+ self.logger.debug("New smtp object created")
+
+ def _log_request(self):
+ """
+ Logs a given request
+
+ This should be called when something goes wrong. It saves the
+ email content that triggered the malfunctioning
+
+ Raises:
+
+ - RuntimeError: if something goes wrong while trying to save the
+ email
+ """
+ self.logger.debug("Logging the mail content...")
+
+ def _check_blacklist(self):
+ """
+ Check if an email is blacklisted
+
+ It opens the corresponding blacklist file and search for the
+ sender address.
+
+ Raises:
+
+ - BlacklistError: if the user is blacklisted.
+ """
+ self.logger.debug("Checking if address %s is blacklisted" %
+ self.from_addr)
+
+ def _get_locale(self):
+ """
+ Get the locale from an email address
+
+ It process the email received and look for the locale in the
+ recipient address (e.g. gettor+en@xxxxxxxxxxxxxx)
+
+ If no locale found, english by default
+
+ Returns a string containing the locale
+ """
+ self.logger.debug("Trying to obtain locale from recipient address")
+
+ # If no match found, english by default
+ locale = 'en'
+
+ # Look for word+locale@something
+ # Should we specify gettor and torproject?
+ m = re.match('\w+\+(\w\w)@', self.to_addr)
+ if m:
+ self.logger.debug("Request for locale %s" % m.groups())
+ locale = "%s" % m.groups()
+
+ return locale
+
+ def _parse_email(self):
+ """
+ Parses the email received
+
+ It obtains the locale and parse the text for the rest of the info
+
+ Returns a 4-tuple with locale, package, os and type
+ """
+ self.logger.debug("Parsing email")
+
+ locale = self._get_locale()
+ req_type, req_pkg, req_os = self._parse_text()
+
+ request = {}
+ request['locale'] = locale
+ request['package'] = req_pkg
+ request['type'] = req_type
+ request['os'] = req_os
+
+ return request
+
+ def _parse_text(self):
+ """
+ Parses the text part of the email received
+
+ It tries to figure out what the user is asking, namely, the type
+ of request, the package and os required (if applies)
+
+ Returns a 3-tuple with the type of request, package and os
+ """
+ self.logger.debug("Parsing email text part")
+
+ return ('links', 'pkg', 'linux')
+
+ def _create_email(self, msg):
+ """
+ Creates an email object
+
+ This object will be used to construct the reply
+
+ Returns the email object
+ """
+ self.logger.debug("Creating email object for replying")
+
+ def _send_email(self, msg):
+ """
+ Send email with msg as content
+ """
+ self._create_email(msg)
+ self.logger.debug("Email sent")
+
+ def _send_delay(self):
+ """
+ Send delay message
+
+ If delay is setted on configuration, then sends a reply to the
+ user saying that the package is on the way
+ """
+ self.logger.debug("Sending delay message...")
+ self._send_email("delay")
+
+ def _send_links(self, links):
+ """
+ Send the links to the user
+ """
+ self.logger.debug("Sending links...")
+ self._send_email(links)
+
+ def _send_help(self):
+ """
+ Send help message to the user
+ """
+ self.logger.debug("Sending help...")
+ self._send_email("help")
+
+ def process_email(self, raw_msg):
+ """
+ Process the email received.
+
+ It create an email object from the string received. The processing
+ flow is as following:
+ - Check for blacklisted address
+ - Parse the email
+ - Check the type of request
+ - Send reply
+
+ Raise:
+ - ValueError if the address is blacklisted, or if the request
+ asks for unsupported locales and/or operating systems, or if
+ it's not possible to recognize what type of request (help, links)
+ the user is asking
+
+ - InternalError if something goes wrong while trying to obtain
+ the links from the Core
+ """
+ self.parsed_msg = email.message_from_string(raw_msg)
+ # Just for easy access
+ # Normalize pending
+ self.from_addr = self.parsed_msg['From']
+ self.to_addr = self.parsed_msg['To']
+
+ # We have the info we need on self.parsed_msg
+ try:
+ self._check_blacklist()
+ except ValueError as e:
+ raise ValueError("The address %s is blacklisted!" %
+ self.from_addr)
+
+ # Try to figure out what the user is asking
+ request = self._parse_email()
+
+ # Two possible options: asking for help or for the links
+ # If not, it means malformed message, and no default values
+ self.logger.info("New request for %s" % request['type'])
+ if request['type'] == 'help':
+ self._send_help(request['locale'])
+ elif request['type'] == 'links':
+ if self.delay:
+ self._send_delay()
+
+ try:
+ self.logger.info("Asking Core for links in %s for %s" %
+ (request['locale'], request['os']))
+ # links = self.core.get_links(request['os'], request['locale'])
+ links = "dummy links"
+ self._send_links(links)
+ except ValueError as e:
+ raise ValueError(str(e))
+ except RuntimeError as e:
+ raise RuntimeError(str(e))
+ else:
+ raise ValueError("Malformed message. No default values either")
diff --git a/src/smtp/log/all.log b/src/smtp/log/all.log
new file mode 100644
index 0000000..21e5f3b
--- /dev/null
+++ b/src/smtp/log/all.log
@@ -0,0 +1,16 @@
+
+[ DEBUG] 2014-07-01 19:32:28 - Redirecting logging to smtp/log/
+[ DEBUG] 2014-07-01 19:32:28 - New smtp object created
+[ DEBUG] 2014-07-01 19:32:28 - Checking if address "Jacob Applebaum" <ioerror@xxxxxxxxx> is blacklisted
+[ DEBUG] 2014-07-01 19:32:28 - Parsing email
+[ DEBUG] 2014-07-01 19:32:28 - Trying to obtain locale from recipient address
+[ DEBUG] 2014-07-01 19:32:28 - Request for locale en
+[ DEBUG] 2014-07-01 19:32:28 - Parsing email text part
+[ INFO] 2014-07-01 19:32:28 - New request for links
+[ DEBUG] 2014-07-01 19:32:28 - Sending delay message...
+[ DEBUG] 2014-07-01 19:32:28 - Creating email object for replying
+[ DEBUG] 2014-07-01 19:32:28 - Email sent
+[ INFO] 2014-07-01 19:32:28 - Asking Core for links in en for linux
+[ DEBUG] 2014-07-01 19:32:28 - Sending links...
+[ DEBUG] 2014-07-01 19:32:28 - Creating email object for replying
+[ DEBUG] 2014-07-01 19:32:28 - Email sent
diff --git a/src/smtp/log/debug.log b/src/smtp/log/debug.log
new file mode 100644
index 0000000..bef7c7a
--- /dev/null
+++ b/src/smtp/log/debug.log
@@ -0,0 +1,14 @@
+
+[ DEBUG] 2014-07-01 19:32:28 - Redirecting logging to smtp/log/
+[ DEBUG] 2014-07-01 19:32:28 - New smtp object created
+[ DEBUG] 2014-07-01 19:32:28 - Checking if address "Jacob Applebaum" <ioerror@xxxxxxxxx> is blacklisted
+[ DEBUG] 2014-07-01 19:32:28 - Parsing email
+[ DEBUG] 2014-07-01 19:32:28 - Trying to obtain locale from recipient address
+[ DEBUG] 2014-07-01 19:32:28 - Request for locale en
+[ DEBUG] 2014-07-01 19:32:28 - Parsing email text part
+[ DEBUG] 2014-07-01 19:32:28 - Sending delay message...
+[ DEBUG] 2014-07-01 19:32:28 - Creating email object for replying
+[ DEBUG] 2014-07-01 19:32:28 - Email sent
+[ DEBUG] 2014-07-01 19:32:28 - Sending links...
+[ DEBUG] 2014-07-01 19:32:28 - Creating email object for replying
+[ DEBUG] 2014-07-01 19:32:28 - Email sent
diff --git a/src/smtp/log/error.log b/src/smtp/log/error.log
new file mode 100644
index 0000000..e69de29
diff --git a/src/smtp/log/info.log b/src/smtp/log/info.log
new file mode 100644
index 0000000..66d40f1
--- /dev/null
+++ b/src/smtp/log/info.log
@@ -0,0 +1,3 @@
+
+[ INFO] 2014-07-01 19:32:28 - New request for links
+[ INFO] 2014-07-01 19:32:28 - Asking Core for links in en for linux
diff --git a/src/smtp/log/warn.log b/src/smtp/log/warn.log
new file mode 100644
index 0000000..e69de29
diff --git a/src/smtp/sample/sample-email.eml b/src/smtp/sample/sample-email.eml
new file mode 100644
index 0000000..1f13b08
--- /dev/null
+++ b/src/smtp/sample/sample-email.eml
@@ -0,0 +1,57 @@
+X-Account-Key: account6
+X-UIDL: 1214981061.25808.faustus,S=2285
+X-Mozilla-Status: 0001
+X-Mozilla-Status2: 02000000
+Return-Path: <ioerror@xxxxxxxxx>
+Delivered-To: jpopped@xxxxxxxxxxxxx
+Received: (qmail 25806 invoked by uid 89); 2 Jul 2008 06:44:21 -0000
+Delivered-To: appelbaum.net-jacob@xxxxxxxxxxxxx
+Received: (qmail 25804 invoked by uid 89); 2 Jul 2008 06:44:21 -0000
+Received: from unknown (HELO wa-out-1112.google.com) (209.85.146.180)
+ by 0 with SMTP; 2 Jul 2008 06:44:21 -0000
+Received-SPF: pass (0: SPF record at _spf.google.com designates 209.85.146.180 as permitted sender)
+Received: by wa-out-1112.google.com with SMTP id j40so170432wah.1
+ for <jacob@xxxxxxxxxxxxx>; Tue, 01 Jul 2008 23:42:01 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+ d=gmail.com; s=gamma;
+ h=domainkey-signature:received:received:message-id:date:from:to
+ :subject:mime-version:content-type;
+ bh=IvFqNkffeoST7vamh2ytuq/b7GpLhg2hStTrQq3I3rE=;
+ b=xQR0hE/J4AXpAqH1UDXTtDrU9Izc6WM8vtFudRBzldWYyRx3Vvfh2I2Opu8+O6wbAv
+ jlDi18anUMbZqlIGSgGOxvXW4CltpX/SFZm1aGL4AisQ1Bi5xEqlrc8OnX3sA2xKeM3g
+ KWsWm+GVSMI4XAqnY9FYAfPx4DmOAfkdMyWCU=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+ d=gmail.com; s=gamma;
+ h=message-id:date:from:to:subject:mime-version:content-type;
+ b=kyzDtGRDbiC5y4Bz/ylQjyHOChiOP2A6QDzybsVXc0C1hjHLImOQYR8gOxcRY+mRkN
+ 1xpBaEF4UloZAxTb79khRRp4TWmjT1DagtLx2MFzIj/F6awtdE/9U3p4QyKr8S43tGcE
+ ET26BSfT5u9zrXblVVAP3JedMPZ8mlIGQxyDs=
+Received: by 10.115.90.1 with SMTP id s1mr6711509wal.51.1214980921268;
+ Tue, 01 Jul 2008 23:42:01 -0700 (PDT)
+Received: by 10.114.184.16 with HTTP; Tue, 1 Jul 2008 23:41:57 -0700 (PDT)
+Message-ID: <7fadd8130807012341n3b3af401mbdb4a29c80310bd3@xxxxxxxxxxxxxx>
+Date: Tue, 1 Jul 2008 23:41:57 -0700
+From: "Jacob Applebaum" <ioerror@xxxxxxxxx>
+To: gettor+en@xxxxxxxxxxxxxx
+Subject: Positive DKIM header
+MIME-Version: 1.0
+Content-Type: multipart/alternative;
+ boundary="----=_Part_462_28562233.1214980917793"
+
+------=_Part_462_28562233.1214980917793
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+This email should have a positive DKIM header.
+
+------=_Part_462_28562233.1214980917793
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+This email should have a positive DKIM header.<br>
+
+------=_Part_462_28562233.1214980917793--
+
+
diff --git a/src/smtp_demo.py b/src/smtp_demo.py
new file mode 100644
index 0000000..390377c
--- /dev/null
+++ b/src/smtp_demo.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+import sys
+
+import smtp
+
+service = smtp.SMTP('smtp.cfg')
+
+# For now we simulate mails reading from stdin
+# In linux test as follows:
+# $ python smtp_demo.py < email.eml
+
+incoming = sys.stdin.read()
+try:
+ print "Email received!"
+ service.process_email(incoming)
+ print "Email sent!"
+except ValueError as e:
+ print "Value error: " + str(e)
+except RuntimeError as e:
+ print "Runtime error: " + str(e)
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits