Add ability to test email from launcher
authorMichael Brown <michael.brown@discourse.org>
Sat, 19 Apr 2014 07:09:10 +0000 (03:09 -0400)
committerMichael Brown <michael.brown@discourse.org>
Sat, 19 Apr 2014 07:09:10 +0000 (03:09 -0400)
launcher
scripts/mailtest [new file with mode: 0755]

index 76c03a59bd52cc7f8e8ab2a6154b42758f17ffa9..2a71164cc57fd65ac5b9054323a7a8904e07cb1d 100755 (executable)
--- a/launcher
+++ b/launcher
@@ -4,6 +4,7 @@ command=$1
 config=$2
 opt=$3
 
+cd "$(dirname "$0")"
 
 config_file=containers/"$config".yml
 cidfile=cids/"$config".cid
@@ -28,6 +29,7 @@ usage () {
   echo "    destroy:    Stop and remove a container"
   echo "    ssh:        Start a bash shell in a running container"
   echo "    logs:       Docker logs for container"
+  echo "    mailtest:   Test the mail settings in a container"
   echo "    bootstrap:  Bootstrap a container for the config based on a template"
   echo "    rebuild:    Rebuild a container (destroy old, bootstrap, start new)"
   echo
@@ -189,6 +191,14 @@ if [ ! -e $config_file ]
 fi
 
 
+run_mailtest(){
+  if [ ! -e $config_file ]; then
+    echo "Config does not exist: $config_file" >&2
+    exit 1
+  fi
+  exec scripts/mailtest $config_file
+}
+
 run_stop(){
   if [ ! -e $cidfile ]
      then
@@ -294,12 +304,16 @@ run_bootstrap(){
 
 case "$command" in
   bootstrap)
-
       run_bootstrap
       echo "Successfully bootstrapped, to startup use ./launcher start $config"
       exit 0
       ;;
 
+  mailtest)
+      run_mailtest
+      exit 0
+      ;;
+
   ssh)
       if [ ! -e $cidfile ]
          then
diff --git a/scripts/mailtest b/scripts/mailtest
new file mode 100755 (executable)
index 0000000..3484310
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+# vim:sw=4 ts=4 et:
+
+import email
+import smtplib
+import ssl
+import socket
+import sys
+import os
+
+try:
+    import yaml
+except ImportError:
+    print("ERROR: python yaml module not installed - run the following and try again:", file=sys.stderr)
+    print("sudo apt-get install python3-yaml", file=sys.stderr)
+    sys.exit(1)
+
+def do_tls(conn, sslv):
+    # Possible values of smtp_sslv: none|peer|client_once|fail_if_no_peer_cert
+    try:
+        # Creating a context with the purpose of server authentication implies verifying the certificate
+        sslcontext=ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
+        # The None below looks like might be a typo but it's not - it represents the ActiveRecord default (to verify)
+        if sslv in (None, 'peer', 'client_once', 'fail_if_no_peer_cert'):
+            # defaults are good
+            conn.starttls(context=sslcontext)
+        elif sslv in ('none',):
+            # disable cert checking
+            sslcontext.check_hostname = False
+            sslcontext.verify_mode = ssl.CERT_NONE
+            conn.starttls(context=sslcontext)
+        else:
+            raise ValueError('invalid value for DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: {}'.format(sslv))
+    except smtplib.SMTPException as e:
+        if (sslv is None) and ('STARTTLS extension not supported by server' in e.args[0]):
+            print("unable to establish TLS, continuing: {}".format(e.args[0]))
+        else:
+            raise
+
+### Start of execution ###
+cfgfile = sys.argv[1]
+try:
+    destemail = sys.argv[2]
+except IndexError:
+    destemail = input('Enter your email address: ')
+srcemail  = 'nobody+launcher-mailtest@discourse.org'
+
+# Read in the container yaml and grab the env section
+cfgdata = yaml.safe_load(open(cfgfile).read())
+envdata = cfgdata['env']
+
+# Here are the variables we'll test
+smtp_addr = envdata.get('DISCOURSE_SMTP_ADDRESS')
+smtp_port = envdata.get('DISCOURSE_SMTP_PORT')
+smtp_user = envdata.get('DISCOURSE_SMTP_USER_NAME')
+smtp_pass = envdata.get('DISCOURSE_SMTP_PASSWORD')
+smtp_sslv = envdata.get('DISCOURSE_SMTP_OPENSSL_VERIFY_MODE')
+
+# Yoink out the settings from the file - we'll print them and put them in the email
+testinfo = 'DISCOURSE_SMTP_ settings:\n'
+for k,v in filter(lambda x: x[0].startswith('DISCOURSE_SMTP_'), envdata.items()):
+    if 'PASSWORD' in k:
+        v = '(hidden)'
+    testinfo += ' {} = {}\n'.format(k,v)
+print(testinfo)
+
+# Ensure at least smtp-addr is specified - everything else is optional
+if smtp_addr is None:
+    print("ERROR: DISCOURSE_SMTP_ADDRESS not specified", file=sys.stderr)
+    sys.exit(1)
+
+if (smtp_user is None and smtp_pass is not None) or (smtp_user is not None and smtp_pass is None):
+    print("ERROR: both username and password must be specified for auth", file=sys.stderr)
+    sys.exit(1)
+
+# Try and ensure the test is valid
+if destemail.split('@',1)[1] in smtp_addr:
+    print('WARNING: {} may be allowed to relay mail to {}, this may not be a valid test!'.format(smtp_addr, destemail))
+
+msg = email.message.Message()
+msg.add_header('From', 'nobody+launcher-mailtest@discourse.org')
+msg.add_header('To', destemail)
+msg.add_header('Subject', 'discourse launcher mailtest for {}'.format(os.path.basename(cfgfile)))
+msg.set_payload(testinfo)
+
+try:
+    smtp = smtplib.SMTP(smtp_addr, smtp_port, timeout=5)
+    #smtp.debuglevel=1
+    do_tls(smtp,smtp_sslv)
+    if smtp_user:
+        smtp.login(smtp_user, smtp_pass)
+    result = smtp.sendmail('nobody+launcher-mailtest@discourse.org', destemail, msg.as_string())
+except socket.gaierror as e:
+    print("ERROR: {}".format(e.args[-1]), file=sys.stderr)
+    print(" Ensure that the host '{}' exists".format(smtp_addr), file=sys.stderr)
+    sys.exit(1)
+except socket.timeout as e:
+    print("ERROR: {}".format(e.args[-1]), file=sys.stderr)
+    print(" Ensure that the host '{}' is up and reachable".format(smtp_addr), file=sys.stderr)
+    sys.exit(1)
+except smtplib.SMTPConnectError as e:
+    print("ERROR: {}".format(e.args[-1]), file=sys.stderr)
+    print(" Ensure that the host '{}' is up and reachable", file=sys.stderr)
+    sys.exit(1)
+except ssl.SSLError as e:
+    print("ERROR: unable to establish TLS: {}".format(e.args[-1]), file=sys.stderr)
+    if 'certificate verify failed' in e.args[-1]:
+        print(" Fix the host certificate or disable validation".format(smtp_addr), file=sys.stderr)
+    sys.exit(1)
+except smtplib.SMTPRecipientsRefused as e:
+    print("ERROR: {}".format(e.args[-1].popitem()[1][1].decode()), file=sys.stderr)
+    print(" You must provide a username/password to send to this host", file=sys.stderr)
+    sys.exit(1)
+except smtplib.SMTPAuthenticationError as e:
+    print("ERROR: {}".format(e.args[-1].decode()), file=sys.stderr)
+    print(" Check to ensure your username and password are correct", file=sys.stderr)
+    sys.exit(1)
+except smtplib.SMTPException as e:
+    print("ERROR: {}".format(e.args[-1]), file=sys.stderr)
+    if 'SMTP AUTH extension not supported by server' in e.args[0]:
+        print(" Authorization is not available - you may need to use TLS", file=sys.stderr)
+    sys.exit(1)
+except ValueError:
+    print("ERROR: {}".format(e.args[-1]), file=sys.stderr)
+    sys.exit(1)
+    
+print("Success!")