Example #1
0
def _connect_imap(session,
                  settings,
                  event,
                  conn_cls=None,
                  timeout=30,
                  throw=False,
                  logged_in_cb=None,
                  source=None):
    def timed(*args, **kwargs):
        if source is not None:
            kwargs['unique_thread'] = 'imap/%s' % (source.my_config._key, )
        return RunTimed(timeout, *args, **kwargs)

    def timed_imap(*args, **kwargs):
        if source is not None:
            kwargs['unique_thread'] = 'imap/%s' % (source.my_config._key, )
        return _parse_imap(RunTimed(timeout, *args, **kwargs))

    conn = None
    try:
        # Prepare the data section of our event, for keeping state.
        for d in ('mailbox_state', ):
            if d not in event.data:
                event.data[d] = {}
        ev = event.data['connection'] = {
            'live': False,
            'error': [False, _('Nothing is wrong')]
        }

        # If we are given a conn class, use that - this allows mocks for
        # testing.
        if not conn_cls:
            req_stls = (settings.get('protocol') == 'imap_tls')
            want_ssl = (settings.get('protocol') == 'imap_ssl')
            conn_cls = IMAP4_SSL if want_ssl else IMAP4
        else:
            req_stls = want_ssl = False

        def mkconn():
            if want_ssl:
                need = [ConnBroker.OUTGOING_IMAPS]
            else:
                need = [ConnBroker.OUTGOING_IMAP]
            with ConnBroker.context(need=need):
                return conn_cls(settings.get('host'),
                                int(settings.get('port')))

        conn = timed(mkconn)
        if hasattr(conn, 'sock'):
            conn.sock.settimeout(120)
        conn.debug = ('imaplib' in session.config.sys.debug) and 4 or 0

        ok, data = timed_imap(conn.capability)
        if ok:
            capabilities = set(' '.join(data).upper().split())
        else:
            capabilities = set()

        if req_stls or ('STARTTLS' in capabilities and not want_ssl):
            try:
                ok, data = timed_imap(conn.starttls)
                if ok:
                    # Fetch capabilities again after STARTTLS
                    ok, data = timed_imap(conn.capability)
                    capabilities = set(' '.join(data).upper().split())
                    # Update the protocol to avoid getting downgraded later
                    if settings.get('protocol', '') != 'imap_ssl':
                        settings['protocol'] = 'imap_tls'
            except (IMAP4.error, IOError, socket.error):
                ok = False
            if not ok:
                ev['error'] = [
                    'tls',
                    _('Failed to make a secure TLS connection'),
                    '%s:%s' % (settings.get('host'), settings.get('port'))
                ]
                if throw:
                    raise throw(ev['error'][1])
                return WithaBool(False)

        username = password = ""
        try:
            error_type = 'auth'
            error_msg = _('Invalid username or password')
            username = settings.get('username', '').encode('utf-8')
            password = IndirectPassword(session.config,
                                        settings.get('password',
                                                     '')).encode('utf-8')

            if (settings.get('auth_type', '').lower() == 'oauth2'
                    and 'AUTH=XOAUTH2' in capabilities):
                error_type = 'oauth2'
                error_msg = _('Access denied by mail server')
                token_info = OAuth2.GetFreshTokenInfo(session, username)
                if not (username and token_info and token_info.access_token):
                    raise ValueError("Missing configuration")
                ok, data = timed_imap(
                    conn.authenticate, 'XOAUTH2',
                    lambda challenge: OAuth2.XOAuth2Response(
                        username, token_info))
                if not ok:
                    token_info.access_token = ''

            else:
                ok, data = timed_imap(conn.login, username, password)

        except (IMAP4.error, UnicodeDecodeError, ValueError):
            ok, data = False, None
        if not ok:
            auth_summary = ''
            if source is not None:
                auth_summary = source._summarize_auth()
            ev['error'] = [error_type, error_msg, username, auth_summary]
            if throw:
                raise throw(ev['error'][1])
            return WithaBool(False)

        if logged_in_cb is not None:
            logged_in_cb(conn, ev, capabilities)

        return conn

    except TimedOut:
        if 'imap' in session.config.sys.debug:
            session.ui.debug('%s' % traceback.format_exc())
        ev['error'] = ['timeout', _('Connection timed out')]
    except (ssl.CertificateError, ssl.SSLError):
        if 'imap' in session.config.sys.debug:
            session.ui.debug('%s' % traceback.format_exc())
        ev['error'] = [
            'tls',
            _('Failed to make a secure TLS connection'),
            '%s:%s' % (settings.get('host'), settings.get('port'))
        ]
    except (IMAP_IOError, IMAP4.error):
        if 'imap' in session.config.sys.debug:
            session.ui.debug('%s' % traceback.format_exc())
        ev['error'] = ['protocol', _('An IMAP protocol error occurred')]
    except (IOError, AttributeError, socket.error):
        if 'imap' in session.config.sys.debug:
            session.ui.debug('%s' % traceback.format_exc())
        ev['error'] = ['network', _('A network error occurred')]

    try:
        if conn:
            # Close the socket directly, in the hopes this will boot
            # any timed-out operations out of a hung state.
            conn.socket().shutdown(socket.SHUT_RDWR)
            conn.file.close()
    except (AttributeError, IOError, socket.error):
        pass
    if throw:
        raise throw(ev['error'])

    return None
Example #2
0
            def sm_startup():
                try:
                    server = sm_connect_server()
                    if not smtp_ssl:
                        # We always try to enable TLS, even if the user just
                        # requested plain-text smtp.  But we only throw errors
                        # if the user asked for encryption.
                        try:
                            server.starttls()
                            server.ehlo_or_helo_if_needed()
                        except:
                            if proto == 'smtptls':
                                raise
                            else:
                                server = sm_connect_server()
                except (ssl.CertificateError, ssl.SSLError):
                    fail(_('Failed to make a secure TLS connection'),
                         events,
                         details={
                             'tls_error': True,
                             'server': '%s:%d' % (host, port)},
                         exception=InsecureSmtpError)

                serverbox[0] = server

                if user:
                    try:
                        if auth_type.lower() == 'oauth2':
                            from mailpile.plugins.oauth import OAuth2
                            tok_info = OAuth2.GetFreshTokenInfo(session, user)
                            if not (user and tok_info and tok_info.access_token):
                                fail(_('Access denied by mail server'),
                                     events,
                                     details={'oauth_error': True,
                                              'username': user})
                            authstr = (OAuth2.XOAuth2Response(user, tok_info)
                                       ).encode('base64').replace('\n', '')
                            server.docmd('AUTH', 'XOAUTH2 ' + authstr)
                        else:
                            server.login(user.encode('utf-8'),
                                         (pwd or '').encode('utf-8'))
                    except UnicodeDecodeError:
                        fail(_('Bad character in username or password'),
                             events,
                             details={'authentication_error': True,
                                      'username': user})
                    except smtplib.SMTPAuthenticationError:
                        fail(_('Invalid username or password'), events,
                             details={'authentication_error': True,
                                      'username': user})
                    except smtplib.SMTPException:
                        # If the server does not support authentication, assume
                        # it's passwordless and try to carry one anyway.
                        pass

                smtp_do_or_die(_('Sender rejected by SMTP server'),
                               events, server.mail, frm)
                for rcpt in to:
                    rc, msg = server.rcpt(rcpt)
                    if (rc == SMTORP_HASHCASH_RCODE and
                            msg.startswith(SMTORP_HASHCASH_PREFIX)):
                        rc, msg = server.rcpt(SMTorP_HashCash(rcpt, msg))
                    if rc != 250:
                        fail(_('Server rejected recipient: %s') % rcpt, events)
                rcode, rmsg = server.docmd('DATA')
                if rcode != 354:
                    fail(_('Server rejected DATA: %s %s') % (rcode, rmsg))