2020-06-28 11:23:02 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2020-12-27 14:20:44 +01:00
|
|
|
import base64
|
|
|
|
import colorama
|
|
|
|
import configargparse
|
2020-06-28 11:23:02 +02:00
|
|
|
import datetime
|
|
|
|
import email.utils
|
2020-12-27 14:20:44 +01:00
|
|
|
import io
|
2020-06-28 11:23:02 +02:00
|
|
|
import pprint
|
2020-12-27 14:20:44 +01:00
|
|
|
import subprocess
|
|
|
|
import sys
|
2021-10-17 14:33:53 +02:00
|
|
|
import uuid
|
2020-06-28 11:23:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2020-12-27 14:20:44 +01:00
|
|
|
parser = configargparse.ArgParser(
|
2020-06-28 11:23:02 +02:00
|
|
|
description="Generate SMTP messages to send a mail"
|
|
|
|
)
|
|
|
|
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
now_email = email.utils.formatdate(now.timestamp(), True)
|
|
|
|
|
2020-12-27 14:20:44 +01:00
|
|
|
parser.add_argument("-o", "--origin", env_var="ORIGIN", default="localhost")
|
|
|
|
parser.add_argument(
|
|
|
|
"-d", "--destination", env_var="DESTINATION", default="localhost"
|
|
|
|
)
|
|
|
|
parser.add_argument("-p", "--port", env_var="PORT", default=25)
|
2020-06-28 11:23:02 +02:00
|
|
|
parser.add_argument(
|
2021-06-13 11:49:21 +02:00
|
|
|
"-S",
|
|
|
|
"--security",
|
|
|
|
env_var="SECURITY",
|
|
|
|
choices=["plain", "ssl", "starttls"],
|
|
|
|
default="plain",
|
2020-06-28 11:23:02 +02:00
|
|
|
)
|
|
|
|
|
2020-12-27 14:20:44 +01:00
|
|
|
parser.add_argument("-l", "--helo", env_var="HELO")
|
|
|
|
parser.add_argument(
|
|
|
|
"-s", "--sender", env_var="SENDER", default="geoffrey@frogeye.fr"
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
2021-12-27 16:45:49 +01:00
|
|
|
"-r",
|
|
|
|
"--receiver",
|
|
|
|
env_var="RECEIVER",
|
|
|
|
default=[],
|
|
|
|
action="append",
|
2020-12-27 14:20:44 +01:00
|
|
|
)
|
|
|
|
# parser.add_argument("-a", "--auth", env_var="AUTH", default="PLAIN")
|
|
|
|
parser.add_argument("-u", "--user", env_var="MUSER")
|
|
|
|
parser.add_argument("-w", "--password", env_var="PASSWORD")
|
|
|
|
|
|
|
|
parser.add_argument("-f", "--from", env_var="FROM")
|
|
|
|
parser.add_argument("-t", "--to", env_var="TO")
|
2022-06-09 18:42:02 +02:00
|
|
|
parser.add_argument("-T", "--reply-to", env_var="REPLYTO")
|
2020-12-27 14:20:44 +01:00
|
|
|
parser.add_argument(
|
|
|
|
"-j",
|
|
|
|
"--subject",
|
|
|
|
env_var="SUBJECT",
|
|
|
|
default=f"Test message {now.strftime('%H:%M:%S')}",
|
|
|
|
)
|
2022-03-20 18:09:04 +01:00
|
|
|
parser.add_argument("-8", "--smtputf8", env_var="SMTPUTF8", action="store_true")
|
2021-06-11 21:42:55 +02:00
|
|
|
parser.add_argument("-c", "--callout", env_var="CALLOUT", action="store_true")
|
2020-12-27 14:20:44 +01:00
|
|
|
|
|
|
|
parser.add_argument("-b", "--body", env_var="BODY", default="")
|
|
|
|
parser.add_argument("-g", "--gtube", env_var="GTUBE", action="store_true")
|
|
|
|
parser.add_argument("-m", "--me", env_var="ME", default="Geoffrey")
|
|
|
|
|
|
|
|
parser.add_argument("-y", "--dryrun", env_var="DRYRUN", action="store_true")
|
|
|
|
parser.add_argument("-q", "--quiet", env_var="QUIET", action="store_true")
|
2020-06-28 11:23:02 +02:00
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
2020-12-27 14:20:44 +01:00
|
|
|
# Default values
|
2021-12-27 16:45:49 +01:00
|
|
|
if not args.receiver:
|
|
|
|
args.receiver = ["geoffrey@frogeye.fr"]
|
2020-12-27 14:20:44 +01:00
|
|
|
if args.helo is None:
|
|
|
|
args.helo = args.origin
|
|
|
|
if getattr(args, "from") is None:
|
|
|
|
setattr(args, "from", args.sender)
|
|
|
|
if args.to is None:
|
2021-12-27 16:45:49 +01:00
|
|
|
args.to = args.receiver[0]
|
2022-06-09 18:42:02 +02:00
|
|
|
if args.reply_to is None:
|
|
|
|
args.reply_to = args.to
|
2020-12-27 14:20:44 +01:00
|
|
|
if args.password:
|
|
|
|
password = args.password
|
2021-06-13 11:49:21 +02:00
|
|
|
args.password = "********"
|
2021-10-17 14:33:53 +02:00
|
|
|
mid = f"{uuid.uuid1()}@{args.helo}"
|
2020-12-27 14:20:44 +01:00
|
|
|
|
|
|
|
# Transmission content
|
2020-06-28 11:23:02 +02:00
|
|
|
|
2020-08-08 11:19:48 +02:00
|
|
|
gtube = ""
|
|
|
|
if args.gtube:
|
|
|
|
gtube = """
|
|
|
|
|
|
|
|
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X"""
|
|
|
|
|
|
|
|
body = ""
|
|
|
|
if args.body:
|
|
|
|
body = f"\n\n{args.body}"
|
|
|
|
|
2020-06-28 11:23:02 +02:00
|
|
|
text = f"""Date: {now_email}
|
2021-12-02 10:53:32 +01:00
|
|
|
From: {args.me} <{getattr(args, 'from')}>
|
2020-06-28 11:23:02 +02:00
|
|
|
Subject: {args.subject}
|
2020-12-27 14:20:44 +01:00
|
|
|
To: {args.to}
|
2022-06-09 18:42:02 +02:00
|
|
|
Reply-To: {args.reply_to}
|
2021-10-17 14:33:53 +02:00
|
|
|
Message-ID: {mid}
|
2020-06-28 11:23:02 +02:00
|
|
|
|
|
|
|
Hello there,
|
|
|
|
|
|
|
|
This is a test message, generated from a template.
|
2020-08-08 11:19:48 +02:00
|
|
|
If you didn't expect to see this message, please contact {args.me}.{gtube}{body}
|
2020-06-28 11:23:02 +02:00
|
|
|
|
|
|
|
Greetings,
|
|
|
|
|
|
|
|
Input arguments:
|
2020-12-27 14:20:44 +01:00
|
|
|
{pprint.pformat(args.__dict__, indent=4)}
|
2020-06-28 11:23:02 +02:00
|
|
|
|
|
|
|
--
|
|
|
|
{args.me}
|
|
|
|
."""
|
|
|
|
|
2020-12-27 14:20:44 +01:00
|
|
|
# Transmission setup
|
2021-12-02 10:53:32 +01:00
|
|
|
cmd = []
|
|
|
|
if args.origin != "localhost":
|
|
|
|
cmd += ["ssh", args.origin]
|
2020-12-27 14:20:44 +01:00
|
|
|
if args.security == "plain":
|
|
|
|
cmd += ["socat", "-", f"tcp:{args.destination}:{args.port}"]
|
|
|
|
elif args.security == "ssl":
|
|
|
|
cmd += ["socat", "-", f"openssl:{args.destination}:{args.port}"]
|
|
|
|
elif args.security == "starttls":
|
|
|
|
cmd += [
|
|
|
|
"openssl",
|
|
|
|
"s_client",
|
|
|
|
"-starttls",
|
|
|
|
"smtp",
|
|
|
|
"-crlf",
|
|
|
|
"-connect",
|
|
|
|
f"{args.destination}:{args.port}",
|
|
|
|
"-quiet",
|
|
|
|
]
|
|
|
|
|
|
|
|
if not args.quiet:
|
|
|
|
print(colorama.Fore.MAGENTA + f"# {' '.join(cmd)}" + colorama.Fore.RESET)
|
|
|
|
|
|
|
|
if not args.dryrun:
|
|
|
|
p = subprocess.Popen(
|
|
|
|
cmd,
|
|
|
|
stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.DEVNULL,
|
|
|
|
)
|
|
|
|
|
|
|
|
def recv() -> None:
|
|
|
|
if args.dryrun:
|
|
|
|
return
|
|
|
|
|
|
|
|
assert isinstance(p.stdout, io.BufferedReader)
|
|
|
|
next = True
|
|
|
|
while next:
|
|
|
|
line = p.stdout.readline()
|
2021-10-17 14:33:33 +02:00
|
|
|
try:
|
|
|
|
code = int(line[:3])
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError(f"Could not parse line: '{line.decode()}'")
|
2020-12-27 14:20:44 +01:00
|
|
|
success = code < 400
|
|
|
|
color = colorama.Fore.GREEN if success else colorama.Fore.RED
|
|
|
|
if not args.quiet:
|
|
|
|
print(color + f"< {line[:-1].decode()}" + colorama.Fore.RESET)
|
|
|
|
next = line[3] == b"-"[0]
|
|
|
|
if not next and not success:
|
|
|
|
send("QUIT") # TODO Can loop if QUIT fails
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
def send(command: str) -> None:
|
|
|
|
if not args.quiet:
|
|
|
|
print(colorama.Fore.BLUE + f"> {command}" + colorama.Fore.RESET)
|
|
|
|
|
|
|
|
if args.dryrun:
|
|
|
|
return
|
|
|
|
|
|
|
|
assert isinstance(p.stdin, io.BufferedWriter)
|
|
|
|
cmd = command.encode() + b"\n"
|
|
|
|
p.stdin.write(cmd)
|
|
|
|
p.stdin.flush()
|
|
|
|
|
|
|
|
recv()
|
|
|
|
|
|
|
|
# Transmission
|
|
|
|
|
2021-06-13 11:49:21 +02:00
|
|
|
if args.security != "starttls":
|
2020-12-27 14:20:44 +01:00
|
|
|
recv()
|
|
|
|
send(f"EHLO {args.helo}")
|
|
|
|
if args.user:
|
|
|
|
encoded = base64.b64encode(
|
|
|
|
args.user.encode()
|
|
|
|
+ b"\x00"
|
|
|
|
+ args.user.encode()
|
|
|
|
+ b"\x00"
|
|
|
|
+ password.encode()
|
|
|
|
).decode()
|
|
|
|
send(f"AUTH PLAIN {encoded}")
|
2022-03-20 18:09:04 +01:00
|
|
|
send(f"MAIL FROM: <{args.sender}>" + (" SMTPUTF8" if args.smtputf8 else ""))
|
2021-12-27 16:45:49 +01:00
|
|
|
for receiver in args.receiver:
|
|
|
|
send(f"RCPT TO: <{receiver}>")
|
2021-06-11 21:42:55 +02:00
|
|
|
if not args.callout:
|
|
|
|
send("DATA")
|
|
|
|
send(text)
|
2020-12-27 14:20:44 +01:00
|
|
|
send("QUIT")
|
|
|
|
sys.exit(0)
|
2020-06-28 11:23:02 +02:00
|
|
|
|
|
|
|
# For reference:
|
2020-12-27 14:20:44 +01:00
|
|
|
# send("RSET")
|
|
|
|
# send("VRFY")
|
|
|
|
# send("NOOP")
|
|
|
|
# send("QUIT")
|