183 lines
7.7 KiB
Python
Executable File
183 lines
7.7 KiB
Python
Executable File
#!/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()
|