Files
zulip/zerver/management/commands/email_mirror.py
K.Kanakhin e3e52e7284 email-mirror: Move postfix email mirror integration to separate script.
This fixes a performance problem where we were previously starting up
a full Django process (~0.7s even on a fast machine) every time a new
email came in, potentially allowing users to accidentally DoS a Zulip
server.  Now, we just post over HTTPS, allowing the existing thread
pool support to do its job.

- Add script wrapper to communicate postfix pipe with django web server
  over HTTP(S). It uses shared_secret authentication mode.
- Add django view to process messages from email mirror server.
- Clean management command `email-mirror`. Left just functional
  for cron email processing.
- Add routes for new tornado view.
- Change pipe script in master process postfix config template
  based on updated script.
- Add tests.

Tweaked by tabbott to adjust the directory and set better defaults.

Fixes #2421.
2017-04-24 21:24:23 -07:00

101 lines
3.3 KiB
Python
Executable File

#!/usr/bin/env python
"""
Forward messages sent to the configured email gateway to Zulip.
For zulip.com, messages to that address go to the Inbox of emailgateway@zulip.com.
Zulip voyager configurations will differ.
Messages meant for Zulip have a special recipient form of
<stream name>+<regenerable stream token>@streams.zulip.com
This pattern is configurable via the EMAIL_GATEWAY_PATTERN settings.py
variable.
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.
We extract and validate the target stream from information in the
recipient address and retrieve, forward, and archive the message.
"""
from __future__ import absolute_import
from __future__ import print_function
import six
from zerver.lib.str_utils import force_text
from typing import Any, List, Generator
from argparse import ArgumentParser
import os
import logging
import sys
import posix
from django.conf import settings
from django.core.management.base import BaseCommand
from zerver.lib.queue import queue_json_publish
from zerver.lib.email_mirror import logger, process_message, \
extract_and_validate, ZulipEmailForwardError, \
mark_missed_message_address_as_used, is_missed_message_address
import email
from email.message import Message
from imaplib import IMAP4_SSL
## Setup ##
log_format = "%(asctime)s: %(message)s"
logging.basicConfig(format=log_format)
formatter = logging.Formatter(log_format)
file_handler = logging.FileHandler(settings.EMAIL_MIRROR_LOG_PATH)
file_handler.setFormatter(formatter)
logger.setLevel(logging.DEBUG)
logger.addHandler(file_handler)
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:
status, num_ids_data = mbox.search(None, 'ALL') # type: bytes, List[bytes]
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()
class Command(BaseCommand):
help = __doc__
def handle(self, *args, **options):
# type: (*Any, **str) -> None
# 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)