1 """mailerdaemon - classes to parse mailer-daemon messages"""
10 Unparseable
= 'mailerdaemon.Unparseable'
12 class ErrorMessage(rfc822
.Message
):
13 def __init__(self
, fp
):
14 rfc822
.Message
.__init
__(self
, fp
)
18 sub
= self
.getheader('Subject')
21 sub
= string
.lower(sub
)
22 if sub
[:12] == 'waiting mail': return 1
23 if string
.find(sub
, 'warning') >= 0: return 1
31 return p(self
.fp
, self
.sub
)
36 # List of re's or tuples of re's.
37 # If a re, it should contain at least a group (?P<email>...) which
38 # should refer to the email address. The re can also contain a group
39 # (?P<reason>...) which should refer to the reason (error message).
40 # If no reason is present, the emparse_list_reason list is used to
42 # If a tuple, the tuple should contain 2 re's. The first re finds a
43 # location, the second re is repeated one or more times to find
44 # multiple email addresses. The second re is matched (not searched)
45 # where the previous match ended.
46 # The re's are compiled using the re module.
48 'error: (?P<reason>unresolvable): (?P<email>.+)',
49 ('----- The following addresses had permanent fatal errors -----\n',
50 '(?P<email>[^ \n].*)\n( .*\n)?'),
51 'remote execution.*\n.*rmail (?P<email>.+)',
52 ('The following recipients did not receive your message:\n\n',
53 ' +(?P<email>.*)\n(The following recipients did not receive your message:\n\n)?'),
54 '------- Failure Reasons --------\n\n(?P<reason>.*)\n(?P<email>.*)',
55 '^<(?P<email>.*)>:\n(?P<reason>.*)',
56 '^(?P<reason>User mailbox exceeds allowed size): (?P<email>.+)',
57 '^5\\d{2} <(?P<email>[^\n>]+)>\\.\\.\\. (?P<reason>.+)',
58 '^Original-Recipient: rfc822;(?P<email>.*)',
59 '^did not reach the following recipient\\(s\\):\n\n(?P<email>.*) on .*\n +(?P<reason>.*)',
60 '^ <(?P<email>[^\n>]+)> \\.\\.\\. (?P<reason>.*)',
61 '^Report on your message to: (?P<email>.*)\nReason: (?P<reason>.*)',
62 '^Your message was not delivered to +(?P<email>.*)\n +for the following reason:\n +(?P<reason>.*)',
63 '^ was not +(?P<email>[^ \n].*?) *\n.*\n.*\n.*\n because:.*\n +(?P<reason>[^ \n].*?) *\n',
65 # compile the re's in the list and store them in-place.
66 for i
in range(len(emparse_list_list
)):
67 x
= emparse_list_list
[i
]
68 if type(x
) is type(''):
69 x
= re
.compile(x
, re
.MULTILINE
)
73 xl
.append(re
.compile(x
, re
.MULTILINE
))
76 emparse_list_list
[i
] = x
80 # list of re's used to find reasons (error messages).
81 # if a string, "<>" is replaced by a copy of the email address.
82 # The expressions are searched for in order. After the first match,
83 # no more expressions are searched for. So, order is important.
84 emparse_list_reason
= [
85 r
'^5\d{2} <>\.\.\. (?P<reason>.*)',
86 '<>\.\.\. (?P<reason>.*)',
87 re
.compile(r
'^<<< 5\d{2} (?P<reason>.*)', re
.MULTILINE
),
88 re
.compile('===== stderr was =====\nrmail: (?P<reason>.*)'),
89 re
.compile('^Diagnostic-Code: (?P<reason>.*)', re
.MULTILINE
),
91 emparse_list_from
= re
.compile('^From:', re
.IGNORECASE|re
.MULTILINE
)
92 def emparse_list(fp
, sub
):
94 res
= emparse_list_from
.search(data
)
96 from_index
= len(data
)
98 from_index
= res
.start(0)
102 for regexp
in emparse_list_list
:
103 if type(regexp
) is type(()):
104 res
= regexp
[0].search(data
, 0, from_index
)
107 reason
= res
.group('reason')
111 res
= regexp
[1].match(data
, res
.end(0), from_index
)
114 emails
.append(res
.group('email'))
117 res
= regexp
.search(data
, 0, from_index
)
119 emails
.append(res
.group('email'))
121 reason
= res
.group('reason')
129 if reason
[:15] == 'returned mail: ':
131 for regexp
in emparse_list_reason
:
132 if type(regexp
) is type(''):
133 for i
in range(len(emails
)-1,-1,-1):
135 exp
= re
.compile(string
.join(string
.split(regexp
, '<>'), re
.escape(email
)), re
.MULTILINE
)
136 res
= exp
.search(data
)
138 errors
.append(string
.join(string
.split(string
.strip(email
)+': '+res
.group('reason'))))
141 res
= regexp
.search(data
)
143 reason
= res
.group('reason')
146 errors
.append(string
.join(string
.split(string
.strip(email
)+': '+reason
)))
149 EMPARSERS
= [emparse_list
, ]
151 def sort_numeric(a
, b
):
158 def parsedir(dir, modify
):
160 pat
= re
.compile('^[0-9]*$')
164 nok
= nwarn
= nbad
= 0
166 # find all numeric file names and sort them
167 files
= filter(lambda fn
, pat
=pat
: pat
.match(fn
) is not None, os
.listdir('.'))
168 files
.sort(sort_numeric
)
171 # Lets try to parse the file.
174 sender
= m
.getaddr('From')
175 print '%s\t%-40s\t'%(fn
, sender
[1]),
182 os
.rename(fn
, ','+fn
)
187 errors
= m
.get_errors()
189 print '** Not parseable'
193 print len(errors
), 'errors'
198 mm
, dd
= m
.getdate('date')[1:1+2]
199 date
= '%s %02d' % (calendar
.month_abbr
[mm
], dd
)
202 if not errordict
.has_key(e
):
204 errorfirst
[e
] = '%s (%s)' % (fn
, date
)
206 errordict
[e
] = errordict
[e
] + 1
207 errorlast
[e
] = '%s (%s)' % (fn
, date
)
212 os
.rename(fn
, ','+fn
)
215 print '--------------'
216 print nok
, 'files parsed,',nwarn
,'files warning-only,',
217 print nbad
,'files unparseable'
218 print '--------------'
220 for e
in errordict
.keys():
221 list.append((errordict
[e
], errorfirst
[e
], errorlast
[e
], e
))
223 for num
, first
, last
, e
in list:
224 print '%d %s - %s\t%s' % (num
, first
, last
, e
)
228 if len(sys
.argv
) > 1 and sys
.argv
[1] == '-d':
231 if len(sys
.argv
) > 1:
232 for folder
in sys
.argv
[1:]:
233 parsedir(folder
, modify
)
235 parsedir('/ufs/jack/Mail/errorsinbox', modify
)
237 if __name__
== '__main__' or sys
.argv
[0] == __name__
: