summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhiro <hiro@torproject.org>2019-02-06 22:38:29 +0100
committerhiro <hiro@torproject.org>2019-02-06 22:38:29 +0100
commit02ae46d0ea3224c041b343832c5f30e8c072c3db (patch)
tree145f098c2655fe021bb13f830e15863e9ea4636e
parentbb0250909fd94a5ba9b6bbda7665e715177d89b7 (diff)
Update structure and code
-rw-r--r--.gitignore3
-rw-r--r--gettor/__init__.py6
-rw-r--r--gettor/core.py481
-rw-r--r--gettor/main.py57
-rw-r--r--gettor/parse/__init__.py1
-rw-r--r--gettor/parse/bridgedb31
-rw-r--r--gettor/parse/email.py217
-rw-r--r--gettor/services/__init__.py63
-rw-r--r--gettor/services/email/sendmail.py224
-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.py535
-rw-r--r--gettor/utils.py131
-rw-r--r--gettor/utils/blacklist.py (renamed from gettor/blacklist.py)2
-rw-r--r--gettor/utils/commons.py58
-rw-r--r--gettor/utils/db.py (renamed from gettor/db.py)0
-rw-r--r--gettor/utils/options.py19
-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/gettor28
20 files changed, 672 insertions, 1195 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..071a4ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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)