/ptsapp/dispatch.py
Python | 184 lines | 164 code | 10 blank | 10 comment | 21 complexity | 66cdf5f6cd332235e573fbc80a01d6c8 MD5 | raw file
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Basic Rewrite of bin/dispatch.pl script.
- import os
- import sys
- import re
- import email
- import subprocess
- import traceback
- from django.core.mail import send_mail
- from django.utils.timezone import now
- from pts import settings
- from models import Subscription, Package, Bounce
- from verp import encode
- class ParsingError(Exception):
- pass
- def log(*args):
- if not os.path.exists("%s/logs/dispatch.log" % settings.PTS_DIR):
- os.makedirs("%s/logs" % settings.PTS_DIR)
- with open("%s/logs/dispatch.log" % settings.PTS_DIR, 'a+') as logfile:
- logfile.write("%s - %s\n" % (now().strftime("%Y-%m-%d %H:%M:%S"), args))
- def get_package_name():
- try:
- package = os.environ.get("LOCAL_PART", settings.LOCAL_PART) + \
- os.environ.get("LOCAL_PART_SUFFIX", settings.LOCAL_PART_SUFFIX)
- if settings.SCRIPT_DEBUG and len(sys.argv) > 1:
- # In debug mode, assume the last arg is the package name
- package = sys.argv[-1]
- if re.search(package, r'^bounces\+'):
- # Execute the bounce handler script (which doesn't exist as of now, so pass)
- pass
- if not package:
- log("Aren't you exim?")
- sys.exit(0)
- return package.lower()
- except Exception as e:
- log("%s - %s" % (type(e), str(e)))
- def get_package_keyword():
- """Separate out the package name and the keyword"""
- match = re.search(r'(\S+)_(\S+)', get_package_name())
- try:
- if match and len(match.groups()) == 2:
- package = match.group(1)
- keyword = match.group(2)
- return package, keyword
- return (None, None)
- except Exception as e:
- log("%s - %s" % (type(e), str(e)))
- def get_message(mail_content):
- """Read email and make an email Message"""
- msg = email.message_from_string(mail_content)
- return msg
- def parse_mail(msg):
- """Parse the mail."""
- try:
- msg_id = msg.get('Message-ID', 'no-msgid-present@localhost')
- if msg.has_key('From'):
- sender_address = msg['From'].lower()
- else:
- raise ParsingError("From field not supplied!")
- log("%s for %s" % (msg, sender_address))
- # Now check for xloop
- package = get_package_keyword()[0]
- if package and msg.has_key('X-Loop'):
- xloop = re.search(package + r'@packages\.qa\.debian\.org', msg['X-Loop'])
- if xloop:
- log("Discarding, loop detected!")
- raise RuntimeError("Discarding, loop detected!")
- return msg, msg_id, sender_address
- except Exception as e:
- log("Parsing Failed: %s" % str(e))
- #print(traceback.print_exc())
- def header_matches(msg, header, regex):
- return re.match(regex, msg.get(header, ""))
- def update_keyword(msg):
- """Update the keyword based on the mail header"""
- package, keyword = get_package_keyword()
- msg = parse_mail(msg)[0]
- if not keyword:
- keyword = 'default'
- elif header_matches(msg, 'X-Loop', r'owner@bugs\.debian\.org') and\
- header_matches(msg, 'X-Debian-PR-Message', r'^transcript'):
- keyword = 'bts-control'
- elif header_matches(msg, 'X-Loop', r'owner@bugs\.debian\.org') and\
- msg.has_key('X-Debian-PR-Message'):
- keyword = 'bts'
- elif header_matches(msg, 'Subject', r'^Accepted|INSTALLED|ACCEPTED') and\
- re.match(r'.*\.dsc\s*$', msg.get_payload()) and msg.has_key('X-DAK'):
- if header_matches(msg, 'Subject', r'INSTALLED|ACCEPTED'):
- sys.exit(0)
- keyword = 'upload-source'
- elif msg.has_key('X-DAK') and header_matches(msg, 'Subject', r'^Accepted|INSTALLED|ACCEPTED'):
- keyword = 'upload-binary'
- elif msg.has_key('X-DAK') or header_matches(msg, 'Subject', r'^Comments Regarding .*\.changes$'):
- keyword = 'katie-other'
- log(":: %s %s" % (package, keyword))
- return keyword
- def check_keyword(msg):
- """Check if the message needs approval, based
- on the assumption that the default keyword might indicate spam"""
- msg = parse_mail(msg)[0]
- keyword = update_keyword(msg)
- if keyword == 'default':
- if msg.has_key('X-Bugzilla-Product'):
- settings.NEEDS_APPROVAL = False
- if settings.NEEDS_APPROVAL and msg.has_key('X-PTS-Approved'):
- log("** Discarded, missing X-PTS-Approved")
- sys.exit(0)
- return keyword
- def filter_tags(addr, keyword):
- """Function to check whether the tags match the keyword"""
- tags = addr.get_tags()
- for elem in tags:
- if elem.name == keyword:
- return True
- return False
- def filter_subscriptions(msg, pkg):
- """Filter out the interested subscriptions"""
- subscription_list = Subscription.objects.filter(package__name=pkg)
- keyword = check_keyword(msg)
- return [addr for addr in subscription_list if filter_tags(addr, keyword)]
- def inject_headers(msg):
- """Inject Revelant headers in the mail"""
- package_name, keyword = get_package_name(), check_keyword(msg)
- msg.add_header('Precedence', 'list')
- msg.add_header('X-Loop', '%s@packages.qa.debian.org' % package_name)
- msg.add_header('X-Debian', 'PTS')
- msg.add_header('X-Debian-Package', package_name)
- msg.add_header('X-PTS-Package', package_name)
- msg.add_header('X-PTS-Keyword', keyword)
- msg.add_header('List-Unsubscribe', '<mailto:pts@qa.debian.org?body=unsubscribe%20' + package_name + '>')
- return msg
- def track_mail(email):
- """Keep track of the sent mails, to be able to match them later with bounces"""
- b = Bounce(email=email, sent=True)
- b.save()
- def send_email(msg, from_addr, to_addr, archive):
- """Use sendmail to send the email"""
- msg = inject_headers(msg)
- sendmail = '/usr/sbin/sendmail'
- try:
- if archive:
- p = subprocess.Popen([sendmail, '-f', from_addr, '-oi',
- 'archive-outgoing@packages.qa.debian.org', to_addr])
- else:
- p = subprocess.Popen([sendmail, '-f', from_addr, '-oi', to_addr])
- p.communicate(msg.as_string())
- except Exception as e:
- log("Can't fork sendmail: %s" % str(e))
- def send_all():
- """Send to all"""
- archive = True
- mail_content = sys.stdin.read()
- msg = get_message(mail_content)
- mail_list = filter_subscriptions(msg, get_package_keyword()[0])
- for email in mail_list:
- print("Sending email to: ", email.address)
- from_addr = encode(settings.BOUNCE_ADDR, email.address)
- print("from_addr: ", from_addr)
- if from_addr:
- send_email(msg, from_addr, email.address, archive)
- track_mail(email)
- log("=> %s" % email)
- archive = False
- log("Completed!")