docbow/docbow_project/pfwb/management/commands/sendmail.py

231 lines
9.8 KiB
Python

from optparse import make_option
import sys
import email
import email.errors
import email.utils
import email.header
import logging
import re
import urllib2
from django.core.management.base import BaseCommand, CommandError
import django.contrib.auth.models as auth_models
from django.core.files.base import ContentFile
from django.db import transaction
from django.core.exceptions import MultipleObjectsReturned
from docbow_project.docbow import models, views, timestamp
from docbow_project.docbow.email_utils import u2u_decode
from docbow_project.docbow.app_settings import BASE_URL
from django_journal import record
from ... import app_settings, models as pfwb_models
logger = logging.getLogger('docbow.mail_interface')
# modes
EXPEDITION = 'expedition'
ATTACHED_FILE = 'attached_file'
class Command(BaseCommand):
args = ''
help = '''Convert a mail to a document send.
In case of failure the following return value is returned:
- 1 failure to parse the message on stdin
- 2 the mail is missing a subject
- 3 the subject could not be decoded
- 4 obligatory attributes or contents were missing
- 5 notifications mails could not be sent
- 6 missing Message-ID
- 7 sender not authorized
- 8 missing sender
'''
option_list = BaseCommand.option_list + (
make_option("--ip", default=''),
make_option("--sender"),
make_option("--file"))
def handle(self, *args, **options):
if not options.get('sender'):
self.error('7.7.1 No sender', exit_code=8)
try:
if options.get('file'):
mail = email.message_from_file(file(options['file']))
else:
mail = email.message_from_file(sys.stdin)
except email.errors.MessageParseError, e:
self.error('7.7.1 Error parsing message', exite_code=1)
try:
self.handle_mail(mail, args, **options)
except Exception, e:
logger.exception('Unknown exception')
self.error('7.7.1 Internal error when handling the mail', exit_code=5)
def error(self, msg, exit_code=None, **kwargs):
print >>sys.stderr, msg.format(**kwargs)
if hasattr(self, 'message_id'):
record('error-smtp-interface', 'message {message_id} refused: ' + msg,
message_id=self.message_id, **kwargs)
else:
record('error-smtp-interface', 'message refused: ' + msg, **kwargs)
if exit_code:
sys.exit(exit_code)
def decode_filename(self, filename):
'''See if the filename contains encoded-word work around bugs in FileMakerPro'''
m = re.match(r'=\?(.*)\?(.*)\?(.*)\?=', filename)
if m:
result = []
for content, encoding in email.header.decode_header(filename):
result.append(unicode(content, encoding or 'ascii'))
return ''.join(result)
else:
return filename
@transaction.commit_on_success
def handle_mail(self, mail, mail_recipients, **options):
content_errors = []
attachments = []
recipients = []
description = u''
from_email = email.utils.parseaddr(options['sender'])[1]
if not from_email:
self.error('7.7.1 No sender', exit_code=8)
if from_email == app_settings.PFWB_SENDMAIL_TABELLIO_EXPEDITION_EMAIL:
mode = EXPEDITION
elif from_email == app_settings.PFWB_SENDMAIL_ATTACHED_FILE_EMAIL:
mode = ATTACHED_FILE
else:
self.error('Email unknown, not authorized to post', exit_code=7)
return
tos = mail.get_all('to', [])
ccs = mail.get_all('cc', [])
resent_tos = mail.get_all('resent-to', [])
resent_ccs = mail.get_all('resent-cc', [])
all_recipients = mail_recipients or [b for a, b in email.utils.getaddresses(tos + ccs + resent_tos +
resent_ccs)]
self.message_id = mail.get('Message-ID', None)
if not self.message_id:
self.error('7.7.1 Mail is missing a Message-ID', exit_code=6)
# determine the filetype
filetype = None
if mode == ATTACHED_FILE:
subject = mail.get('Subject', None)
if not subject:
self.error('7.7.1 Mail is missing a subject', exit_code=2)
try:
subject = u2u_decode(subject)
except Exception, e:
self.error('7.7.1 The subject cannot be decoded', exit_code=3)
try:
filetype = models.FileType.objects.get(name=subject)
except models.FileType.DoesNotExist:
record('warning', 'unknown filetype '
'{filetype}, using default filetype',
filetype=subject)
else:
tabellio_doc_type = mail.get('x-tabellio-doc-type')
if tabellio_doc_type:
try:
filetype = models.FileType.objects.get(
tabelliodoctype__tabellio_doc_type=tabellio_doc_type)
except models.FileType.DoesNotExist:
record('warning', 'unknown x-tabellio-doc-type '
'{tabellio_doc_type}, using default filetype',
tabellio_doc_type=tabellio_doc_type)
except models.FileType.MultipleObjectsReturned:
record('warning', 'unknown x-tabellio-doc-type '
'{tabellio_doc_type}, using default filetype',
tabellio_doc_type=tabellio_doc_type)
if filetype is None:
try:
filetype = models.FileType.objects.get(
id=app_settings.PFWB_SENDMAIL_DEFAULT_TYPE_ID)
except models.FileType.DoesNotExist:
filetype, created = models.FileType.objects.get_or_create(
name=app_settings.PFWB_SENDMAIL_DEFAULT_TYPE_NAME)
if mode == ATTACHED_FILE:
for part in mail.walk():
filename = part.get_filename(None)
if part.get_content_type() == 'text/plain' and \
('Content-Disposition' not in part or 'inline' in part['Content-Disposition']):
charset = part.get_content_charset('us-ascii')
description = unicode(part.get_payload(decode=True), charset)
if filename:
attachments.append((self.decode_filename(filename),
part.get_payload(decode=True)))
else:
url = mail.get('x-tabellio-doc-url')
stream = urllib2.urlopen(url)
content = stream.read()
subject = mail.get('Subject', None)
if not subject:
self.error('7.7.1 Mail is missing a subject', exit_code=2)
try:
subject = u2u_decode(subject)
except Exception, e:
self.error('7.7.1 The subject cannot be decoded', exit_code=3)
try:
name = subject.split(':', 1)[1].strip()
except IndexError:
self.error('7.7.1 Filename cannot be extracted from the subject', exit_code=3)
attachments.append((name, content))
for email_address in all_recipients:
try:
user = auth_models.User.objects.get(email=email_address, delegations_by__isnull=True)
recipients.append(user)
except auth_models.User.DoesNotExist:
msg = 'Recipient %r is not an user of the platform' \
% email_address
content_errors.append(msg)
except MultipleObjectsReturned:
msg = 'Recipient %r has more than 1 user in the platform' \
% email_address
content_errors.append(msg)
if not len(attachments):
content_errors.append('You must send at least one attached file')
if not len(all_recipients):
content_errors.append('You must have at least one recipient in your message.')
try:
if mode == ATTACHED_FILE:
user_id = app_settings.PFWB_SENDMAIL_ATTACHED_FILE_USER_ID
else:
user_id = app_settings.PFWB_SENDMAIL_TABELLIO_EXPEDITION_USER_ID
sender = auth_models.User.objects.get(id=user_id)
except auth_models.User.DoesNotExist:
content_errors.append('No user match the sender user_id %s in mode '
'%s' % (user_id, mode))
if content_errors:
msg = [ '7.7.1 The email sent contains many errors:' ]
for error in content_errors:
msg.append(' - %s' % error)
self.error('\n'.join(msg), exit_code=4)
else:
if mode == ATTACHED_FILE:
record('smtp-received-document', 'mode: {mode} message-id: {message_id} subject: {subject}',
mode=mode, message_id=self.message_id, subject=subject)
else:
record('smtp-received-document', 'mode: {mode} message-id: '
'{message_id} x-tabellio-doc-url: {x_tabellio_doc_url} '
'x-tabellio-doc-type: {x_tabellio_doc_type}',
mode=mode, message_id=self.message_id,
subject=subject, x_tabellio_doc_url=url,
x_tabellio_doc_type=tabellio_doc_type)
document = models.Document(sender=sender,
comment=description, filetype=filetype)
document.save()
document.to_user = recipients
for filename, payload in attachments:
content = ContentFile(payload)
attached_file = models.AttachedFile(document=document,
name=filename)
attached_file.content.save(filename, content, save=False)
attached_file.save()
document.post()