# -*- encoding: utf-8 -*- import json import os import sys import email import hashlib import tempfile import uuid import smtplib import subprocess import traceback import redminelib from redminelib import Redmine REDMINE_URL = os.environ.get('REDMINE_URL', 'https://dev.entrouvert.org') REDMINE_KEY = os.environ.get('REDMINE_KEY') TRACKER_ID = os.environ.get('REDMINE_TRACKER', '3') PROJECT_ID = os.environ.get('PROJECT') TECH_MANAGER_ROLE_ID = os.environ.get('TECH_MANAGER_ROLE_ID', 10) FALLBACK_EMAIL = os.environ.get('FALLBACK_EMAIL', 'support@entrouvert.com') ATTACHMENTS_BLACKLIST = os.environ.get('ATTACHMENTS_BLACKLIST', None) class UnknownUser(Exception): pass def parse_header(header_text): if not header_text: return '' default_charset = 'ascii' try: headers = email.header.decode_header(header_text) except email.errors.HeaderParseError: return header_text.encode(default_charset, 'replace').decode(default_charset) else: res = '' for i, (text, charset) in enumerate(headers): if isinstance(text, str): res += text else: res += text.decode(charset or default_charset, errors='replace') return res return '' def parse_attachment(data, blacklist=None): if data.get('Content-type', '').startswith('multipart/'): return None if data.get('Content-type', '').startswith('text/plain') and not ( data.get('Content-Disposition', '').startswith('attachment')): return None file_data = data.get_payload(decode=True) if not file_data: return None if blacklist and hashlib.sha1(file_data).hexdigest() in blacklist.get('sha1sums'): return None with tempfile.NamedTemporaryFile(delete=False) as attachment: attachment.write(file_data) attachment.flush() attachment = {'path': attachment.name, 'content_type': data.get_content_type(), 'filename': parse_header(data.get_filename())} return attachment def send_mail(to, subject, message): s = smtplib.SMTP('localhost') from_mail = 'noreply@entrouvert.com' msg = email.mime.Text.MIMEText(message, 'plain', 'utf-8') msg['From'] = from_mail msg['To'] = to msg['Subject'] = subject s.sendmail(from_mail, to, msg.as_string()) s.quit() def create_ticket(mail): mail = email.message_from_string(mail) r = Redmine(REDMINE_URL, key=REDMINE_KEY) from_name, from_email = email.utils.parseaddr(mail['From']) users = r.user.filter(name=from_email) if not users: raise UnknownUser blacklist = {'sha1sums': []} if ATTACHMENTS_BLACKLIST: try: blacklist = json.load(open(ATTACHMENTS_BLACKLIST)) except (IOError, ValueError): pass r = Redmine(REDMINE_URL, key=REDMINE_KEY, impersonate=users[0].login) attachments = [] body = u'' for data in mail.walk(): attachment = parse_attachment(data, blacklist) if attachment: attachments.append(attachment) elif data.get_content_type() == "text/plain": body = data.get_payload() # get project tech manager tech_manager = None for membership in r.project_membership.filter(project_id=PROJECT_ID): try: if membership.roles.get(TECH_MANAGER_ROLE_ID): tech_manager = membership.user break except redminelib.resources.ResourceAttrError: continue issue_data = {'project_id': PROJECT_ID, 'subject': parse_header(mail['Subject']), 'tracker_id': TRACKER_ID, 'description': body, 'uploads': attachments} if tech_manager: issue_data['assigned_to_id'] = tech_manager.id issue = r.issue.create(**issue_data) message = u"""[ Ce courriel est envoyé par un automate. Merci de ne pas y répondre, votre message ne serait pas pris en compte. ] Bonjour, Votre demande a bien été prise en compte et enregistrée dans notre système sous le numéro %s. Vous pouvez suivre son évolution à l'adresse : %s . Cordialement, L'équipe Entr'ouvert""" message = message % (issue.id, issue.url) send_mail(mail['From'], u'Votre demande a été bien prise en compte', message) # cleanup temporary attachment files for attachment in attachments: if os.path.exists(attachment['path']): os.unlink(attachment['path']) if __name__ == '__main__': mail = sys.stdin.read() mail_dumps_dir = '/var/tmp' if not os.path.exists(mail_dumps_dir): os.mkdir(mail_dumps_dir) file_id = uuid.uuid4() filename = os.path.join(mail_dumps_dir, '%s.mail' % file_id) with open(filename, 'w') as mail_dump: mail_dump.write(mail) try: create_ticket(mail) except Exception as e: exim = subprocess.Popen(['/usr/sbin/exim', FALLBACK_EMAIL], stdin=file(filename)) trace = traceback.format_exc() filename = os.path.join(mail_dumps_dir, '%s.trace' % file_id) with open(filename, 'w') as mail_dump: mail_dump.write(trace)