diff --git a/reaction-mailcreate/createMails.py b/reaction-mailcreate/createMails.py index 189a828833ee25e5ff2948589f942b0e60e33951..93a73c38becb43113f41fc1fbc86580338912334 100755 --- a/reaction-mailcreate/createMails.py +++ b/reaction-mailcreate/createMails.py @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. +from email.generator import Generator from email.message import EmailMessage from email.utils import formatdate import argparse @@ -27,6 +28,7 @@ import getpass import hashlib import jinja2 import os +import magic import requests import secrets import smtplib @@ -43,17 +45,19 @@ if sys.version_info < (3, 8): parser.register('action', 'extend', ExtendAction) # Parse args +parser.add_argument('-a', '--attach', dest='attach', default=[], nargs=1, action='extend', help='add attachment(s) to mail (default: None)') parser.add_argument('-b', '--basedir', dest='basedir', default='Mails', help='base directory for all output (default: "Mails")') -parser.add_argument('-B', '--bcc', dest='bcc', default=[], nargs=1, action='extend', help='additional mail recipient to bcc (default: None)') +parser.add_argument('-B', '--bcc', dest='bcc', default=[], nargs=1, action='extend', help='additional mail recipient(s) to bcc (default: None)') parser.add_argument('-c', '--campaign', dest='campaign', default='Test', help='campaign name (default: "Test")') +parser.add_argument('-C', '--cc', dest='cc', default=[], nargs=1, action='extend', help='additional mail recipient(s) to cc (default: None)') parser.add_argument('-d', '--dry-run', dest='dryrun', default=False, action='store_true', help='dry run -- do not actually send mails or create targets (default: False)') parser.add_argument('-f', '--from', dest='sender', default='Nobody <nobody@example.com>', help='sender mail address (default: "Nobody <nobody@example.com>"; implies dry-run if not set)') parser.add_argument('-F', '--force', dest='force', default=False, action='store_true', help='force insecure login without TLS/SSL (default: False)') parser.add_argument('-H', '--hashstring', dest='hashstring', default='{salt}{campaign}{infix}-{site}', help='string to be hashed for the URL (default: "{salt}{campaign}{infix}-{site}" where "{salt}" is a random string)') parser.add_argument('-i', '--input', dest='input', default='{basedir}/{campaign}/Input{infix}.lst', help='input file (default: "{basedir}/{campaign}/Input{infix}.lst")') parser.add_argument('-o', '--output', dest='output', default='{basedir}/{campaign}/{site}/Mail{infix}.txt', help='output file name template (default: "{basedir}/{campaign}/{site}/Mail{infix}.txt")') -parser.add_argument( '--salt', dest='salt', default=None, help='salt to use for hashing (default: random 8-byte hex string)') parser.add_argument('-R', '--reply-to', dest='replyto', default=None, help='reply-to mail address (default: None)') +parser.add_argument( '--salt', dest='salt', default=None, help='salt to use for hashing (default: random 8-byte hex string)') parser.add_argument('-s', '--subject', dest='subject', default='Security Challenge for {site} -- {campaign}{infix}', help='mail subject (default: "Security Challenge Message -- {campaign}{infix}")') parser.add_argument('-S', '--smtpserver', dest='smtpserver', default='localhost', help='SMTP server to use (default: "localhost"); port can be specified with "<host>:<port>" notation and takes precedence over implied ports and port specification') parser.add_argument( '--smtpport', dest='smtpport', default=0, type=int, help='SMTP port to use (default: 25); takes precedence over implied ports') @@ -63,8 +67,9 @@ parser.add_argument( '--starttls', dest='starttls', default=False, acti parser.add_argument('-t', '--template', dest='template', default='{basedir}/{campaign}/Mail.template', help='mail template file (default: "{basedir}/{campaign}/Mail.template")') parser.add_argument('-T', '--to', dest='to', default='{firstname} {lastname} <{email}>', help='recipient mail address (default: "{firstname} {lastname} <{email}>")') parser.add_argument('-u', '--url', dest='url', default='{webserver}/{campaign}{infix}-{hash}', help='URL template to use (default: "{webserver}/{campaign}{infix}-{hash}"') +parser.add_argument('-U', '--createurl', dest='createurl', default='{webserver}/{campaign}{infix}-{hash}/create', help='URL template to use for creation URL (default: "{webserver}/{campaign}{infix}-{hash}/create"') parser.add_argument('-v', '--verbose', dest='verbose', default=False, action='store_true', help='increase verbosity') -parser.add_argument('-w', '--webserver', dest='webserver', default='https://challenge.example.com', help='web server to use (default: "https://challenge.example.com"; implies dry-run if not set') +parser.add_argument('-w', '--webserver', dest='webserver', default='https://challenge.example.com', help='web server to use (default: "https://challenge.example.com"; implies dry-run if not set; to suppress web-hook calling, set to empty string ""') parser.add_argument('infix', default='', nargs='?', help='infix for ID purposes, default empty') args = parser.parse_args() @@ -110,10 +115,14 @@ if not args.salt: args.salt = secrets.token_hex(8) if args.verbose: + if args.attach: + print(f'Using "{", ".join(args.attach)}" as mail attachment(s).') print(f'Using "{args.basedir}" as base directory.') if args.bcc: - print(f'Using "{", ".join(args.bcc)}" as silent mail recipients.') + print(f'Using "{", ".join(args.bcc)}" as bcc mail recipient(s).') print(f'Using "{args.campaign}" as campaign.') + if args.cc: + print(f'Using "{", ".join(args.cc)}" as cc mail recipient(s).') print(f'Using "{args.sender}" as sender mail address.') if args.replyto: print(f'Using "{args.replyto}" as reply-to mail address.') @@ -125,6 +134,7 @@ if args.verbose: print(f'Using "{args.template.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as template file.') print(f'Using "{args.to.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as recipient mail address.') print(f'Using "{args.url.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as URL template.') + print(f'Using "{args.createurl.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt))}" as URL template.') print(f'Using "{args.webserver}" as web server.') print(f'Using "{args.infix}" as infix.') print(f'Using "{args.smtpserver}" as SMTP server.') @@ -158,44 +168,65 @@ def toHex(serial): def createMail(data): output = args.output.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) directory = os.path.dirname(output) - print(f'Writing mail to {output}') - if not os.path.exists(directory): - os.makedirs(directory) - with open(output, 'wb') as mailfile: - template.stream(data).dump(mailfile, encoding='utf-8') + + # Generate email body and attachments message = EmailMessage() - with open(output, 'r') as mailfile: - message.set_content(mailfile.read()) + message.set_content(template.render(data)) + for attach in args.attach: + with open(attach, 'rb') as attachment: + message.add_attachment(attachment.read(), *magic.from_file(attach, mime=True).split('/')) + message['Date'] = formatdate(localtime=True) message['Subject'] = args.subject.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) message['From'] = args.sender.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) - message['Reply-To'] = args.replyto.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) + if args.replyto: + message['Reply-To'] = args.replyto.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) message['To'] = args.to.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) - message['Bcc'] = ', '.join(args.bcc).format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) + if args.cc: + message['Cc'] = ', '.join(args.cc).format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) + if args.bcc: + message['Bcc'] = ', '.join(args.bcc).format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'], URL=data['URL'])) + + # Record mail + print(f'Writing mail to {output}') + if not os.path.exists(directory): + os.makedirs(directory) + with open(output, 'w') as mailfile: + Generator(mailfile).flatten(message) + if not args.dryrun: + # Actually send the mail + print(f'Sending mail to {message["To"]}') + if message['Cc']: + print(f'... and cc to {message["Cc"]}') if message['Bcc']: - print(f'Sending mail to {message["To"]} (and {message["Bcc"]})') - else: - print(f'Sending mail to {message["To"]}') - if args.starttls: - with smtplib.SMTP(args.smtpserver, args.smtpport) as smtp: - smtp.starttls() - smtp.login(args.smtpuser, args.smtppass) - smtp.send_message(message) - elif args.smtpuser or args.smtppass: - with smtplib.SMTP_SSL(args.smtpserver, args.smtpport) as smtp: - smtp.login(args.smtpuser, args.smtppass) - smtp.send_message(message) - else: - with smtplib.SMTP(args.smtpserver, args.smtpport) as smtp: - smtp.send_message(message) - print(f'Creating target at {data["URL"]}/create') - requests.get(f'{data["URL"]}/create') + print(f'... and bcc to {message["Bcc"]}') + try: + if args.starttls: + with smtplib.SMTP(args.smtpserver, args.smtpport) as smtp: + smtp.starttls() + smtp.login(args.smtpuser, args.smtppass) + smtp.send_message(message) + elif args.smtpuser or args.smtppass: + with smtplib.SMTP_SSL(args.smtpserver, args.smtpport) as smtp: + smtp.login(args.smtpuser, args.smtppass) + smtp.send_message(message) + else: + with smtplib.SMTP(args.smtpserver, args.smtpport) as smtp: + smtp.send_message(message) + except: + print(f'ERROR sending the mail!') + + # Call the webhook if webserver is specified + if args.webserver: + print(f'Creating target at {data["CreateURL"]}') + requests.get(f'{data["CreateURL"]}') def generateHashAndURL(data): data['hash'] = hashlib.sha256(args.hashstring.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'])).encode('utf-8')).hexdigest() data['URL'] = args.url.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'])) + data['CreateURL'] = args.createurl.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt, site=data['site'], firstname=data['firstname'], lastname=data['lastname'], email=data['email'], hash=data['hash'])) with open(args.input.format_map(SafeDict(basedir=args.basedir, campaign=args.campaign, infix=args.infix, webserver=args.webserver, salt=args.salt)), 'r') as inputfile: