12 from xtermcolor
import colorize
13 COLOR_ERROR
= 0xff0000
16 def print_error(val
="", *args
, **kwargs
):
17 return print(colorize(val
, COLOR_ERROR
), *args
, **kwargs
)
18 def print_warn(val
="", *args
, **kwargs
):
19 return print(colorize(val
, COLOR_WARN
), *args
, **kwargs
)
20 def print_good(val
="", *args
, **kwargs
):
21 return print(colorize(val
, COLOR_GOOD
), *args
, **kwargs
)
23 print("Install the python3-xtermcolor package for coloured output")
31 print_error("ERROR: python yaml module not installed - run the following and try again:", file=sys
.stderr
)
32 print("sudo apt-get install python3-yaml", file=sys
.stderr
)
35 def do_tls(conn
, sslv
):
36 # Possible values of smtp_sslv: none|peer|client_once|fail_if_no_peer_cert
38 # Creating a context with the purpose of server authentication implies verifying the certificate
39 if not hasattr(ssl
,'create_default_context'):
40 # ssl.create_default_context is in Python 3.4+
41 print_warn('WARNING: cannot attempt verification of server certificate:')
42 print_warn(' (need Python 3.4+ to attempt verification)')
43 # Damn you, openssl. Why don't you support IPv6?
44 if conn
.sock
.family
== socket
.AF_INET
:
45 print_warn(' You can verify the certificate manually by running:')
46 print_warn(' echo quit | openssl s_client -CAfile /etc/ssl/certs/ca-certificates.crt \\')
47 print_warn(' -starttls smtp -connect {}:{}'.format(*conn
.sock
.getpeername()[0:2]))
48 return conn
.starttls()
49 sslcontext
=ssl
.create_default_context(purpose
=ssl
.Purpose
.SERVER_AUTH
)
50 # The None below looks like might be a typo but it's not - it represents the ActiveRecord default (to verify)
51 if sslv
in (None, 'peer', 'client_once', 'fail_if_no_peer_cert'):
53 conn
.starttls(context
=sslcontext
)
54 elif sslv
in ('none',):
55 # disable cert checking
56 sslcontext
.check_hostname
= False
57 sslcontext
.verify_mode
= ssl
.CERT_NONE
58 conn
.starttls(context
=sslcontext
)
60 raise ValueError('invalid value for DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: {}'.format(sslv
))
61 except smtplib
.SMTPException
as e
:
62 if (sslv
is None) and ('STARTTLS extension not supported by server' in e
.args
[0]):
63 print_warn("unable to establish TLS, continuing: {}".format(e
.args
[0]))
67 ### Start of execution ###
70 destemail
= sys
.argv
[2]
72 destemail
= input('Enter your email address: ')
73 srcemail
= 'nobody+launcher-mailtest@discourse.org'
75 # Read in the container yaml and grab the env section
76 cfgdata
= yaml
.safe_load(open(cfgfile
).read())
77 envdata
= cfgdata
['env']
79 # Here are the variables we'll test
80 smtp_addr
= envdata
.get('DISCOURSE_SMTP_ADDRESS')
81 smtp_port
= envdata
.get('DISCOURSE_SMTP_PORT')
82 smtp_user
= envdata
.get('DISCOURSE_SMTP_USER_NAME')
83 smtp_pass
= envdata
.get('DISCOURSE_SMTP_PASSWORD')
84 smtp_sslv
= envdata
.get('DISCOURSE_SMTP_OPENSSL_VERIFY_MODE')
86 # Yoink out the settings from the file - we'll print them and put them in the email
87 testinfo
= 'DISCOURSE_SMTP_ settings:\n'
88 for k
,v
in filter(lambda x
: x
[0].startswith('DISCOURSE_SMTP_'), envdata
.items()):
91 testinfo
+= ' {} = {}\n'.format(k
,v
)
94 # Ensure at least smtp-addr is specified - everything else is optional
96 print_error("ERROR: DISCOURSE_SMTP_ADDRESS not specified", file=sys
.stderr
)
99 if (smtp_user
is None and smtp_pass
is not None) or (smtp_user
is not None and smtp_pass
is None):
100 print_error("ERROR: both username and password must be specified for auth", file=sys
.stderr
)
103 # Do we have a known good set of parameters?
104 known_good_settings
= {
105 ('smtp.mandrillapp.com', 587): 'Mandrill',
108 print_good('You are correctly configured to use: {}'.format(known_good_settings
[smtp_addr
,smtp_port
]))
112 # Try and ensure the test is valid
113 if destemail
.split('@',1)[1] in smtp_addr
:
114 print_warn('WARNING: {} may be allowed to relay mail to {}, this may not be a valid test!'.format(smtp_addr
, destemail
))
116 # Outbound port smtp?
117 if smtp_port
== 25 or smtp_port
is None:
118 print_warn('WARNING: many networks block outbound port 25 - consider an alternative (587?)')
120 # Outbound port smtps?
122 print_warn("WARNING: I can't yet handle testing port 465.")
123 print_warn(" It's probably wrong though - most servers use 587 or 25 for submission.")
125 # Outbound port submission?
127 if smtp_user
is None:
128 print_warn('WARNING: trying to use the submission (587) port without authenticating will probably fail')
130 # Build the message and send!
131 msg
= email
.message
.Message()
132 msg
.add_header('From', 'nobody+launcher-mailtest@discourse.org')
133 msg
.add_header('To', destemail
)
134 msg
.add_header('Subject', 'discourse launcher mailtest for {}'.format(os
.path
.basename(cfgfile
)))
135 msg
.set_payload(testinfo
)
138 smtp
= smtplib
.SMTP(smtp_addr
, smtp_port
, timeout
=5)
140 do_tls(smtp
,smtp_sslv
)
142 smtp
.login(smtp_user
, smtp_pass
)
143 result
= smtp
.sendmail('nobody+launcher-mailtest@discourse.org', destemail
, msg
.as_string())
144 except socket
.gaierror
as e
:
145 print_error("ERROR: {}".format(e
.args
[-1]), file=sys
.stderr
)
146 print(" Ensure that the host '{}' exists".format(smtp_addr
), file=sys
.stderr
)
148 except socket
.timeout
as e
:
149 print_error("ERROR: {}".format(e
.args
[-1]), file=sys
.stderr
)
150 print(" Ensure that the host '{}' is up and port {} is reachable".format(smtp_addr
, smtp_port
), file=sys
.stderr
)
151 print(" If your settings are known-good, ensure outbound port {} is not blocked".format(smtp_port
), file=sys
.stderr
)
153 except smtplib
.SMTPConnectError
as e
:
154 print_error("ERROR: {}".format(e
.args
[-1]), file=sys
.stderr
)
155 print(" Ensure that the host '{}' is up and port {} is reachable".format(smtp_addr
, smtp_port
), file=sys
.stderr
)
157 except ssl
.SSLError
as e
:
158 print_error("ERROR: unable to establish TLS: {}".format(e
.args
[-1]), file=sys
.stderr
)
159 if 'certificate verify failed' in e
.args
[-1]:
160 print(" Fix the host certificate or disable validation".format(smtp_addr
), file=sys
.stderr
)
162 except smtplib
.SMTPRecipientsRefused
as e
:
163 print_error("ERROR: {}".format(e
.args
[-1].popitem()[1][1].decode()), file=sys
.stderr
)
164 print(" You must provide a username/password to send to this host", file=sys
.stderr
)
166 except smtplib
.SMTPAuthenticationError
as e
:
167 print_error("ERROR: {}".format(e
.args
[-1].decode()), file=sys
.stderr
)
168 print(" Check to ensure your username and password are correct", file=sys
.stderr
)
170 except smtplib
.SMTPException
as e
:
171 print_error("ERROR: {}".format(e
.args
[-1]), file=sys
.stderr
)
172 if 'SMTP AUTH extension not supported by server' in e
.args
[0]:
173 print(" Authorization is not available - you may need to use TLS", file=sys
.stderr
)
176 print_error("ERROR: {}".format(e
.args
[-1]), file=sys
.stderr
)
179 print_good("Success!")