PageRenderTime 58ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Tools/scripts/mailerdaemon.py

https://bitbucket.org/mirror/cpython/
Python | 246 lines | 231 code | 5 blank | 10 comment | 21 complexity | 1e0b88e8b835893fe375c98fb1eeae7c MD5 | raw file
Possible License(s): Unlicense, 0BSD, BSD-3-Clause
  1. #!/usr/bin/env python3
  2. """Classes to parse mailer-daemon messages."""
  3. import calendar
  4. import email.message
  5. import re
  6. import os
  7. import sys
  8. class Unparseable(Exception):
  9. pass
  10. class ErrorMessage(email.message.Message):
  11. def __init__(self):
  12. email.message.Message.__init__(self)
  13. self.sub = ''
  14. def is_warning(self):
  15. sub = self.get('Subject')
  16. if not sub:
  17. return 0
  18. sub = sub.lower()
  19. if sub.startswith('waiting mail'):
  20. return 1
  21. if 'warning' in sub:
  22. return 1
  23. self.sub = sub
  24. return 0
  25. def get_errors(self):
  26. for p in EMPARSERS:
  27. self.rewindbody()
  28. try:
  29. return p(self.fp, self.sub)
  30. except Unparseable:
  31. pass
  32. raise Unparseable
  33. # List of re's or tuples of re's.
  34. # If a re, it should contain at least a group (?P<email>...) which
  35. # should refer to the email address. The re can also contain a group
  36. # (?P<reason>...) which should refer to the reason (error message).
  37. # If no reason is present, the emparse_list_reason list is used to
  38. # find a reason.
  39. # If a tuple, the tuple should contain 2 re's. The first re finds a
  40. # location, the second re is repeated one or more times to find
  41. # multiple email addresses. The second re is matched (not searched)
  42. # where the previous match ended.
  43. # The re's are compiled using the re module.
  44. emparse_list_list = [
  45. 'error: (?P<reason>unresolvable): (?P<email>.+)',
  46. ('----- The following addresses had permanent fatal errors -----\n',
  47. '(?P<email>[^ \n].*)\n( .*\n)?'),
  48. 'remote execution.*\n.*rmail (?P<email>.+)',
  49. ('The following recipients did not receive your message:\n\n',
  50. ' +(?P<email>.*)\n(The following recipients did not receive your message:\n\n)?'),
  51. '------- Failure Reasons --------\n\n(?P<reason>.*)\n(?P<email>.*)',
  52. '^<(?P<email>.*)>:\n(?P<reason>.*)',
  53. '^(?P<reason>User mailbox exceeds allowed size): (?P<email>.+)',
  54. '^5\\d{2} <(?P<email>[^\n>]+)>\\.\\.\\. (?P<reason>.+)',
  55. '^Original-Recipient: rfc822;(?P<email>.*)',
  56. '^did not reach the following recipient\\(s\\):\n\n(?P<email>.*) on .*\n +(?P<reason>.*)',
  57. '^ <(?P<email>[^\n>]+)> \\.\\.\\. (?P<reason>.*)',
  58. '^Report on your message to: (?P<email>.*)\nReason: (?P<reason>.*)',
  59. '^Your message was not delivered to +(?P<email>.*)\n +for the following reason:\n +(?P<reason>.*)',
  60. '^ was not +(?P<email>[^ \n].*?) *\n.*\n.*\n.*\n because:.*\n +(?P<reason>[^ \n].*?) *\n',
  61. ]
  62. # compile the re's in the list and store them in-place.
  63. for i in range(len(emparse_list_list)):
  64. x = emparse_list_list[i]
  65. if type(x) is type(''):
  66. x = re.compile(x, re.MULTILINE)
  67. else:
  68. xl = []
  69. for x in x:
  70. xl.append(re.compile(x, re.MULTILINE))
  71. x = tuple(xl)
  72. del xl
  73. emparse_list_list[i] = x
  74. del x
  75. del i
  76. # list of re's used to find reasons (error messages).
  77. # if a string, "<>" is replaced by a copy of the email address.
  78. # The expressions are searched for in order. After the first match,
  79. # no more expressions are searched for. So, order is important.
  80. emparse_list_reason = [
  81. r'^5\d{2} <>\.\.\. (?P<reason>.*)',
  82. '<>\.\.\. (?P<reason>.*)',
  83. re.compile(r'^<<< 5\d{2} (?P<reason>.*)', re.MULTILINE),
  84. re.compile('===== stderr was =====\nrmail: (?P<reason>.*)'),
  85. re.compile('^Diagnostic-Code: (?P<reason>.*)', re.MULTILINE),
  86. ]
  87. emparse_list_from = re.compile('^From:', re.IGNORECASE|re.MULTILINE)
  88. def emparse_list(fp, sub):
  89. data = fp.read()
  90. res = emparse_list_from.search(data)
  91. if res is None:
  92. from_index = len(data)
  93. else:
  94. from_index = res.start(0)
  95. errors = []
  96. emails = []
  97. reason = None
  98. for regexp in emparse_list_list:
  99. if type(regexp) is type(()):
  100. res = regexp[0].search(data, 0, from_index)
  101. if res is not None:
  102. try:
  103. reason = res.group('reason')
  104. except IndexError:
  105. pass
  106. while 1:
  107. res = regexp[1].match(data, res.end(0), from_index)
  108. if res is None:
  109. break
  110. emails.append(res.group('email'))
  111. break
  112. else:
  113. res = regexp.search(data, 0, from_index)
  114. if res is not None:
  115. emails.append(res.group('email'))
  116. try:
  117. reason = res.group('reason')
  118. except IndexError:
  119. pass
  120. break
  121. if not emails:
  122. raise Unparseable
  123. if not reason:
  124. reason = sub
  125. if reason[:15] == 'returned mail: ':
  126. reason = reason[15:]
  127. for regexp in emparse_list_reason:
  128. if type(regexp) is type(''):
  129. for i in range(len(emails)-1,-1,-1):
  130. email = emails[i]
  131. exp = re.compile(re.escape(email).join(regexp.split('<>')), re.MULTILINE)
  132. res = exp.search(data)
  133. if res is not None:
  134. errors.append(' '.join((email.strip()+': '+res.group('reason')).split()))
  135. del emails[i]
  136. continue
  137. res = regexp.search(data)
  138. if res is not None:
  139. reason = res.group('reason')
  140. break
  141. for email in emails:
  142. errors.append(' '.join((email.strip()+': '+reason).split()))
  143. return errors
  144. EMPARSERS = [emparse_list]
  145. def sort_numeric(a, b):
  146. a = int(a)
  147. b = int(b)
  148. if a < b:
  149. return -1
  150. elif a > b:
  151. return 1
  152. else:
  153. return 0
  154. def parsedir(dir, modify):
  155. os.chdir(dir)
  156. pat = re.compile('^[0-9]*$')
  157. errordict = {}
  158. errorfirst = {}
  159. errorlast = {}
  160. nok = nwarn = nbad = 0
  161. # find all numeric file names and sort them
  162. files = list(filter(lambda fn, pat=pat: pat.match(fn) is not None, os.listdir('.')))
  163. files.sort(sort_numeric)
  164. for fn in files:
  165. # Lets try to parse the file.
  166. fp = open(fn)
  167. m = email.message_from_file(fp, _class=ErrorMessage)
  168. sender = m.getaddr('From')
  169. print('%s\t%-40s\t'%(fn, sender[1]), end=' ')
  170. if m.is_warning():
  171. fp.close()
  172. print('warning only')
  173. nwarn = nwarn + 1
  174. if modify:
  175. os.rename(fn, ','+fn)
  176. ## os.unlink(fn)
  177. continue
  178. try:
  179. errors = m.get_errors()
  180. except Unparseable:
  181. print('** Not parseable')
  182. nbad = nbad + 1
  183. fp.close()
  184. continue
  185. print(len(errors), 'errors')
  186. # Remember them
  187. for e in errors:
  188. try:
  189. mm, dd = m.getdate('date')[1:1+2]
  190. date = '%s %02d' % (calendar.month_abbr[mm], dd)
  191. except:
  192. date = '??????'
  193. if e not in errordict:
  194. errordict[e] = 1
  195. errorfirst[e] = '%s (%s)' % (fn, date)
  196. else:
  197. errordict[e] = errordict[e] + 1
  198. errorlast[e] = '%s (%s)' % (fn, date)
  199. fp.close()
  200. nok = nok + 1
  201. if modify:
  202. os.rename(fn, ','+fn)
  203. ## os.unlink(fn)
  204. print('--------------')
  205. print(nok, 'files parsed,',nwarn,'files warning-only,', end=' ')
  206. print(nbad,'files unparseable')
  207. print('--------------')
  208. list = []
  209. for e in errordict.keys():
  210. list.append((errordict[e], errorfirst[e], errorlast[e], e))
  211. list.sort()
  212. for num, first, last, e in list:
  213. print('%d %s - %s\t%s' % (num, first, last, e))
  214. def main():
  215. modify = 0
  216. if len(sys.argv) > 1 and sys.argv[1] == '-d':
  217. modify = 1
  218. del sys.argv[1]
  219. if len(sys.argv) > 1:
  220. for folder in sys.argv[1:]:
  221. parsedir(folder, modify)
  222. else:
  223. parsedir('/ufs/jack/Mail/errorsinbox', modify)
  224. if __name__ == '__main__' or sys.argv[0] == __name__:
  225. main()