2020-01-30 21:32:09 +01:00
|
|
|
"""Cron job implementation of Zulip's incoming email gateway's helper
|
|
|
|
for forwarding emails into Zulip.
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2020-10-27 22:30:35 +01:00
|
|
|
https://zulip.readthedocs.io/en/latest/production/email-gateway.html
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2020-01-30 21:32:09 +01:00
|
|
|
The email gateway supports two major modes of operation: An email
|
|
|
|
server (using postfix) where the email address configured in
|
|
|
|
EMAIL_GATEWAY_PATTERN delivers emails directly to Zulip, and this, a
|
|
|
|
cron job that connects to an IMAP inbox (which receives the emails)
|
|
|
|
periodically.
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2022-02-08 00:13:33 +01:00
|
|
|
Run this in a cron job every N minutes if you have configured Zulip to
|
2020-01-30 21:32:09 +01:00
|
|
|
poll an external IMAP mailbox for messages. The script will then
|
|
|
|
connect to your IMAP server and batch-process all messages.
|
2013-12-17 22:38:32 +01:00
|
|
|
|
2017-04-18 17:28:55 +02:00
|
|
|
We extract and validate the target stream from information in the
|
|
|
|
recipient address and retrieve, forward, and archive the message.
|
2013-12-17 22:38:32 +01:00
|
|
|
|
2013-08-08 16:44:40 +02:00
|
|
|
"""
|
2017-11-16 00:43:27 +01:00
|
|
|
import email
|
2020-06-05 23:26:35 +02:00
|
|
|
import email.policy
|
2013-08-08 16:44:40 +02:00
|
|
|
import logging
|
2020-06-05 23:26:35 +02:00
|
|
|
from email.message import EmailMessage
|
2017-11-16 00:43:27 +01:00
|
|
|
from imaplib import IMAP4_SSL
|
2019-02-02 23:53:29 +01:00
|
|
|
from typing import Any, Generator
|
2013-08-08 16:44:40 +02:00
|
|
|
|
|
|
|
from django.conf import settings
|
2019-05-03 23:20:39 +02:00
|
|
|
from django.core.management.base import BaseCommand, CommandError
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2017-11-02 10:19:24 +01:00
|
|
|
from zerver.lib.email_mirror import logger, process_message
|
2013-08-08 16:44:40 +02:00
|
|
|
|
|
|
|
## Setup ##
|
|
|
|
|
|
|
|
log_format = "%(asctime)s: %(message)s"
|
|
|
|
logging.basicConfig(format=log_format)
|
|
|
|
|
|
|
|
formatter = logging.Formatter(log_format)
|
2013-11-01 19:31:00 +01:00
|
|
|
file_handler = logging.FileHandler(settings.EMAIL_MIRROR_LOG_PATH)
|
2013-08-08 16:44:40 +02:00
|
|
|
file_handler.setFormatter(formatter)
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
logger.addHandler(file_handler)
|
|
|
|
|
2017-04-18 17:28:55 +02:00
|
|
|
|
2020-06-05 23:26:35 +02:00
|
|
|
def get_imap_messages() -> Generator[EmailMessage, None, None]:
|
2021-08-14 16:51:57 +02:00
|
|
|
# We're probably running from cron, try to batch-process mail
|
|
|
|
if (
|
|
|
|
not settings.EMAIL_GATEWAY_BOT
|
|
|
|
or not settings.EMAIL_GATEWAY_LOGIN
|
|
|
|
or not settings.EMAIL_GATEWAY_PASSWORD
|
|
|
|
or not settings.EMAIL_GATEWAY_IMAP_SERVER
|
|
|
|
or not settings.EMAIL_GATEWAY_IMAP_PORT
|
|
|
|
or not settings.EMAIL_GATEWAY_IMAP_FOLDER
|
|
|
|
):
|
|
|
|
raise CommandError(
|
|
|
|
"Please configure the email mirror gateway in /etc/zulip/, "
|
|
|
|
"or specify $ORIGINAL_RECIPIENT if piping a single mail."
|
|
|
|
)
|
2016-07-27 17:17:21 +02:00
|
|
|
mbox = IMAP4_SSL(settings.EMAIL_GATEWAY_IMAP_SERVER, settings.EMAIL_GATEWAY_IMAP_PORT)
|
|
|
|
mbox.login(settings.EMAIL_GATEWAY_LOGIN, settings.EMAIL_GATEWAY_PASSWORD)
|
|
|
|
try:
|
|
|
|
mbox.select(settings.EMAIL_GATEWAY_IMAP_FOLDER)
|
|
|
|
try:
|
2021-02-12 08:20:45 +01:00
|
|
|
status, num_ids_data = mbox.search(None, "ALL")
|
2019-08-15 08:05:44 +02:00
|
|
|
for message_id in num_ids_data[0].split():
|
2021-02-12 08:20:45 +01:00
|
|
|
status, msg_data = mbox.fetch(message_id, "(RFC822)")
|
2020-04-18 03:55:04 +02:00
|
|
|
assert isinstance(msg_data[0], tuple)
|
2016-07-27 17:17:21 +02:00
|
|
|
msg_as_bytes = msg_data[0][1]
|
2020-06-05 23:26:35 +02:00
|
|
|
message = email.message_from_bytes(msg_as_bytes, policy=email.policy.default)
|
2020-09-02 02:50:08 +02:00
|
|
|
# https://github.com/python/typeshed/issues/2417
|
|
|
|
assert isinstance(message, EmailMessage)
|
2016-07-27 17:17:21 +02:00
|
|
|
yield message
|
2021-02-12 08:20:45 +01:00
|
|
|
mbox.store(message_id, "+FLAGS", "\\Deleted")
|
2016-07-27 17:17:21 +02:00
|
|
|
mbox.expunge()
|
|
|
|
finally:
|
|
|
|
mbox.close()
|
|
|
|
finally:
|
|
|
|
mbox.logout()
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2017-04-18 17:28:55 +02:00
|
|
|
|
2013-08-08 16:44:40 +02:00
|
|
|
class Command(BaseCommand):
|
2013-12-17 22:21:48 +01:00
|
|
|
help = __doc__
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2017-10-26 11:35:57 +02:00
|
|
|
def handle(self, *args: Any, **options: str) -> None:
|
2017-04-18 17:28:55 +02:00
|
|
|
for message in get_imap_messages():
|
|
|
|
process_message(message)
|