1 """mailerdaemon - classes to parse mailer-daemon messages"""
9 Unparseable
= 'mailerdaemon.Unparseable'
11 class ErrorMessage(email
.message
.Message
):
13 email
.message
.Message
.__init
__(self
)
17 sub
= self
.get('Subject')
21 if sub
.startswith('waiting mail'): return 1
22 if 'warning' in sub
: return 1
30 return p(self
.fp
, self
.sub
)
35 # List of re's or tuples of re's.
36 # If a re, it should contain at least a group (?P<email>...) which
37 # should refer to the email address. The re can also contain a group
38 # (?P<reason>...) which should refer to the reason (error message).
39 # If no reason is present, the emparse_list_reason list is used to
41 # If a tuple, the tuple should contain 2 re's. The first re finds a
42 # location, the second re is repeated one or more times to find
43 # multiple email addresses. The second re is matched (not searched)
44 # where the previous match ended.
45 # The re's are compiled using the re module.
47 'error: (?P<reason>unresolvable): (?P<email>.+)',
48 ('----- The following addresses had permanent fatal errors -----\n',
49 '(?P<email>[^ \n].*)\n( .*\n)?'),
50 'remote execution.*\n.*rmail (?P<email>.+)',
51 ('The following recipients did not receive your message:\n\n',
52 ' +(?P<email>.*)\n(The following recipients did not receive your message:\n\n)?'),
53 '------- Failure Reasons --------\n\n(?P<reason>.*)\n(?P<email>.*)',
54 '^<(?P<email>.*)>:\n(?P<reason>.*)',
55 '^(?P<reason>User mailbox exceeds allowed size): (?P<email>.+)',
56 '^5\\d{2} <(?P<email>[^\n>]+)>\\.\\.\\. (?P<reason>.+)',
57 '^Original-Recipient: rfc822;(?P<email>.*)',
58 '^did not reach the following recipient\\(s\\):\n\n(?P<email>.*) on .*\n +(?P<reason>.*)',
59 '^ <(?P<email>[^\n>]+)> \\.\\.\\. (?P<reason>.*)',
60 '^Report on your message to: (?P<email>.*)\nReason: (?P<reason>.*)',
61 '^Your message was not delivered to +(?P<email>.*)\n +for the following reason:\n +(?P<reason>.*)',
62 '^ was not +(?P<email>[^ \n].*?) *\n.*\n.*\n.*\n because:.*\n +(?P<reason>[^ \n].*?) *\n',
64 # compile the re's in the list and store them in-place.
65 for i
in range(len(emparse_list_list
)):
66 x
= emparse_list_list
[i
]
67 if type(x
) is type(''):
68 x
= re
.compile(x
, re
.MULTILINE
)
72 xl
.append(re
.compile(x
, re
.MULTILINE
))
75 emparse_list_list
[i
] = x
79 # list of re's used to find reasons (error messages).
80 # if a string, "<>" is replaced by a copy of the email address.
81 # The expressions are searched for in order. After the first match,
82 # no more expressions are searched for. So, order is important.
83 emparse_list_reason
= [
84 r
'^5\d{2} <>\.\.\. (?P<reason>.*)',
85 '<>\.\.\. (?P<reason>.*)',
86 re
.compile(r
'^<<< 5\d{2} (?P<reason>.*)', re
.MULTILINE
),
87 re
.compile('===== stderr was =====\nrmail: (?P<reason>.*)'),
88 re
.compile('^Diagnostic-Code: (?P<reason>.*)', re
.MULTILINE
),
90 emparse_list_from
= re
.compile('^From:', re
.IGNORECASE|re
.MULTILINE
)
91 def emparse_list(fp
, sub
):
93 res
= emparse_list_from
.search(data
)
95 from_index
= len(data
)
97 from_index
= res
.start(0)
101 for regexp
in emparse_list_list
:
102 if type(regexp
) is type(()):
103 res
= regexp
[0].search(data
, 0, from_index
)
106 reason
= res
.group('reason')
110 res
= regexp
[1].match(data
, res
.end(0), from_index
)
113 emails
.append(res
.group('email'))
116 res
= regexp
.search(data
, 0, from_index
)
118 emails
.append(res
.group('email'))
120 reason
= res
.group('reason')
128 if reason
[:15] == 'returned mail: ':
130 for regexp
in emparse_list_reason
:
131 if type(regexp
) is type(''):
132 for i
in range(len(emails
)-1,-1,-1):
134 exp
= re
.compile(re
.escape(email
).join(regexp
.split('<>')), re
.MULTILINE
)
135 res
= exp
.search(data
)
137 errors
.append(' '.join((email
.strip()+': '+res
.group('reason')).split()))
140 res
= regexp
.search(data
)
142 reason
= res
.group('reason')
145 errors
.append(' '.join((email
.strip()+': '+reason
).split()))
148 EMPARSERS
= [emparse_list
, ]
150 def sort_numeric(a
, b
):
157 def parsedir(dir, modify
):
159 pat
= re
.compile('^[0-9]*$')
163 nok
= nwarn
= nbad
= 0
165 # find all numeric file names and sort them
166 files
= list(filter(lambda fn
, pat
=pat
: pat
.match(fn
) is not None, os
.listdir('.')))
167 files
.sort(sort_numeric
)
170 # Lets try to parse the file.
172 m
= email
.message_from_file(fp
, _class
=ErrorMessage
)
173 sender
= m
.getaddr('From')
174 print('%s\t%-40s\t'%(fn
, sender
[1]), end
=' ')
178 print('warning only')
181 os
.rename(fn
, ','+fn
)
186 errors
= m
.get_errors()
188 print('** Not parseable')
192 print(len(errors
), 'errors')
197 mm
, dd
= m
.getdate('date')[1:1+2]
198 date
= '%s %02d' % (calendar
.month_abbr
[mm
], dd
)
201 if e
not in errordict
:
203 errorfirst
[e
] = '%s (%s)' % (fn
, date
)
205 errordict
[e
] = errordict
[e
] + 1
206 errorlast
[e
] = '%s (%s)' % (fn
, date
)
211 os
.rename(fn
, ','+fn
)
214 print('--------------')
215 print(nok
, 'files parsed,',nwarn
,'files warning-only,', end
=' ')
216 print(nbad
,'files unparseable')
217 print('--------------')
219 for e
in errordict
.keys():
220 list.append((errordict
[e
], errorfirst
[e
], errorlast
[e
], e
))
222 for num
, first
, last
, e
in list:
223 print('%d %s - %s\t%s' % (num
, first
, last
, e
))
227 if len(sys
.argv
) > 1 and sys
.argv
[1] == '-d':
230 if len(sys
.argv
) > 1:
231 for folder
in sys
.argv
[1:]:
232 parsedir(folder
, modify
)
234 parsedir('/ufs/jack/Mail/errorsinbox', modify
)
236 if __name__
== '__main__' or sys
.argv
[0] == __name__
: