view tests/dummysmtpd.py @ 29859:a1092e2d70a3

help: internals topic for wire protocol The Mercurial wire protocol is under-documented. This includes a lack of source docstrings and comments as well as pages on the official wiki. This patch adds the beginnings of "internals" documentation on the wire protocol. The documentation should have nearly complete coverage on the lower-level parts of the protocol, such as the different transport mechanims, how commands and arguments are sent, capabilities, and, of course, the commands themselves. As part of writing this documentation, I discovered a number of deficiencies in the protocol and bugs in the implementation. I've started sending patches for some of the issues. I hope to send a lot more. This patch starts with the scaffolding for a new internals page.
author Gregory Szorc <gregory.szorc@gmail.com>
date Mon, 22 Aug 2016 19:46:39 -0700
parents 1b8b6adb2365
children d9d8d78e6bc9
line wrap: on
line source

#!/usr/bin/env python

"""dummy SMTP server for use in tests"""

from __future__ import absolute_import

import asyncore
import optparse
import smtpd
import ssl
import sys

from mercurial import (
    cmdutil,
    sslutil,
    ui as uimod,
)

def log(msg):
    sys.stdout.write(msg)
    sys.stdout.flush()

class dummysmtpserver(smtpd.SMTPServer):
    def __init__(self, localaddr):
        smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None)

    def process_message(self, peer, mailfrom, rcpttos, data):
        log('%s from=%s to=%s\n' % (peer[0], mailfrom, ', '.join(rcpttos)))

class dummysmtpsecureserver(dummysmtpserver):
    def __init__(self, localaddr, certfile):
        dummysmtpserver.__init__(self, localaddr)
        self._certfile = certfile

    def handle_accept(self):
        pair = self.accept()
        if not pair:
            return
        conn, addr = pair
        ui = uimod.ui()
        try:
            # wrap_socket() would block, but we don't care
            conn = sslutil.wrapserversocket(conn, ui, certfile=self._certfile)
        except ssl.SSLError:
            log('%s ssl error\n' % addr[0])
            conn.close()
            return
        smtpd.SMTPChannel(self, conn, addr)

def run():
    try:
        asyncore.loop()
    except KeyboardInterrupt:
        pass

def main():
    op = optparse.OptionParser()
    op.add_option('-d', '--daemon', action='store_true')
    op.add_option('--daemon-postexec', action='append')
    op.add_option('-p', '--port', type=int, default=8025)
    op.add_option('-a', '--address', default='localhost')
    op.add_option('--pid-file', metavar='FILE')
    op.add_option('--tls', choices=['none', 'smtps'], default='none')
    op.add_option('--certificate', metavar='FILE')

    opts, args = op.parse_args()
    if opts.tls == 'smtps' and not opts.certificate:
        op.error('--certificate must be specified')

    addr = (opts.address, opts.port)
    def init():
        if opts.tls == 'none':
            dummysmtpserver(addr)
        else:
            dummysmtpsecureserver(addr, opts.certificate)
        log('listening at %s:%d\n' % addr)

    cmdutil.service(vars(opts), initfn=init, runfn=run,
                    runargs=[sys.executable, __file__] + sys.argv[1:])

if __name__ == '__main__':
    main()