py3: Switch almost all shebang lines to use `python3`.
This causes `upgrade-zulip-from-git`, as well as a no-option run of
`tools/build-release-tarball`, to produce a Zulip install running
Python 3, rather than Python 2. In particular this means that the
virtualenv we create, in which all application code runs, is Python 3.
One shebang line, on `zulip-ec2-configure-interfaces`, explicitly
keeps Python 2, and at least one external ops script, `wal-e`, also
still runs on Python 2. See discussion on the respective previous
commits that made those explicit. There may also be some other
third-party scripts we use, outside of this source tree and running
outside our virtualenv, that still run on Python 2.
2017-08-02 23:15:16 +02:00
|
|
|
#!/usr/bin/env python3
|
2013-08-08 16:44:40 +02:00
|
|
|
|
|
|
|
"""
|
2013-10-08 21:02:47 +02:00
|
|
|
Forward messages sent to the configured email gateway to Zulip.
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2015-08-21 18:34:54 +02:00
|
|
|
For zulip.com, messages to that address go to the Inbox of emailgateway@zulip.com.
|
2015-08-21 11:48:43 +02:00
|
|
|
Zulip voyager configurations will differ.
|
2013-08-08 16:44:40 +02:00
|
|
|
|
|
|
|
Messages meant for Zulip have a special recipient form of
|
|
|
|
|
2013-12-17 22:38:32 +01:00
|
|
|
<stream name>+<regenerable stream token>@streams.zulip.com
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2013-12-17 22:38:32 +01:00
|
|
|
This pattern is configurable via the EMAIL_GATEWAY_PATTERN settings.py
|
|
|
|
variable.
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2017-04-18 17:28:55 +02:00
|
|
|
Run this in a cronjob every N minutes if you have configured Zulip to 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
|
|
|
"""
|
|
|
|
|
2013-12-17 22:38:32 +01:00
|
|
|
|
2015-11-01 17:11:06 +01:00
|
|
|
from __future__ import print_function
|
2013-10-10 21:37:26 +02:00
|
|
|
|
2016-07-15 20:07:51 +02:00
|
|
|
import six
|
2016-07-27 17:17:21 +02:00
|
|
|
from typing import Any, List, Generator
|
2016-06-04 16:52:18 +02:00
|
|
|
|
2013-08-08 16:44:40 +02:00
|
|
|
import logging
|
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.core.management.base import BaseCommand
|
|
|
|
|
2013-12-17 22:38:18 +01:00
|
|
|
from zerver.lib.queue import queue_json_publish
|
2014-07-25 10:40:40 +02:00
|
|
|
from zerver.lib.email_mirror import logger, process_message, \
|
|
|
|
extract_and_validate, ZulipEmailForwardError, \
|
|
|
|
mark_missed_message_address_as_used, is_missed_message_address
|
2013-08-08 16:44:40 +02:00
|
|
|
|
2016-07-27 17:17:21 +02:00
|
|
|
import email
|
|
|
|
from email.message import Message
|
|
|
|
from imaplib import IMAP4_SSL
|
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
|
|
|
|
2016-07-27 17:17:21 +02:00
|
|
|
def get_imap_messages():
|
|
|
|
# type: () -> Generator[Message, None, None]
|
|
|
|
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:
|
2017-05-07 17:15:02 +02:00
|
|
|
status, num_ids_data = mbox.search(None, 'ALL') # type: bytes, List[bytes]
|
2016-07-27 17:17:21 +02:00
|
|
|
for msgid in num_ids_data[0].split():
|
|
|
|
status, msg_data = mbox.fetch(msgid, '(RFC822)')
|
|
|
|
msg_as_bytes = msg_data[0][1]
|
|
|
|
if six.PY2:
|
|
|
|
message = email.message_from_string(msg_as_bytes)
|
|
|
|
else:
|
|
|
|
message = email.message_from_bytes(msg_as_bytes)
|
|
|
|
yield message
|
|
|
|
mbox.store(msgid, '+FLAGS', '\\Deleted')
|
|
|
|
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
|
|
|
|
2014-01-30 18:25:24 +01:00
|
|
|
def handle(self, *args, **options):
|
2016-06-04 16:52:18 +02:00
|
|
|
# type: (*Any, **str) -> None
|
2017-04-18 17:28:55 +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):
|
|
|
|
print("Please configure the Email Mirror Gateway in /etc/zulip/, "
|
|
|
|
"or specify $ORIGINAL_RECIPIENT if piping a single mail.")
|
|
|
|
exit(1)
|
|
|
|
for message in get_imap_messages():
|
|
|
|
process_message(message)
|