예제 #1
0
파일: tags.py 프로젝트: kitsuta/Mailpile
    def _do_tagging(self, ops, msg_ids, conversations, save=True, auto=False):
        idx = self._idx()
        rv = {"conversations": False, "msg_ids": [b36(i) for i in msg_ids], "tagged": [], "untagged": []}

        for op in ops:
            tag = self.session.config.get_tag(op[1:])
            if tag:
                # FIXME: This should depend on more factors!
                #    - Tags should have metadata about default scope
                if conversations is None:
                    conversation = "flat" not in (self.session.order or "")
                    if tag.flag_msg_only or tag.flag_editable or tag.type == "attribute":
                        conversation = False
                else:
                    conversation = conversations
                if conversation:
                    rv["conversations"] = True

                tag_id = tag._key
                tag = tag.copy()
                tag["tid"] = tag_id
                if op[0] == "-":
                    removed = idx.remove_tag(self.session, tag_id, msg_idxs=msg_ids, conversation=conversation)
                    rv["untagged"].append((tag, sorted([b36(i) for i in removed])))
                else:
                    added = idx.add_tag(self.session, tag_id, msg_idxs=msg_ids, conversation=conversation)
                    rv["tagged"].append((tag, sorted([b36(i) for i in added])))
                # Record behavior
                if len(msg_ids) < 15:
                    for t in self.session.config.get_tags(type="tagged"):
                        idx.add_tag(self.session, t._key, msg_idxs=msg_ids)
            else:
                self.session.ui.warning("Unknown tag: %s" % op)

        if rv["conversations"]:
            undo_msg = _n("Untag %d conversation", "Untag %d conversations", len(msg_ids)) % len(msg_ids)
            done_msg = _n("Tagged %d conversation", "Tagged %d conversations", len(msg_ids)) % len(msg_ids)
        else:
            undo_msg = _n("Untag %d message", "Untag %d messages", len(msg_ids)) % len(msg_ids)
            done_msg = _n("Tagged %d message", "Tagged %d messages", len(msg_ids)) % len(msg_ids)

        self.event.data["undo"] = undo_msg
        self.event.private_data["undo"] = {
            "tagged": [[t["tid"], mids] for t, mids in rv["tagged"]],
            "untagged": [[t["tid"], mids] for t, mids in rv["untagged"]],
        }

        self.finish(save=save)
        return self._success(done_msg, rv)
예제 #2
0
    def command(self):
        args = list(self.args)

        if len(args) > 1:
            allowremote = args.pop()
        else:
            allowremote = self.data.get('allowremote', ['Y'])[0]
        if allowremote.lower()[:1] in ('n', 'f'):
            allowremote = False

        origins = self.data.get('origins')
        if '*' in (origins or []):
            origins = [h.NAME for h in KEY_LOOKUP_HANDLERS]

        email = " ".join(self.data.get('email', []))
        address = " ".join(self.data.get('address', args))
        result = lookup_crypto_keys(self.session,
                                    email or address,
                                    strict_email_match=email,
                                    event=self.event,
                                    allowremote=allowremote,
                                    origins=origins)

        return self._success(
            _n('Found %d encryption key', 'Found %d encryption keys',
               len(result)) % len(result),
            result=result)
예제 #3
0
    def command(self):
        args = list(self.args)
        if args:
            address, fprints, origins = args[0], args[1].split(','), args[2:]
        else:
            address = self.data.get('address', [''])[0]
            fprints = self.data.get('fingerprints', [])
            origins = self.data.get('origins', [])
        safe_assert(address or fprints or origins)

        result = lookup_crypto_keys(self.session, address,
                                    get=[f.strip() for f in fprints],
                                    origins=origins,
                                    event=self.event)

        if len(result) > 0:
            # Update the VCards!
            PGPKeysImportAsVCards(self.session,
                                  arg=[k['fingerprint'] for k in result]
                                  ).run()
            # Previous crypto evaluations may now be out of date, so we
            # clear the cache so users can see results right away.
            ClearParseCache(pgpmime=True)

        return self._success(_n('Imported %d encryption key',
                                'Imported %d encryption keys',
                                len(result)) % len(result),
                             result=result)
예제 #4
0
    def command(self):
        args = list(self.args)

        if '--' in args:
            spoint = args.index('--')
            origins = (' '.join(args[(spoint + 1):])).split(', ')
            args = args[:spoint]
        else:
            origins = self.data.get('origins')

        if len(args) > 1:
            allowremote = args.pop()
        else:
            allowremote = self.data.get('allowremote', ['Y'])[0]
        if allowremote.lower()[:1] in ('n', 'f'):
            allowremote = False

        if '*' in (origins or []):
            origins = [h.NAME for h in KEY_LOOKUP_HANDLERS]

        email = " ".join(self.data.get('email', []))
        address = " ".join(self.data.get('address', args))
        result = dict((k.summary(), k)
                      for k in lookup_crypto_keys(self.session,
                                                  email or address,
                                                  strict_email_match=email,
                                                  event=self.event,
                                                  allowremote=allowremote,
                                                  origins=origins))

        return self._success(
            _n('Found %d encryption key', 'Found %d encryption keys',
               len(result)) % len(result),
            result=result)
예제 #5
0
파일: tags.py 프로젝트: kitsuta/Mailpile
 def as_text(self):
     if not self.result:
         return "Failed"
     if not self.result["msg_ids"]:
         return "Nothing happened"
     what = []
     if self.result["tagged"]:
         what.append("Tagged " + ", ".join([k["name"] for k, ids in self.result["tagged"]]))
     if self.result["untagged"]:
         what.append("Untagged " + ", ".join([k["name"] for k, ids in self.result["untagged"]]))
     count = len(self.result["msg_ids"])
     whats = ", ".join(what)
     convs = (
         _n("%d conversation", "%d conversation", count)
         if self.result.get("conversations")
         else _n("%d message", "%d messages", count)
     ) % count
     return "%s (%s)" % (whats, convs)
예제 #6
0
 def as_text(self):
     if not self.result:
         return 'Failed'
     if not self.result['msg_ids']:
         return 'Nothing happened'
     what = []
     if self.result['tagged']:
         what.append(
             'Tagged ' +
             ', '.join([k['name'] for k, ids in self.result['tagged']]))
     if self.result['untagged']:
         what.append('Untagged ' + ', '.join(
             [k['name'] for k, ids in self.result['untagged']]))
     count = len(self.result['msg_ids'])
     whats = ', '.join(what)
     convs = (_n('%d conversation', '%d conversation', count)
              if self.result.get('conversations') else _n(
                  '%d message', '%d messages', count)) % count
     return '%s (%s)' % (whats, convs)
예제 #7
0
 def as_text(self):
     if not self.result:
         return 'Failed'
     if not self.result['msg_ids']:
         return 'Nothing happened'
     what = []
     if self.result['tagged']:
         what.append('Tagged ' +
                     ', '.join([k['name'] for k, ids
                                in self.result['tagged']]))
     if self.result['untagged']:
         what.append('Untagged ' +
                     ', '.join([k['name'] for k, ids
                                in self.result['untagged']]))
     count = len(self.result['msg_ids'])
     whats = ', '.join(what)
     convs = (_n('%d conversation', '%d conversation', count)
              if self.result.get('conversations') else
              _n('%d message', '%d messages', count)) % count
     return '%s (%s)' % (whats, convs)
예제 #8
0
파일: __init__.py 프로젝트: Ayzak/Mailpile
    def command(self):
        if len(self.args) > 1:
            allowremote = self.args.pop()
        else:
            allowremote = self.data.get('allowremote', True)

        address = " ".join(self.data.get('address', self.args))
        result = lookup_crypto_keys(self.session, address, event=self.event,
                                    allowremote=allowremote)
        return self._success(_n('Found %d key', 'Found %d keys', len(result)
                                ) % len(result),
                             result=result)
예제 #9
0
    def command(self):
        if len(self.args) > 1:
            allowremote = self.args.pop()
        else:
            allowremote = self.data.get('allowremote', True)

        address = " ".join(self.data.get('address', self.args))
        result = lookup_crypto_keys(self.session, address, event=self.event,
                                    allowremote=allowremote)
        return self._success(_n('Found %d encryption key',
                                'Found %d encryption keys', 
                                len(result)) % len(result),
                             result=result)
예제 #10
0
    def command(self):
        if len(self.args) > 1:
            allowremote = self.args.pop()
        else:
            allowremote = self.data.get('allowremote', True)

        email = " ".join(self.data.get('email', []))
        address = " ".join(self.data.get('address', self.args))
        result = lookup_crypto_keys(self.session, email or address,
                                    strict_email_match=email,
                                    event=self.event,
                                    allowremote=allowremote)
        return self._success(_n('Found %d encryption key',
                                'Found %d encryption keys', 
                                len(result)) % len(result),
                             result=result)
예제 #11
0
파일: __init__.py 프로젝트: Ayzak/Mailpile
    def command(self):
        args = list(self.args)
        if args:
            address, fprints, origins = args[0], args[1].split(','), args[2:]
        else:
            address = self.data.get('address', [''])[0]
            fprints = self.data.get('fingerprints', [])
            origins = self.data.get('origins', [])
        assert(address or fprints or origins)

        result = lookup_crypto_keys(self.session, address,
                                    get=[f.strip() for f in fprints],
                                    origins=origins,
                                    event=self.event)

        return self._success(_n('Imported %d key', 'Imported %d keys',
                                len(result)) % len(result),
                             result=result)
예제 #12
0
    def command(self):
        args = list(self.args)
        if args:
            address, fprints, origins = args[0], args[1].split(','), args[2:]
        else:
            address = self.data.get('address', [''])[0]
            fprints = self.data.get('fingerprints', [])
            origins = self.data.get('origins', [])
        assert (address or fprints or origins)

        result = lookup_crypto_keys(self.session,
                                    address,
                                    get=[f.strip() for f in fprints],
                                    origins=origins,
                                    event=self.event)

        return self._success(
            _n('Imported %d key', 'Imported %d keys', len(result)) %
            len(result),
            result=result)
예제 #13
0
    def command(self):
        args = list(self.args)
        if args:
            pin_key = False
            address, fprints, origins = args[0], args[1].split(','), args[2:]
        else:
            pin_key = self.data.get('pinned',
                                    [''])[0].lower()[:1] in ('y', 't')
            address = self.data.get('address', [''])[0]
            fprints = self.data.get('fingerprints', [])
            origins = self.data.get('origins', [])
        safe_assert(address or fprints or origins)

        result = lookup_crypto_keys(self.session,
                                    address,
                                    get=[f.strip() for f in fprints],
                                    pin_key=pin_key,
                                    vcard=self._get_or_create_vcard(address),
                                    origins=origins,
                                    event=self.event)

        if len(result) > 0:
            # Update the VCards!
            PGPKeysImportAsVCards(self.session,
                                  arg=[k['fingerprint']
                                       for k in result]).run()

            # The key was looked up based on the given address, so it must have
            # a user id containing that address, so when it is imported to
            # VCards, the VCard for that address will list the key.
            for k in result:
                k.in_vcards = True
            # Previous crypto evaluations may now be out of date, so we
            # clear the cache so users can see results right away.
            ClearParseCache(pgpmime=True)

        return self._success(
            _n('Imported %d encryption key', 'Imported %d encryption keys',
               len(result)) % len(result),
            result=result)
예제 #14
0
    def command(self):
        if len(self.args) > 1:
            allowremote = self.args.pop()
        else:
            allowremote = self.data.get('allowremote', ['Y'])[0]
            if allowremote.lower()[:1] in ('n', 'f'):
                allowremote = False

        origins = self.data.get('origins')
        if '*' in (origins or []):
            origins = [h.NAME for h in KEY_LOOKUP_HANDLERS]

        email = " ".join(self.data.get('email', []))
        address = " ".join(self.data.get('address', self.args))
        result = lookup_crypto_keys(self.session, email or address,
                                    strict_email_match=email,
                                    event=self.event,
                                    allowremote=allowremote,
                                    origins=origins)
        return self._success(_n('Found %d encryption key',
                                'Found %d encryption keys',
                                len(result)) % len(result),
                             result=result)
예제 #15
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()
예제 #16
0
    def _do_tagging(self, ops, msg_ids, conversations, save=True, auto=False):
        idx = self._idx()
        rv = {
            'conversations': False,
            'msg_ids': [b36(i) for i in msg_ids],
            'tagged': [],
            'untagged': []
        }

        for op in ops:
            tag = self.session.config.get_tag(op[1:])
            if tag:
                # FIXME: This should depend on more factors!
                #    - Tags should have metadata about default scope
                if conversations is None:
                    conversation = ('flat' not in (self.session.order or ''))
                    if (tag.flag_msg_only or tag.flag_editable
                            or tag.type == 'attribute'):
                        conversation = False
                else:
                    conversation = conversations
                if conversation:
                    rv['conversations'] = True

                tag_id = tag._key
                tag = tag.copy()
                tag["tid"] = tag_id
                if op[0] == '-':
                    removed = idx.remove_tag(self.session,
                                             tag_id,
                                             msg_idxs=msg_ids,
                                             conversation=conversation)
                    rv['untagged'].append(
                        (tag, sorted([b36(i) for i in removed])))
                else:
                    added = idx.add_tag(self.session,
                                        tag_id,
                                        msg_idxs=msg_ids,
                                        conversation=conversation)
                    rv['tagged'].append((tag, sorted([b36(i) for i in added])))
                # Record behavior
                if len(msg_ids) < 15:
                    for t in self.session.config.get_tags(type='tagged',
                                                          default=[]):
                        idx.add_tag(self.session, t._key, msg_idxs=msg_ids)
            else:
                self.session.ui.warning('Unknown tag: %s' % op)

        if rv['conversations']:
            undo_msg = _n('Untag %d conversation', 'Untag %d conversations',
                          len(msg_ids)) % len(msg_ids)
            done_msg = _n('Tagged %d conversation', 'Tagged %d conversations',
                          len(msg_ids)) % len(msg_ids)
        else:
            undo_msg = _n('Untag %d message', 'Untag %d messages',
                          len(msg_ids)) % len(msg_ids)
            done_msg = _n('Tagged %d message', 'Tagged %d messages',
                          len(msg_ids)) % len(msg_ids)

        self.event.data['undo'] = undo_msg
        self.event.private_data['undo'] = {
            'tagged': [[t['tid'], mids] for t, mids in rv['tagged']],
            'untagged': [[t['tid'], mids] for t, mids in rv['untagged']],
        }

        self.finish(save=save)
        return self._success(done_msg, rv)
예제 #17
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()
                    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
예제 #18
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()
예제 #19
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
예제 #20
0
    def _do_tagging(self, ops, msg_ids, conversations, save=True, auto=False):
        idx = self._idx()
        rv = {
            'conversations': False,
            'msg_ids': [b36(i) for i in msg_ids],
            'tagged': [],
            'untagged': []
        }

        for op in ops:
            tag = self.session.config.get_tag(op[1:])
            if tag:
                # FIXME: This should depend on more factors!
                #    - Tags should have metadata about default scope
                if conversations is None:
                    conversation = ('flat' not in (self.session.order or ''))
                    if (tag.flag_msg_only or
                            tag.flag_editable or
                            tag.type == 'attribute'):
                        conversation = False
                else:
                    conversation = conversations
                if conversation:
                    rv['conversations'] = True

                tag_id = tag._key
                tag = tag.copy()
                tag["tid"] = tag_id
                if op[0] == '-':
                    removed = idx.remove_tag(self.session, tag_id,
                                             msg_idxs=msg_ids,
                                             conversation=conversation)
                    rv['untagged'].append((tag, sorted([b36(i)
                                                        for i in removed])))
                else:
                    added = idx.add_tag(self.session, tag_id,
                                        msg_idxs=msg_ids,
                                        conversation=conversation)
                    rv['tagged'].append((tag, sorted([b36(i)
                                                      for i in added])))
                # Record behavior
                if len(msg_ids) < 15:
                    for t in self.session.config.get_tags(type='tagged',
                                                          default=[]):
                        idx.add_tag(self.session, t._key, msg_idxs=msg_ids)
            else:
                self.session.ui.warning('Unknown tag: %s' % op)

        if rv['conversations']:
            undo_msg = _n('Untag %d conversation',
                          'Untag %d conversations',
                          len(msg_ids)) % len(msg_ids)
            done_msg = _n('Tagged %d conversation',
                          'Tagged %d conversations',
                          len(msg_ids)) % len(msg_ids)
        else:
            undo_msg = _n('Untag %d message',
                          'Untag %d messages', len(msg_ids)) % len(msg_ids)
            done_msg = _n('Tagged %d message',
                          'Tagged %d messages', len(msg_ids)) % len(msg_ids)

        self.event.data['undo'] = undo_msg
        self.event.private_data['undo'] = {
            'tagged': [[t['tid'], mids] for t, mids in rv['tagged']],
            'untagged': [[t['tid'], mids] for t, mids in rv['untagged']],
        }

        self.finish(save=save)
        return self._success(done_msg, rv)