From 0927345d3b4ed0b06af335a2e4d1a857580d7ae7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 28 Mar 2013 06:51:31 -0400 Subject: [PATCH] email: Stub out send_imap() Arun Persaud suggested IMAP as an additional email delivery mechanism. The benefit of using IMAP over SMTP is that you can set the target mailbox directly (instead of filtering the incoming mail with procmail or a similar external tool). This commit restructures the 'send' configuration to support IMAP output with a configurable mailbox. That means you can do something like: [DEFAULT] email-protocol: imap imap-auth: True imap-username: myname imap-password: mypass imap-server: imap.yourisp.net imap-port: 993 imap-ssl: True [feed.rss2email] url = http://www.allthingsrss.com/rss2email/feed/ imap-mailbox = rss2email [feed.xkcd] url = http://xkcd.com/atom.xml imap-mailbox = xkcd For non-IMAP users, note that the boolean `use-smtp` configuration variable is gone, replaced by the more flexible `email-protocol`. You'll want to replace: use-smtp = False with: email-protocol = sendmail and replace: use-smtp = True with: email-protocol = smtp Signed-off-by: W. Trevor King --- rss2email/config.py | 17 +++++++++++++--- rss2email/email.py | 48 ++++++++++++++++++++++++++++++++++++++++++++- rss2email/error.py | 28 ++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/rss2email/config.py b/rss2email/config.py index 22f8626..b108d69 100644 --- a/rss2email/config.py +++ b/rss2email/config.py @@ -152,14 +152,25 @@ CONFIG['DEFAULT'] = _collections.OrderedDict(( ('body-width', str(0)), ### Mailing + # Select protocol from: sendmail, smtp, imap + ('email-protocol', 'sendmail'), # True: Use SMTP_SERVER to send mail. - # False: Use sendmail (or compatible) to send mail. - ('use-smtp', str(False)), + # Sendmail (or compatible) configuration ('sendmail', '/usr/sbin/sendmail'), # Path to sendmail (or compatible) - ('smtp-server', 'smtp.yourisp.net:25'), ('smtp-auth', str(False)), # set to True to use SMTP AUTH + # SMTP configuration + ('smtp-auth', str(False)), # set to True to use SMTP AUTH ('smtp-username', 'username'), # username for SMTP AUTH ('smtp-password', 'password'), # password for SMTP AUTH + ('smtp-server', 'smtp.yourisp.net:25'), ('smtp-ssl', str(False)), # Connect to the SMTP server using SSL + # IMAP configuration + ('imap-auth', str(False)), # set to True to use IMAP auth. + ('imap-username', 'username'), # username for IMAP authentication + ('imap-password', 'password'), # password for IMAP authentication + ('imap-server', 'imap.yourisp.net'), + ('imap-port', str(143)), + ('imap-ssl', str(False)), # connect to the IMAP server using SSL + ('imap-mailbox', 'INBOX'), # where we should store new messages ### Miscellaneous # Verbosity (one of 'error', 'warning', 'info', or 'debug'). diff --git a/rss2email/email.py b/rss2email/email.py index a5818cd..33d8ff0 100644 --- a/rss2email/email.py +++ b/rss2email/email.py @@ -28,10 +28,12 @@ from email.header import Header as _Header from email.mime.text import MIMEText as _MIMEText from email.utils import formataddr as _formataddr from email.utils import parseaddr as _parseaddr +import imaplib as _imaplib import io as _io import smtplib as _smtplib import subprocess as _subprocess import sys as _sys +import time as _time from . import LOG as _LOG from . import config as _config @@ -163,6 +165,47 @@ def smtp_send(sender, recipient, message, config=None, section='DEFAULT'): smtp.send_message(message, sender, [recipient]) smtp.quit() +def imap_send(message, config=None, section='DEFAULT'): + if config is None: + config = _config.CONFIG + server = config.get(section, 'imap-server') + port = config.getint(section, 'imap-port') + _LOG.debug('sending message to {}:{}'.format(server, port)) + ssl = config.getboolean(section, 'imap-ssl') + if ssl: + imap = _imaplib.IMAP4_SSL(server, port) + else: + imap = _imaplib.IMAP4(server, port) + try: + imap.connect(server) + except KeyboardInterrupt: + raise + except Exception as e: + raise _error.IMAPConnectionError(server=server) from e + try: + if config.getboolean(section, 'imap-auth'): + username = config.get(section, 'imap-username') + password = config.get(section, 'imap-password') + try: + if not ssl: + imap.starttls() + imap.login(username, password) + except KeyboardInterrupt: + raise + except Exception as e: + raise _error.IMAPAuthenticationError( + server=server, username=username) + mailbox = config.get(section, 'imap-mailbox') + date = _imaplib.Time2Internaldate(_time.localtime()) + message_bytes = _flatten(message) + imap.append(mailbox, None, date, message_bytes) + finally: + try: + imap.close() + except Exception as e: + _LOG.error(e) + imap.logout() + def _decode_header(header): """Decode RFC-2047-encoded headers to Unicode strings @@ -298,7 +341,10 @@ def sendmail_send(sender, recipient, message, config=None, section='DEFAULT'): raise _error.SendmailError() from e def send(sender, recipient, message, config=None, section='DEFAULT'): - if config.getboolean(section, 'use-smtp'): + protocol = config.get(section, 'email-protocol'): + if protocol == 'smtp': smtp_send(sender, recipient, message) + elif protocol == 'imap': + smtp_send(message) else: sendmail_send(sender, recipient, message) diff --git a/rss2email/error.py b/rss2email/error.py index 2a12cb5..b5ad130 100644 --- a/rss2email/error.py +++ b/rss2email/error.py @@ -86,6 +86,34 @@ class SMTPAuthenticationError (SMTPConnectionError): self.username = username +class IMAPConnectionError (ValueError, RSS2EmailError): + def __init__(self, server, port, message=None): + if message is None: + message = 'could not connect to mail server {}:{}'.format( + server, port) + super(IMAPConnectionError, self).__init__(message=message) + self.server = server + self.port = port + + def log(self): + super(IMAPConnectionError, self).log() + _LOG.warning( + 'check your config file to confirm that imap-server and other ' + 'mail server settings are configured properly') + if hasattr(self.__cause__, 'reason'): + _LOG.error('reason: {}'.format(self.__cause__.reason)) + + +class IMAPAuthenticationError (IMAPConnectionError): + def __init__(self, server, port, username): + message = ( + 'could not authenticate with mail server {}:{} as user {}'.format( + server, port, username)) + super(IMAPAuthenticationError, self).__init__( + server=server, port=port, message=message) + self.username = username + + class SendmailError (RSS2EmailError): def __init__(self, status=None, stdout=None, stderr=None): if status: -- 2.26.2