#!/usr/bin/env python # Mail Flow Checker version 2.3. #Copyright (C) 2017 by National Board of eHealth, Denmark, and Troels Arvin. # #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: # #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. # #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. import os import sys import smtplib import email import random import syslog from ConfigParser import SafeConfigParser import getopt import psycopg2 import platform import socket # ==================== # === PREPARATIONS === # ==================== # preparing to parse config file default_config_path_basename = 'mail_flow.cfg' default_config_path = '/etc/' + default_config_path_basename alt_config_path = os.path.dirname(__file__)+'/'+default_config_path_basename config_file = None # the above paths may be overridden by the -C argument # don't let database or SMTP connection attempts run for more than 10 seconds: connect_timeout = 10 this_script = os.path.basename(__file__) my_hostname = platform.node() # --------------- Helper functions --------------- def usage(exit_error=True): print """Usage: %s [-C ] [-h|--help] config_file: Complete path to optional config file. If no configuration file is indicated, the system will look for the configuration at the following paths (first path preferred): - %s - %s""" % (this_script,default_config_path,alt_config_path) if exit_error: sys.exit(1) def format_msg(msg): with_config = '' if config_file is not None: with_config = " with config file '%s'" % config_file return "When running script '%s'%s: %s" % (this_script,with_config,msg) def complain(msg): sys.stderr.write(format_msg(msg)) def log(msg): syslog.syslog(format_msg(msg)) def err(msg): complain('Error: '+msg) log('Error: '+msg) sys.exit(1) # --------------- Parse args --------------- try: options, args = getopt.getopt \ ( sys.argv[1:], "hC:", [ "help", "config_file=" ] ) except getopt.GetoptError: usage() for name, value in options: if name in ("-h", "--help"): usage(False) sys.exit(0) else: config_file = value if config_file is None: if os.path.exists(default_config_path): config_file = default_config_path else: config_file = alt_config_path if not os.path.exists(config_file): err("No configuration file '%s'" % config_file) # --------------- Setup configuration --------------- defaults = { 'smtp_server': 'localhost', 'smtp_use_ssl': 'no', 'smtp_username': None, 'smtp_password': None, 'from_addr': 'nagios', 'to_addr': 'nagios', 'subject': 'Mail flow test', 'days_retain': '7', 'cleanup_seldomness': '100', 'name': 'nagios', 'schema': 'mail_flow', 'sequence': 'messageid_sequence', 'message_table': 'messages', } config = SafeConfigParser(defaults) config.read(config_file) try: mail_smtp_server = config.get('mail','smtp_server') mail_smtp_use_ssl = config.get('mail','smtp_use_ssl') mail_smtp_username = config.get('mail','smtp_username') mail_smtp_password = config.get('mail','smtp_password') mail_from_addr = config.get('mail','from_addr') mail_to_addr = config.get('mail','to_addr') mail_subject = config.get('mail','subject') db_days_retain = int(config.get('db','days_retain')) db_cleanup_seldomness = int(config.get('db','cleanup_seldomness')) db_name = config.get('db','name') db_schema = config.get('db','schema') db_sequence = config.get('db','sequence') db_message_table = config.get('db','message_table') except Exception, e: err("Configuration error: %s" % e) # --------------- Prepare DB --------------- # prepare db try: os.environ['PGCONNECT_TIMEOUT'] = str(connect_timeout) db_conn = psycopg2.connect("dbname='%s'" % db_name); cursor = db_conn.cursor() except psycopg2.DatabaseError, e: # Lets not make DB connection errors generate cron error mails: log( "Could not connect to DB '%s', or could not open cursor, error message: '%s'" % ( db_name, str(e) ) ) sys.exit(0) # =================== # === GET TO WORK === # =================== # --------------- Fetch message ID from DB --------------- # Prepare message id by getting next integer from db sequence cursor.execute("select nextval('%s.%s')" % (db_schema,db_sequence)) seq = cursor.fetchone()[0] # --------------- Prepare outgoing mail --------------- body = 'Mail flow testing.' msg_id = "<%d.%s.%s@%s>" % (seq,mail_smtp_server,this_script,my_hostname) headers = "From: %s\r\nTo: %s\r\nMessage-ID: %s\r\nSubject: %s\r\nPrecedence: bulk\r\n\r\n" % ( mail_from_addr, mail_to_addr, msg_id, mail_subject ) message = headers + body # --------------- Prepare DB insert --------------- sql = """ INSERT INTO %s.%s (msg_id,smtp_server,to_addr,subj) VALUES (%%(msg_id)s,%%(mail_smtp_server)s,%%(mail_to_addr)s,%%(mail_subject)s) """ % (db_schema, db_message_table) cursor.execute( sql, { 'msg_id':msg_id, 'mail_smtp_server': mail_smtp_server, 'mail_to_addr':mail_to_addr, 'mail_subject':mail_subject } ) # --------------- Key step: Send mail, and log it to the database --------------- try: socket.setdefaulttimeout(connect_timeout) smtp = smtplib.SMTP(mail_smtp_server) if mail_smtp_use_ssl.lower()=='yes': smtp.starttls() if mail_smtp_username or mail_smtp_password: smtp.login(mail_smtp_username,mail_smtp_password) # actually send mail smtp.sendmail(mail_from_addr,mail_to_addr,message) smtp.quit() except (smtplib.SMTPException,socket.timeout,socket.error), e: # Lets not make SMTP connection errors generate cron error mails: log( "Could not communicate with SMTP server '%s'. Error message: '%s'" % ( mail_smtp_server, str(e) ) ) sys.exit(0) # most important part done, so commit db_conn.commit() # ================================ # === HOUSEKEEPING AND CLEANUP === # ================================ # --------------- Clean up database once in a while --------------- if db_days_retain is not None: random.seed() r = random.randint(1,db_cleanup_seldomness) if r == db_cleanup_seldomness: sql = """ DELETE FROM %s.%s WHERE smtp_server=%%(mail_smtp_server)s AND to_addr=%%(mail_to_addr)s AND subj=%%(mail_subject)s AND sent + INTERVAL '%d day' < CURRENT_TIMESTAMP """ % (db_schema,db_message_table,db_days_retain) cursor.execute( sql,{ 'mail_smtp_server':mail_smtp_server, 'mail_to_addr':mail_to_addr, 'mail_subject':mail_subject } ) db_conn.commit() # --------------- The end --------------- db_conn.close() sys.exit(0)