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

165 lines
7.0 KiB
Python

from optparse import make_option
import sys
import email
import email.errors
import email.utils
import email.header
import logging
import re
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 ... import models, views, timestamp
from docbow_project.email_utils import u2u_decode
logger = logging.getLogger('docbow.mail_interface')
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
'''
option_list = BaseCommand.option_list + (
make_option("--sender"),
make_option("--base-url", default="http://localhost:8000"),
make_option("--ip", default=''))
def handle(self, *args, **options):
mail_sender = None
self.logger = logging.LoggerAdapter(logging.getLogger('docbow'), {
'ip': options['ip'], 'user': 'Anonymous' })
if options.get('sender'):
try:
mail_sender = auth_models.User.objects.get(username=options['sender'])
except auth_models.User.DoesNotExist:
raise CommandError('Not found')
try:
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, mail_sender, args, **options)
except Exception, e:
self.logger.exception('Unknown exception')
self.error('7.7.1 Uknown error when handling the mail', exit_code=5)
def error(self, msg, exit_code=None, *args):
if args:
print >>sys.stderr, msg % args
else:
print >>sys.stderr, msg
if hasattr(self, 'message_id'):
msg = ('smtp interface: message %s refused, ' % self.message_id) + msg
else:
msg = 'smtp interface: message refused, ' + msg
self.logger.error(msg, *args)
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
def handle_mail(self, mail, mail_sender, mail_recipients, **options):
content_errors = []
attachments = []
recipients = []
description = u''
mail_from = email.utils.parseaddr(mail.get('From'))[1]
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)
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:
content_errors.append('The subject "%s" does not match any known file type' % subject)
for part in mail.walk():
filename = self.decode_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((filename, part.get_payload(decode=True)))
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 sent at least one attached file')
if not len(all_recipients):
content_errors.append('You must have at least one recipient in your message.')
try:
sender = mail_sender or auth_models.User.objects.get(email=mail_from)
except auth_models.User.DoesNotExist:
content_errors.append('Unable to find an unique sender for the mail %s' % mail.get('From'))
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:
with transaction.commit_on_success():
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()
if not views.send_mail_notifications(document, options['base_url']):
transaction.rollback()
self.error('7.7.1 Document send failed because '
'some notifications could not be sent, administrators '
'have been informed.', code=5)
else:
blob = document.timestamp_blob()
tst = timestamp.timestamp_json(blob)
self.logger.info('smtp interface: message %s accepted, sent %s, timestamp %s' % (self.message_id, document, tst))