[tor-commits] [bridgedb/develop] Refactor script.

phw at torproject.org phw at torproject.org
Mon May 11 19:34:46 UTC 2020


commit 91af6c1bbad6a71663372642c5b6531cd197e806
Author: Philipp Winter <phw at nymity.ch>
Date:   Tue Feb 18 10:59:14 2020 -0800

    Refactor script.
---
 scripts/nagios-email-check | 191 +++++++++++++++++++++++++++++++++++++++++++++
 scripts/nagios_email_check | 162 --------------------------------------
 2 files changed, 191 insertions(+), 162 deletions(-)

diff --git a/scripts/nagios-email-check b/scripts/nagios-email-check
new file mode 100755
index 0000000..7287646
--- /dev/null
+++ b/scripts/nagios-email-check
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# This file is part of  BridgeDB, a Tor bridge distribution system.
+#
+# This script sends an email request for bridges to BridgeDB and then checks if
+# it got a response.  The result is written to STATUS_FILE, which is consumed
+# by Nagios.  Whenever BridgeDB fails to respond with bridges, we will get a
+# Nagios alert.
+#
+# Run this script via crontab every three hours as follows:
+#   0 */3 * * * path/to/nagios-email-check $(cat path/to/gmail.key)
+
+
+import sys
+import smtplib
+import time
+import imaplib
+import email
+import email.utils
+
+# Standard Nagios return codes
+OK, WARNING, CRITICAL, UNKNOWN = range(4)
+
+FROM_EMAIL = "testbridgestorbrowser at gmail.com"
+TO_EMAIL = "bridges at torproject.org"
+SMTP_SERVER = "imap.gmail.com"
+SMTP_PORT = 993
+
+MESSAGE_FROM = TO_EMAIL
+MESSAGE_BODY = "Here are your bridges:"
+
+STATUS_FILE = "/srv/bridges.torproject.org/check/status"
+
+# This will contain our test email's message ID.  We later make sure that this
+# message ID is referenced in the In-Reply-To header of BridgeDB's response.
+MESSAGE_ID = None
+
+
+def log(*args, **kwargs):
+    """
+    Generic log function.
+    """
+
+    print("[+]", *args, file=sys.stderr, **kwargs)
+
+
+def get_email_response(password):
+    """
+    Open our Gmail inbox and see if we got a response.
+    """
+
+    log("Checking for email response.")
+    mail = imaplib.IMAP4_SSL(SMTP_SERVER)
+    try:
+        mail.login(FROM_EMAIL, password)
+    except Exception as e:
+        return WARNING, str(e)
+
+    mail.select("INBOX")
+
+    _, data = mail.search(None, "ALL")
+    email_ids = data[0].split()
+    if len(email_ids) == 0:
+        log("Found no response.")
+        return CRITICAL, "No emails from BridgeDB found"
+
+    return check_email(mail, email_ids)
+
+
+def check_email(mail, email_ids):
+    """
+    Check if we got our expected email response.
+    """
+
+    log("Checking {:,} emails.".format(len(email_ids)))
+    for email_id in email_ids:
+        _, data = mail.fetch(email_id, "(RFC822)")
+
+        # The variable `data` contains the full email object fetched by imaplib
+        # <https://docs.python.org/3/library/imaplib.html#imaplib.IMAP4.fetch>
+        # We are only interested in the response part containing the email
+        # envelope.
+        for response_part in data:
+            if isinstance(response_part, tuple):
+                m = str(response_part[1], "utf-8")
+                msg = email.message_from_string(m)
+                email_from = "{}".format(msg["From"])
+                email_body = "{}".format(msg.as_string())
+                email_reply_to = "{}".format(msg["In-Reply-To"])
+
+                if (MESSAGE_FROM == email_from) and \
+                   (MESSAGE_BODY in email_body) and \
+                   (MESSAGE_ID == email_reply_to):
+                    mail.store(email_id, '+X-GM-LABELS', '\\Trash')
+                    mail.expunge()
+                    mail.close()
+                    mail.logout()
+                    log("Found correct response (referencing {})."
+                        .format(MESSAGE_ID))
+                    return OK, "BridgeDB's email responder works"
+                else:
+                    mail.store(email_id, '+X-GM-LABELS', '\\Trash')
+    mail.expunge()
+    mail.close()
+    mail.logout()
+    log("Found no response.")
+    return WARNING, "No emails from BridgeDB found"
+
+
+def send_email_request(password):
+    """
+    Attempt to send a bridge request over Gmail.
+    """
+
+    subject = "Bridges"
+    body = "get bridges"
+
+    log("Sending email.")
+    global MESSAGE_ID
+    MESSAGE_ID = email.utils.make_msgid(idstring="test-bridgedb",
+                                        domain="gmail.com")
+    email_text = "From: %s\r\nTo: %s\r\nMessage-ID: %s\r\nSubject: %s\r\n" \
+                 "\r\n%s" % (FROM_EMAIL, TO_EMAIL, MESSAGE_ID, subject, body)
+
+    try:
+        mail = smtplib.SMTP_SSL("smtp.gmail.com", 465)
+        mail.login(FROM_EMAIL, password)
+        mail.sendmail(FROM_EMAIL, TO_EMAIL, email_text)
+        mail.close()
+        log("Email successfully sent (message ID: %s)." % MESSAGE_ID)
+        return OK, "Sent email bridge request"
+    except Exception as e:
+        log("Error while sending email: %s" % err)
+        return UNKNOWN, str(e)
+
+
+def write_status_file(status, message):
+    """
+    Write the given `status` and `message` to our Nagios status file.
+    """
+
+    codes = {
+        0: "OK",
+        1: "WARNING",
+        2: "CRITICAL",
+        3: "UNKNOWN"
+    }
+    code = codes.get(status, UNKNOWN)
+
+    with open(STATUS_FILE, "w") as fd:
+        fd.write("{}\n{}: {}".format(code, status, message))
+    log("Wrote status='%s', message='%s' to status file." % (status, message))
+
+
+if __name__ == "__main__":
+    status, message = None, None
+
+    # Our Gmail password should be in sys.argv[1].
+
+    if len(sys.argv) == 2:
+        password = sys.argv[1]
+    else:
+        log("No email password provided.")
+        write_status_file(UNKNOWN, "No email password provided")
+        sys.exit(1)
+
+    # Send an email request to BridgeDB.
+
+    try:
+        status, message = send_email_request(password)
+    except Exception as e:
+        write_status_file(UNKNOWN, repr(e))
+        sys.exit(1)
+
+    wait_time = 60
+    log("Waiting %d seconds for a response." % wait_time)
+    time.sleep(wait_time)
+
+    # Check if we've received an email response.
+
+    try:
+        status, message = get_email_response(password)
+    except KeyboardInterrupt:
+        status, message = CRITICAL, "Caught Control-C..."
+    except Exception as e:
+        status = CRITICAL
+        message = repr(e)
+    finally:
+        write_status_file(status, message)
+        sys.exit(status)
diff --git a/scripts/nagios_email_check b/scripts/nagios_email_check
deleted file mode 100644
index 767d055..0000000
--- a/scripts/nagios_email_check
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# This file is part of  BridgeDB, a Tor bridge distribution system.
-#
-# This scripts send an email to bridgedb requesting a bridge and check if the
-# service reply with a valid bridge. A status file is written in path specified
-# by STATUS_FILE.
-#
-# The STATUS_FILE is read by nagios that will send an alert whenever bridgedb
-# will not send a valid email response.
-#
-# Run via crontab
-# Ex: */10 * * * * <path_to_bridgedb>/scripts/nagios_email_check $(cat <path_to_email_key/gmail.key)
-#
-# :authors: hiro <hiro at torproject.org>
-#           see also AUTHORS file
-#
-# :license: This is Free Software. See LICENSE for license information.
-#
-#
-
-
-import sys
-import smtplib
-import time
-import imaplib
-import email
-import time
-
-# Standard Nagios return codes
-OK, WARNING, CRITICAL, UNKNOWN = range(4)
-
-ORG_EMAIL   = "@gmail.com"
-FROM_EMAIL  = "test.bridges.browser" + ORG_EMAIL
-SMTP_SERVER = "imap.gmail.com"
-SMTP_PORT   = 993
-
-MESSAGE_FROM = "bridges at torproject.org"
-MESSAGE_SUBJECT = "Bridges"
-MESSAGE_BODY = "Here are your bridges:"
-
-STATUS_FILE = "/srv/bridgedb.torproject.org/check/status"
-
-# -------------------------------------------------
-#
-# Utility to read email from Gmail Using Python
-#
-# ------------------------------------------------
-
-def test_email_from_gmail(password):
-    mail = imaplib.IMAP4_SSL(SMTP_SERVER)
-    try:
-        mail.login(FROM_EMAIL, password)
-    except Exception as e:
-        return WARNING, str(e)
-
-    mail.select('INBOX')
-
-    _, data = mail.search(None, 'ALL')
-    mail_ids = data[0]
-
-    id_list = mail_ids.split()
-
-    status, message = check_email(id_list)
-
-    return status, message
-
-
-def check_email(id_list):
-    first_email_id = int(str(id_list[0], 'utf-8'))
-    latest_email_id = int(str(id_list[-1], 'utf-8'))
-
-    for i in range(int(latest_email_id), int(first_email_id), -1):
-        _, data = mail.fetch(str(i), '(RFC822)')
-
-
-        # The variable data contains the full email object fetched by imaplib
-        # https://docs.python.org/3/library/imaplib.html#imaplib.IMAP4.fetch
-        # We are only interested in the response part containing the email envelope.
-
-        for response_part in data:
-            if isinstance(response_part, tuple):
-                m = str(response_part[1], 'utf-8')
-                msg = email.message_from_string(m)
-                email_subject = "{}".format(msg['subject'])
-                email_from = "{}".format(msg['from'])
-                email_body = "{}".format(msg.as_string())
-
-                if (MESSAGE_FROM == email_from) and (MESSAGE_SUBJECT == email_subject) and (MESSAGE_BODY in email_body):
-                    mail.store(str(i), '+FLAGS', '\\Deleted')
-                    mail.close()
-                    return OK, "Bridgedb is good and sending emails with working bridges"
-                else:
-                    mail.store(str(i), '+FLAGS', '\\Deleted')
-
-        mail.close()
-        return WARNING, "No emails from gettor found"
-
-
-def send_email_from_gmail(password):
-    sent_from = FROM_EMAIL
-    sent_to = ["{}".format(MESSAGE_FROM)]
-    subject = 'Bridges'
-    body = 'get bridges'
-
-    email_text = """From: %s\nTo: %s\nSubject: %s\n\n%s""" % (sent_from, ", ".join(sent_to), subject, body)
-
-    try:
-        mail = smtplib.SMTP_SSL('smtp.gmail.com', 465)
-        mail.login(sent_from, password)
-        mail.sendmail(sent_from, sent_to, email_text)
-        mail.close()
-        return OK, "Test email sent"
-    except Exception as e:
-        return UNKNOWN, str(e)
-
-if __name__ == "__main__":
-    status, message = None, None
-
-    if len(sys.argv) == 2:
-        password = sys.argv[1]
-    else:
-        return UNKNOWN, "Empty email password"
-
-    try:
-        status_file = open(STATUS_FILE, 'r')
-        message = status_file.read()
-        status_file.close()
-    except OSError:
-        status = UNKNOWN
-        message = "Status file has been created {}".format(STATUS_FILE)
-        status_file = open(STATUS_FILE,'w')
-        status_file.write("UNKNOWN\n3: %s" % message)
-        status_file.close()
-
-    try:
-        status, message = send_email_from_gmail(password)
-    except Exception as e:
-        status = UNKNOWN
-        message = repr(e)
-        status_file = open(STATUS_FILE,'w')
-        status_file.write("UNKNOWN\n3: %s" % message)
-        status_file.close()
-
-    time.sleep(600)
-
-    try:
-        status, message = test_email_from_gmail(password)
-    except KeyboardInterrupt:
-        status, message = CRITICAL, "Caught Control-C..."
-    except Exception as e:
-        status = CRITICAL
-        message = repr(e)
-    finally:
-        d = {0: "OK", 1: "WARNING", 2: "CRITICAL", 3: "UNKNOWN"}
-        status_file = open(STATUS_FILE,'w')
-        status_file.write("{}\n{}: {}" % (d[status], status, message))
-
-        status_file.close()
-
-        sys.exit(status)





More information about the tor-commits mailing list