commit f323b691dbac8cea840974c0cb42a75518febb89 Author: Daniel Langbein Date: Sun Dec 6 13:18:32 2020 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..899a131 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config/cfg.ini +__pycache__/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.idea/execNotify.iml b/.idea/execNotify.iml new file mode 100644 index 0000000..84b184a --- /dev/null +++ b/.idea/execNotify.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..e96f6e0 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d4e4db6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..1e55ac7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..533968a --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1607247615238 + + + + + + + + + + + + \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..4f46515 --- /dev/null +++ b/config.py @@ -0,0 +1,34 @@ +from pathlib import Path +import configparser + + +def getHostAndPort(): + return config['mail']['host'], config['mail']['port'] + + +def getPassword(): + return config['mail']['password'] + + +def getFrom(): + return config['mail']['from'] # used for mail login as well + + +def getTo(): + return config['mail']['to'] + + +def getErrorFile() -> Path: + return Path(config['file']['errorfile']) + + +def _getCfgFile() -> Path: + return _getCfgDir().joinpath('cfg.ini') + + +def _getCfgDir() -> Path: + return Path('config') + + +config: configparser.ConfigParser = configparser.ConfigParser() +config.read(_getCfgFile()) diff --git a/config/cfg.ini.example b/config/cfg.ini.example new file mode 100644 index 0000000..b6a0a8b --- /dev/null +++ b/config/cfg.ini.example @@ -0,0 +1,12 @@ +[DEFAULT] + +[mail] +host = smtp.example.com +port = 465 +password = examplePassword + +from = noreply@example.com +to = me@example.com + +[file] +errorfile = /home/exampleUser/ERROR.mail diff --git a/exec.py b/exec.py new file mode 100644 index 0000000..1a89044 --- /dev/null +++ b/exec.py @@ -0,0 +1,22 @@ +from typing import List +import sys +import subprocess + + +def execute(command: List[str]): + """ + Run the given command in a subprocess and pass stdin (if given) to that process. + Wait for command to complete. + + :param command: A command to executed as list of words, e.g. ["echo", "hello"] + :return: (status, stdout, stderr) + """ + + completed: subprocess.CompletedProcess = subprocess.run( + command, + stdin=sys.stdin, + capture_output=True, + check=False, + text=True + ) + return completed.returncode, completed.stdout, completed.stderr diff --git a/execNotify.py b/execNotify.py new file mode 100755 index 0000000..df917e3 --- /dev/null +++ b/execNotify.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +from sys import argv +import socket + +import exec, mail, config + + +def main(): + prevMailError = checkPrevError() + success = executeCommand() + if prevMailError: + tryToInform() + + if not prevMailError or not success: + exit(1) + else: + exit(0) + + +def executeCommand() -> bool: + if len(argv) < 2: + return True + else: + keys = ['command', 'status', 'stderr', 'stdout'] + code, stdout, stderr = exec.execute(argv[1:]) + values = [str(argv[1:]), str(code), stderr, stdout] + + BODY = '' + for key, value in zip(keys, values): + BODY += '=== {} ===\n{}\n'.format(key, value) + print(BODY) + + if code != 0: + hostname = socket.gethostname() + + SUBJECT = '{} | {}'.format(hostname, str(argv[1:])) + mail.sendMailOrWriteToFile(SUBJECT=SUBJECT, BODY=BODY) + return False + + +def checkPrevError(): + return config.getErrorFile().exists() + + +def tryToInform(): + """ + Try to inform user via mail about previous error(s) that could not be sent to him before. + Maybe this time sending of an email works ;) + """ + + SUBJECT = '{} | Some mails not sent!'.format(socket.gethostname()) + BODY = 'Please check the file {} for mails which could previously not be sent to you!\n' \ + 'Note: You may delete the file after reading it ;)'.format( + config.getErrorFile()) + mail.sendMail(SUBJECT=SUBJECT, BODY=BODY) + + +if __name__ == '__main__': + main() diff --git a/mail.py b/mail.py new file mode 100644 index 0000000..2906628 --- /dev/null +++ b/mail.py @@ -0,0 +1,69 @@ +import sys +import datetime +import smtplib, ssl + +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from pathlib import Path + +import util, config + + +def sendMailOrWriteToFile(SUBJECT: str, BODY: str): + try: + sendMail(SUBJECT=SUBJECT, BODY=BODY) + except Exception as e: + print("Error: Could not send mail! Writing to file instead ...", file=sys.stderr) + print(e, file=sys.stderr) + + # Instead, try to save the mail so that the user can read + # it later when connected to this computer + saveMail(SUBJECT=SUBJECT, BODY=BODY) + + +def sendMail(SUBJECT: str, BODY: str): + """ + :throws Exception: if mail could not be sent + """ + + FROM = config.getFrom() + TO = config.getTo() + password = config.getPassword() + + # Create a secure SSL context + context = ssl.create_default_context() + + host, port = config.getHostAndPort() + with smtplib.SMTP_SSL(host=host, port=port, context=context) as server: + server.login(FROM, password) + message = MIMEMultipart() + message["Subject"] = SUBJECT + message["From"] = FROM + message["To"] = TO + message["Bcc"] = TO # Recommended for mass emails + # Turn plain/html message_body into plain/html MIMEText objects + message.attach(MIMEText(BODY, "plain")) + + server.sendmail(FROM, TO, message.as_string()) + + +def saveMail(SUBJECT: str, BODY: str): + """ + This method does NOT throw exceptions + """ + + time = datetime.datetime.now() + timeStr = time.strftime('%Y%m%d_%H%M%S') + + DATE = '=' * 20 + '\n=== date ===\n' + timeStr + '\n' + SUBJECT = '=== subject ===\n' + SUBJECT + '\n' + + try: + # append to file; create file if not existent + with open(config.getErrorFile(), "a") as f: + f.write(DATE) + f.write(SUBJECT) + f.write(BODY) + except Exception as e: + print('Error: Could not write to file!', file=sys.stderr) + print(e, file=sys.stderr) diff --git a/password.txt b/password.txt new file mode 100644 index 0000000..8decc4e --- /dev/null +++ b/password.txt @@ -0,0 +1 @@ +odf48p7k3 diff --git a/util.py b/util.py new file mode 100644 index 0000000..8507c3d --- /dev/null +++ b/util.py @@ -0,0 +1,10 @@ +from pathlib import Path + + +def readFirstLine(file: Path) -> str: + """ + :param file: Path to file + :return: first line of file + """ + with open(file, "r") as f: + return f.readline()