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 s |
|
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 |
$ |
|
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