From ec0c7450ac29856457960817b6dd2b66091ff214 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Thu, 14 Nov 2019 11:44:44 +0100 Subject: [PATCH] Allow sending mail to external recipients. Email sending functions had always assumed they send to local recipients. However, with a little modification functions can be used to send to external recipients, too. --- src/mail_validator.py | 67 +++++++++++++++++++++++++++++++++++------------- 1 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/mail_validator.py b/src/mail_validator.py index aac4a2a..8c93af7 100644 --- a/src/mail_validator.py +++ b/src/mail_validator.py @@ -156,6 +156,9 @@ class MailValidator(): """ Inject emails from `source_path` to `target_path`. + This uses the script *restore_mail_inject.pl* which injects the mails + using IMAP (as opposed to :py:meth:`inject_smtp`). + :param str username: username for the mail injection script :param str original_user: original username for the mail injection script @@ -175,27 +178,53 @@ class MailValidator(): result = subprocess.check_output(cmd, shell=True) log.debug(result) + def _prepare_recipients(self, recipients): + """ + Prepare recipient list: ensure list of proper addresses. + + If given a simple string, make a list of strings out of it. + If any recipient is just a username, append "@" + localhost to it. + Also check that recipients are just email addresses. + """ + hostname = socket.gethostname() + if isinstance(recipients, str): + recipients = [recipients, ] + result = [] + for recipient in recipients: + if '@' in recipient: + result.append(recipient) + else: + result.append(recipient + '@' + hostname) + for bad_char in '<>"\'': + if bad_char in recipient: + raise ValueError('Recipient must be a "raw" email address,' + ' not {!r}'.format(recipient)) + return result + def inject_smtp(self, usernames, emails): """ Inject emails from `source_path` using python's SMTP library. - :param usernames: usernames of the localhost receivers for each email - :type usernames: [str] - :param emails: emails to be sent to each user + As opposed to :py:meth:`inject_emails`, this actually sends the mail + to the local mail server (meaning filtering, archiving, ... will + happen). + + :param usernames: username(s) of the localhost receiver(s) for each + email or proper email address(es) + :type usernames: str or [str] + :param emails: paths to files including full emails (header + body) + to be sent to each user :type emails: [str] """ - usernames_string = ",".join(usernames) - log.info("Sending emails to %s", usernames_string) + recipients = self._prepare_recipients(usernames) + log.info("Sending emails to %s", ','.join(recipients)) with smtplib.SMTP('localhost') as server: - hostname = socket.gethostname() - users = [username + "@" + hostname for username in usernames] - for email in emails: log.info("Sending email %s", email) with open(os.path.join(self.source_path, email), 'rb') \ as file_handle: email_content = file_handle.read() - server.sendmail(self.smtp_sender, users, email_content) + server.sendmail(self.smtp_sender, recipients, email_content) # Wait till SMTP queue is processed arnied_wrapper.wait_for_email_transfer() @@ -386,15 +415,17 @@ class MailValidator(): log.info("Message content '%s' in %s is valid!", content_type, email_path) - def send_email_with_files(self, username, file_list, + def send_email_with_files(self, usernames, file_list, wait_for_transfer=True, autotest_signature=None, subject="my subject"): """ - Send a generated email with attachments. + Send a generated email with optional attachments. - :param str username: username of a localhost receiver of the email - :param file_list: files attached to an email + :param usernames: username(s) of the localhost receiver(s) or proper + email address(es) + :type usernames: str or [str] + :param file_list: files attached to an email; can be empty :type file_list: [str] :param wait_for_transfer: specify whether to wait until arnied_wrapper confirms email transfer; you can also specify @@ -407,8 +438,7 @@ class MailValidator(): """ text = 'This is an autogenerated email.\n' - hostname = socket.gethostname() - user = username + "@" + hostname + recipients = self._prepare_recipients(usernames) if file_list: # empty or None or so msg = MIMEMultipart() # pylint: disable=redefined-variable-type @@ -416,7 +446,7 @@ class MailValidator(): else: msg = MIMEText(text, _charset='utf-8') # pylint: disable=redefined-variable-type msg['From'] = self.smtp_sender - msg['To'] = user + msg['To'] = ', '.join(recipients) msg['Subject'] = subject msg['Date'] = formatdate(localtime=True) msg.preamble = 'This is a multi-part message in MIME format.\n' @@ -470,9 +500,10 @@ class MailValidator(): log.debug("Message successfully created") # send via SMTP - log.debug("Sending message from %s to %s" % (self.smtp_sender, user)) + log.debug("Sending message from %s to %s" + % (self.smtp_sender, ', '.join(recipients))) with smtplib.SMTP('localhost') as server: - server.sendmail(self.smtp_sender, user, msg.as_string()) + server.sendmail(self.smtp_sender, recipients, msg.as_string()) # wait for transfer; complicated by isinstance(False, int) == True if wait_for_transfer is False: -- 1.7.1