email: Stub out send_imap()
authorW. Trevor King <wking@tremily.us>
Thu, 28 Mar 2013 10:51:31 +0000 (06:51 -0400)
committerW. Trevor King <wking@tremily.us>
Fri, 5 Apr 2013 20:21:24 +0000 (16:21 -0400)
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 <wking@tremily.us>
rss2email/config.py
rss2email/email.py
rss2email/error.py

index 22f862667789a71beeb76dd60ef905fd94f1fad6..b108d69ba227ddcb7e6383c8b926940bd9e6eadb 100644 (file)
@@ -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').
index a5818cd995e0047f44930d797ca9c549c1627ffc..33d8ff06cc01aada36987ba46521a4dbc4cfb799 100644 (file)
@@ -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)
index 2a12cb5a55698f640438b93807c8c731cc39de29..b5ad130ddf1be8b2afec6279e08273ebae829717 100644 (file)
@@ -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: