Merge pull request #33 from Supermathie/emailtest
[discourse_docker.git] / scripts / mailtest
1 #!/usr/bin/env python3
2 # vim:sw=4 ts=4 et:
3
4 import email
5 import smtplib
6 import ssl
7 import socket
8 import sys
9 import os
10
11 try:
12 from xtermcolor import colorize
13 COLOR_ERROR = 0xff0000
14 COLOR_WARN = 0xffff00
15 COLOR_GOOD = 0x00ff00
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)
22 except ImportError:
23 print("Install the python3-xtermcolor package for coloured output")
24 print_error = print
25 print_warn = print
26 print_good = print
27
28 try:
29 import yaml
30 except ImportError:
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)
33 sys.exit(1)
34
35 def do_tls(conn, sslv):
36 # Possible values of smtp_sslv: none|peer|client_once|fail_if_no_peer_cert
37 try:
38 # Creating a context with the purpose of server authentication implies verifying the certificate
39 sslcontext=ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
40 # The None below looks like might be a typo but it's not - it represents the ActiveRecord default (to verify)
41 if sslv in (None, 'peer', 'client_once', 'fail_if_no_peer_cert'):
42 # defaults are good
43 conn.starttls(context=sslcontext)
44 elif sslv in ('none',):
45 # disable cert checking
46 sslcontext.check_hostname = False
47 sslcontext.verify_mode = ssl.CERT_NONE
48 conn.starttls(context=sslcontext)
49 else:
50 raise ValueError('invalid value for DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: {}'.format(sslv))
51 except smtplib.SMTPException as e:
52 if (sslv is None) and ('STARTTLS extension not supported by server' in e.args[0]):
53 print_warn("unable to establish TLS, continuing: {}".format(e.args[0]))
54 else:
55 raise
56
57 ### Start of execution ###
58 cfgfile = sys.argv[1]
59 try:
60 destemail = sys.argv[2]
61 except IndexError:
62 destemail = input('Enter your email address: ')
63 srcemail = 'nobody+launcher-mailtest@discourse.org'
64
65 # Read in the container yaml and grab the env section
66 cfgdata = yaml.safe_load(open(cfgfile).read())
67 envdata = cfgdata['env']
68
69 # Here are the variables we'll test
70 smtp_addr = envdata.get('DISCOURSE_SMTP_ADDRESS')
71 smtp_port = envdata.get('DISCOURSE_SMTP_PORT')
72 smtp_user = envdata.get('DISCOURSE_SMTP_USER_NAME')
73 smtp_pass = envdata.get('DISCOURSE_SMTP_PASSWORD')
74 smtp_sslv = envdata.get('DISCOURSE_SMTP_OPENSSL_VERIFY_MODE')
75
76 # Yoink out the settings from the file - we'll print them and put them in the email
77 testinfo = 'DISCOURSE_SMTP_ settings:\n'
78 for k,v in filter(lambda x: x[0].startswith('DISCOURSE_SMTP_'), envdata.items()):
79 if 'PASSWORD' in k:
80 v = '(hidden)'
81 testinfo += ' {} = {}\n'.format(k,v)
82 print(testinfo)
83
84 # Ensure at least smtp-addr is specified - everything else is optional
85 if smtp_addr is None:
86 print_error("ERROR: DISCOURSE_SMTP_ADDRESS not specified", file=sys.stderr)
87 sys.exit(1)
88
89 if (smtp_user is None and smtp_pass is not None) or (smtp_user is not None and smtp_pass is None):
90 print_error("ERROR: both username and password must be specified for auth", file=sys.stderr)
91 sys.exit(1)
92
93 # Do we have a known good set of parameters?
94 known_good_settings = {
95 ('smtp.mandrillapp.com', 587): 'Mandrill',
96 }
97 try:
98 print_good('You are correctly configured to use: {}'.format(known_good_settings[smtp_addr,smtp_port]))
99 except KeyError:
100 pass
101
102 # Try and ensure the test is valid
103 if destemail.split('@',1)[1] in smtp_addr:
104 print_warn('WARNING: {} may be allowed to relay mail to {}, this may not be a valid test!'.format(smtp_addr, destemail))
105
106 # Outbound port smtp?
107 if smtp_port == 25 or smtp_port is None:
108 print_warn('WARNING: many networks block outbound port 25 - consider an alternative (587?)')
109
110 # Outbound port submission?
111 if smtp_port == 587:
112 if smtp_user is None:
113 print_warn('WARNING: trying to use the submission (587) port without authenticating will probably fail')
114
115 # Build the message and send!
116 msg = email.message.Message()
117 msg.add_header('From', 'nobody+launcher-mailtest@discourse.org')
118 msg.add_header('To', destemail)
119 msg.add_header('Subject', 'discourse launcher mailtest for {}'.format(os.path.basename(cfgfile)))
120 msg.set_payload(testinfo)
121
122 try:
123 smtp = smtplib.SMTP(smtp_addr, smtp_port, timeout=5)
124 #smtp.debuglevel=1
125 do_tls(smtp,smtp_sslv)
126 if smtp_user:
127 smtp.login(smtp_user, smtp_pass)
128 result = smtp.sendmail('nobody+launcher-mailtest@discourse.org', destemail, msg.as_string())
129 except socket.gaierror as e:
130 print_error("ERROR: {}".format(e.args[-1]), file=sys.stderr)
131 print(" Ensure that the host '{}' exists".format(smtp_addr), file=sys.stderr)
132 sys.exit(1)
133 except socket.timeout as e:
134 print_error("ERROR: {}".format(e.args[-1]), file=sys.stderr)
135 print(" Ensure that the host '{}' is up and port {} is reachable".format(smtp_addr, smtp_port), file=sys.stderr)
136 print(" If your settings are known-good, ensure outbound port {} is not blocked".format(smtp_port), file=sys.stderr)
137 sys.exit(1)
138 except smtplib.SMTPConnectError as e:
139 print_error("ERROR: {}".format(e.args[-1]), file=sys.stderr)
140 print(" Ensure that the host '{}' is up and port {} is reachable".format(smtp_addr, smtp_port), file=sys.stderr)
141 sys.exit(1)
142 except ssl.SSLError as e:
143 print_error("ERROR: unable to establish TLS: {}".format(e.args[-1]), file=sys.stderr)
144 if 'certificate verify failed' in e.args[-1]:
145 print(" Fix the host certificate or disable validation".format(smtp_addr), file=sys.stderr)
146 sys.exit(1)
147 except smtplib.SMTPRecipientsRefused as e:
148 print_error("ERROR: {}".format(e.args[-1].popitem()[1][1].decode()), file=sys.stderr)
149 print(" You must provide a username/password to send to this host", file=sys.stderr)
150 sys.exit(1)
151 except smtplib.SMTPAuthenticationError as e:
152 print_error("ERROR: {}".format(e.args[-1].decode()), file=sys.stderr)
153 print(" Check to ensure your username and password are correct", file=sys.stderr)
154 sys.exit(1)
155 except smtplib.SMTPException as e:
156 print_error("ERROR: {}".format(e.args[-1]), file=sys.stderr)
157 if 'SMTP AUTH extension not supported by server' in e.args[0]:
158 print(" Authorization is not available - you may need to use TLS", file=sys.stderr)
159 sys.exit(1)
160 except ValueError:
161 print_error("ERROR: {}".format(e.args[-1]), file=sys.stderr)
162 sys.exit(1)
163
164 print_good("Success!")