PageRenderTime 36ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/ptsapp/dispatch.py

https://bitbucket.org/mgill25/debian-pts
Python | 184 lines | 164 code | 10 blank | 10 comment | 21 complexity | 66cdf5f6cd332235e573fbc80a01d6c8 MD5 | raw file
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Basic Rewrite of bin/dispatch.pl script.
  4. import os
  5. import sys
  6. import re
  7. import email
  8. import subprocess
  9. import traceback
  10. from django.core.mail import send_mail
  11. from django.utils.timezone import now
  12. from pts import settings
  13. from models import Subscription, Package, Bounce
  14. from verp import encode
  15. class ParsingError(Exception):
  16. pass
  17. def log(*args):
  18. if not os.path.exists("%s/logs/dispatch.log" % settings.PTS_DIR):
  19. os.makedirs("%s/logs" % settings.PTS_DIR)
  20. with open("%s/logs/dispatch.log" % settings.PTS_DIR, 'a+') as logfile:
  21. logfile.write("%s - %s\n" % (now().strftime("%Y-%m-%d %H:%M:%S"), args))
  22. def get_package_name():
  23. try:
  24. package = os.environ.get("LOCAL_PART", settings.LOCAL_PART) + \
  25. os.environ.get("LOCAL_PART_SUFFIX", settings.LOCAL_PART_SUFFIX)
  26. if settings.SCRIPT_DEBUG and len(sys.argv) > 1:
  27. # In debug mode, assume the last arg is the package name
  28. package = sys.argv[-1]
  29. if re.search(package, r'^bounces\+'):
  30. # Execute the bounce handler script (which doesn't exist as of now, so pass)
  31. pass
  32. if not package:
  33. log("Aren't you exim?")
  34. sys.exit(0)
  35. return package.lower()
  36. except Exception as e:
  37. log("%s - %s" % (type(e), str(e)))
  38. def get_package_keyword():
  39. """Separate out the package name and the keyword"""
  40. match = re.search(r'(\S+)_(\S+)', get_package_name())
  41. try:
  42. if match and len(match.groups()) == 2:
  43. package = match.group(1)
  44. keyword = match.group(2)
  45. return package, keyword
  46. return (None, None)
  47. except Exception as e:
  48. log("%s - %s" % (type(e), str(e)))
  49. def get_message(mail_content):
  50. """Read email and make an email Message"""
  51. msg = email.message_from_string(mail_content)
  52. return msg
  53. def parse_mail(msg):
  54. """Parse the mail."""
  55. try:
  56. msg_id = msg.get('Message-ID', 'no-msgid-present@localhost')
  57. if msg.has_key('From'):
  58. sender_address = msg['From'].lower()
  59. else:
  60. raise ParsingError("From field not supplied!")
  61. log("%s for %s" % (msg, sender_address))
  62. # Now check for xloop
  63. package = get_package_keyword()[0]
  64. if package and msg.has_key('X-Loop'):
  65. xloop = re.search(package + r'@packages\.qa\.debian\.org', msg['X-Loop'])
  66. if xloop:
  67. log("Discarding, loop detected!")
  68. raise RuntimeError("Discarding, loop detected!")
  69. return msg, msg_id, sender_address
  70. except Exception as e:
  71. log("Parsing Failed: %s" % str(e))
  72. #print(traceback.print_exc())
  73. def header_matches(msg, header, regex):
  74. return re.match(regex, msg.get(header, ""))
  75. def update_keyword(msg):
  76. """Update the keyword based on the mail header"""
  77. package, keyword = get_package_keyword()
  78. msg = parse_mail(msg)[0]
  79. if not keyword:
  80. keyword = 'default'
  81. elif header_matches(msg, 'X-Loop', r'owner@bugs\.debian\.org') and\
  82. header_matches(msg, 'X-Debian-PR-Message', r'^transcript'):
  83. keyword = 'bts-control'
  84. elif header_matches(msg, 'X-Loop', r'owner@bugs\.debian\.org') and\
  85. msg.has_key('X-Debian-PR-Message'):
  86. keyword = 'bts'
  87. elif header_matches(msg, 'Subject', r'^Accepted|INSTALLED|ACCEPTED') and\
  88. re.match(r'.*\.dsc\s*$', msg.get_payload()) and msg.has_key('X-DAK'):
  89. if header_matches(msg, 'Subject', r'INSTALLED|ACCEPTED'):
  90. sys.exit(0)
  91. keyword = 'upload-source'
  92. elif msg.has_key('X-DAK') and header_matches(msg, 'Subject', r'^Accepted|INSTALLED|ACCEPTED'):
  93. keyword = 'upload-binary'
  94. elif msg.has_key('X-DAK') or header_matches(msg, 'Subject', r'^Comments Regarding .*\.changes$'):
  95. keyword = 'katie-other'
  96. log(":: %s %s" % (package, keyword))
  97. return keyword
  98. def check_keyword(msg):
  99. """Check if the message needs approval, based
  100. on the assumption that the default keyword might indicate spam"""
  101. msg = parse_mail(msg)[0]
  102. keyword = update_keyword(msg)
  103. if keyword == 'default':
  104. if msg.has_key('X-Bugzilla-Product'):
  105. settings.NEEDS_APPROVAL = False
  106. if settings.NEEDS_APPROVAL and msg.has_key('X-PTS-Approved'):
  107. log("** Discarded, missing X-PTS-Approved")
  108. sys.exit(0)
  109. return keyword
  110. def filter_tags(addr, keyword):
  111. """Function to check whether the tags match the keyword"""
  112. tags = addr.get_tags()
  113. for elem in tags:
  114. if elem.name == keyword:
  115. return True
  116. return False
  117. def filter_subscriptions(msg, pkg):
  118. """Filter out the interested subscriptions"""
  119. subscription_list = Subscription.objects.filter(package__name=pkg)
  120. keyword = check_keyword(msg)
  121. return [addr for addr in subscription_list if filter_tags(addr, keyword)]
  122. def inject_headers(msg):
  123. """Inject Revelant headers in the mail"""
  124. package_name, keyword = get_package_name(), check_keyword(msg)
  125. msg.add_header('Precedence', 'list')
  126. msg.add_header('X-Loop', '%s@packages.qa.debian.org' % package_name)
  127. msg.add_header('X-Debian', 'PTS')
  128. msg.add_header('X-Debian-Package', package_name)
  129. msg.add_header('X-PTS-Package', package_name)
  130. msg.add_header('X-PTS-Keyword', keyword)
  131. msg.add_header('List-Unsubscribe', '<mailto:pts@qa.debian.org?body=unsubscribe%20' + package_name + '>')
  132. return msg
  133. def track_mail(email):
  134. """Keep track of the sent mails, to be able to match them later with bounces"""
  135. b = Bounce(email=email, sent=True)
  136. b.save()
  137. def send_email(msg, from_addr, to_addr, archive):
  138. """Use sendmail to send the email"""
  139. msg = inject_headers(msg)
  140. sendmail = '/usr/sbin/sendmail'
  141. try:
  142. if archive:
  143. p = subprocess.Popen([sendmail, '-f', from_addr, '-oi',
  144. 'archive-outgoing@packages.qa.debian.org', to_addr])
  145. else:
  146. p = subprocess.Popen([sendmail, '-f', from_addr, '-oi', to_addr])
  147. p.communicate(msg.as_string())
  148. except Exception as e:
  149. log("Can't fork sendmail: %s" % str(e))
  150. def send_all():
  151. """Send to all"""
  152. archive = True
  153. mail_content = sys.stdin.read()
  154. msg = get_message(mail_content)
  155. mail_list = filter_subscriptions(msg, get_package_keyword()[0])
  156. for email in mail_list:
  157. print("Sending email to: ", email.address)
  158. from_addr = encode(settings.BOUNCE_ADDR, email.address)
  159. print("from_addr: ", from_addr)
  160. if from_addr:
  161. send_email(msg, from_addr, email.address, archive)
  162. track_mail(email)
  163. log("=> %s" % email)
  164. archive = False
  165. log("Completed!")