Ejemplo n.º 1
0
def SendMail(session, msg_mid, from_to_msg_ev_tuples):
    routes = _RouteTuples(session, from_to_msg_ev_tuples)

    # Randomize order of routes, so we don't always try the broken
    # one first. Any failure will bail out, but we do keep track of
    # our successes via. the event, so eventually everything sendable
    # should get sent.
    routes.sort(key=lambda k: random.randint(0, 10))

    # Update initial event state before we go through and start
    # trying to deliver stuff.
    for frm, sendmail, to, msg, events in routes:
        for ev in (events or []):
            for rcpt in to:
                ev.private_data['>'.join([frm, rcpt])] = False

    for frm, sendmail, to, msg, events in routes:
        for ev in events:
            ev.data['recipients'] = len(ev.private_data.keys())
            ev.data['delivered'] = len(
                [k for k in ev.private_data if ev.private_data[k]])

    def mark(msg, events, log=True):
        for ev in events:
            ev.flags = Event.RUNNING
            ev.message = msg
            if log:
                session.config.event_log.log_event(ev)
        session.ui.mark(msg)

    def fail(msg, events):
        mark(msg, events, log=True)
        for ev in events:
            ev.data['last_error'] = msg
        raise SendMailError(msg)

    def smtp_do_or_die(msg, events, method, *args, **kwargs):
        rc, msg = method(*args, **kwargs)
        if rc != 250:
            fail(msg + ' (%s %s)' % (rc, msg), events)

    # Do the actual delivering...
    for frm, sendmail, to, msg, events in routes:
        frm_vcard = session.config.vcards.get_vcard(frm)

        if 'sendmail' in session.config.sys.debug:
            sys.stderr.write(
                _('SendMail: from %s (%s), to %s via %s\n') %
                (frm, frm_vcard and frm_vcard.random_uid
                 or '', to, sendmail.split('@')[-1]))
        sm_write = sm_close = lambda: True

        mark(_('Connecting to %s') % sendmail.split('@')[-1], events)

        if sendmail.startswith('|'):
            sendmail %= {"rcpt": ",".join(to)}
            cmd = sendmail[1:].strip().split()
            proc = Popen(cmd, stdin=PIPE, long_running=True)
            sm_startup = None
            sm_write = proc.stdin.write

            def sm_close():
                proc.stdin.close()
                rv = proc.wait()
                if rv != 0:
                    fail(_('%s failed with exit code %d') % (cmd, rv), events)

            sm_cleanup = lambda: [proc.stdin.close(), proc.wait()]
            # FIXME: Update session UI with progress info
            for ev in events:
                ev.data['proto'] = 'subprocess'
                ev.data['command'] = cmd[0]

        elif (sendmail.startswith('smtp:') or sendmail.startswith('smtorp:')
              or sendmail.startswith('smtpssl:')
              or sendmail.startswith('smtptls:')):
            proto = sendmail.split(':', 1)[0]
            host, port = sendmail.split(':', 1)[1].replace('/',
                                                           '').rsplit(':', 1)
            smtp_ssl = proto in ('smtpssl', )  # FIXME: 'smtorp'
            if '@' in host:
                userpass, host = host.rsplit('@', 1)
                user, pwd = userpass.split(':', 1)
            else:
                user = pwd = None

            for ev in events:
                ev.data['proto'] = proto
                ev.data['host'] = host
                ev.data['auth'] = bool(user and pwd)

            if 'sendmail' in session.config.sys.debug:
                sys.stderr.write(
                    _('SMTP connection to: %s:%s as %s\n') %
                    (host, port, user or '(anon)'))

            server = (smtp_ssl and SMTP_SSL
                      or SMTP)(local_hostname='mailpile.local', timeout=25)

            def sm_startup():
                if 'sendmail' in session.config.sys.debug:
                    server.set_debuglevel(1)
                if proto == 'smtorp':
                    server.connect(host,
                                   int(port),
                                   socket_cls=session.config.get_tor_socket())
                else:
                    server.connect(host, int(port))
                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()
                    except:
                        if sendmail.startswith('smtptls'):
                            raise InsecureSmtpError()
                if user and pwd:
                    try:
                        server.login(user, pwd)
                    except smtplib.SMTPAuthenticationError:
                        fail(_('Invalid username or password'), events)

                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 recpient: %s') % rcpt, events)
                rcode, rmsg = server.docmd('DATA')
                if rcode != 354:
                    fail(_('Server rejected DATA: %s %s') % (rcode, rmsg))

            def sm_write(data):
                for line in data.splitlines(True):
                    if line.startswith('.'):
                        server.send('.')
                    server.send(line)

            def sm_close():
                server.send('\r\n.\r\n')
                smtp_do_or_die(_('Error spooling mail'), events,
                               server.getreply)

            def sm_cleanup():
                if hasattr(server, 'sock'):
                    server.close()
        else:
            fail(_('Invalid sendmail command/SMTP server: %s') % sendmail)

        try:
            # Run the entire connect/login sequence in a single timer...
            if sm_startup:
                RunTimed(30, sm_startup)

            mark(_('Preparing message...'), events)
            msg_string = MessageAsString(CleanMessage(session.config, msg))
            total = len(msg_string)
            while msg_string:
                if mailpile.util.QUITTING:
                    raise TimedOut(_('Quitting'))
                mark(('Sending message... (%d%%)') %
                     (100 * (total - len(msg_string)) / total),
                     events,
                     log=False)
                RunTimed(20, sm_write, msg_string[:20480])
                msg_string = msg_string[20480:]
            RunTimed(10, sm_close)

            mark(
                _n('Message sent, %d byte', 'Message sent, %d bytes', total) %
                total, events)
            for ev in events:
                for rcpt in to:
                    vcard = session.config.vcards.get_vcard(rcpt)
                    if vcard:
                        vcard.record_history('send', time.time(), msg_mid)
                        if frm_vcard:
                            vcard.prefer_sender(rcpt, frm_vcard)
                        vcard.save()
                    ev.private_data['>'.join([frm, rcpt])] = True
                ev.data['bytes'] = total
                ev.data['delivered'] = len(
                    [k for k in ev.private_data if ev.private_data[k]])
        finally:
            sm_cleanup()
Ejemplo n.º 2
0
def SendMail(session, from_to_msg_tuples):

    # FIXME: We need to handle the case where we are retrying a message
    #        that we could not deliver before. Some of the tuples may
    # have been delivered already, and some not.  #eventlog

    for frm, sendmail, to, msg in _RouteTuples(session, from_to_msg_tuples):
        if 'sendmail' in session.config.sys.debug:
            sys.stderr.write(
                _('SendMail: from %s, to %s via %s\n') % (frm, to, sendmail))
        sm_write = sm_close = lambda: True
        session.ui.mark(_('Connecting to %s') % sendmail)

        if sendmail.startswith('|'):
            cmd = sendmail[1:].strip().split()
            proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
            sm_write = proc.stdin.write
            sm_close = proc.stdin.close
            sm_cleanup = lambda: proc.wait()
            # FIXME: Update session UI with progress info

        elif (sendmail.startswith('smtp:') or sendmail.startswith('smtorp:')
              or sendmail.startswith('smtpssl:')
              or sendmail.startswith('smtptls:')):
            proto = sendmail.split(':', 1)[0]
            host, port = sendmail.split(':', 1)[1].replace('/',
                                                           '').rsplit(':', 1)
            smtp_ssl = proto in ('smtpssl', )  # FIXME: 'smtorp'
            if '@' in host:
                userpass, host = host.rsplit('@', 1)
                user, pwd = userpass.split(':', 1)
            else:
                user = pwd = None

            if 'sendmail' in session.config.sys.debug:
                sys.stderr.write(
                    _('SMTP connection to: %s:%s as %s@%s\n') %
                    (host, port, user, pwd))

            server = smtp_ssl and SMTP_SSL() or SMTP()
            if proto == 'smtorp':
                server.connect(host,
                               int(port),
                               socket_cls=session.config.get_tor_socket())
            else:
                server.connect(host, int(port))
            server.ehlo()
            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()
                except:
                    if sendmail.startswith('smtptls'):
                        raise InsecureSmtpError()
            if user and pwd:
                server.login(user, pwd)

            server.mail(frm)
            for rcpt in to:
                server.rcpt(rcpt)
            server.docmd('DATA')

            def sender(data):
                for line in data.splitlines(1):
                    if line.startswith('.'):
                        server.send('.')
                    server.send(line)

            def closer():
                server.send('\r\n.\r\n')
                server.quit()

            sm_write = sender
            sm_close = closer
            sm_cleanup = lambda: True
        else:
            raise Exception(
                _('Invalid sendmail command/SMTP server: %s') % sendmail)

        session.ui.mark(_('Preparing message...'))
        msg_string = MessageAsString(CleanMessage(session.config, msg))
        total = len(msg_string)
        while msg_string:
            sm_write(msg_string[:20480])
            msg_string = msg_string[20480:]
            session.ui.mark(('Sending message... (%d%%)') %
                            (100 * (total - len(msg_string)) / total))
        sm_close()
        sm_cleanup()
        session.ui.mark(_('Message sent, %d bytes') % total)
Ejemplo n.º 3
0
def SendMail(session, from_to_msg_ev_tuples):
    routes = _RouteTuples(session, from_to_msg_ev_tuples)

    # Randomize order of routes, so we don't always try the broken
    # one first. Any failure will bail out, but we do keep track of
    # our successes via. the event, so eventually everything sendable
    # should get sent.
    routes.sort(key=lambda k: random.randint(0, 10))

    # Update initial event state before we go through and start
    # trying to deliver stuff.
    for frm, sendmail, to, msg, events in routes:
        for ev in (events or []):
            for rcpt in to:
                ev.private_data['>'.join([frm, rcpt])] = False

    for frm, sendmail, to, msg, events in routes:
        for ev in events:
            ev.data['recipients'] = len(ev.private_data.keys())
            ev.data['delivered'] = len(
                [k for k in ev.private_data if ev.private_data[k]])

    def mark(msg, events, log=True):
        for ev in events:
            ev.flags = Event.RUNNING
            ev.message = msg
            if log:
                session.config.event_log.log_event(ev)
        session.ui.mark(msg)

    # Do the actual delivering...
    for frm, sendmail, to, msg, events in routes:

        if 'sendmail' in session.config.sys.debug:
            sys.stderr.write(
                _('SendMail: from %s, to %s via %s\n') % (frm, to, sendmail))
        sm_write = sm_close = lambda: True
        mark(_('Connecting to %s') % sendmail, events)

        if sendmail.startswith('|'):
            sendmail %= {"rcpt": ",".join(to)}
            cmd = sendmail[1:].strip().split()
            proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
            sm_write = proc.stdin.write
            sm_close = proc.stdin.close
            sm_cleanup = lambda: proc.wait()
            # FIXME: Update session UI with progress info
            for ev in events:
                ev.data['proto'] = 'subprocess'
                ev.data['command'] = cmd[0]

        elif (sendmail.startswith('smtp:') or sendmail.startswith('smtorp:')
              or sendmail.startswith('smtpssl:')
              or sendmail.startswith('smtptls:')):
            proto = sendmail.split(':', 1)[0]
            host, port = sendmail.split(':', 1)[1].replace('/',
                                                           '').rsplit(':', 1)
            smtp_ssl = proto in ('smtpssl', )  # FIXME: 'smtorp'
            if '@' in host:
                userpass, host = host.rsplit('@', 1)
                user, pwd = userpass.split(':', 1)
            else:
                user = pwd = None

            for ev in events:
                ev.data['proto'] = proto
                ev.data['host'] = host
                ev.data['auth'] = bool(user and pwd)

            if 'sendmail' in session.config.sys.debug:
                sys.stderr.write(
                    _('SMTP connection to: %s:%s as %s@%s\n') %
                    (host, port, user, pwd))

            server = smtp_ssl and SMTP_SSL() or SMTP()
            if proto == 'smtorp':
                server.connect(host,
                               int(port),
                               socket_cls=session.config.get_tor_socket())
            else:
                server.connect(host, int(port))
            server.ehlo()
            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()
                except:
                    if sendmail.startswith('smtptls'):
                        raise InsecureSmtpError()
            if user and pwd:
                server.login(user, pwd)

            server.mail(frm)
            for rcpt in to:
                server.rcpt(rcpt)
            server.docmd('DATA')

            def sender(data):
                for line in data.splitlines(1):
                    if line.startswith('.'):
                        server.send('.')
                    server.send(line)

            def closer():
                server.send('\r\n.\r\n')
                server.quit()

            sm_write = sender
            sm_close = closer
            sm_cleanup = lambda: True
        else:
            raise Exception(
                _('Invalid sendmail command/SMTP server: %s') % sendmail)

        mark(_('Preparing message...'), events)
        msg_string = MessageAsString(CleanMessage(session.config, msg))
        total = len(msg_string)
        while msg_string:
            sm_write(msg_string[:20480])
            msg_string = msg_string[20480:]
            mark(('Sending message... (%d%%)') %
                 (100 * (total - len(msg_string)) / total),
                 events,
                 log=False)
        sm_close()
        sm_cleanup()
        for ev in events:
            for rcpt in to:
                ev.private_data['>'.join([frm, rcpt])] = True
            ev.data['bytes'] = total
            ev.data['delivered'] = len(
                [k for k in ev.private_data if ev.private_data[k]])
        mark(
            _n('Message sent, %d byte', 'Message sent, %d bytes', total) %
            total, events)
Ejemplo n.º 4
0
def SendMail(session,
             msg_mid,
             from_to_msg_ev_tuples,
             test_only=False,
             test_route=None):
    routes = _RouteTuples(session,
                          from_to_msg_ev_tuples,
                          test_route=test_route)

    # Randomize order of routes, so we don't always try the broken
    # one first. Any failure will bail out, but we do keep track of
    # our successes via. the event, so eventually everything sendable
    # should get sent.
    routes.sort(key=lambda k: random.randint(0, 10))

    # Update initial event state before we go through and start
    # trying to deliver stuff.
    for frm, route, to, msg, events in routes:
        for ev in (events or []):
            for rcpt in to:
                ev.private_data['>'.join([frm, rcpt])] = False

    for frm, route, to, msg, events in routes:
        for ev in events:
            ev.data['recipients'] = len(ev.private_data.keys())
            ev.data['delivered'] = len(
                [k for k in ev.private_data if ev.private_data[k]])

    def mark(msg, events, log=True):
        for ev in events:
            ev.flags = Event.RUNNING
            ev.message = msg
            if log:
                session.config.event_log.log_event(ev)
        session.ui.mark(msg)

    def fail(msg, events, details=None):
        mark(msg, events, log=True)
        for ev in events:
            ev.data['last_error'] = msg
        raise SendMailError(msg, details=details)

    def smtp_do_or_die(msg, events, method, *args, **kwargs):
        rc, msg = method(*args, **kwargs)
        if rc != 250:
            fail(msg + ' (%s %s)' % (rc, msg),
                 events,
                 details={'smtp_error': '%s: %s' % (rc, msg)})

    # Do the actual delivering...
    for frm, route, to, msg, events in routes:
        route_description = route['command'] or route['host']

        frm_vcard = session.config.vcards.get_vcard(frm)
        update_to_vcards = msg and msg["x-mp-internal-pubkeys-attached"]

        if 'sendmail' in session.config.sys.debug:
            sys.stderr.write(
                _('SendMail: from %s (%s), to %s via %s\n') %
                (frm, frm_vcard and frm_vcard.random_uid
                 or '', to, route_description))
        sm_write = sm_close = lambda: True

        mark(_('Sending via %s') % route_description, events)

        if route['command']:
            # Note: The .strip().split() here converts our cmd into a list,
            #       which should ensure that Popen does not spawn a shell
            #       with potentially exploitable arguments.
            cmd = (route['command'] % {"rcpt": ",".join(to)}).strip().split()
            proc = Popen(cmd, stdin=PIPE, long_running=True)
            sm_startup = None
            sm_write = proc.stdin.write

            def sm_close():
                proc.stdin.close()
                rv = proc.wait()
                if rv != 0:
                    fail(_('%s failed with exit code %d') % (cmd, rv),
                         events,
                         details={
                             'failed_command': cmd,
                             'exit_code': rv
                         })

            sm_cleanup = lambda: [proc.stdin.close(), proc.wait()]
            # FIXME: Update session UI with progress info
            for ev in events:
                ev.data['proto'] = 'subprocess'
                ev.data['command'] = cmd[0]

        elif route['protocol'] in ('smtp', 'smtorp', 'smtpssl', 'smtptls'):
            proto = route['protocol']
            host, port = route['host'], route['port']
            user, pwd = route['username'], route['password']
            smtp_ssl = proto in ('smtpssl', )  # FIXME: 'smtorp'

            for ev in events:
                ev.data['proto'] = proto
                ev.data['host'] = host
                ev.data['auth'] = bool(user and pwd)

            if 'sendmail' in session.config.sys.debug:
                sys.stderr.write(
                    _('SMTP connection to: %s:%s as %s\n') %
                    (host, port, user or '(anon)'))

            serverbox = [None]

            def sm_connect_server():
                server = (smtp_ssl and SMTP_SSL
                          or SMTP)(local_hostname='mailpile.local', timeout=25)
                if 'sendmail' in session.config.sys.debug:
                    server.set_debuglevel(1)
                if smtp_ssl or proto in ('smtorp', 'smtptls'):
                    conn_needs = [ConnBroker.OUTGOING_ENCRYPTED]
                else:
                    conn_needs = [ConnBroker.OUTGOING_SMTP]
                try:
                    with ConnBroker.context(need=conn_needs) as ctx:
                        server.connect(host, int(port))
                        server.ehlo_or_helo_if_needed()
                except (IOError, OSError, smtplib.SMTPServerDisconnected):
                    fail(_('Failed to connect to %s') % host,
                         events,
                         details={'connection_error': True})

                return server

            def sm_startup():
                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 InsecureSmtpError()
                        else:
                            server = sm_connect_server()
                serverbox[0] = server

                if user and pwd:
                    try:
                        server.login(user.encode('utf-8'), pwd.encode('utf-8'))
                    except UnicodeDecodeError:
                        fail(_('Bad character in username or password'),
                             events,
                             details={'authentication_error': True})
                    except smtplib.SMTPAuthenticationError:
                        fail(_('Invalid username or password'),
                             events,
                             details={'authentication_error': True})
                    except smtplib.SMTPException:
                        fail(_('Authentication not supported'),
                             events,
                             details={'authentication_error': True})

                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))

            def sm_write(data):
                server = serverbox[0]
                for line in data.splitlines(True):
                    if line.startswith('.'):
                        server.send('.')
                    server.send(line)

            def sm_close():
                server = serverbox[0]
                server.send('\r\n.\r\n')
                smtp_do_or_die(_('Error spooling mail'), events,
                               server.getreply)

            def sm_cleanup():
                server = serverbox[0]
                if hasattr(server, 'sock'):
                    server.close()
        else:
            fail(_('Invalid route: %s') % route, events)

        try:
            # Run the entire connect/login sequence in a single timer, but
            # give it plenty of time in case the network is lame.
            if sm_startup:
                RunTimed(300, sm_startup)

            if test_only:
                return True

            mark(_('Preparing message...'), events)

            msg_string = MessageAsString(CleanMessage(session.config, msg))
            total = len(msg_string)
            while msg_string:
                if mailpile.util.QUITTING:
                    raise TimedOut(_('Quitting'))
                mark(('Sending message... (%d%%)') %
                     (100 * (total - len(msg_string)) / total),
                     events,
                     log=False)
                RunTimed(120, sm_write, msg_string[:20480])
                msg_string = msg_string[20480:]
            RunTimed(30, sm_close)

            mark(
                _n('Message sent, %d byte', 'Message sent, %d bytes', total) %
                total, events)

            for ev in events:
                for rcpt in to:
                    vcard = session.config.vcards.get_vcard(rcpt)
                    if vcard:
                        vcard.record_history('send', time.time(), msg_mid)
                        if frm_vcard:
                            vcard.prefer_sender(rcpt, frm_vcard)
                        if update_to_vcards:
                            vcard.pgp_key_shared = int(time.time())
                        vcard.save()
                    ev.private_data['>'.join([frm, rcpt])] = True
                ev.data['bytes'] = total
                ev.data['delivered'] = len(
                    [k for k in ev.private_data if ev.private_data[k]])
        finally:
            sm_cleanup()
    return True