예제 #1
0
파일: gpgi.py 프로젝트: rjp/Mailpile
    def chat(self, gpg_args, callback, *args, **kwargs):
        """This lets a callback have a chat with the GPG process..."""
        gpg_args = [self.gpgbinary,
                    "--utf8-strings",
                    "--no-use-agent",
                    "--no-tty",
                    "--command-fd=0",
                    "--status-fd=1"] + (gpg_args or [])
        if self.homedir:
            gpg_args.insert(1, "--homedir=%s" % self.homedir)

        proc = None
        try:
            # Here we go!
            proc = Popen(gpg_args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
                         bufsize=0)

            return callback(proc, *args, **kwargs)
        finally:
            # Close this so GPG will terminate. This should already have
            # been done, but we're handling errors here...
            if proc and proc.stdin:
                proc.stdin.close()
            if proc:
                proc.wait()
예제 #2
0
 def _popen(self, command, fd, long_running):
     self._reading = True
     proc = Popen(command,
                  stdin=fd,
                  stdout=PIPE,
                  stderr=PIPE,
                  bufsize=0,
                  long_running=long_running)
     return proc, proc.stdout
예제 #3
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()
예제 #4
0
파일: gpgi.py 프로젝트: rjp/Mailpile
    def run(self,
            args=None, gpg_input=None, outputfd=None, partial_read_ok=False,
            send_passphrase=False, _raise=None):
        self.outputbuffers = dict([(x, []) for x in self.outputfds])
        self.threads = {}

        wtf = ' '.join(args)
        args = args[:] if args else []
        args.insert(0, self.gpgbinary)
        args.insert(1, "--utf8-strings")
        args.insert(1, "--with-colons")
        args.insert(1, "--verbose")
        args.insert(1, "--batch")
        args.insert(1, "--enable-progress-filter")
        if not self.use_agent:
            args.insert(1, "--no-use-agent")

        if self.homedir:
            args.insert(1, "--homedir=%s" % self.homedir)

        gpg_retcode = -1
        proc = None
        try:
            args.insert(1, "--status-fd=2")

            if self.passphrase and send_passphrase:
                if self.use_agent:
                    args.insert(1, "--no-use-agent")
                args.insert(2, "--passphrase-fd=0")

            if not self.passphrase and send_passphrase:
                self.debug('Running WITHOUT PASSPHRASE %s' % ' '.join(args))
                self.debug(traceback.format_stack())
            else:
                self.debug('Running %s' % ' '.join(args))

            # Here we go!
            proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0)

            # GnuPG is a bit crazy, and requires that the passphrase
            # be sent and the filehandle closed before anything else
            # interesting happens.
            if self.passphrase and send_passphrase:
                c = self.passphrase.read(BLOCKSIZE)
                while c != '':
                    proc.stdin.write(c)
                    c = self.passphrase.read(BLOCKSIZE)
                proc.stdin.write('\n')

            self.threads = {
                "stderr": StreamReader('gpgi-stderr(%s)' % wtf,
                                       proc.stderr, self.parse_stderr)
            }

            if outputfd:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-to-fd(%s)' % wtf,
                    proc.stdout, outputfd.write, lines=False)
            else:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-parsed(%s)' % wtf,
                    proc.stdout, self.parse_stdout)

            if gpg_input:
                # If we have output, we just stream it. Technically, this
                # doesn't really need to be a thread at the moment.
                self.debug('<<STDOUT<< %s' % gpg_input)
                StreamWriter('gpgi-output(%s)' % wtf,
                             proc.stdin, gpg_input,
                             partial_write_ok=partial_read_ok).join()
            else:
                proc.stdin.close()

            # Reap GnuPG
            gpg_retcode = proc.wait()

        finally:
            # Close this so GPG will terminate. This should already have
            # been done, but we're handling errors here...
            if proc and proc.stdin:
                proc.stdin.close()

        # Reap the threads
        self._reap_threads()

        if outputfd:
            outputfd.close()

        if gpg_retcode != 0 and _raise:
            raise _raise('GnuPG failed, exit code: %s' % gpg_retcode)

        return gpg_retcode, self.outputbuffers
예제 #5
0
파일: gpgi.py 프로젝트: whiteyeti/Mailpile
    def run(self,
            args=None, gpg_input=None, outputfd=None, partial_read_ok=False,
            send_passphrase=False, _raise=None):
        self.outputbuffers = dict([(x, []) for x in self.outputfds])
        self.threads = {}

        wtf = ' '.join(args)
        args = args[:] if args else []
        args.insert(0, self.gpgbinary)
        args.insert(1, "--utf8-strings")
        args.insert(1, "--with-colons")
        args.insert(1, "--verbose")
        args.insert(1, "--batch")
        args.insert(1, "--enable-progress-filter")
        if not self.use_agent:
            args.insert(1, "--no-use-agent")

        if self.homedir:
            args.insert(1, "--homedir=%s" % self.homedir)

        gpg_retcode = -1
        proc = None
        try:
            args.insert(1, "--status-fd=2")

            if self.passphrase and send_passphrase:
                if self.use_agent:
                    args.insert(1, "--no-use-agent")
                args.insert(2, "--passphrase-fd=0")

            if not self.passphrase and send_passphrase:
                self.debug('Running WITHOUT PASSPHRASE %s' % ' '.join(args))
                self.debug(traceback.format_stack())
            else:
                self.debug('Running %s' % ' '.join(args))

            # Here we go!
            proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0)

            # GnuPG is a bit crazy, and requires that the passphrase
            # be sent and the filehandle closed before anything else
            # interesting happens.
            if self.passphrase and send_passphrase:
                c = self.passphrase.read(BLOCKSIZE)
                while c != '':
                    proc.stdin.write(c)
                    c = self.passphrase.read(BLOCKSIZE)
                proc.stdin.write('\n')

            self.threads = {
                "stderr": StreamReader('gpgi-stderr(%s)' % wtf,
                                       proc.stderr, self.parse_stderr)
            }

            if outputfd:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-to-fd(%s)' % wtf,
                    proc.stdout, outputfd.write, lines=False)
            else:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-parsed(%s)' % wtf,
                    proc.stdout, self.parse_stdout)

            if gpg_input:
                # If we have output, we just stream it. Technically, this
                # doesn't really need to be a thread at the moment.
                self.debug('<<STDOUT<< %s' % gpg_input)
                StreamWriter('gpgi-output(%s)' % wtf,
                             proc.stdin, gpg_input,
                             partial_write_ok=partial_read_ok).join()
            else:
                proc.stdin.close()

            # Reap GnuPG
            gpg_retcode = proc.wait()

        finally:
            # Close this so GPG will terminate. This should already have
            # been done, but we're handling errors here...
            if proc and proc.stdin:
                proc.stdin.close()

        # Reap the threads
        self._reap_threads()

        if outputfd:
            outputfd.close()

        if gpg_retcode != 0 and _raise:
            raise _raise('GnuPG failed, exit code: %s' % gpg_retcode)

        return gpg_retcode, self.outputbuffers
예제 #6
0
파일: gui.py 프로젝트: esaye/Mailpile
def _real_startup(config):
    while config.http_worker is None:
        time.sleep(0.1)

    try:
        session_id = config.http_worker.httpd.make_session_id(None)
        mailpile.auth.SetLoggedIn(None,
                                  user='******',
                                  session_id=session_id)
        cookie = config.http_worker.httpd.session_cookie
        sspec = config.http_worker.httpd.sspec
        base_url = 'http://%s:%s' % sspec

        script_dir = os.path.dirname(os.path.realpath(__file__))
        script = os.path.join(script_dir, 'gui-o-matic.py')

        global __GUI__
        gui = __GUI__ = Popen(
            ['python', '-u', script],
            bufsize=1,  # line buffered
            stdin=PIPE,
            stderr=PIPE,
            long_running=True)
        stderr = []
        eater = threading.Thread(target=output_eater,
                                 args=[gui.stderr, stderr])
        eater.name = 'GUI(stderr)'
        eater.daemon = True
        eater.start()

        ico = lambda s: os.path.join(script_dir, 'icons-%(theme)s', s)
        gui.stdin.write(
            json.dumps({
                'app_name':
                'Mailpile',
                'indicator_icons': {
                    'startup': ico('startup.png'),
                    'normal': ico('normal.png'),
                    'working': ico('working.png'),
                    'attention': ico('attention.png'),
                    'shutdown': ico('shutdown.png')
                },
                'indicator_menu': [{
                    'label': _('Starting up ...'),
                    'item': 'status'
                }, {
                    'label': _('Open Mailpile'),
                    'item': 'open',
                    'op': 'show_url',
                    'args': [base_url]
                }, {
                    'label': _('Quit'),
                    'item': 'quit',
                    'op': 'get_url',
                    'args': [base_url + '/api/0/quitquitquit/']
                }],
                'http_cookies': {
                    base_url: [[cookie, session_id]]
                },
            }).strip() + '\nOK GO\n')

        indicator('set_menu_sensitive', item='quit')
        indicator('set_menu_sensitive', item='open')

        # FIXME: This sleep is lame
        time.sleep(5)
        if (gui.poll() is not None) or mailpile.util.QUITTING:
            return
    except:
        # If the basic indicator setup fails, we just assume it doesn't
        # work and go silently dead...
        return

    try:
        # ...however, getting this far means if the indicator dies, then
        # the user tried to quit the app, so we should cooperate and die
        # (via the except below).

        while config.index is None or not config.tags:
            if mailpile.util.QUITTING:
                return
            if gui.poll() is not None:
                return
            time.sleep(1)
        indicator('set_status_normal')

        # FIXME: We should do more with the indicator... this is a bit lame.
        while True:
            if mailpile.util.QUITTING:
                indicator('set_status_shutdown')
                indicator('set_menu_sensitive', item='open', sensitive=False)
                indicator('set_menu_sensitive', item='quit', sensitive=False)
                indicator('set_menu_label',
                          item='status',
                          label=_('Shutting down...'))
                time.sleep(300)
            else:
                indicator('set_menu_label',
                          item='status',
                          label=_('%d messages') %
                          len(config.index and config.index.INDEX or []))
                time.sleep(5)

    except AttributeError:
        pass
    finally:
        try:
            if not mailpile.util.QUITTING:
                Quit(Session(config)).run()
        except:
            pass
예제 #7
0
파일: gpgi.py 프로젝트: astridliu/Mailpile
    def run(self,
            args=[], gpg_input=None, outputfd=None, partial_read_ok=False,
            _raise=None):
        self.outputbuffers = dict([(x, []) for x in self.outputfds])
        self.pipes = {}
        self.threads = {}

        wtf = ' '.join(args)
        args.insert(0, self.gpgbinary)
        args.insert(1, "--utf8-strings")
        args.insert(1, "--with-colons")
        args.insert(1, "--verbose")
        args.insert(1, "--batch")
        args.insert(1, "--enable-progress-filter")
        if not self.use_agent:
            args.insert(1, "--no-use-agent")

        if self.homedir:
            args.insert(1, "--homedir=%s" % self.homedir)

        gpg_retcode = -1
        proc = status_handle = passphrase_handle = None
        status_pipe = passphrase_pipe = [None, None]
        popen_keeps_open = []
        try:
            status_pipe = os.pipe()
            status_handle = os.fdopen(status_pipe[0], "r")
            args.insert(1, "--status-fd=%d" % status_pipe[1])
            popen_keeps_open.append(status_pipe[1])

            if self.passphrase:
                passphrase_pipe = os.pipe()
                passphrase_handle = os.fdopen(passphrase_pipe[1], "w")
                if self.use_agent:
                    args.insert(1, "--no-use-agent")
                args.insert(2, "--passphrase-fd=%d" % passphrase_pipe[0])
                popen_keeps_open.append(passphrase_pipe[0])

            # Here we go!
            proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
                         bufsize=0, keep_open=popen_keeps_open)

            # GnuPG is a bit crazy, and requires that the passphrase
            # be sent and the filehandle closed before anything else
            # interesting happens.
            if self.passphrase:
                c = self.passphrase.read(BLOCKSIZE)
                while c != '':
                    passphrase_handle.write(c)
                    c = self.passphrase.read(BLOCKSIZE)
                passphrase_handle.write('\n')
                passphrase_handle.close()

            self.threads = {
                "stderr": StreamReader('gpgi-stderr(%s)' % wtf,
                                       proc.stderr, self.parse_stderr),
                "status": StreamReader('gpgi-status(%s)' % wtf,
                                       status_handle, self.parse_status),
            }

            if outputfd:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-to-fd(%s)' % wtf,
                    proc.stdout, outputfd.write, lines=False)
            else:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-parsed(%s)' % wtf,
                    proc.stdout, self.parse_stdout)

            if gpg_input:
                # If we have output, we just stream it. Technically, this
                # doesn't really need to be a thread at the moment.
                StreamWriter('gpgi-output(%s)' % wtf,
                             proc.stdin, gpg_input,
                             partial_write_ok=partial_read_ok).join()
            else:
                proc.stdin.close()

            # Reap GnuPG
            gpg_retcode = proc.wait()

        finally:
            def closer(method, *args):
                try:
                    method(*args)
                except (IOError, OSError):
                    pass
            # Here we close GPG's end of the status pipe, because
            # otherwise things may hang. We also close the passphrase pipe
            # at both ends, as it should be completely finished.
            for fdn in (status_pipe[1],
                        passphrase_pipe[0], passphrase_pipe[1]):
                if fdn is not None:
                    closer(os.close, fdn)
            for fd in (passphrase_handle,):
                if fd is not None:
                    closer(fd.close)
            # Close this so GPG will terminate. This should already have
            # been done, but we're handling errors here...
            if proc and proc.stdin:
                closer(proc.stdin.close)

        # Reap the threads
        for name, thr in self.threads.iteritems():
            if thr.isAlive():
                thr.join(timeout=15)
                if thr.isAlive():
                    print 'SCARY WARNING: FAILED TO REAP THREAD %s' % thr

        if outputfd:
            outputfd.close()

        if gpg_retcode != 0 and _raise:
            raise _raise('GnuPG failed, exit code: %s' % gpg_retcode)

        return gpg_retcode, self.outputbuffers
예제 #8
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
예제 #9
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()
예제 #10
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
예제 #11
0
    def run(self,
            args=[], gpg_input=None, outputfd=None, partial_read_ok=False,
            _raise=None):
        self.outputbuffers = dict([(x, []) for x in self.outputfds])
        self.pipes = {}
        self.threads = {}

        wtf = ' '.join(args)
        args.insert(0, self.gpgbinary)
        args.insert(1, "--utf8-strings")
        args.insert(1, "--with-colons")
        args.insert(1, "--verbose")
        args.insert(1, "--batch")
        args.insert(1, "--enable-progress-filter")
        if not self.use_agent:
            args.insert(1, "--no-use-agent")

        if self.homedir:
            args.insert(1, "--homedir=%s" % self.homedir)

        gpg_retcode = -1
        proc = status_handle = passphrase_handle = None
        status_pipe = passphrase_pipe = [None, None]
        popen_keeps_open = []
        try:
            status_pipe = os.pipe()
            status_handle = os.fdopen(status_pipe[0], "r")
            args.insert(1, "--status-fd=%d" % status_pipe[1])
            popen_keeps_open.append(status_pipe[1])

            if self.passphrase:
                passphrase_pipe = os.pipe()
                passphrase_handle = os.fdopen(passphrase_pipe[1], "w")
                if self.use_agent:
                    args.insert(1, "--no-use-agent")
                args.insert(2, "--passphrase-fd=%d" % passphrase_pipe[0])
                popen_keeps_open.append(passphrase_pipe[0])

            # Here we go!
            proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
                         bufsize=0, keep_open=popen_keeps_open)

            # GnuPG is a bit crazy, and requires that the passphrase
            # be sent and the filehandle closed before anything else
            # interesting happens.
            if self.passphrase:
                c = self.passphrase.read(BLOCKSIZE)
                while c != '':
                    passphrase_handle.write(c)
                    c = self.passphrase.read(BLOCKSIZE)
                passphrase_handle.write('\n')
                passphrase_handle.close()

            self.threads = {
                "stderr": StreamReader('gpgi-stderr(%s)' % wtf,
                                       proc.stderr, self.parse_stderr),
                "status": StreamReader('gpgi-status(%s)' % wtf,
                                       status_handle, self.parse_status),
            }

            if outputfd:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-to-fd(%s)' % wtf,
                    proc.stdout, outputfd.write, lines=False)
            else:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-parsed(%s)' % wtf,
                    proc.stdout, self.parse_stdout)

            if gpg_input:
                # If we have output, we just stream it. Technically, this
                # doesn't really need to be a thread at the moment.
                StreamWriter('gpgi-output(%s)' % wtf,
                             proc.stdin, gpg_input,
                             partial_write_ok=partial_read_ok).join()
            else:
                proc.stdin.close()

            # Reap GnuPG
            gpg_retcode = proc.wait()

        finally:
            def closer(method, *args):
                try:
                    method(*args)
                except (IOError, OSError):
                    pass
            # Here we close GPG's end of the status pipe, because
            # otherwise things may hang. We also close the passphrase pipe
            # at both ends, as it should be completely finished.
            for fdn in (status_pipe[1],
                        passphrase_pipe[0], passphrase_pipe[1]):
                if fdn is not None:
                    closer(os.close, fdn)
            for fd in (passphrase_handle,):
                if fd is not None:
                    closer(fd.close)
            # Close this so GPG will terminate. This should already have
            # been done, but we're handling errors here...
            if proc and proc.stdin:
                closer(proc.stdin.close)

        # Reap the threads
        for name, thr in self.threads.iteritems():
            if thr.isAlive():
                thr.join(timeout=15)
                if thr.isAlive():
                    print 'SCARY WARNING: FAILED TO REAP THREAD %s' % thr

        if outputfd:
            outputfd.close()

        if gpg_retcode != 0 and _raise:
            raise _raise('GnuPG failed, exit code: %s' % gpg_retcode)

        return gpg_retcode, self.outputbuffers
예제 #12
0
    def run(self,
            args=None,
            gpg_input=None,
            outputfd=None,
            partial_read_ok=False,
            _raise=None):
        self.outputbuffers = dict([(x, []) for x in self.outputfds])
        self.pipes = {}
        self.threads = {}

        wtf = ' '.join(args)
        args = args[:] if args else []
        args.insert(0, self.gpgbinary)
        args.insert(1, "--utf8-strings")
        args.insert(1, "--with-colons")
        args.insert(1, "--verbose")
        args.insert(1, "--batch")
        args.insert(1, "--enable-progress-filter")
        if not self.use_agent:
            args.insert(1, "--no-use-agent")

        if self.homedir:
            args.insert(1, "--homedir=%s" % self.homedir)

        gpg_retcode = -1
        proc = status_pipe = passphrase_pipe = None
        popen_keeps_open = []
        try:
            status_pipe = Safe_Pipe()
            args.insert(1, "--status-fd=%d" % status_pipe.write_end.fileno())
            popen_keeps_open.append(status_pipe.write_end)

            if self.passphrase:
                passphrase_pipe = Safe_Pipe()
                if self.use_agent:
                    args.insert(1, "--no-use-agent")
                args.insert(
                    2,
                    "--passphrase-fd=%d" % passphrase_pipe.read_end.fileno())
                popen_keeps_open.append(passphrase_pipe.read_end)

            # Here we go!
            proc = Popen(args,
                         stdin=PIPE,
                         stdout=PIPE,
                         stderr=PIPE,
                         bufsize=0,
                         keep_open=popen_keeps_open)

            # GnuPG is a bit crazy, and requires that the passphrase
            # be sent and the filehandle closed before anything else
            # interesting happens.
            if self.passphrase:
                c = self.passphrase.read(BLOCKSIZE)
                while c != '':
                    passphrase_pipe.write(c)
                    c = self.passphrase.read(BLOCKSIZE)
                passphrase_pipe.write('\n')
                passphrase_pipe.write_end.close()

            self.threads = {
                "stderr":
                StreamReader('gpgi-stderr(%s)' % wtf, proc.stderr,
                             self.parse_stderr),
                "status":
                StreamReader('gpgi-status(%s)' % wtf, status_pipe.read_end,
                             self.parse_status),
            }

            if outputfd:
                self.threads["stdout"] = StreamReader('gpgi-stdout-to-fd(%s)' %
                                                      wtf,
                                                      proc.stdout,
                                                      outputfd.write,
                                                      lines=False)
            else:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-parsed(%s)' % wtf, proc.stdout,
                    self.parse_stdout)

            if gpg_input:
                # If we have output, we just stream it. Technically, this
                # doesn't really need to be a thread at the moment.
                StreamWriter('gpgi-output(%s)' % wtf,
                             proc.stdin,
                             gpg_input,
                             partial_write_ok=partial_read_ok).join()
            else:
                proc.stdin.close()

            # Reap GnuPG
            gpg_retcode = proc.wait()

        finally:
            # Here we close GPG's end of the status pipe, because
            # otherwise things may hang. We also close the passphrase pipe
            # at both ends, as it should be completely finished.
            if status_pipe:
                status_pipe.write_end.close()
            if passphrase_pipe:
                passphrase_pipe.close()

            # Close this so GPG will terminate. This should already have
            # been done, but we're handling errors here...
            if proc and proc.stdin:
                proc.stdin.close()

        # Reap the threads
        self._reap_threads()

        if outputfd:
            outputfd.close()

        if gpg_retcode != 0 and _raise:
            raise _raise('GnuPG failed, exit code: %s' % gpg_retcode)

        return gpg_retcode, self.outputbuffers
예제 #13
0
    def run(self,
            args=None, gpg_input=None, outputfd=None, partial_read_ok=False,
            _raise=None):
        self.outputbuffers = dict([(x, []) for x in self.outputfds])
        self.pipes = {}
        self.threads = {}

        wtf = ' '.join(args)
        args = args[:] if args else []
        args.insert(0, self.gpgbinary)
        args.insert(1, "--utf8-strings")
        args.insert(1, "--with-colons")
        args.insert(1, "--verbose")
        args.insert(1, "--batch")
        args.insert(1, "--enable-progress-filter")
        if not self.use_agent:
            args.insert(1, "--no-use-agent")

        if self.homedir:
            args.insert(1, "--homedir=%s" % self.homedir)

        gpg_retcode = -1
        proc = status_pipe = passphrase_pipe = None
        popen_keeps_open = []
        try:
            status_pipe = Safe_Pipe()
            args.insert(1, "--status-fd=%d" % status_pipe.write_end.fileno())
            popen_keeps_open.append(status_pipe.write_end)

            if self.passphrase:
                passphrase_pipe = Safe_Pipe()
                if self.use_agent:
                    args.insert(1, "--no-use-agent")
                args.insert(2, "--passphrase-fd=%d"
                               % passphrase_pipe.read_end.fileno())
                popen_keeps_open.append(passphrase_pipe.read_end)

            # Here we go!
            proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
                         bufsize=0, keep_open=popen_keeps_open)

            # GnuPG is a bit crazy, and requires that the passphrase
            # be sent and the filehandle closed before anything else
            # interesting happens.
            if self.passphrase:
                c = self.passphrase.read(BLOCKSIZE)
                while c != '':
                    passphrase_pipe.write(c)
                    c = self.passphrase.read(BLOCKSIZE)
                passphrase_pipe.write('\n')
                passphrase_pipe.write_end.close()

            self.threads = {
                "stderr": StreamReader('gpgi-stderr(%s)' % wtf,
                                       proc.stderr, self.parse_stderr),
                "status": StreamReader('gpgi-status(%s)' % wtf,
                                       status_pipe.read_end,
                                       self.parse_status),
            }

            if outputfd:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-to-fd(%s)' % wtf,
                    proc.stdout, outputfd.write, lines=False)
            else:
                self.threads["stdout"] = StreamReader(
                    'gpgi-stdout-parsed(%s)' % wtf,
                    proc.stdout, self.parse_stdout)

            if gpg_input:
                # If we have output, we just stream it. Technically, this
                # doesn't really need to be a thread at the moment.
                StreamWriter('gpgi-output(%s)' % wtf,
                             proc.stdin, gpg_input,
                             partial_write_ok=partial_read_ok).join()
            else:
                proc.stdin.close()

            # Reap GnuPG
            gpg_retcode = proc.wait()

        finally:
            # Here we close GPG's end of the status pipe, because
            # otherwise things may hang. We also close the passphrase pipe
            # at both ends, as it should be completely finished.
            if status_pipe:
                status_pipe.write_end.close()
            if passphrase_pipe:
                passphrase_pipe.close()

            # Close this so GPG will terminate. This should already have
            # been done, but we're handling errors here...
            if proc and proc.stdin:
                proc.stdin.close()

        # Reap the threads
        self._reap_threads()

        if outputfd:
            outputfd.close()

        if gpg_retcode != 0 and _raise:
            raise _raise('GnuPG failed, exit code: %s' % gpg_retcode)

        return gpg_retcode, self.outputbuffers