commit e69c33647c6c352c240647b7a8fdc096c86a2d53 Author: ilv ilv@users.noreply.github.com Date: Fri Aug 1 20:32:13 2014 -0400
New file structure --- src/core.cfg | 11 ++ src/gettor.cfg | 11 -- src/gettor.py | 445 ------------------------------------------------- src/smtp.py | 504 -------------------------------------------------------- 4 files changed, 11 insertions(+), 960 deletions(-)
diff --git a/src/core.cfg b/src/core.cfg new file mode 100644 index 0000000..3492681 --- /dev/null +++ b/src/core.cfg @@ -0,0 +1,11 @@ +[general] +basedir: ./ + +[links] +dir: providers/ +os: linux, windows, osx +locales: es, en + +[log] +dir: log/ +level: DEBUG diff --git a/src/gettor.cfg b/src/gettor.cfg deleted file mode 100644 index 3492681..0000000 --- a/src/gettor.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[general] -basedir: ./ - -[links] -dir: providers/ -os: linux, windows, osx -locales: es, en - -[log] -dir: log/ -level: DEBUG diff --git a/src/gettor.py b/src/gettor.py deleted file mode 100644 index a71a062..0000000 --- a/src/gettor.py +++ /dev/null @@ -1,445 +0,0 @@ -import os -import re -import inspect -import logging -import tempfile -import ConfigParser - -""" - GetTor main module. - - Classes: - SingleLevelFilter: Filter logging levels. - Core: Get links from providers. - - Methods: - SingleLevelFilter.filter(): Filter logging levels. All except - the one specified will be filtered. - - Core.get_links(): Get the links. It throws ValueError and - RuntimeError on failure. - - Core.create_links_file(): Create a file to store links of a given - provider. - - Core.add_link(): Add a link to a links file of a given provider. - - Exceptions: - ValueError: Request for an unsupported locale/operating system. - RuntimeError: Something went wrong internally. -""" - - -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 Core(object): - """ - Gets links from providers and delivers them to other modules. - - Public methods: - get_links(operating_system, locale) - """ - - def __init__(self, config_file): - """ - Initialize a new object by reading a configuration file. - - Raises a RuntimeError if the configuration file doesn't exists - or if something goes wrong while reading options from it. - - Arguments: - config_file: path for the configuration file - """ - - logging.basicConfig(format='[%(levelname)s] %(asctime)s - %(message)s', - datefmt="%Y-%m-%d %H:%M:%S") - logger = logging.getLogger(__name__) - config = ConfigParser.ConfigParser() - - if os.path.isfile(config_file): - logger.info("Reading configuration from %s" % config_file) - config.read(config_file) - else: - logger.error("Error while trying to read %s" % config_file) - raise RuntimeError("Couldn't read the configuration file %s" - % config_file) - - # Handle the gets internally to catch proper exceptions - try: - self.basedir = self._get_config_option('general', - 'basedir', config) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - try: - self.linksdir = self._get_config_option('links', 'dir', config) - self.linksdir = os.path.join(self.basedir, self.linksdir) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.supported_locales = self._get_config_option('links', - 'locales', - config) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.supported_os = self._get_config_option('links', 'os', config) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.loglevel = self._get_config_option('log', 'level', config) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.logdir = self._get_config_option('log', 'dir', config) - self.logdir = os.path.join(self.basedir, self.logdir) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - # 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.info('Redirecting logging to %s' % self.logdir) - - # Stop logging on stdout from now on - logger.propagate = False - self.logger.debug("New core object created") - - def get_links(self, service, operating_system, locale): - """ - Public method to obtain links. - - Checks for supported locales and operating systems. It returns - ValueError if the locale or operating system is not supported. - It raises RuntimeError if something goes wrong while trying - to obtain the links. It returns a string on success. This - method should be called from the services modules of GetTor - (e.g. SMTP). - """ - - # Which module called us and what was asking for? - self.logger.info("%s did a request for %s, %s." % - (service, operating_system, locale)) - - if locale not in self.supported_locales: - self.logger.warning("Request for unsupported locale: %s" % locale) - raise ValueError("Locale %s not supported at the moment" % locale) - - if operating_system not in self.supported_os: - self.logger.warning("Request for unsupported operating system: %s" - % operating_system) - raise ValueError("Operating system %s not supported at the moment" - % operating_system) - - # This could change in the future, let's leave it isolated. - links = self._get_links(operating_system, locale) - - if links is None: - self.logger.error("Couldn't get the links", exc_info=True) - raise RuntimeError("Something went wrong internally. See logs for \ - detailed info.") - - self.logger.info("Returning the links") - return links - - def _get_links(self, operating_system, locale): - """ - Private method to obtain the links. - - Looks for the links inside each provider file. On success - returns a string with the links. On failure returns None. - This should only be called from get_links() method. - - Parameters: - os: string describing the operating system - locale: string describing the locale - """ - - # Read the links files using ConfigParser - # See the README for more details on the format used - links = [] - - # Look for files ending with .links - p = re.compile('.*.links$') - - for name in os.listdir(self.linksdir): - path = os.path.abspath(os.path.join(self.linksdir, name)) - if os.path.isfile(path) and p.match(path): - links.append(path) - - # Let's create a dictionary linking each provider with the links - # found for operating_system and locale. This way makes it easy - # to check if no links were found. - providers = {} - - self.logger.info("Reading links from providers directory") - for name in links: - self.logger.debug("-- Reading %s" % name) - # We're reading files listed on linksdir, so they must exist! - config = ConfigParser.ConfigParser() - config.read(name) - - try: - pname = self._get_config_option('provider', - 'name', config) - except RuntimeError as e: - self.logger.warning("Links misconfiguration %s" % str(e)) - - self.logger.debug("-- Checking if %s has links for %s in %s" % - (pname, operating_system, locale)) - - try: - providers[pname] = self._get_config_option(operating_system, - locale, config) - except RuntimeError as e: - self.logger.warning("-- Links misconfiguration %s" % str(e)) - - # Each provider must have a fingerprint of the key used to - # sign the uploaded packages - try: - self.logger.debug("-- Trying to get fingerprint from %s", - pname) - fingerprint = self._get_config_option('key', 'fingerprint', - config) - providers[pname] = providers[pname] + "\nFingerprint: " - providers[pname] = providers[pname] + fingerprint - self.logger.debug("-- Fingerprint added %s", fingerprint) - except ValueError as e: - self.logger.warning("-- No fingerprint found for provider %s" % - pname) - - # Create the final links list with all providers - all_links = [] - - self.logger.debug("Joining all links found for %s in %s" % - (operating_system, locale)) - for key in providers.keys(): - all_links.append( - "\n%s\n%s\n" % (key, ''.join(providers[key])) - ) - - if all_links: - return "".join(all_links) - else: - self.logger.warning("Trying to get supported os and locales, but \ - no links were found") - return None - - def get_supported_os(self): - """ - Public method to obtain the list of supported operating systems - - Returns a list of strings - """ - return self.supported_os.split(',') - - def _get_config_option(self, section, option, config): - """ - Private method to get configuration options. - - It tries to obtain a value from a section in config using - ConfigParser. It catches possible exceptions and raises - RuntimeError if something goes wrong. - - Arguments: - config: ConfigParser object - section: section inside config - option: option inside section - - Returns the value of the option inside the section in the - config object. - """ - - try: - value = config.get(section, option) - return value - # This exceptions should appear when messing with the configuration - except (ConfigParser.NoSectionError, - ConfigParser.NoOptionError, - ConfigParser.InterpolationError, - ConfigParser.MissingSectionHeaderError, - ConfigParser.ParsingError) as e: - raise RuntimeError("%s" % str(e)) - # No other errors should occurr, unless something's terribly wrong - except ConfigParser.Error as e: - raise RuntimeError("Unexpected error: %s" % str(e)) - - def create_links_file(self, provider, fingerprint): - """ - Public method to create a links file for a provider. - - This should be used by all providers since it writes the links - file with the proper format. It backs up the old links file - (if exists) and creates a new one. The name for the links file - is the provider's name in lowercase. It receives the fingerprint - of the key that signed the packages. - - It raises a general exception if something goes wrong while - creating the new file. - - Arguments: - provider: Provider's name. The links file will use this - name in lower case. - fingerprint: Fingerprint of the key that signed the packages - to be uploaded to the provider. - """ - linksfile = os.path.join(self.linksdir, provider.lower() + '.links') - linksfile_backup = "" - self.logger.info("Request to create new %s" % linksfile) - - if os.path.isfile(linksfile): - # Backup the old file in case something fails - linksfile_backup = linksfile + '.backup' - self.logger.info("Backing up %s to %s" - % (linksfile, linksfile_backup)) - os.rename(linksfile, linksfile_backup) - - try: - # This creates an empty links file (with no links) - content = ConfigParser.RawConfigParser() - content.add_section('provider') - content.set('provider', 'name', provider) - content.add_section('key') - content.set('key', 'fingerprint', fingerprint) - content.add_section('linux') - content.add_section('windows') - content.add_section('osx') - with open(linksfile, 'w+') as f: - content.write(f) - self.logger.info("New %s created" % linksfile) - except Exception as e: - if linksfile_backup: - os.rename(linksfile_backup, linksfile) - - def add_link(self, provider, operating_system, locale, link): - """ - Public method to add a link to a provider's links file. - - It uses ConfigParser to add a link into the operating_system - section, under the locale option. It check for valid format; - the provider's script should use the right format (see design). - It raises ValueError in case the operating_system or locale - are not supported (see config file for supported ones), or if - if there is not a section for the operating_system in the - links file, or if there is no links file for the given provider, - or if the link format doesn't seem legit. - """ - linksfile = os.path.join(self.linksdir, provider.lower() + '.links') - - # Don't try to add unsupported stuff - if locale not in self.supported_locales: - self.logger.warning("Trying to add link for unsupported locale: %s" - % locale) - raise ValueError("Locale %s not supported at the moment" % locale) - - if operating_system not in self.supported_os: - self.logger.warning("Trying to add link for unsupported operating \ - system: %s" % operating_system) - raise ValueError("Operating system %s not supported at the moment" - % operating_system) - - # Check if the link has a legit format - # e.g. https://db.tt/JjfUTb04 https://db.tt/MEfUTb04 - p = re.compile('^https://.+%5Cshttps://.+$') - - if not p.match(link): - self.logger.warning("Trying to add an invalid link: %s" - % link) - raise ValueError("Link '%s' doesn't seem to have a valid format" - % link) - - if os.path.isfile(linksfile): - content = ConfigParser.RawConfigParser() - content.readfp(open(linksfile)) - # Check if exists and entry for locale; if not, create it - try: - links = content.get(operating_system, locale) - links = links + ",\n" + link - content.set(operating_system, locale, links) - with open(linksfile, 'w') as f: - content.write(f) - self.logger.info("Link %s added to %s %s in %s" - % (link, operating_system, locale, provider)) - except ConfigParser.NoOptionError: - content.set(operating_system, locale, link) - with open(linksfile, 'w') as f: - content.write(f) - self.logger.info("Link %s added to %s-%s in %s" - % (link, operating_system, locale, provider)) - except ConfigParser.NoSectionError: - # This shouldn't happen, but just in case - self.logger.error("Unknown section %s in links file") - raise ValueError("Unknown %s section in links file" - % operating_system) - else: - raise ValueError("There is no links file for %s" % provider) diff --git a/src/smtp.py b/src/smtp.py deleted file mode 100644 index e29aa2d..0000000 --- a/src/smtp.py +++ /dev/null @@ -1,504 +0,0 @@ -import os -import re -import sys -import time -import email -import gettext -import hashlib -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_file): - """ - Create 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__) - config = ConfigParser.ConfigParser() - - if os.path.isfile(config_file): - logger.info("Reading configuration from %s" % config_file) - config.read(config_file) - else: - logger.error("Error while trying to read %s" % config_file) - raise RuntimeError("Couldn't read the configuration file %s" - % config_file) - - # Handle the gets internally to catch proper exceptions - try: - self.basedir = self._get_config_option('general', - 'basedir', config) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.delay = self._get_config_option('general', - 'delay', config) - # There has to be a better way for this... - if self.delay == 'False': - self.delay = False - - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.our_addr = self._get_config_option('general', - 'our_addr', config) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.logdir = self._get_config_option('log', - 'dir', config) - self.logdir = os.path.join(self.basedir, self.logdir) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.logdir_emails = self._get_config_option('log', - 'emails_dir', - config) - self.logdir_emails = os.path.join(self.logdir, self.logdir_emails) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - try: - self.loglevel = self._get_config_option('log', - 'level', config) - except RuntimeError as e: - logger.warning("%s misconfigured. %s" % (config_file, str(e))) - - self.core = gettor.Core('gettor.cfg') - - # 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 _get_sha1(self, string): - """ - Get the sha1 of a string - - Used whenever we want to do things with addresses (log, blacklist, etc) - - Returns a string - """ - return str(hashlib.sha1(string).hexdigest()) - - def _log_request(self, addr, content): - """ - Log 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 - """ - # We don't store the original address, but rather its sha1 digest - # in order to know when some specific addresses are doing weird - # requests - log_addr = self._get_sha1(addr) - filename = str(time.time()) + '.log' - path = self.logdir_emails + filename - abs_path = os.path.abspath(path) - - log_file = open(abs_path, 'w+') - log_file.write(content) - log_file.close() - - self.logger.debug("Logging request from %s in %s" - % (log_addr, abs_path)) - - def _check_blacklist(self, addr): - """ - 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. - """ - anon_addr = self._get_sha1(addr) - self.logger.debug("Checking if address %s is blacklisted" % - anon_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@torproject.org) - - 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 gettor+locale@torproject.org - m = re.match('gettor+(\w\w)@torproject.org', self.to_addr) - if m: - self.logger.debug("Request for locale %s" % m.groups()) - locale = "%s" % m.groups() - - return locale - - def _get_normalized_address(self, addr): - """ - Get normalized address - - It looks for anything inside the last '<' and '>'. Code taken - from the old GetTor (utils.py) - - On success, returns the normalized address - On failure, returns ValueError - """ - if '<' in addr: - idx = addr.rindex('<') - addr = addr[idx:] - m = re.search(r'<([^>]*)>', addr) - if m is None: - raise ValueError("Couldn't extract normalized address from %s" - % addr) - addr = m.group(1) - return addr - - def _parse_email(self): - """ - Parse the email received - - It obtains the locale and parse the text for the rest of the info - - Returns a 3-tuple with locale, os and type - """ - self.logger.debug("Parsing email") - - locale = self._get_locale() - request = self._parse_text() - request['locale'] = locale - - return request - - def _parse_text(self): - """ - Parse 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 tuple with the type of request and os (None if request - is for help) - """ - self.logger.debug("Parsing email text part") - - # By default we asume the request is asking for links - request = {} - request['type'] = 'links' - request['os'] = None - - # The core knows what OS are supported - supported_os = self.core.get_supported_os() - - lines = self.raw_msg.split('\n') - found_os = False - for line in lines: - # Check for help request - if re.match('.*help.*', line, re.IGNORECASE): - request['type'] = 'help' - break - # Check for os - for supported in supported_os: - p = '.*' + supported + '.*' - if re.match(p, line, re.IGNORECASE): - request['os'] = supported - found_os = True - if found_os: - break - - if request['type'] == 'links' and not request['os']: - # Windows by default? - request['os'] = 'windows' - - return request - - def _create_email(self, from_addr, to_addr, subject, msg): - """ - Create an email object - - This object will be used to construct the reply. Comment lines - 331-334, 339, and uncomment lines 336, 337, 340 to test it - without having an SMTP server - - Returns the email object - """ - self.logger.debug("Creating email object for replying") - # email_obj = MIMEtext(msg) - # email_obj['Subject'] = subject - # email_obj['From'] = from_addr - # email_obj['To'] = to_addr - - reply = "From: " + from_addr + ", To: " + to_addr - reply = reply + ", Subject: " + subject + "\n\n" + msg - - # return email_obj - return reply - - def _send_email(self, from_addr, to_addr, subject, msg): - """ - Send an email - - It takes a from and to addresses, a subject and the content, creates - an email and send it. Comment lines 350-352 and uncomment line 353 - to test it without having an SMTP server - """ - email_obj = self._create_email(from_addr, to_addr, subject, msg) - # s = smtplib.SMTP("localhost") - # s.sendmail(from_addr, to_addr, msg.as_string()) - # s.quit() - print email_obj - self.logger.debug("Email sent") - - def _send_delay(self, locale, from_addr, to_addr): - """ - 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("Delay is setted. Sending a delay message.") - - # Obtain the content in the proper language and send it - t = gettext.translation(locale, './i18n', languages=[locale]) - _ = t.ugettext - - delay_msg = _('delay_msg') - delay_subject = _('delay_subject') - self._send_email(from_addr, to_addr, delay_subject, delay_msg) - - def _send_links(self, links, locale, from_addr, to_addr): - """ - Send the links to the user - - It gets the message in the proper language (according to the - locale), replace variables in that message and call to send the - email - """ - self.logger.debug("Request for links in %s" % locale) - - # Obtain the content in the proper language and send it - t = gettext.translation(locale, './i18n', languages=[locale]) - _ = t.ugettext - - links_msg = _('links_msg') - links_subject = _('links_subject') - links_msg = links_msg % ('linux', locale, links, links) - self._send_email(from_addr, to_addr, links_subject, links_msg) - - def _send_help(self, locale, from_addr, to_addr): - """ - Send help message to the user - - It gets the message in the proper language (according to the - locale), replace variables in that message (if any) and call to send - the email - """ - self.logger.debug("Request for help in %s" % locale) - - # Obtain the content in the proper language and send it - t = gettext.translation(locale, './i18n', languages=[locale]) - _ = t.ugettext - - help_msg = _('help_msg') - help_subject = _('help_subject') - self._send_email(from_addr, to_addr, help_subject, help_msg) - - def process_email(self, raw_msg): - """ - Process the email received. - - It creates 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 - - Raises: - - 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.raw_msg = raw_msg - self.parsed_msg = email.message_from_string(raw_msg) - # Just for easy access - self.from_addr = self.parsed_msg['From'] - self.norm_from_addr = self._get_normalized_address(self.from_addr) - self.to_addr = self.parsed_msg['To'] - - # We have the info we need on self.parsed_msg - try: - self._check_blacklist(self._get_sha1(self.from_addr)) - except ValueError as e: - raise ValueError("The address %s is blacklisted!" % - self._get_sha1(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'], self.our_addr, - self.norm_from_addr) - elif request['type'] == 'links': - if self.delay: - self._send_delay(request['locale'], self.our_addr, - self.norm_from_addr) - - try: - self.logger.info("Asking Core for links in %s for %s" % - (request['locale'], request['os'])) - - links = self.core.get_links('SMTP', request['os'], - request['locale']) - - self._send_links(links, request['locale'], self.our_addr, - self.norm_from_addr) - 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") - - def _get_config_option(self, section, option, config): - """ - Private method to get configuration options. - - It tries to obtain a value from a section in config using - ConfigParser. It catches possible exceptions and raises - RuntimeError if something goes wrong. - - Arguments: - config: ConfigParser object - section: section inside config - option: option inside section - - Returns the value of the option inside the section in the - config object. - """ - - try: - value = config.get(section, option) - return value - # This exceptions should appear when messing with the configuration - except (ConfigParser.NoSectionError, - ConfigParser.NoOptionError, - ConfigParser.InterpolationError, - ConfigParser.MissingSectionHeaderError, - ConfigParser.ParsingError) as e: - raise RuntimeError("%s" % str(e)) - # No other errors should occurr, unless something's terribly wrong - except ConfigParser.Error as e: - raise RuntimeError("Unexpected error: %s" % str(e))