170 lines
7.2 KiB
Python
170 lines
7.2 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=''),
|
|
make_option("--file"))
|
|
|
|
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:
|
|
if 'file' in options:
|
|
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, 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 = 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)))
|
|
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'], logger=self.logger):
|
|
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))
|