PonyConf/mailing/utils.py

124 lines
4.1 KiB
Python
Raw 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
import imaplib
import ssl
import logging
from email import policy
from email.parser import BytesParser
import chardet
import re
from .models import MessageThread, MessageCorrespondent, Message, hexdigest_sha256
class NoTokenFoundException(Exception):
pass
class InvalidTokenException(Exception):
pass
class InvalidKeyException(Exception):
pass
def fetch_imap_box(user, password, host, port=993, inbox='INBOX', trash='Trash'):
logging.basicConfig(level=logging.DEBUG)
context = ssl.create_default_context()
success, failure = 0, 0
with imaplib.IMAP4_SSL(host=host, port=port, ssl_context=context) 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:
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')
key = token[64:]
try:
thread = MessageThread.objects.get(token__iexact=token[:32])
sender = MessageCorrespondent.objects.get(token__iexact=token[32:64])
except models.ObjectDoesNotExist:
raise InvalidTokenException
if key.lower() != hexdigest_sha256(settings.SECRET_KEY, thread.token, sender.token)[:16]:
raise InvalidKeyException
Message.objects.create(thread=thread, from_email=sender.email, content=content)