PonyConf/mailing/utils.py

191 lines
6.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.conf import settings
from django.db import models
from django.contrib.contenttypes.models import ContentType
import imaplib
import ssl
import logging
from email import policy
from email.parser import BytesParser
import chardet
import re
from cfp.models import User, Conference, Participant
from .models import MessageThread, MessageCorrespondent, MessageAuthor, Message, hexdigest_sha256
class NoTokenFoundException(Exception):
pass
class InvalidTokenException(Exception):
pass
class InvalidKeyException(Exception):
pass
def send_message(thread, author, subject, content, in_reply_to=None):
author_type = ContentType.objects.get_for_model(author)
author, _ = MessageAuthor.objects.get_or_create(author_type=author_type, author_id=author.pk)
Message.objects.create(
thread=thread,
author=author,
subject=subject,
content=content,
in_reply_to=in_reply_to,
)
def fetch_imap_box(user, password, host, port=993, use_ssl=True, inbox='INBOX', trash='Trash'):
logging.basicConfig(level=logging.DEBUG)
context = ssl.create_default_context()
success, failure = 0, 0
kwargs = {'host': host, 'port': port}
if use_ssl:
IMAP4 = imaplib.IMAP4_SSL
kwargs.update({'ssl_context': ssl.create_default_context()})
else:
IMAP4 = imaplib.IMAP4
with IMAP4(**kwargs) as M:
typ, data = M.login(user, password)
if typ != 'OK':
raise Exception(data[0].decode('utf-8'))
typ, data = M.enable('UTF8=ACCEPT')
if typ != 'OK':
raise Exception(data[0].decode('utf-8'))
if trash is not None:
# Vérification de lexistence de la poubelle
typ, data = M.select(mailbox=trash)
if typ != 'OK':
raise Exception(data[0].decode('utf-8'))
typ, data = M.select(mailbox=inbox)
if typ != 'OK':
raise Exception(data[0].decode('utf-8'))
typ, data = M.uid('search', None, 'UNSEEN')
if typ != 'OK':
raise Exception(data[0].decode('utf-8'))
for num in data[0].split():
typ, data = M.uid('fetch', num, '(RFC822)')
if typ != 'OK':
failure += 1
logging.warning(data[0].decode('utf-8'))
continue
raw_email = data[0][1]
try:
process_email(raw_email)
except Exception as e:
failure += 1
logging.exception("An error occured during mail processing")
if type(e) == NoTokenFoundException:
tag = 'NoTokenFound'
if type(e) == InvalidTokenException:
tag = 'InvalidToken'
if type(e) == InvalidKeyException:
tag = 'InvalidKey'
else:
print('Unexpected error:', e)
tag = 'UnknowError'
typ, data = M.uid('store', num, '+FLAGS', tag)
if typ != 'OK':
logging.warning(data[0].decode('utf-8'))
continue
if trash is not None:
typ, data = M.uid('copy', num, trash)
if typ != 'OK':
failure += 1
logging.warning(data[0].decode('utf-8'))
continue
typ, data = M.uid('store', num, '+FLAGS', '\Deleted')
if typ != 'OK':
failure += 1
logging.warning(data[0].decode('utf-8'))
continue
success += 1
typ, data = M.expunge()
if typ != 'OK':
failure += 1
raise Exception(data[0].decode('utf-8'))
if failure:
total = success + failure
logging.info("Total: %d, success: %d, failure: %d" % (total, success, failure))
def process_email(raw_email):
msg = BytesParser(policy=policy.default).parsebytes(raw_email)
body = msg.get_body(preferencelist=['plain'])
content = body.get_payload(decode=True)
charset = body.get_content_charset()
if not charset:
charset = chardet.detect(content)['encoding']
content = content.decode(charset)
regex = re.compile('^[^+@]+\+(?P<token>[a-zA-Z0-9]{80})@[^@]+$')
for addr in msg.get('To', '').split(','):
m = regex.match(addr.strip())
if m:
break
if not m:
raise NoTokenFoundException
token = m.group('token')
try:
in_reply_to, author = process_new_token(token)
except InvalidTokenException:
in_reply_to, author = process_old_token(token)
subject = msg.get('Subject', '')
Message.objects.create(thread=in_reply_to.thread, in_reply_to=in_reply_to, author=author, subject=subject, content=content)
def process_new_token(token):
try:
in_reply_to = Message.objects.get(token__iexact=token[:32])
author = MessageAuthor.objects.get(token__iexact=token[32:64])
except models.ObjectDoesNotExist:
raise InvalidTokenException
if token[64:].lower() != hexdigest_sha256(settings.SECRET_KEY, in_reply_to.token, author.token)[:16]:
raise InvalidKeyException
return in_reply_to, author
def process_old_token(token):
try:
thread = MessageThread.objects.get(token__iexact=token[:32])
sender = MessageCorrespondent.objects.get(token__iexact=token[32:64])
except models.ObjectDoesNotExist:
raise InvalidTokenException
if token[64:].lower() != hexdigest_sha256(settings.SECRET_KEY, thread.token, sender.token)[:16]:
raise InvalidKeyException
in_reply_to = thread.message_set.last()
author = None
if author is None:
try:
author = User.objects.get(email=sender.email)
except User.DoesNotExist:
pass
if author is None:
try:
author = Participant.objects.get(email=sender.email)
except Participant.DoesNotExist:
pass
if author is None:
try:
author = Conference.objects.get(contact_email=sender.email)
except Conference.DoesNotExist:
raise # this was last hope...
author_type = ContentType.objects.get_for_model(author)
author, _ = MessageAuthor.objects.get_or_create(author_type=author_type, author_id=author.pk)
return in_reply_to, author