commit 91af6c1bbad6a71663372642c5b6531cd197e806 Author: Philipp Winter phw@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@gmail.com" +TO_EMAIL = "bridges@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@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@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)
tor-commits@lists.torproject.org