# -*- coding: utf-8 -*- # smtpbootstrapper.py # Copyright (C) 2013 LEAP # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ SMTP bootstrapping """ import os import warnings from collections import namedtuple from requests.exceptions import HTTPError from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.certs import download_client_cert from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.mail.smtpconfig import SMTPConfig from leap.common import certs as leap_certs from leap.common.check import leap_assert from leap.common.files import check_and_fix_urw_only logger = get_logger() class NoSMTPHosts(Exception): """This is raised when there is no SMTP host to use.""" class MalformedUserId(Exception): """This is raised when an userid does not have the form user@provider.""" class SMTPBootstrapper(AbstractBootstrapper): """ SMTP init procedure """ PORT_KEY = "port" IP_KEY = "ip_address" def __init__(self): AbstractBootstrapper.__init__(self) self._provider_config = None self._smtp_config = None self._userid = None self._download_if_needed = False self._smtp_service = None self._smtp_port = None def _download_config_and_cert(self): """ Downloads the SMTP config and cert for the given provider. """ leap_assert(self._provider_config, "We need a provider configuration!") logger.debug("Downloading SMTP config for %s" % (self._provider_config.get_domain(),)) download_service_config( self._provider_config, self._smtp_config, self._session, self._download_if_needed) hosts = self._smtp_config.get_hosts() if len(hosts) == 0: raise NoSMTPHosts() # TODO handle more than one host and define how to choose hostname = hosts.keys()[0] logger.debug("Using hostname %s for SMTP" % (hostname,)) client_cert_path = self._smtp_config.get_client_cert_path( self._userid, self._provider_config, about_to_download=True) needs_download = leap_certs.should_redownload(client_cert_path) if needs_download: # For re-download if something is wrong with the cert # FIXME this doesn't read well. should reword the logic here. self._download_if_needed = ( self._download_if_needed and not needs_download) if self._download_if_needed and os.path.isfile(client_cert_path): check_and_fix_urw_only(client_cert_path) return try: download_client_cert(self._provider_config, client_cert_path, self._session, kind="smtp") except HTTPError as exc: if exc.message.startswith('403 Client Error'): logger.debug( 'Auth problem downloading smtp certificate... ' 'It might be a provider problem, will try ' 'fetching from vpn pool') warnings.warn( 'Compatibility hack for platform 0.7 not fully ' 'supporting smtp certificates. Will be deprecated in ' 'bitmask 0.10') download_client_cert(self._provider_config, client_cert_path, self._session, kind="vpn") else: raise def _start_smtp_service(self): """ Start the smtp service using the downloaded configurations. """ # TODO Make the encrypted_only configurable # TODO handle more than one host and define how to choose hosts = self._smtp_config.get_hosts() hostname = hosts.keys()[0] host = hosts[hostname][self.IP_KEY].encode("utf-8") port = hosts[hostname][self.PORT_KEY] client_cert_path = self._smtp_config.get_client_cert_path( self._userid, self._provider_config, about_to_download=True) # XXX this should be defined in leap.mail.smtp, it's in bitmask.core # right now. SendmailOpts = namedtuple( 'SendmailOpts', ['cert', 'key', 'hostname', 'port']) userid = self._userid soledad_sessions = {userid: self._soledad} keymanager_sessions = {userid: self._keymanager} key = cert = client_cert_path opts = SendmailOpts(cert, key, host, port) sendmail_opts = {userid: opts} from leap.mail.smtp import run_service self._smtp_service, self._smtp_port = run_service( soledad_sessions, keymanager_sessions, sendmail_opts) def start_smtp_service(self, soledad, keymanager, userid, download_if_needed=False): """ Starts the SMTP service. :param keymanager: a transparent proxy that eventually will point to a Keymanager Instance. :type keymanager: zope.proxy.ProxyBase :param userid: the user id, in the form "user@provider" :type userid: str :param download_if_needed: True if it should check for mtime for the file :type download_if_needed: bool """ try: username, domain = userid.split('@') except ValueError: logger.critical("Malformed userid parameter!") raise MalformedUserId() self._provider_config = ProviderConfig.get_provider_config(domain) self._soledad = soledad self._keymanager = keymanager self._smtp_config = SMTPConfig() self._userid = str(userid) self._download_if_needed = download_if_needed try: self._download_config_and_cert() logger.debug("Starting SMTP service.") self._start_smtp_service() except NoSMTPHosts: logger.warning("There is no SMTP host to use.") except Exception as e: # TODO: we should handle more specific exceptions in here logger.exception("Error while bootstrapping SMTP: %r" % (e, )) def stop_smtp_service(self): """ Stops the smtp service (port and factory). """ if self._smtp_service is not None: logger.debug('Stopping SMTP service.') self._smtp_port.stopListening() self._smtp_service.doStop() else: logger.debug('SMTP service not running.')