##// END OF EJS Templates
tests: use simple mock smtp server instead of deprecated asyncore smtpd...
Mads Kiilerich -
r51625:b3a5af04 stable
parent child Browse files
Show More
@@ -3,12 +3,11 b''
3 """dummy SMTP server for use in tests"""
3 """dummy SMTP server for use in tests"""
4
4
5
5
6 import asyncore
7 import optparse
6 import optparse
8 import smtpd
7 import os
8 import socket
9 import ssl
9 import ssl
10 import sys
10 import sys
11 import traceback
12
11
13 from mercurial import (
12 from mercurial import (
14 pycompat,
13 pycompat,
@@ -18,57 +17,97 b' from mercurial import ('
18 )
17 )
19
18
20
19
20 if os.environ.get('HGIPV6', '0') == '1':
21 family = socket.AF_INET6
22 else:
23 family = socket.AF_INET
24
25
21 def log(msg):
26 def log(msg):
22 sys.stdout.write(msg)
27 sys.stdout.write(msg)
23 sys.stdout.flush()
28 sys.stdout.flush()
24
29
25
30
26 class dummysmtpserver(smtpd.SMTPServer):
31 def mocksmtpserversession(conn, addr):
27 def __init__(self, localaddr):
32 conn.send(b'220 smtp.example.com ESMTP\r\n')
28 smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None)
33
34 line = conn.recv(1024)
35 if not line.lower().startswith(b'ehlo '):
36 log('no hello: %s\n' % line)
37 return
38
39 conn.send(b'250 Hello\r\n')
40
41 line = conn.recv(1024)
42 if not line.lower().startswith(b'mail from:'):
43 log('no mail from: %s\n' % line)
44 return
45 mailfrom = line[10:].decode().rstrip()
46 if mailfrom.startswith('<') and mailfrom.endswith('>'):
47 mailfrom = mailfrom[1:-1]
48
49 conn.send(b'250 Ok\r\n')
29
50
30 def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
51 rcpttos = []
31 log(
52 while True:
32 '%s from=%s to=%s\n%s\n'
53 line = conn.recv(1024)
33 % (peer[0], mailfrom, ', '.join(rcpttos), data.decode())
54 if not line.lower().startswith(b'rcpt to:'):
34 )
55 break
56 rcptto = line[8:].decode().rstrip()
57 if rcptto.startswith('<') and rcptto.endswith('>'):
58 rcptto = rcptto[1:-1]
59 rcpttos.append(rcptto)
60
61 conn.send(b'250 Ok\r\n')
62
63 if not line.lower().strip() == b'data':
64 log('no rcpt to or data: %s' % line)
65
66 conn.send(b'354 Go ahead\r\n')
35
67
36 def handle_error(self):
68 data = b''
37 # On Windows, a bad SSL connection sometimes generates a WSAECONNRESET.
69 while True:
38 # The default handler will shutdown this server, and then both the
70 line = conn.recv(1024)
39 # current connection and subsequent ones fail on the client side with
71 if not line:
40 # "No connection could be made because the target machine actively
72 log('connection closed before end of data')
41 # refused it". If we eat the error, then the client properly aborts in
73 break
42 # the expected way, and the server is available for subsequent requests.
74 data += line
43 traceback.print_exc()
75 if data.endswith(b'\r\n.\r\n'):
76 data = data[:-5]
77 break
78
79 conn.send(b'250 Ok\r\n')
80
81 log(
82 '%s from=%s to=%s\n%s\n'
83 % (addr[0], mailfrom, ', '.join(rcpttos), data.decode())
84 )
44
85
45
86
46 class dummysmtpsecureserver(dummysmtpserver):
87 def run(host, port, certificate):
47 def __init__(self, localaddr, certfile):
88 ui = uimod.ui.load()
48 dummysmtpserver.__init__(self, localaddr)
89 with socket.socket(family, socket.SOCK_STREAM) as s:
49 self._certfile = certfile
90 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
50
91 s.bind((host, port))
51 def handle_accept(self):
92 # log('listening at %s:%d\n' % (host, port))
52 pair = self.accept()
93 s.listen(1)
53 if not pair:
54 return
55 conn, addr = pair
56 ui = uimod.ui.load()
57 try:
94 try:
58 # wrap_socket() would block, but we don't care
95 while True:
59 conn = sslutil.wrapserversocket(conn, ui, certfile=self._certfile)
96 conn, addr = s.accept()
60 except ssl.SSLError as e:
97 if certificate:
61 log('%s ssl error: %s\n' % (addr[0], e))
98 try:
62 conn.close()
99 conn = sslutil.wrapserversocket(
63 return
100 conn, ui, certfile=certificate
64 smtpd.SMTPChannel(self, conn, addr)
101 )
65
102 except ssl.SSLError as e:
66
103 log('%s ssl error: %s\n' % (addr[0], e))
67 def run():
104 conn.close()
68 try:
105 continue
69 asyncore.loop()
106 log("connection from %s:%s\n" % addr)
70 except KeyboardInterrupt:
107 mocksmtpserversession(conn, addr)
71 pass
108 conn.close()
109 except KeyboardInterrupt:
110 pass
72
111
73
112
74 def _encodestrsonly(v):
113 def _encodestrsonly(v):
@@ -102,19 +141,9 b' def main():'
102 if (opts.tls == 'smtps') != bool(opts.certificate):
141 if (opts.tls == 'smtps') != bool(opts.certificate):
103 op.error('--certificate must be specified with --tls=smtps')
142 op.error('--certificate must be specified with --tls=smtps')
104
143
105 addr = (opts.address, opts.port)
106
107 def init():
108 if opts.tls == 'none':
109 dummysmtpserver(addr)
110 else:
111 dummysmtpsecureserver(addr, opts.certificate)
112 log('listening at %s:%d\n' % addr)
113
114 server.runservice(
144 server.runservice(
115 bytesvars(opts),
145 bytesvars(opts),
116 initfn=init,
146 runfn=lambda: run(opts.address, opts.port, opts.certificate),
117 runfn=run,
118 runargs=[pycompat.sysexecutable, pycompat.fsencode(__file__)]
147 runargs=[pycompat.sysexecutable, pycompat.fsencode(__file__)]
119 + pycompat.sysargv[1:],
148 + pycompat.sysargv[1:],
120 logfile=opts.logfile,
149 logfile=opts.logfile,
@@ -7,7 +7,6 b' Set up SMTP server:'
7
7
8 $ "$PYTHON" "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid --logfile log -d \
8 $ "$PYTHON" "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid --logfile log -d \
9 > --tls smtps --certificate `pwd`/server.pem
9 > --tls smtps --certificate `pwd`/server.pem
10 listening at localhost:$HGPORT (?)
11 $ cat a.pid >> $DAEMON_PIDS
10 $ cat a.pid >> $DAEMON_PIDS
12
11
13 Set up repository:
12 Set up repository:
@@ -47,6 +46,11 b' we are able to load CA certs:'
47 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
46 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
48 (?i)abort: .*?certificate.verify.failed.* (re)
47 (?i)abort: .*?certificate.verify.failed.* (re)
49 [255]
48 [255]
49
50 $ cat ../log
51 * ssl error: * (glob)
52 $ : > ../log
53
50 #endif
54 #endif
51
55
52 #if defaultcacertsloaded
56 #if defaultcacertsloaded
@@ -58,6 +62,10 b' we are able to load CA certs:'
58 (?i)abort: .*?certificate.verify.failed.* (re)
62 (?i)abort: .*?certificate.verify.failed.* (re)
59 [255]
63 [255]
60
64
65 $ cat ../log
66 * ssl error: * (glob)
67 $ : > ../log
68
61 #endif
69 #endif
62
70
63 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
71 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
@@ -76,7 +84,8 b' Without certificates:'
76 [150]
84 [150]
77
85
78 $ cat ../log
86 $ cat ../log
79 * ssl error: * (glob)
87 connection from * (glob)
88 no hello: b''
80 $ : > ../log
89 $ : > ../log
81
90
82 With global certificates:
91 With global certificates:
@@ -91,6 +100,7 b' With global certificates:'
91 sending [PATCH] a ...
100 sending [PATCH] a ...
92
101
93 $ cat ../log
102 $ cat ../log
103 connection from * (glob)
94 * from=quux to=foo, bar (glob)
104 * from=quux to=foo, bar (glob)
95 MIME-Version: 1.0
105 MIME-Version: 1.0
96 Content-Type: text/plain; charset="us-ascii"
106 Content-Type: text/plain; charset="us-ascii"
General Comments 0
You need to be logged in to leave comments. Login now