#!/usr/bin/python3 import smtplib, imaplib import random import logging from datetime import datetime import os, time, shutil import re import subprocess def days_between(d1, d2): return abs((d2 - d1).days) def need_to_rotate_log(logfile): # the log file contains the date per line, so get first line of log, it is # when the log started try: file_obj = open(logfile, 'r') except: return 0 Lines = file_obj.readlines() first_line = Lines[0] file_obj.close() # get just the data to convert m = re.search('(\d+:\d+:\d+ \d+-\d+-\d+) .*', first_line) start_of_file = datetime. strptime(m.group(1), '%H:%M:%S %d-%m-%y') now = datetime.now() log_file_age_days = days_between(now, start_of_file ) return log_file_age_days > 7 # function that sends a message using smtp to port 467 def SendMessage( server, username, password, nonce, f ): msg='To: {} \r\nFrom: {}\r\nSubject: Test from python\r\nDate: \r\nTEST {}'.format( username, username, str(nonce)) # connect to mail server try: logging.info( "about to smtp_sll" ); s = smtplib.SMTP_SSL(host=server) conn_time = time.time_ns(); logging.info( "about to login" ); s.login(username,password) login_time = time.time_ns(); logging.info( "about to send" ); s.sendmail( username, username, msg) send_time = time.time_ns(); logging.info( "sent" ); except smtplib.SMTPConnectError as e: logging.error( "Failed to connect to {} ({})".format(server, e.strerror)) print('node_mail_synth_trans{what="mail_working"} 0', file=f ) except smtplib.SMTPAuthenticationError as e: logging.error( "Failed to autheticate to {} with user: {} ({})".format(server, username, e.strerror)) print('node_mail_synth_trans{what="mail_working"} 0', file=f ) except smtplib.SMTPRecipientsRefused as e: logging.error( "Recipient failed {} : ({})".format(username, e.strerror)) print('node_mail_synth_trans{what="mail_working"} 0', file=f ) except smtplib.SMTPException as e: logging.error( "Generic SMTP exception, not one I cared about ({})".format(e.strerror)) print('node_mail_synth_trans{what="mail_working"} 0', file=f ) finally: if s: s.quit() quit_time = time.time_ns(); logging.info("Successfully sent email: %d" % nonce ) smtp_elapsed=(quit_time-start_time)/1000000 logging.info("Time to send (ms): "+ str(smtp_elapsed)) print('node_mail_synth_trans{what="smtp_time_in_ms"} %d' % smtp_elapsed, file=f) print('node_mail_synth_trans{what="smtp_working"} 1', file=f ) # function that searches for message just sent in imap, and deletes it def ReceiveMessage( server, username, password, nonce, start_time, f ): try: imap_start_time = time.time_ns(); conn = imaplib.IMAP4_SSL(server) imap_conn_time = time.time_ns(); except imaplib.IMAP4.error as e: logging.error( "IMAP: Failed to connect ({})".format(e.strerror)) print('node_mail_synth_trans{what="mail_working"} 0', file=f ) try: conn.login(username,password) imap_login_time = time.time_ns(); except imaplib.IMAP4.error as e: logging.error( "IMAP: failed to login ({})".format(e.strerror)) print('node_mail_synth_trans{what="mail_working"} 0', file=f ) try: conn.select('Inbox') search_str="TEST " + str(nonce) typ, data = conn.search(None,'(BODY "' + search_str + '")' ) imap_search_time = time.time_ns(); if data[0] == b'': raise Exception("search returned no data") for num in data[0].split(): typ, data = conn.fetch(num,'(RFC822)') conn.store(num, '+FLAGS', '\\Deleted') imap_retr_time = time.time_ns(); conn.expunge() imap_del_time = time.time_ns(); except imaplib.IMAP4.error as e: logging.error( "IMAP: Could not find email {}".format(e.strerror)) print('node_mail_synth_trans{what="mail_working"} 0', file=f ) finally: if conn: conn.close() imap_close_time = time.time_ns(); # adding a 1 second sleep because my logs say it is deleting emails, but the emails are still in my folder, and also deleting ANY TEST emails, so if a subsequent run # works, it gets rid of the old ones that did fail for some reason time.sleep(1) conn.select('Inbox') search_str="TEST " typ, data = conn.search(None,'(BODY "' + search_str + '")' ) # if there is still data, something didn't delete as expected, so # just clean it up if data[0] != b'': for num in data[0].split(): typ, data = conn.fetch(num,'(RFC822)') conn.store(num, '+FLAGS', '\\Deleted') conn.expunge() conn.logout() imap_elapsed=(imap_close_time-imap_start_time)/1000000 total_elapsed=(imap_close_time-start_time)/1000000 - 5000 logging.info("time to recv email: %d" % imap_elapsed ) logging.info("Successfully found and deleted email: %d" % nonce) print('node_mail_synth_trans{what="imap_time_in_ms"} %d' % imap_elapsed, file=f) print('node_mail_synth_trans{what="total_time_in_ms"} %d' % total_elapsed, file=f) print('node_mail_synth_trans{what="imap_working"} 1', file=f ) print('node_mail_synth_trans{what="mail_working"} 1', file=f ) def Fail2BanCount(): try: o = subprocess.run(["sudo", "/bin/docker", "exec", "mail", "fail2ban"], stdout=subprocess.PIPE, text=True ) logging.info( f"Fail2ban returned: '{str(o.stdout.strip())}' ") out=str(o.stdout.strip()).split('\n')[1] ip_addresses = re.findall(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', out) unique_ip_count = len(set(ip_addresses)) print( 'node_mail_synth_trans{what="fail2ban_cnt"} ' + str(unique_ip_count), file=f ) except Exception as e: logging.error( f"Fail2ban: failed to get output for fail2ban ({e})" ) print('node_mail_synth_trans{what="fail2ban_cnt"} 0', file=f ) # login using travel as its never really used (if I do anything dumb, it should # have no real impact on mail that matters) server='mail.depaoli.id.au' username='travel@depaoli.id.au' password='%fId0bPIE0Y2JZTzODJ%#p9V' # set up unique message to send nonce = random.randint(1,99999999) # open file for writing prometheus formatted data into f = open('/srv/docker/container/node-exporter/textfile_collector/mail.prom', 'w') # put required help/type text in print('# HELP node_mail_synth_trans details of last synthetic mail transaction (full send/recv of an email)', file=f) print('# TYPE node_mail_synth_trans gauge', file=f ) # check for time, if its been a week or so, rotate the log logfile='/var/tmp/mail-tester.log' if( need_to_rotate_log(logfile) ): print( 'Rotating log file, its a week old (deletes file from 2 weeks ago)' ) shutil.move( logfile, logfile + '.1' ) # also set up an actual log file while I am learning python / testing this out logging.basicConfig(filename=logfile, format='%(asctime)s - %(message)s', datefmt='%H:%M:%S %d-%m-%y', level=logging.INFO) # all the time_ns() calls are just timestamps to compare back to this one for # relative time elapsed for operations, good for graphing in prometheus start_time = time.time_ns(); logging.info("start up for: %d" % nonce ); SendMessage( server, username, password, nonce, f ) logging.info("waiting 5 seconds for email to actually be delivered") time.sleep(5) ReceiveMessage( server, username, password, nonce, start_time, f ) Fail2BanCount() # close output file (used for prometheus) f.close()