diff options
| author | hiro <hiro@torproject.org> | 2019-02-06 22:38:29 +0100 |
|---|---|---|
| committer | hiro <hiro@torproject.org> | 2019-02-06 22:38:29 +0100 |
| commit | 02ae46d0ea3224c041b343832c5f30e8c072c3db (patch) | |
| tree | 145f098c2655fe021bb13f830e15863e9ea4636e | |
| parent | bb0250909fd94a5ba9b6bbda7665e715177d89b7 (diff) | |
Update structure and code
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | gettor/__init__.py | 6 | ||||
| -rw-r--r-- | gettor/core.py | 481 | ||||
| -rw-r--r-- | gettor/main.py | 57 | ||||
| -rw-r--r-- | gettor/parse/__init__.py | 1 | ||||
| -rw-r--r-- | gettor/parse/bridgedb | 31 | ||||
| -rw-r--r-- | gettor/parse/email.py | 217 | ||||
| -rw-r--r-- | gettor/services/__init__.py | 63 | ||||
| -rw-r--r-- | gettor/services/email/sendmail.py | 224 | ||||
| -rw-r--r-- | gettor/services/twitter/twitter.py (renamed from gettor/twitter.py) | 2 | ||||
| -rw-r--r-- | gettor/services/xmpp/xmpp.py (renamed from gettor/xmpp.py) | 2 | ||||
| -rw-r--r-- | gettor/smtp.py | 535 | ||||
| -rw-r--r-- | gettor/utils.py | 131 | ||||
| -rw-r--r-- | gettor/utils/blacklist.py (renamed from gettor/blacklist.py) | 2 | ||||
| -rw-r--r-- | gettor/utils/commons.py | 58 | ||||
| -rw-r--r-- | gettor/utils/db.py (renamed from gettor/db.py) | 0 | ||||
| -rw-r--r-- | gettor/utils/options.py | 19 | ||||
| -rw-r--r-- | gettor/utils/strings.py (renamed from gettor/strings.py) | 5 | ||||
| -rw-r--r-- | gettor/web/http.py (renamed from gettor/http.py) | 2 | ||||
| -rw-r--r-- | scripts/gettor | 28 |
20 files changed, 672 insertions, 1195 deletions
@@ -0,0 +1,3 @@ +venv +__pycache__ +*.pyc diff --git a/gettor/__init__.py b/gettor/__init__.py index ef52564..ab001d5 100644 --- a/gettor/__init__.py +++ b/gettor/__init__.py @@ -10,7 +10,7 @@ the Tor Browser. :license: see included LICENSE for information """ -from . import strings +from .utils import strings -__version__ = get_version() -__locales__ = get_locales() +__version__ = strings.get_version() +__locales__ = strings.get_locales() diff --git a/gettor/core.py b/gettor/core.py deleted file mode 100644 index a12853b..0000000 --- a/gettor/core.py +++ /dev/null @@ -1,481 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of GetTor, a Tor Browser distribution system. -# -# :authors: Israel Leiva <ilv@riseup.net> -# see also AUTHORS file -# -# :copyright: (c) 2008-2014, The Tor Project, Inc. -# (c) 2014, Israel Leiva -# -# :license: This is Free Software. See LICENSE for license information. - -import os -import re -import logging -import gettext -import tempfile -import ConfigParser - -import db -import utils - -"""Core module for getting links from providers.""" - - -class ConfigError(Exception): - pass - - -class NotSupportedError(Exception): - pass - - -class LinkFormatError(Exception): - pass - - -class LinkFileError(Exception): - pass - - -class InternalError(Exception): - pass - - -class Core(object): - """Get links from providers and deliver them to other modules. - - Public methods: - - get_links(): Get the links for the OS and locale requested. - create_links_file(): Create a file to store links of a provider. - add_link(): Add a link to a links file of a provider. - get_supported_os(): Get a list of supported operating systems. - get_supported_lc(): Get a list of supported locales. - - Exceptions: - - UnsupportedOSError: OS and/or locale not supported. - ConfigError: Something's misconfigured. - LinkFormatError: The link added doesn't seem legit. - LinkFileError: Error related to the links file of a provider. - InternalError: Something went wrong internally. - - """ - - def __init__(self, cfg=None): - """Create a new core object by reading a configuration file. - - :param: cfg (string) the path of the configuration file. - :raise: ConfigurationError if the configuration file doesn't exists - or if something goes wrong while reading options from it. - - """ - default_cfg = 'core.cfg' - config = ConfigParser.ConfigParser() - - if cfg is None or not os.path.isfile(cfg): - cfg = default_cfg - - try: - with open(cfg) as f: - config.readfp(f) - except IOError: - raise ConfigError("File %s not found!" % cfg) - - try: - self.supported_lc = config.get('links', 'locales') - self.supported_os = config.get('links', 'os') - - basedir = config.get('general', 'basedir') - self.linksdir = config.get('links', 'dir') - self.linksdir = os.path.join(basedir, self.linksdir) - self.i18ndir = config.get('i18n', 'dir') - - loglevel = config.get('log', 'level') - logdir = config.get('log', 'dir') - logfile = os.path.join(logdir, 'core.log') - - dbname = config.get('general', 'db') - dbname = os.path.join(basedir, dbname) - self.db = db.DB(dbname) - - except ConfigParser.Error as e: - raise ConfigError("Configuration error: %s" % str(e)) - except db.Exception as e: - raise InternalError("%s" % e) - - # logging - log = logging.getLogger(__name__) - - logging_format = utils.get_logging_format() - date_format = utils.get_date_format() - formatter = logging.Formatter(logging_format, date_format) - - log.info('Redirecting CORE logging to %s' % logfile) - logfileh = logging.FileHandler(logfile, mode='a+') - logfileh.setFormatter(formatter) - logfileh.setLevel(logging.getLevelName(loglevel)) - log.addHandler(logfileh) - - # stop logging on stdout from now on - log.propagate = False - self.log = log - - def _get_msg(self, msgid, lc): - """Get message identified by msgid in a specific locale. - - :param: msgid (string) the identifier of a string. - :param: lc (string) the locale. - - :return: (string) the message from the .po file. - - """ - # obtain the content in the proper language - try: - t = gettext.translation(lc, self.i18ndir, languages=[lc]) - _ = t.ugettext - - msgstr = _(msgid) - return msgstr - except IOError as e: - raise ConfigError("%s" % str(e)) - - def get_links(self, service, os, lc): - """Get links for OS in locale. - - This method should be called from the services modules of - GetTor (e.g. SMTP). To make it easy we let the module calling us - specify the name of the service (for stats purpose). - - :param: service (string) the service trying to get the links. - :param: os (string) the operating system. - :param: lc (string) tthe locale. - - :raise: InternalError if something goes wrong while internally. - - :return: (string) the links. - - """ - # english and windows by default - if lc not in self.supported_lc: - self.log.debug("Request for locale not supported. Default to en") - lc = 'en' - - if os not in self.supported_os: - self.log.debug("Request for OS not supported. Default to windows") - os = 'windows' - - # this could change in the future, let's leave it isolated. - self.log.debug("Trying to get the links...") - try: - links = self._get_links(os, lc) - self.log.debug("OK") - except InternalError as e: - self.log.debug("FAILED") - raise InternalError("%s" % str(e)) - - if links is None: - self.log.debug("No links found") - raise InternalError("No links. Something is wrong.") - - return links - - def _get_links(self, osys, lc): - """Internal method to get the links. - - Looks for the links inside each provider file. This should only be - called from get_links() method. - - :param: osys (string) the operating system. - :param: lc (string) the locale. - - :return: (string/None) links on success, None otherwise. - - """ - - # read the links files using ConfigParser - # see the README for more details on the format used - links_files = [] - - links32 = {} - links64 = {} - - # for the message to be sent - if osys == 'windows': - arch = '32/64' - elif osys == 'osx': - arch = '64' - else: - arch = '32' - - # 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_files.append(path) - - # let's create a dictionary linking each provider with the links - # found for os and lc. This way makes it easy to check if no - # links were found - providers = {} - - # separator - spt = '=' * 72 - - # reading links from providers directory - for name in links_files: - # we're reading files listed on linksdir, so they must exist! - config = ConfigParser.ConfigParser() - # but just in case they don't - try: - with open(name) as f: - config.readfp(f) - except IOError: - raise InternalError("File %s not found!" % name) - - try: - pname = config.get('provider', 'name') - - # check if current provider pname has links for os in lc - providers[pname] = config.get(osys, lc) - except ConfigParser.Error as e: - # we should at least have the english locale available - self.log.error("Request for %s, returning 'en' instead" % lc) - providers[pname] = config.get(osys, 'en') - try: - #test = providers[pname].split("$") - #self.log.debug(test) - if osys == 'linux': - t32, t64 = [t for t in providers[pname].split(",") if t] - - link, signature, chs32 = [l for l in t32.split("$") if l] - link = " %s: %s" % (pname, link) - links32[link] = signature - - link, signature, chs64 = [l for l in t64.split("$") if l] - link = " %s: %s" % (pname, link.lstrip()) - links64[link] = signature - - else: - link, signature, chs32 = [l for l in providers[pname].split("$") if l] - link = " %s: %s" % (pname, link) - links32[link] = signature - - #providers[pname] = providers[pname].replace(",", "") - #providers[pname] = providers[pname].replace("$", "\n\n") - - ### We will improve and add the verification section soon ### - # all packages are signed with same key - # (Tor Browser developers) - # fingerprint = config.get('key', 'fingerprint') - # for now, english messages only - # fingerprint_msg = self._get_msg('fingerprint', 'en') - # fingerprint_msg = fingerprint_msg % fingerprint - except ConfigParser.Error as e: - raise InternalError("%s" % str(e)) - - # create the final links list with all providers - all_links = [] - - msg = "Tor Browser %s-bit:\n" % arch - for link in links32: - msg = "%s\n%s" % (msg, link) - - all_links.append(msg) - - if osys == 'linux': - msg = "\n\n\nTor Browser 64-bit:\n" - for link in links64: - msg = "%s\n%s" % (msg, link) - - all_links.append(msg) - - ### We will improve and add the verification section soon ### - """ - msg = "\n\n\nTor Browser's signature %s-bit:" %\ - arch - for link in links32: - msg = "%s\n%s" % (msg, links32[link]) - - all_links.append(msg) - - if osys == 'linux': - msg = "\n\n\nTor Browser's signature 64-bit:" - for link in links64: - msg = "%s%s" % (msg, links64[link]) - - all_links.append(msg) - - msg = "\n\n\nSHA256 of Tor Browser %s-bit (advanced): %s\n" %\ - (arch, chs32) - all_links.append(msg) - - if osys == 'linux': - msg = "SHA256 of Tor Browser 64-bit (advanced): %s\n" % chs64 - all_links.append(msg) - - """ - ### end verification ### - - """ - for key in providers.keys(): - # get more friendly description of the provider - try: - # for now, english messages only - provider_desc = self._get_msg('provider_desc', 'en') - provider_desc = provider_desc % key - - all_links.append( - "%s\n%s\n\n%s%s\n\n\n" % - (provider_desc, spt, ''.join(providers[key]), spt) - ) - except ConfigError as e: - raise InternalError("%s" % str(e)) - """ - - ### We will improve and add the verification section soon ### - # add fingerprint after the links - # all_links.append(fingerprint_msg) - - if all_links: - return "".join(all_links) - else: - # we're trying to get supported os an lc - # but there aren't any links! - return None - - def get_supported_os(self): - """Public method to get the list of supported operating systems. - - :return: (list) the supported operating systems. - - """ - return self.supported_os.split(',') - - def get_supported_lc(self): - """Public method to get the list of supported locales. - - :return: (list) the supported locales. - - """ - return self.supported_lc.split(',') - - 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. - - :param: provider (string) the provider (links file will use this - name in slower case). - :param: fingerprint (string) the 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.log.debug("Request to create a new links file") - if os.path.isfile(linksfile): - self.log.debug("Trying to backup the old one...") - try: - # backup the old file in case something fails - linksfile_backup = linksfile + '.backup' - os.rename(linksfile, linksfile_backup) - except OSError as e: - self.log.debug("FAILED %s" % str(e)) - raise LinkFileError( - "Error while creating new links file: %s" % str(e) - ) - - self.log.debug("Creating empty links file...") - try: - # this creates an empty links file - 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) - except Exception as e: - self.log.debug("FAILED: %s" % str(e)) - # if we passed the last exception, then this shouldn't - # be a problem... - if linksfile_backup: - os.rename(linksfile_backup, linksfile) - raise LinkFileError( - "Error while creating new links file: %s" % str(e) - ) - - def add_link(self, provider, osys, lc, link): - """Public method to add a link to a provider's links file. - - Use ConfigParser to add a link into the os section, under the lc - option. It checks for valid format; the provider's script should - use the right format (see design). - - :param: provider (string) the provider. - :param: os (string) the operating system. - :param: lc (string) the locale. - :param: link (string) link to be added. - - :raise: NotsupportedError if the OS and/or locale is not supported. - :raise: LinkFileError if there is no links file for the provider. - :raise: LinkFormatError if the link format doesn't seem legit. - :raise: InternalError if the links file doesn't have a section for - the OS requested. This *shouldn't* happen because it means - the file wasn't created correctly. - - """ - linksfile = os.path.join(self.linksdir, provider.lower() + '.links') - - self.log.debug("Request to add a new link") - # don't try to add unsupported stuff - if lc not in self.supported_lc: - self.log.debug("Request for locale %s not supported" % lc) - raise NotSupportedError("Locale %s not supported" % lc) - - if osys not in self.supported_os: - self.log.debug("Request for OS %s not supported" % osys) - raise NotSupportedError("OS %s not supported" % osys) - - self.log.debug("Opening links file...") - if os.path.isfile(linksfile): - content = ConfigParser.RawConfigParser() - - try: - with open(linksfile) as f: - content.readfp(f) - except IOError as e: - self.log.debug("FAILED %s" % str(e)) - raise LinksFileError("File %s not found!" % linksfile) - # check if exists and entry for locale; if not, create it - self.log.debug("Trying to add the link...") - try: - links = content.get(osys, lc) - links = "%s,\n%s" % (links, link) - content.set(osys, lc, links) - self.log.debug("Link added") - with open(linksfile, 'w') as f: - content.write(f) - except ConfigParser.NoOptionError: - content.set(osys, lc, link) - self.log.debug("Link added (with new locale created)") - with open(linksfile, 'w') as f: - content.write(f) - except ConfigParser.NoSectionError as e: - # this shouldn't happen, but just in case - self.log.debug("FAILED (OS not found)") - raise InternalError("Unknown section %s" % str(e)) - else: - self.log.debug("FAILED (links file doesn't seem legit)") - raise LinkFileError("No links file for %s" % provider) diff --git a/gettor/main.py b/gettor/main.py index 2622f35..267d37f 100644 --- a/gettor/main.py +++ b/gettor/main.py @@ -12,49 +12,28 @@ the Tor Browser. """This module sets up GetTor and starts the servers running.""" -import logging -import os -import signal import sys -import time -from twisted.internet import reactor -from twisted.internet import task +from twisted.application import service -from . import Storage +from .utils.commons import log +from .services import BaseService +from .services.email.sendmail import Sendmail -def run(options, reactor=reactor): + +def run(options): """ This is GetTor's main entry point and main runtime loop. """ - # Change to the directory where we're supposed to run. This must be done - # before parsing the config file, otherwise there will need to be two - # copies of the config file, one in the directory GetTor is started in, - # and another in the directory it changes into. - os.chdir(options['rundir']) - if options['verbosity'] <= 10: # Corresponds to logging.DEBUG - print("Changed to runtime directory %r" % os.getcwd()) - - config = loadConfig(options['config']) - config.RUN_IN_DIR = options['rundir'] - - # Set up logging as early as possible. We cannot import from the gettor - # package any of our modules which import :mod:`logging` and start using - # it, at least, not until :func:`safelog.configureLogging` is - # called. Otherwise a default handler that logs to the console will be - # created by the imported module, and all further calls to - # :func:`logging.basicConfig` will be ignored. - util.configureLogging(config) - - if options.subCommand is not None: - runSubcommand(options, config) - - # Write the pidfile only after any options.subCommands are run (because - # these exit when they are finished). Otherwise, if there is a subcommand, - # the real PIDFILE would get overwritten with the PID of the temporary - # gettor process running the subcommand. - if config.PIDFILE: - logging.debug("Writing server PID to file: '%s'" % config.PIDFILE) - with open(config.PIDFILE, 'w') as pidfile: - pidfile.write("%s\n" % os.getpid()) - pidfile.flush() + sendmail = Sendmail() + + log.info("Starting services.") + sendmail_service = BaseService( + "sendmail", sendmail.get_interval(), sendmail + ) + + gettor = service.MultiService() + gettor.addService(sendmail_service) + + application = service.Application("gettor") + gettor.setServiceParent(application) diff --git a/gettor/parse/__init__.py b/gettor/parse/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/gettor/parse/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/gettor/parse/bridgedb b/gettor/parse/bridgedb new file mode 100644 index 0000000..876fde9 --- /dev/null +++ b/gettor/parse/bridgedb @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# This file is part of BridgeDB, a Tor bridge distribution system. +# +# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis@torproject.org> +# please also see AUTHORS file +# :copyright: (c) 2007-2013, The Tor Project, Inc. +# (c) 2007-2013, all entities within the AUTHORS file +# :license: 3-clause BSD, see included LICENSE for information + +from __future__ import print_function + +import os.path +import sys + +from os import getcwd + +from bridgedb.main import run +from bridgedb.parse.options import parseOptions + + +option = parseOptions() + +if option.subCommand is not None: + # Hack to set the PYTHONPATH: + sys.path[:] = map(os.path.abspath, sys.path) + sys.path.insert(0, os.path.abspath(getcwd())) + sys.path.insert(0, os.path.abspath(os.path.join(getcwd(), '..'))) + +run(option) diff --git a/gettor/parse/email.py b/gettor/parse/email.py new file mode 100644 index 0000000..f798320 --- /dev/null +++ b/gettor/parse/email.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- +# +# This file is part of GetTor, a Tor Browser distribution system. +# +# :authors: isra <ilv@torproject.org> +# see also AUTHORS file +# +# :copyright: (c) 2008-2014, The Tor Project, Inc. +# (c) 2014-2018, Israel Leiva +# +# :license: This is Free Software. See LICENSE for license information. + +from __future__ import absolute_import + +import re +import dkim +import hashlib +import validate_email + +from datetime import datetime +import configparser + +from email import message_from_string +from email.utils import parseaddr + +from twisted.python import log +from twisted.internet import defer +from twisted.enterprise import adbapi + +from .. import PLATFORMS, EMAIL_REQUESTS_LIMIT +from ..db import SQLite3 + + +class AddressError(Exception): + """ + Error if email address is not valid or it can't be normalized. + """ + pass + + +class DKIMError(Exception): + """ + Error if DKIM signature verification fails. + """ + pass + + +class EmailParser(object): + """Class for parsing email requests.""" + + def __init__(self, to_addr=None, dkim=False): + """ + Constructor. + + param (Boolean) dkim: Set dkim verification to True or False. + """ + self.dkim = dkim + self.to_addr = to_addr + + + def parse(self, msg_str): + """ + Parse message content. Check if email address is well formed, if DKIM + signature is valid, and prevent service flooding. Finally, look for + commands to process the request. Current commands are: + + - links: request links for download. + - help: help request. + + :param msg_str (str): incomming message as string. + + :return dict with email address and command (`links` or `help`). + """ + + log.msg("Building email message from string.", system="email parser") + msg = message_from_string(msg_str) + + # Normalization will convert <Alice Wonderland> alice@wonderland.net + # into alice@wonderland.net + name, norm_addr = parseaddr(msg['From']) + to_name, norm_to_addr = parseaddr(msg['To']) + log.msg( + "Normalizing and validating FROM email address.", + system="email parser" + ) + + # Validate_email will do a bunch of regexp to see if the email address + # is well address. Additional options for validate_email are check_mx + # and verify, which check if the SMTP host and email address exist. + # See validate_email package for more info. + if norm_addr and validate_email.validate_email(norm_addr): + log.msg( + "Email address normalized and validated.", + system="email parser" + ) + else: + log.err( + "Error normalizing/validating email address.", + system="email parser" + ) + raise AddressError("Invalid email address {}".format(msg['From'])) + + hid = hashlib.sha256(norm_addr) + log.msg( + "Request from {}".format(hid.hexdigest()), system="email parser" + ) + + if self.to_addr: + if self.to_addr != norm_to_addr: + log.msg("Got request for a different instance of gettor") + log.msg("Intended recipient: {}".format(norm_to_addr)) + return {} + + # DKIM verification. Simply check that the server has verified the + # message's signature + if self.dkim: + log.msg("Checking DKIM signature.", system="email parser") + # Note: msg.as_string() changes the message to conver it to + # string, so DKIM will fail. Use the original string instead + if dkim.verify(msg_str): + log.msg("Valid DKIM signature.", system="email parser") + else: + log.msg("Invalid DKIM signature.", system="email parser") + username, domain = norm_addr.split("@") + raise DkimError( + "DKIM failed for {} at {}".format( + hid.hexdigest(), domain + ) + ) + + # Search for commands keywords + subject_re = re.compile(r"Subject: (.*)\r\n") + subject = subject_re.search(msg_str) + + request = { + "id": norm_addr, + "command": None, "platform": None, + "service": "email" + } + + if subject: + subject = subject.group(1) + for word in re.split(r"\s+", subject.strip()): + if word.lower() in PLATFORMS: + request["command"] = "links" + request["platform"] = word.lower() + break + if word.lower() == "help": + request["command"] = "help" + break + + if not request["command"]: + for word in re.split(r"\s+", body_str.strip()): + if word.lower() in PLATFORMS: + request["command"] = "links" + request["platform"] = word.lower() + break + if word.lower() == "help": + request["command"] = "help" + break + + return request + + @defer.inlineCallbacks + def parse_callback(self, request): + """ + Callback invoked when the message has been parsed. It stores the + obtained information in the database for further processing by the + Sendmail service. + + :param (dict) request: the built request based on message's content. + It contains the `email_addr` and command `fields`. + + :return: deferred whose callback/errback will log database query + execution details. + """ + + log.msg( + "Found request for {}.".format(request['command']), + system="email parser" + ) + + if request["command"]: + now_str = datetime.now().strftime("%Y%m%d%H%M%S") + conn = SQLite3() + + hid = hashlib.sha256(request['id']) + # check limits first + num_requests = yield conn.get_num_requests( + id=hid.hexdigest(), service=request['service'] + ) + + if num_requests[0][0] > EMAIL_REQUESTS_LIMIT: + log.msg( + "Discarded. Too many requests from {}.".format( + hid.hexdigest + ), system="email parser" + ) + + else: + conn.new_request( + id=request['id'], + command=request['command'], + platform=request['platform'], + service=request['service'], + date=now_str, + status="ONHOLD", + ) + + def parse_errback(self, error): + """ + Errback if we don't/can't parse the message's content. + """ + log.msg( + "Error while parsing email content: {}.".format(error), + system="email parser" + ) diff --git a/gettor/services/__init__.py b/gettor/services/__init__.py new file mode 100644 index 0000000..c829c5b --- /dev/null +++ b/gettor/services/__init__.py @@ -0,0 +1,63 @@ +# +from __future__ import absolute_import + +from twisted.application import internet +from ..utils.commons import log + +PLATFORMS = ["linux", "osx", "windows"] + +DBNAME = "gettor.db" +EMAIL_PARSER_LOGFILE = "email_parser.log" +EMAIL_REQUESTS_LIMIT = 5 + +SENDMAIL_INTERVAL=10 +SENDMAIL_ADDR="email@addr" +SENDMAIL_HOST="host" +SENDMAIL_PORT=587 + +class BaseService(internet.TimerService): + """ + Base service for Accounts, Messages and Fetchmail. It extends the + TimerService providing asynchronous connection to database by default. + """ + + def __init__(self, name, step, instance, *args, **kwargs): + """ + Constructor. Initiate connection to database and link one of Accounts, + Messages or Fetchmail instances to TimerService behavour. + + :param name (str): name of the service being initiated (just for log + purposes). + :param step (float): time interval for TimerService, in seconds. + :param instance (object): instance of Accounts, Messages, or + Fetchmail classes. + """ + + log.info("SERVICE:: Initializing {} service.".format(name)) + self.name = name + self.instance = instance + log.debug("SERVICE:: Initializing TimerService.") + internet.TimerService.__init__( + self, step, self.instance.get_new, **kwargs + ) + + def startService(self): + """ + Starts the service. Overridden from parent class to add extra logging + information. + """ + log.info("SERVICE:: Starting {} service.".format(self.name)) + internet.TimerService.startService(self) + log.info("SERVICE:: Service started.") + + def stopService(self): + """ + Stop the service. Overridden from parent class to close connection to + database, shutdown the service and add extra logging information. + """ + log.info("SERVICE:: Stopping {} service.".format(self.name)) + log.debug("SERVICE:: Calling shutdown on {}".format(self.name)) + self.instance.shutdown() + log.debug("SERVICE:: Shutdown for {} done".format(self.name)) + internet.TimerService.stopService(self) + log.info("SERVICE:: Service stopped.") diff --git a/gettor/services/email/sendmail.py b/gettor/services/email/sendmail.py new file mode 100644 index 0000000..70a8c09 --- /dev/null +++ b/gettor/services/email/sendmail.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +# +# This file is part of GetTor, a Tor Browser distribution system. +# +# :authors: isra <ilv@torproject.org> +# see also AUTHORS file +# +# :copyright: (c) 2008-2014, The Tor Project, Inc. +# (c) 2014-2018, Israel Leiva +# +# :license: This is Free Software. See LICENSE for license information. + +from __future__ import absolute_import + +import gettext +import hashlib + +import configparser +from email import encoders +from email import mime +from twisted.internet import defer +from twisted.mail.smtp import sendmail + +from .. import SENDMAIL_INTERVAL, SENDMAIL_ADDR, SENDMAIL_HOST, SENDMAIL_PORT +from ...utils.db import SQLite3 +from ...utils.commons import log + + +class SMTPError(Exception): + """ + Error if we can't send emails. + """ + pass + + +class Sendmail(object): + """ + Class for sending email replies to `help` and `links` requests. + """ + def __init__(self): + """ + Constructor. It opens and stores a connection to the database. + """ + self.conn = SQLite3() + + + def get_interval(self): + """ + Get time interval for service periodicity. + + :return: time interval (float) in seconds. + """ + return SENDMAIL_INTERVAL + + + def sendmail_callback(self, message): + """ + Callback invoked after an email has been sent. + + :param message (string): Success details from the server. + """ + log.info("Email sent successfully.") + + def sendmail_errback(self, error): + """ + Errback if we don't/can't send the message. + """ + log.debug("Could not send email.") + raise SMTPError("{}".format(error)) + + def sendmail(self, email_addr, subject, body): + """ + Send an email message. It creates a plain text message, set headers + and content and finally send it. + + :param email_addr (str): email address of the recipient. + :param subject (str): subject of the message. + :param content (str): content of the message. + + :return: deferred whose callback/errback will handle the SMTP + execution details. + """ + log.debug("Creating plain text email") + message = MIMEText(body) + + message['Subject'] = subject + message['From'] = SENDMAIL_ADDR + message['To'] = email_addr + + log.debug("Calling asynchronous sendmail.") + + return sendmail( + SENDMAIL_HOST, SENDMAIL_ADDR, email_addr, message, + port=SENDMAIL_PORT, + requireAuthentication=True, requireTransportSecurity=True + ).addCallback(self.sendmail_callback).addErrback(self.sendmail_errback) + + + + @defer.inlineCallbacks + def get_new(self): + """ + Get new requests to process. This will define the `main loop` of + the Sendmail service. + """ + + # Manage help and links messages separately + help_requests = yield self.conn.get_requests( + status="ONHOLD", command="help", service="email" + ) + + link_requests = yield self.conn.get_requests( + status="ONHOLD", command="links", service="email" + ) + + + if help_requests: + try: + log.info("Got new help request.") + + # for now just english + en = gettext.translation( + 'email', localedir='locales', languages=['en'] + ) + en.install() + _ = en.gettext + + for request in help_requests: + id = request[0] + date = request[4] + + hid = hashlib.sha256(id) + log.info( + "Sending help message to {}.".format( + hid.hexdigest() + ) + ) + + yield self.sendmail( + email_addr=id, + subject=_("help_subject"), + body=_("help_body") + ) + + yield self.conn.update_stats( + command="help", service="email" + ) + + yield self.conn.update_request( + id=id, hid=hid.hexdigest(), status="SENT", + service="email", date=date + ) + + except SMTPError as e: + log.info("Error sending email: {}.".format(e)) + + elif link_requests: + try: + log.info("Got new links request.") + + # for now just english + en = gettext.translation( + 'email', localedir='locales', languages=['en'] + ) + en.install() + _ = en.gettext + + for request in link_requests: + id = request[0] + date = request[4] + platform = request[2] + + log.info("Getting links for {}.".format(platform)) + links = yield self.conn.get_links( + platform=platform, status="ACTIVE" + ) + + # build message + link_msg = None + for link in links: + provider = link[4] + version = link[3] + arch = link[2] + url = link[0] + + link_str = "Tor Browser {} for {}-{} ({}): {}".format( + version, platform, arch, provider, url + ) + + if link_msg: + link_msg = "{}\n{}".format(link_msg, link_str) + else: + link_msg = link_str + + body_msg = _("links_body") + body_msg = body_msg.format(links=link_msg) + subject_msg = _("links_subject") + + hid = hashlib.sha256(id) + log.info( + "Sending links to {}.".format( + hid.hexdigest() + ) + ) + + yield self.sendmail( + email_addr=id, + subject=subject_msg, + body=body_msg + ) + + yield self.conn.update_stats( + command="links", platform=platform, service="email" + ) + + yield self.conn.update_request( + id=id, hid=hid.hexdigest(), status="SENT", + service="email", date=date + ) + + except SMTPError as e: + log.info("Error sending email: {}.".format(e)) + else: + log.debug("No pending email requests. Keep waiting.") diff --git a/gettor/twitter.py b/gettor/services/twitter/twitter.py index deced9b..b5e08ba 100644 --- a/gettor/twitter.py +++ b/gettor/services/twitter/twitter.py @@ -16,7 +16,7 @@ import re import tweepy import logging import gettext -import ConfigParser +import configparser import core import utils diff --git a/gettor/xmpp.py b/gettor/services/xmpp/xmpp.py index 4df7ac9..ff5e207 100644 --- a/gettor/xmpp.py +++ b/gettor/services/xmpp/xmpp.py @@ -17,7 +17,7 @@ import time import gettext import hashlib import logging -import ConfigParser +import configparser from sleekxmpp import ClientXMPP from sleekxmpp.xmlstream.stanzabase import JID diff --git a/gettor/smtp.py b/gettor/smtp.py deleted file mode 100644 index 32b43ae..0000000 --- a/gettor/smtp.py +++ /dev/null @@ -1,535 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of GetTor, a Tor Browser distribution system. -# -# :authors: Israel Leiva <ilv@riseup.net> -# see also AUTHORS file -# -# :copyright: (c) 2008-2014, The Tor Project, Inc. -# (c) 2014, Israel Leiva -# -# :license: This is Free Software. See LICENSE for license information. - - -import os -import re -import sys -import time -import email -import gettext -import logging -import smtplib -import datetime -import ConfigParser - -from email import Encoders -from email.MIMEBase import MIMEBase -from email.mime.text import MIMEText -from email.MIMEMultipart import MIMEMultipart - -import core -import utils -import blacklist - -"""SMTP module for processing email requests.""" - -OS = { - 'osx': 'Mac OS X', - 'linux': 'Linux', - 'windows': 'Windows' -} - - -class ConfigError(Exception): - pass - - -class AddressError(Exception): - pass - - -class SendEmailError(Exception): - pass - - -class InternalError(Exception): - pass - - -class SMTP(object): - """Receive and reply requests by email. - - Public methods: - - process_email(): Process the email received. - - Exceptions: - - ConfigError: Bad configuration. - AddressError: Address of the sender malformed. - SendEmailError: SMTP server not responding. - InternalError: Something went wrong internally. - - """ - - def __init__(self, cfg=None): - """Create new object by reading a configuration file. - - :param: cfg (string) path of the configuration file. - - """ - default_cfg = 'smtp.cfg' - config = ConfigParser.ConfigParser() - - if cfg is None or not os.path.isfile(cfg): - cfg = default_cfg - - try: - with open(cfg) as f: - config.readfp(f) - except IOError: - raise ConfigError("File %s not found!" % cfg) - - try: - self.our_domain = config.get('general', 'our_domain') - self.mirrors = config.get('general', 'mirrors') - self.i18ndir = config.get('i18n', 'dir') - - logdir = config.get('log', 'dir') - logfile = os.path.join(logdir, 'smtp.log') - loglevel = config.get('log', 'level') - - blacklist_cfg = config.get('blacklist', 'cfg') - self.bl = blacklist.Blacklist(blacklist_cfg) - self.bl_max_req = config.get('blacklist', 'max_requests') - self.bl_max_req = int(self.bl_max_req) - self.bl_wait_time = config.get('blacklist', 'wait_time') - self.bl_wait_time = int(self.bl_wait_time) - - core_cfg = config.get('general', 'core_cfg') - self.core = core.Core(core_cfg) - - except ConfigParser.Error as e: - raise ConfigError("Configuration error: %s" % str(e)) - except blacklist.ConfigError as e: - raise InternalError("Blacklist error: %s" % str(e)) - except core.ConfigError as e: - raise InternalError("Core error: %s" % str(e)) - - # logging - log = logging.getLogger(__name__) - - logging_format = utils.get_logging_format() - date_format = utils.get_date_format() - formatter = logging.Formatter(logging_format, date_format) - - log.info('Redirecting SMTP logging to %s' % logfile) - logfileh = logging.FileHandler(logfile, mode='a+') - logfileh.setFormatter(formatter) - logfileh.setLevel(logging.getLevelName(loglevel)) - log.addHandler(logfileh) - - # stop logging on stdout from now on - log.propagate = False - self.log = log - - def _is_blacklisted(self, addr): - """Check if a user is blacklisted. - - :param: addr (string) the hashed address of the user. - - :return: true is the address is blacklisted, false otherwise. - - """ - - try: - self.bl.is_blacklisted( - addr, 'SMTP', self.bl_max_req, self.bl_wait_time - ) - return False - except blacklist.BlacklistError as e: - return True - - def _get_lc(self, addr): - """Get the locale from an email address. - - 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. - - :param: (string) the email address we want to get the locale from. - - :return: (string) the locale (english if none). - - """ - # if no match found, english by default - lc = 'en' - - # look for gettor+locale@torproject.org - m = re.match('gettor\+(\w\w)@\w+\.\w+', addr) - if m: - # we found a request for locale lc - lc = "%s" % m.groups() - - return lc.lower() - - def _get_normalized_address(self, addr): - """Get normalized address. - - We look for anything inside the last '<' and '>'. Code taken from - the old GetTor (utils.py). - - :param: addr (string) the address we want to normalize. - - :raise: AddressError if the address can't be normalized. - - :return: (string) the normalized address. - - """ - if '<' in addr: - idx = addr.rindex('<') - addr = addr[idx:] - m = re.search(r'<([^>]*)>', addr) - if m is None: - # malformed address - raise AddressError("Couldn't extract normalized address " - "from %s" % self_get_sha256(addr)) - addr = m.group(1) - return addr - - def _get_content(self, email): - """Get the body content of an email. - - :param: email (object) the email object to extract the content from. - - :return: (string) body of the message. - - """ - # get the body content of the email - maintype = email.get_content_maintype() - if maintype == 'multipart': - for part in email.get_payload(): - if part.get_content_maintype() == 'text': - return part.get_payload() - elif maintype == 'text': - return email.get_payload() - - def _get_msg(self, msgid, lc): - """Get message identified by msgid in a specific locale. - - :param: msgid (string) the identifier of a string. - :param: lc (string) the locale. - - :return: (string) the message from the .po file. - - """ - # obtain the content in the proper language - try: - t = gettext.translation(lc, self.i18ndir, languages=[lc]) - _ = t.ugettext - - msgstr = _(msgid) - return msgstr - except IOError as e: - raise ConfigError("%s" % str(e)) - - def _parse_email(self, msg, addr): - """Parse the email received. - - Get the locale and parse the text for the rest of the info. - - :param: msg (string) the content of the email to be parsed. - :param: addr (string) the address of the recipient (i.e. us). - - :return: (list) 4-tuple with locale, os and type of request. - - """ - req = self._parse_text(msg) - lc = self._get_lc(addr) - supported_lc = self.core.get_supported_lc() - - if lc in supported_lc: - req['lc'] = lc - else: - req['lc'] = 'en' - - return req - - def _parse_text(self, msg): - """Parse the text part of the email received. - - Try to figure out what the user is asking, namely, the type - of request, the package and os required (if applies). - - :param: msg (string) the content of the email to be parsed. - - :return: (list) 3-tuple with the type of request, os and pt info. - - """ - # by default we asume the request is asking for help - req = {} - req['type'] = 'help' - req['os'] = None - - # core knows what OS are supported - supported_os = self.core.get_supported_os() - - # search for OS or mirrors request - # if nothing is found, help by default - found_request = False - words = re.split('\s+', msg.strip()) - for word in words: - if not found_request: - # OS first - for os in supported_os: - if re.match(os, word, re.IGNORECASE): - req['os'] = os - req['type'] = 'links' - found_request = True - break - # mirrors - if re.match("mirrors?", word, re.IGNORECASE): - req['type'] = 'mirrors' - found_request = True - else: - break - return req - - def _create_email(self, from_addr, to_addr, subject, msg): - """Create an email object. - - This object will be used to construct the reply. - - :param: from_addr (string) the address of the sender. - :param: to_addr (string) the address of the recipient. - :param: subject (string) the subject of the email. - :param: msg (string) the content of the email. - - :return: (object) the email object. - - """ - email_obj = MIMEMultipart() - email_obj.set_charset("utf-8") - email_obj['Subject'] = subject - email_obj['From'] = from_addr - email_obj['To'] = to_addr - - msg_attach = MIMEText(msg, 'plain') - email_obj.attach(msg_attach) - - return email_obj - - def _send_email(self, from_addr, to_addr, subject, msg, attach=None): - """Send an email. - - Take a 'from' and 'to' addresses, a subject and the content, creates - the email and send it. - - :param: from_addr (string) the address of the sender. - :param: to_addr (string) the address of the recipient. - :param: subject (string) the subject of the email. - :param: msg (string) the content of the email. - :param: attach (string) the path of the mirrors list. - - """ - email_obj = self._create_email(from_addr, to_addr, subject, msg) - - if(attach): - # for now, the only email with attachment is the one for mirrors - try: - part = MIMEBase('application', "octet-stream") - part.set_payload(open(attach, "rb").read()) - Encoders.encode_base64(part) - - part.add_header( - 'Content-Disposition', - 'attachment; filename="mirrors.txt"' - ) - - email_obj.attach(part) - except IOError as e: - raise SendEmailError('Error with mirrors: %s' % str(e)) - - try: - s = smtplib.SMTP("localhost") - s.sendmail(from_addr, to_addr, email_obj.as_string()) - s.quit() - except smtplib.SMTPException as e: - raise SendEmailError("Error with SMTP: %s" % str(e)) - - def _send_links(self, links, lc, os, from_addr, to_addr): - """Send links to the user. - - Get the message in the proper language (according to the locale), - replace variables and send the email. - - :param: links (string) the links to be sent. - :param: lc (string) the locale. - :param: os (string) the operating system. - :param: from_addr (string) the address of the sender. - :param: to_addr (string) the address of the recipient. - - """ - # obtain the content in the proper language and send it - try: - links_subject = self._get_msg('links_subject', 'en') - links_msg = self._get_msg('links_msg', 'en') - links_msg = links_msg % (OS[os], links) - - self._send_email( - from_addr, - to_addr, - links_subject, - links_msg, - None - ) - except ConfigError as e: - raise InternalError("Error while getting message %s" % str(e)) - except SendEmailError as e: - raise InternalError("Error while sending links message") - - def _send_mirrors(self, lc, from_addr, to_addr): - """Send mirrors message. - - Get the message in the proper language (according to the locale), - replace variables (if any) and send the email. - - :param: lc (string) the locale. - :param: from_addr (string) the address of the sender. - :param: to_addr (string) the address of the recipient. - - """ - # obtain the content in the proper language and send it - try: - mirrors_subject = self._get_msg('mirrors_subject', lc) - mirrors_msg = self._get_msg('mirrors_msg', lc) - - self._send_email( - from_addr, to_addr, mirrors_subject, mirrors_msg, self.mirrors - ) - except ConfigError as e: - raise InternalError("Error while getting message %s" % str(e)) - except SendEmailError as e: - raise InternalError("Error while sending mirrors message") - - def _send_help(self, lc, from_addr, to_addr): - """Send help message. - - Get the message in the proper language (according to the locale), - replace variables (if any) and send the email. - - :param: lc (string) the locale. - :param: from_addr (string) the address of the sender. - :param: to_addr (string) the address of the recipient. - - """ - # obtain the content in the proper language and send it - try: - help_subject = self._get_msg('help_subject', lc) - help_msg = self._get_msg('help_msg', lc) - - self._send_email(from_addr, to_addr, help_subject, help_msg, None) - except ConfigError as e: - raise InternalError("Error while getting message %s" % str(e)) - except SendEmailError as e: - raise InternalError("Error while sending help message") - - def process_email(self, raw_msg): - """Process the email received. - - 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. - - :param: raw_msg (string) the email received. - - :raise: InternalError if something goes wrong while asking for the - links to the Core module. - - """ - self.log.debug("Processing email") - parsed_msg = email.message_from_string(raw_msg) - content = self._get_content(parsed_msg) - from_addr = parsed_msg['From'] - to_addr = parsed_msg['To'] - bogus_request = False - status = '' - req = None - - try: - # two ways for a request to be bogus: address malformed or - # blacklisted - try: - self.log.debug("Normalizing address...") - norm_from_addr = self._get_normalized_address(from_addr) - except AddressError as e: - bogus_request = True - self.log.info('invalid; none; none') - - if norm_from_addr: - anon_addr = utils.get_sha256(norm_from_addr) - - if self._is_blacklisted(anon_addr): - bogus_request = True - self.log.info('blacklist; none; none') - - if not bogus_request: - # try to figure out what the user is asking - self.log.debug("Request seems legit; parsing it...") - req = self._parse_email(content, to_addr) - - # our address should have the locale requested - our_addr = "gettor+%s@%s" % (req['lc'], self.our_domain) - - # possible options: help, links, mirrors - if req['type'] == 'help': - self.log.debug("Trying to send help...") - self.log.info('help; none; %s' % req['lc']) - # make sure we can send emails - try: - self._send_help('en', our_addr, norm_from_addr) - except SendEmailError as e: - self.log.debug("FAILED: %s" % str(e)) - raise InternalError("Something's wrong with the SMTP " - "server: %s" % str(e)) - - elif req['type'] == 'mirrors': - self.log.debug("Trying to send the mirrors...") - self.log.info('mirrors; none; %s' % req['lc']) - # make sure we can send emails - try: - self._send_mirrors('en', our_addr, norm_from_addr) - except SendEmailError as e: - self.log.debug("FAILED: %s" % str(e)) - raise SendEmailError("Something's wrong with the SMTP " - "server: %s" % str(e)) - - elif req['type'] == 'links': - self.log.debug("Trying to obtain the links...") - self.log.info('links; %s; %s' % (req['os'], req['lc'])) - - try: - links = self.core.get_links( - 'SMTP', req['os'], req['lc'] - ) - # if core fails, we fail too - except (core.InternalError, core.ConfigError) as e: - self.log.debug("FAILED: %s" % str(e)) - # something went wrong with the core - raise InternalError("Error obtaining the links") - - # make sure we can send emails - self.log.debug("Trying to send the links...") - try: - self._send_links(links, req['lc'], req['os'], our_addr, - norm_from_addr) - except SendEmailError as e: - self.log.debug("FAILED: %s" % str(e)) - raise SendEmailError("Something's wrong with the SMTP " - "server: %s" % str(e)) - finally: - self.log.debug("Request processed") diff --git a/gettor/utils.py b/gettor/utils.py deleted file mode 100644 index 2780564..0000000 --- a/gettor/utils.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of GetTor, a Tor Browser distribution system. -# -# :authors: Israel Leiva <ilv@riseup.net> -# see also AUTHORS file -# -# :copyright: (c) 2008-2014, The Tor Project, Inc. -# (c) 2014, Israel Leiva -# -# :license: This is Free Software. See LICENSE for license information. - -import os -import re -import hashlib - -"""Common utilities for GetTor modules.""" - - -LOGGING_FORMAT = "[%(levelname)s] %(asctime)s; %(message)s" -DATE_FORMAT = "%Y-%m-%d" # %H:%M:%S - -windows_regex = '^torbrowser-install-\d\.\d(\.\d)?_(\w\w)(-\w\w)?\.exe$' -linux_regex = '^tor-browser-linux(\d\d)-\d\.\d(\.\d)?_(\w\w)(-\w\w)?\.tar\.xz$' -osx_regex = '^TorBrowser-\d\.\d(\.\d)?-osx\d\d_(\w\w)(-\w\w)?\.dmg$' - - -def get_logging_format(): - """Get the logging format. - - :return: (string) the logging format. - - """ - return LOGGING_FORMAT - - -def get_date_format(): - """Get the date format for logging. - - :return: (string) the date format for logging. - - """ - return DATE_FORMAT - - -def get_sha256(string): - """Get sha256 of a string. - - :param: (string) the string to be hashed. - - :return: (string) the sha256 of string. - - """ - return str(hashlib.sha256(string).hexdigest()) - - -def get_bundle_info(filename, osys=None): - """Get the os, arch and lc from a bundle string. - - :param: file (string) the name of the file. - :param: osys (string) the OS. - - :raise: ValueError if the bundle doesn't have a valid bundle format. - - :return: (list) the os, arch and lc. - - """ - m_windows = re.search(windows_regex, filename) - m_linux = re.search(linux_regex, filename) - m_osx = re.search(osx_regex, filename) - - if m_windows: - return 'windows', '32/64', m_windows.group(2) - elif m_linux: - return 'linux', m_linux.group(1), m_linux.group(3) - elif m_osx: - return 'osx', '64', m_osx.group(2) - else: - raise ValueError("Invalid bundle format %s" % file) - - -def valid_format(filename, osys=None): - """Check for valid bundle format - - Check if the given file has a valid bundle format - (e.g. tor-browser-linux32-3.6.2_es-ES.tar.xz) - - :param: file (string) the name of the file. - - :return: (boolean) true if the bundle format is valid, false otherwise. - - """ - m_windows = re.search(windows_regex, filename) - m_linux = re.search(linux_regex, filename) - m_osx = re.search(osx_regex, filename) - if any((m_windows, m_linux, m_osx)): - return True - else: - return False - - -def get_file_sha256(file): - """Get the sha256 of a file. - - :param: file (string) the path of the file. - - :return: (string) the sha256 hash. - - """ - # as seen on the internetz - BLOCKSIZE = 65536 - hasher = hashlib.sha256() - with open(file, 'rb') as afile: - buf = afile.read(BLOCKSIZE) - while len(buf) > 0: - hasher.update(buf) - buf = afile.read(BLOCKSIZE) - return hasher.hexdigest() - - -def find_files_to_upload(upload_dir): - """ - Find the files which are named correctly and have a .asc file - """ - files = [] - for name in os.listdir(upload_dir): - asc_file = os.path.join(upload_dir, "{}.asc".format(name)) - if valid_format(name) and os.path.isfile(asc_file): - files.extend([name, "{}.asc".format(name)]) - - return files diff --git a/gettor/blacklist.py b/gettor/utils/blacklist.py index 635d9a7..0223fc3 100644 --- a/gettor/blacklist.py +++ b/gettor/utils/blacklist.py @@ -15,7 +15,7 @@ import time import logging import sqlite3 import datetime -import ConfigParser +import configparser import db import utils diff --git a/gettor/utils/commons.py b/gettor/utils/commons.py new file mode 100644 index 0000000..212c4bb --- /dev/null +++ b/gettor/utils/commons.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# +# This file is part of GetTor, a Tor Browser distribution system. +# +# :authors: isra <ilv@torproject.org> +# see also AUTHORS file +# +# :copyright: (c) 2008-2014, The Tor Project, Inc. +# (c) 2014-2018, Israel Leiva +# +# :license: This is Free Software. See LICENSE for license information. + +from twisted.logger import Logger +from twisted.enterprise import adbapi +from twisted.application import internet + +# Define an application logger +log = Logger(namespace="gettor") + +class BaseService(internet.TimerService): + """ + Base service for Email and Sendmail. It extends the TimerService. + """ + + def __init__(self, name, step, instance, *args, **kwargs): + """ + Constructor. + + :param name (str): name of the service (just for logging purposes). + :param step (float): time interval for TimerService, in seconds. + :param instance (object): instance of Email, Sendmail classes + """ + + log.info("SERVICE:: Initializing {} service.".format(name)) + self.name = name + self.instance = instance + log.debug("SERVICE:: Initializing TimerService.") + internet.TimerService.__init__( + self, step, self.instance.get_new, **kwargs + ) + + def startService(self): + """ + Starts the service. Overridden from parent class to add extra logging + information. + """ + log.info("SERVICE:: Starting {} service.".format(self.name)) + internet.TimerService.startService(self) + log.info("SERVICE:: Service started.") + + def stopService(self): + """ + Stop the service. Overridden from parent class to close connection to + database, shutdown the service and add extra logging information. + """ + log.info("SERVICE:: Stopping {} service.".format(self.name)) + internet.TimerService.stopService(self) + log.info("SERVICE:: Service stopped.") diff --git a/gettor/db.py b/gettor/utils/db.py index 7a595a3..7a595a3 100644 --- a/gettor/db.py +++ b/gettor/utils/db.py diff --git a/gettor/utils/options.py b/gettor/utils/options.py new file mode 100644 index 0000000..742eb7f --- /dev/null +++ b/gettor/utils/options.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +""" +This file is part of GetTor, a service providing alternative methods to download +the Tor Browser. + +:authors: Hiro <hiro@torproject.org> + please also see AUTHORS file +:copyright: (c) 2008-2014, The Tor Project, Inc. + (c) 2014, all entities within the AUTHORS file +:license: see included LICENSE for information +""" + +import argparse + + +def parseOptions(): + # Parse arguments + parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=28)) + return parser.parse_args() diff --git a/gettor/strings.py b/gettor/utils/strings.py index 45c33ce..512116b 100644 --- a/gettor/strings.py +++ b/gettor/utils/strings.py @@ -13,6 +13,7 @@ the Tor Browser. import json import locale import os +import inspect strings = {} translations = {} @@ -22,9 +23,9 @@ def get_resource_path(filename): """ Returns the absolute path of a resource """ - prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share') + prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), '../share') if not os.path.exists(prefix): - prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share') + prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), '../share') return os.path.join(prefix, filename) diff --git a/gettor/http.py b/gettor/web/http.py index 081b81c..77120d6 100644 --- a/gettor/http.py +++ b/gettor/web/http.py @@ -15,7 +15,7 @@ import re import json import codecs import urllib2 -import ConfigParser +import configparser from time import gmtime, strftime diff --git a/scripts/gettor b/scripts/gettor new file mode 100644 index 0000000..2a39892 --- /dev/null +++ b/scripts/gettor @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +This file is part of GetTor, a service providing alternative methods to download +the Tor Browser. + +:authors: Hiro <hiro@torproject.org> + please also see AUTHORS file +:copyright: (c) 2008-2014, The Tor Project, Inc. + (c) 2014, all entities within the AUTHORS file +:license: see included LICENSE for information +""" + +from __future__ import print_function + +import os.path +import sys + +from os import getcwd + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from gettor.main import run +from gettor.utils.options import parseOptions + +option = parseOptions() + +run(option) |
