Codebase list logwatch / 3210151
Use postfix-logwatch 1.39.05 as postfix script Willi Mann 12 years ago
11 changed file(s) with 8227 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 - Perl 5.10 causes taint error complaints. Will be fixed in the next release after
1 1.37.07.
2
3 - Percentiles reports do not honor max_report_width
0 2010-11-12 (version: 1.39.05)
1 - Fix: Check input value to commify() against undef; required for perl 5.12.
2 Thanks: Michael Orlitzky
3 - Fix: Eliminate use of $re_IP, as it is too complicated, expensive, and did
4 not correctly match all valid IPv6 addresses. Note: one remains in
5 RBL lookup error section, as parsing out IP is nontrivial w/IPv6.
6 - Internal: Update URL to Sourceforge.
7
8 2010-03-23 (version: 1.39.04)
9 - Fix: Support Postfix 2.8's reject_rhsbl_reverse_client in RejectRBL.
10 Thanks: Noel Jones
11
12 2010-03-06 (version: 1.39.03)
13 - Fix: Support for reject_rhsbl_helo in RejectRBL section.
14 Thanks: Steve Bytnar
15
16 2010-03-03 (version: 1.39.02)
17 - Change: Merged section SaslAuthRelay into SaslAuth. The purported SASL
18 sender is now included after an existing SASL_username.
19
20 2010-03-02 (version: 1.39.01)
21 - New: Allow enabling/djsabling the summary section in config file, using
22 var $postfix_Show_Summary and command line option --[no]summary.
23 Thanks: Benedikt Bohm
24
25 2010-01-11 (version: 1.39.00)
26 - New: Support 2.7 experimental branch logging:
27 + smtpd's proxy-reject: placed in standard reject sections.
28 + smtpd's proxy-accept: ignored.
29 + postfix/postscreen: new section 'postscreen'.
30 Note: memory intensive, so limiter is set to 1, showing only counts
31 of various postscreen result.
32 + postfix/dnsblog: new section 'dnsblog'.
33 Note: memory intensive, so limiter is set to 1, showing only a list
34 of DNSBL'd IPs. Disable this section by setting limiter to 0.
35 + postfix/verify: currently ignored
36 Thanks: Stefan Forster, Noel Jones
37 - New: Support 2.6 access(5) action BCC. New section 'bcced'.
38 - New: New option --[no]unknown to show/suppress output of hostname of
39 'unknown' in formatted ip/hostname pairs. Default: on.
40 - New: Updated postfix-logwatch.conf files to include instructions on how to
41 increase log scanning performance when not using policy services such as
42 policy-spf, etc. See "Performance Note" in the .conf files.
43 - Change/Fix: Warning sections 'ratelimit and 'concurrenclylimit merged into
44 a new, generic 'anvil' section, which includes all of anvil's various limit
45 exceeded messages. If you see errors after updating about these unknown
46 limiters, be sure to replace these two deprecated limiters with the new
47 one to your config file(s).
48 - Change: Use DSN from delivery verification probes as first level
49 key (section: 'deliverable').
50 - Change: Replace databasegeneration section header 'Database file
51 needs update' with 'Database is older than source file', as per request
52 on the Postfix mailing list.
53 - Fix: Handle more, and restructure, SASL authentication failed messages.
54 Thanks: Stefan
55 - Fix: Ignore some smtpd cache lines; handle postmaster delay notifications.
56 Thanks: Gabriele Beltrame
57 - Fix: Handle more postfwd lines.
58 - Fix: Prevent useless level 3 ':' output in section 'saslauthfail'.
59 Thanks: Armin Tüting
60 - Fix: Corrected some typos in man page.
61 - Fix: Added missing Error section (how was this missed!).
62 - Fix: Handle unmatched milter-{reject,hold,discard} lines that do not
63 include smtpd envelope data.
64 - Fix: reduce memory footprint for Delays reports (~30Mb per 1Mb log file).
65 - Fix: performance increases.
66
67 2009-07-14 (version: 1.38.01)
68 - Fix: RE typo caused policydweight lines to be unmatched.
69 - Fix: Reduce to-be-ignored RE list searching by moving most common
70 REs to head of the list.
71
72 2009-07-10 (version: 1.38.00pre6)
73 - Fix: Handle Softfail and Temperror in pypolicyd-spf (PolicySPF).
74 postfix/policy-spf. Thanks: Chris Burton.
75
76 2009-07-10 (version: 1.38.00pre5)
77 - Fix: Comment out extra OnlyService line in postfix-logwatch.conf* files,
78 leaving as a sample. The duplicate causes logwatch to filter out postgrey,
79 postfwd, and policyd-spf lines. Also include '-' in the RE to capture
80 postfix/policy-spf. Thanks: Malte Koestner, Chris Burton.
81
82 2009-07-07 (version: 1.38.00pre4)
83 - New: Error and diagnostic output from non-postfix programs will be moved
84 into the WarningsOther section.
85 - Fix: Added Tarpit whitelisted to postgrey output.
86 - Fix: Handle additional output for pypolicyd-spf, postgrey, and postfix.
87 - Fix: Add policyd-spf to postfix_Syslog_Name and OnlyService.
88 Thanks: Chris Burton
89 - Fix: logic error which inadvertently incremented Notification Sent counter
90 on ignored postfix/bounce lines.
91 - Fix: Add -T to interpreter line (removed inadvertently to use perl debugger).
92 - Change: Remove 'postgrey' from default syslog_name. This only affects users
93 without a configuration file, where it is enabled by default.
94
95 2009-07-06 (version: 1.38.00pre3)
96 - New: Handle pypolicyd-spf. Thanks: Chris Burton
97 - New: ConnectionInbound section shows IP/hostname. Disabled by default,
98 for performance reasons. Requested by: Jernej Porenta
99 - Change: MxErrors changed to DNSErrors, generalizing DNS lookup errors.
100 A beneficial side affect is the removal of the numerous DNS lookup
101 informational "warnings" from the top of the Summary section; they
102 can also, of course, be disabled in the Detailed section.
103 - Fix: Handle END-OF-MESSAGE stage in reject code.
104
105 2009-07-02 (version: 1.38.00pre2)
106 - Fix: Remove debug print
107 - New: Section SMTP protocol violation (smtpprotocolviolation)
108 - Change: Removed section "toomanyerrors", merging it into new section
109 smtpprotocolviolation.
110 - Fix: Handle "improper command pipelining ..." messages in new section
111 smtpprotocolviolation.
112 - Fix: Ignore more debug lines and miscellaneous canonicalization.
113
114 2009-06-29 (version: 1.38.00pre1)
115 - New: Support milter-hold (aka: "quarantine") and milter-discard lines.
116 Stats are located in "hold" and "discarded" sections, respectively.
117 Requested by: Gary Casterline;
118 - Fix: Perl v5.10 introduces tighter taint control in sprintf format
119 strings. Perform strict data checking on $Opts{'ipaddr_width'} to
120 untaint it, as it is used directly as an sprintf() field width specifier.
121 Thanks: Dudi Goldenberg
122 - Fix: Some Policy-SPF lines were unmatched, due to log format change
123 in Mail::SPF v2.006, which changed "identify=mfrom" to "identity=mailfrom".
124 Thanks: Chris Burton
125 - Fix: Resolved several bugs in the Policy-SPF module: some missing return
126 return statements, which for certain unmatched log lines would both add to
127 the Unmatched list and increment a PolicySPF hit counter; a few older
128 log lines would show blank IP addresses.
129 - Fix: Postgrey whitelisted lines that did not include a host IP address
130 were reported as unmatched.
131 - Fix: Relax email address capture in to=, orig_to=, and from= fields in
132 log lines.
133 - Fix: Better diagnostic when limiter level is not specified on
134 the command line using the --limit option. Eg:
135 "--limit rejectbody" v. "--limit rejectbody=10"
136 - Fix: Remove bogus --content_filter option from man page (feature not yet
137 implemented). Thanks: David DeFranco
138 - Fix: Better handle "Delivery temporarily suspended: conversation with xxx timed
139 out..." deferred messages.
140 - Fix: Handle multi-word XYZ in "blocked using XYZ" in RejectRBL.
141 Thanks: Sergey Pylinsky
142 - Fix: Ignore lines from postlog service.
143 - Change: Detail level now included in Detail title
144 - Internal: Support variable hash key lengths, but using the special key
145 sequence of two ASCII bell's ("\a\a"). This removes the existing
146 requirement that dummy keys must be used at the end of the %Counts
147 accumulator hash.
148 - Internal: Test data generation
149
150 2008-10-21 (version: 1.37.08)
151 - Fix: Saslauth messages were not being detected if the only field in a
152 smtpd "client=..." line was "sasl_sender=".
153
154 2008-10-20 (version: 1.37.07)
155 - New: Support "Temporary lookup failure" temporary rejects. New reject
156 limiter 'XXXrejectlookupfailure', where XXX is any of the set of
157 reject codes in effect.
158 - New: Support smtp_body_checks and smtp_*header_checks.
159 Requested by: Noel Jones
160 - Fix: Previous fix to support reload log line that includes postfix
161 version number broke previous postfix reload log lines.
162 Thanks: Armin Tüting
163 - Fix: Setting recipient_delimiter caused RE failure, due to missing
164 \Q \E quoting. Thanks: Armin Tüting
165 - Fix: BCC action support from 1.37.06 had a "if" vs. "elsif" typo.
166 Thanks: Noel Jones
167 - Fix: Allow postgrey filter to handle absent recipient= field.
168 Thanks: Alexander Kolesnik
169 - Fix: Limiters were not case insensitive on the command line.
170 - Fix: Use of bare reject limiters did not set each reply code variant of
171 the given limiter after using --nodetail, resulting in no output for the
172 given section.
173 - Fix: Change Logwatch's OnlyService variable in the postfix-logwatch.conf
174 file to also capture postfwd and postgrey. Thanks: Alex Schuilenburg
175 - Fix: Ignore more debug lines.
176
177 2008-08-22 (version: 1.37.06)
178 - New: Handle message_reject_characters cleanup rejects. New reject
179 limiter 'XXXrejectcontent', where XXX is any of the set of reject
180 reply codes in effect.
181 - New: Support BCC action from 2.6 experimental branch. New limiter
182 'bcced'.
183 - Fix: Ignore "mapping DSN status" lines.
184 - Fix: Support reload log line including postfix version number.
185 Postfix snapshot 2.6-2008081
186 - Thanks: Noel Jones
187 - Fix: Ignore more debug lines.
188
189 2008-08-19 (version: 1.37.05)
190 - New: Initial implementation of Postfwd reporting.
191 Requested by: Sahil Tandon
192 - Change: Undeliverable (address verification) section now
193 grouped by DSN, canonicalized host reply, domainpart, localpart,
194 and formatted host/hostip. This dramatically cleans up this
195 section's output for systems that perform many address verifications.
196 - Fix: Postgrey lines were being ignored due to change made to
197 implement Ignore_Service.
198 - Fix: handle "status=undeliverable-but-not-cached" address verification
199 response, which is coerced into a simple "status=undeliverable".
200 If there is any value in distinguishing the two status types, let me
201 known and I'll create a new limiter. Thanks: Gary Casterline
202 - Fix: missed conversion of keyword "next" to "return" in postfix_postsuper
203 routine after inline code was converted into the subroutine.
204
205 2008-07-22 (version: 1.37.04)
206 - Fix: recognize "approximately" in TooManyErrors section messages
207 starting introduced in Postfix 2.6 20080621. Thanks: Noel Jones
208 - Fix: Reset to level 0 the inadvertently changed EnvelopeSenders
209 and EnvelopeSenderDomains (in postfix-logwatch.conf).
210
211 2008-07-18 (version: 1.37.03)
212 - Change: TimeoutInbound will include byte count if available;
213 default level changed to 1 to reduce noise.
214 - Change: some additional canonicalization is performed on
215 various "host XXX said: yyy" messages in sections like Deferrals.
216 Much of the "said" verbiage from some large mail houses is redundant
217 or excessive, and causes additional memory consumption. If this
218 canonicalization presents problems, I may include an additional level
219 of detail to present the raw message. Feedback welcome.
220 - Fix: Supplemental sections could not be re-enabled after using
221 --nodetail.
222 - Fix: Handle some additional RejectRBL variants, esp. the
223 "day old bread" list (dob.sibl.support-intelligence.net).
224 Thanks: Michael Monnerie
225 - Fix: Present the remote MTAs reply code/DSN, if available, at the
226 beginning of canonicalized "host XXX said: yyy" messages.
227 Thanks: Jorey Bump
228 - Fix: Provide remote MTAs SMTP reply code/DSN if available
229 in various Delay, Deferral, etc. reports. Some additional
230 canonicalization is performed on various flavors of deferral
231 replies.
232 - Fix: recognize "approximately" in lost connection byte count
233 messages starting introduced in Postfix 2.6 20080621.
234 Thanks: Noel Jones
235 - Internal: Ignored lines are now placed into a list, instead of
236 hard coded into the code. In the future, this will allow users
237 to configure ignore patterns without making code modifications.
238 In doing this, I discovered and reported a bug in perl (#56202),
239 but worked around the problem.
240 - Internal: move postsuper, panic, and fatal message processing into
241 their own subroutines.
242
243 2008-05-30 (version: 1.37.02)
244 - Change: Some changes in Delays report. Now includes total delay
245 time as reported in delivery agent log entry delay=x. Removed
246 leading numbers in row titles, and cleaned up the title names.
247 Also, reduced fields to 2 decimal places.
248 - New: By IP (permanent) Reject report controlled with ByIpRejects
249 limiter. Disabled by default.
250 - Fix: Two minor fixes to PolicydWeight module: it was not the required
251 importing inc_unmatched, and ignore diagnostic "master: ..." lines.
252 - Fix: Eliminate some extraneous newline output.
253 - Fix: Ignore more debug log lines.
254
255 2008-05-09 (version: 1.37.01)
256 - Change: level limiters are no longer unique command line options,
257 but are now parameters to the single option "--level" or "-l".
258 This reduces the number of command line options in the help list,
259 and simplifies the code added to support reject_reply_patterns.
260 For example, the config file level limiter $postfix_Sent = 2,
261 the command line option would be --limit sent=2 or -l sent=2,
262 rather than the previous --sent=2. This also means the --no
263 variants for level limiters are gone (eg. --nosent; instead use
264 -l sent=0). There is no change within the configuration files.
265 Limiters can still be abbreviated so long as they are unambiguous.
266 - Change: Section connectionlostoverload is removed, by being merged
267 into connectionlostinbound.
268 - Change: PrematureEOI is now AttrError, broadened to include errors
269 reading attributes from services.
270 - New: Support RFC 4954 Enhanced status codes (postfix 2.5+).
271 - New: Report "status=deferred (bounce failed)" messages under
272 Summary section "Bounce failed".
273 - New: Support postfix 2.6+ check_reverse_client_hostname_access
274 Includes new reject config variable: RejectUnverifiedClient.
275 - New: option --line_style specifies how to handle lines lengths
276 longer than max_report_width. Options are "wrap", "full", or
277 "truncate" (default). The older --detail >= 11 is equivalent
278 to line_style=full; line_style=truncate or line_style=wrap has
279 higher precedence and will dictate how long lines are handled.
280 Alternative options are --truncate, --wrap, or --full.
281 - New: The beginnings of a Top N config file is provided in
282 postfix-logwatch.conf-topn. Suggestions and improvements welcome.
283 - New: single letter options for some long options; run with
284 --help to see list. More may be added in the future.
285 - New: Option --ignore_services (config var $postfix_Ignore_Services)
286 provides a mechanism to ignore postfix/SERVICE log lines, where
287 SERVICE is a regular expression pattern.
288 - Fix: Support Anonymous TLS in TlsServerConnect (postfix 2.5+).
289 - Fix: Ignore output from "postfix status" command (postfix 2.5+)
290 - Fix: Ignore more debug log lines.
291 - Fix: When postsuper held more than one message in a given call,
292 the Hold count shown in the Summary section indicated how many
293 messages were held, but the Detail section showed how many
294 postsuper calls were made. Thanks: Stefan Jakobs
295 - Fix: Postgrey sender and recipient fields improperly reported
296 - Fix: Allow ":unknown" as an acceptable port in smtpd's "client=..."
297 log lines. Occurs in pre-queue content_filter setup.
298 Thanks: Robert Brooks
299 - Fix: At detail > 10, log lines were truncated to max_report_width.
300 - Fix: Documentation cleanup. Thanks: Chris Pepper
301 - Fix: Relax capture RE for resent-message-id which may contain
302 < or > chars. Thanks: Stefan Jakobs
303 - Fix: Reason for deferral/bounce would sometimes be incorrectly
304 shown as unknown recipient.
305 - Fix: Better handle the trigger subject (SMTP_NAME_*) in discard,
306 filter, hold, redirect, and warn actions (eg. Client host, Sender
307 address, Recipient Address, Client certificate, etc.).
308 - Change: reduce length of some extended status codes for readability.
309 - Change: remove hard-coded "postgrey" from the syslog name pattern
310 matching code, and instead add it to the configuration variable
311 postfix_Syslog_Name.
312 - Internal: converted all Section key names to lowercase to avoid
313 silly case errors due to hash key case differences in Sections,
314 Opts, and Collecting hashes.
315 - Internal: debug output is now controlled by keywords.
316
317 2008-05-09 (version: 1.36.13)
318 Final version skipped
319
320 2008-01-14 (version: 1.36.13pre7)
321 - New: Support ETRN rejects (option: RejectEtrn).
322 - Fix: Include optional text in L1 output for header/body checks Hold
323 messages.
324 - Fix: Accept "unknown" as an IP in Connection rate limit messages.
325 Thanks: Stefan Jakobs
326 - Fix: Improve MxError captures
327 - Fix: Ignore "sql auxprop plugin ..." messages
328 - Fix: Ignore more debug_peer_level=2 messages
329 - Fix: Correct failure to ignore "connect to subsystem..." debug lines
330 - Fix: Add missing RejectVerify to config file
331
332 2007-12-15 (version: 1.36.13pre6)
333 - Fix: in BounceLocal and EnvelopeSenderDomains, set null domain
334 and formatted host to '*unknown'.
335 - Fix: Move postfix_warning before postfix_cleanup, as some cleanup
336 warnings were caught as unmatched.
337 - Fix: An Accepted message is now triggered by smtpd "client=..."
338 and pickup "uid=..." log entries, instead of qmgr's "from=xxx,
339 size=nnn, nrcpt=nnn" log lines. A message may have been accepted
340 during a previous time period, but delivery delays result in
341 multiple qmgr delivery attempts, resulting in over counting
342 Accepted messages. Thanks: Stefan Jakobs
343 - New: Bytes delivered is now broken down by Bytes sent via SMTP,
344 LMTP, and forwarded, to be orthogonal with message counts.
345 - Fix: Resent messages no longer reduce the number of messages
346 accepted - this was a naive attempt at not counting, for example,
347 messages released and re-queued from a content filter's quarantine.
348
349 2007-11-14 (version: 1.36.13pre5)
350 - New: Threshold limiting and Top N lists for every level in each
351 Detail section. Every level in each section can now be limited
352 with minimum count thresholds and top N lists. See the updated
353 README file, the comments in the postfix-logwatch.conf file, and
354 the new postfix-logwatch man page. Requested by: Pavel Urban
355 - New: Rejects can now be categorized by reject reply code. A new
356 option/variable "reject_reply_patterns" is a list of reject reply
357 code regular expressions, which are used for categorizing rejects.
358 This feature allows, for example, distinguishing 421 transmission
359 channel closes from 45x errors. (eg. 450 mailbox unavailable, 451
360 local processing errors, 452 insufficient storage). The default
361 list is: "5.. 4.. Warn" which creates three groups of rejects:
362 permanent rejects, temporary failures, and reject warnings (as in
363 warn_if_reject). Requested by: Noel Jones
364 - New: postfix-logwatch man page created (net yet complete)
365 - New: Support for all access(5) actions. See the "Level Limiter
366 Options" section in the postfix-logwatch(1) man page, and "Common
367 access control actions" in postfix-logwatch.conf.
368 - New: Added envelope senders and envelope sender domains reports.
369 These are disabled by default. Enable with level limiter options
370 --envelopesenders 1 and --envelopesenderdomains 1 (or 2 to
371 also see senders listed under domains). See also the
372 postfix-logwatch.conf file. Suggested by: Brendan
373 - Change: Uncomment all variables in the config file. This should
374 help ensure the variables in the config file stay in sync with
375 those used in the source.
376 - Change: Merged sections SenderDelayNotification, DSNDelivered, and
377 DSNUndelivered into NotificationSent, with sub-sections indicating
378 the type of notification. This gives the total number of sent
379 notifications in the Summary section, and the breakdown by type
380 of notification in the Detailed section.
381 - Change: Removed "msgs" prefix from several options: msgsdeferred,
382 msgsdelivered, msgsforwarded, msgsresent, msgssent, and msgssentlmtp
383 are now deferred, delivered, forwarded, resent, sent and sentlmtp.
384 The old options are still usable.
385 - Change: force --help output to 80 chars.
386 - Change: Taint mode is now on by default in standalone mode. It is
387 disabled upon installation in logwatch mode, as logwatch fails with
388 taint mode enabled.
389 - Change: Set the primary key in tlsserverconnect/tlsclientconnect
390 options to type/cipher to reduce excessive level 2 output.
391 - Change: Reported values that cannot be determined or are unavailable
392 are prefixed with an asterisk (Eg. *unknown, *unspecified).
393 - Change: Warn section title 'Warn action logged' changed simply to
394 "Warned", to be consistent with other access/header_ and body_checks.
395 The option is --warned, but --warn is still acceptable.
396 - Fix: Significantly reduce memory footprint when detail < 5.
397 - Fix: Usage and --help now correctly show only detail section
398 level limiter options that are available. Previously, summary-
399 only counts were also display as level limiter options.
400 - Fix: "too many errors after DATA" and "timeout after DATA" may
401 include a byte count, as in "(348 bytes)". Thanks: John Beaver
402 - Fix: Ignore postgrey 'delayed ...' lines
403 - Fix: Allow logwatch --debug option to pass into postfix-logwatch
404 - Fix: configuration file reading code was not properly warning on
405 non-existent files
406 - Fix: "filter" actions were incremented on "redirect" actions
407 - Fix: Give more room to percentiles in delays percentiles table.
408 A 5 day delay is 432000.000 seconds, and the previous table width
409 was not sufficient.
410 - Fix: --show_sect_vars command line option inadvertently required an
411 argument; the name has been shortened to --sect_vars/--nosect_vars
412 and correctly no longer requires an argument. The longer names
413 --[no]show_sect_vars work as well. Thanks: Noel Jones
414 - Fix: Handle some unmatched connect to failures in section
415 ConnectToFailure. Cleanup and consolidate several similar messages;
416 add additional detail at level 3.
417 - Fix: Increment postsuper Hold messages by the number of messages
418 placed on hold.
419 - Fix: Add "non-ESMTP response ..." messages to SmtpConversationError.
420 First level is now the general SMTP error description.
421 - Internal: Converted source to be package-based to allow code sharing
422 with amavis-logwatch. The single-file executable is auto-generated
423 from the packages.
424 - Internal: internal gen_test_log function to create sample log data
425 for testing. Reads '#TD' comments from within postfix-logwatch.
426
427 2007-10-16 (version: 1.36.13pre4)
428 - Fix: Handle messages "SSL_connect error to example.com: 0" and
429 "Cannot start TLS: handshake failure" by coercing into warnings
430 Thanks: Rob Sterenborg
431
432 2007-10-15 (version: 1.36.13pre3)
433 - Fix: Handle "postmaster" DSNs
434 - Fix: Handle "discarding EHLO keywords:" (ignored)
435 - Internal: create bounce subroutine to handle bounce messages
436
437 2007-10-05 (version: 1.36.13pre2)
438 - New: Initial support for postgrey (http://postgrey.schweikert.ch/)
439 Requested by: Sebastian Wolfgarten
440
441 2007-10-05 (version: 1.36.13pre1)
442 - New: Support for policy-spf software postfix-policyd-spf-perl
443 Requested by: Nicodemo P. and Rob Sterenborg
444
445 2007-10-05 (version: 1.36.12)
446 - New: support postfix 2.5 log changes (20071004, 20071003)
447 - Incompatible Change: Config variable/command line option name change:
448 * WarningHeader changed to Warn
449 * MsgsRedirected changed to Redirected
450 * ConnectionLost split into ConnectionLost{Inbound,Outbound}
451 - Incompatible Change: Distinguish inbound (smtpd) vs. outbound (smtp),
452 which replaces ConnectionsLost with ConnectionsLostInbound and
453 ConnectionsLostOutbound. ConnectionsLostInbound includes number of
454 bytes received if the connection was lost during DATA. Encouraged by
455 2.5 20071003 change in logging.
456 - New: Support for all header_checks(5)/body_checks(5) actions. See
457 also postfix-logwatch.conf for "Common access control actions"
458 - New: Option show_sect_vars shows names of section configuration
459 variables/command line options in titles of detailed report sections.
460 This allows easy correlation of corresponding configuration file
461 variables/command line option for each section. [ Default: 0 ]
462 - Fix: catch postsuper's pluralized form of Delete message"s".
463 - New: handle "cannot load Certificate Authority data" as Misc. warning
464 - Fix: Makefile: no -D option to install in FreeBSD. Use -d instead
465 - Change: ConnectionLostInbound now defaults to level 1 only
466 - Internal: Numerous code cleanups, re-factoring and restructuring
467 - Internal: Test data migrating to include the correct postfix service, as
468 internal code moves towards using the service name as an initial log
469 line match qualifier
470
471 2007-09-13 (version: 1.36.11)
472 - Incompatible Change: All TempRejectXXX and RejectWarnXXX options/config vars
473 have been renamed for easier identification in the code, and consistency.
474 Now, all Reject variants look like RejectXXXyyy, where XXX is the
475 given reject name (Helo, RBL, etc), and yyy is the optional reject type
476 of "Warn" (warn_if_reject) or "Temp" (4xx temporary rejects).
477 For example, the previously named RejectRelay, RejectWarnRelay and
478 TempRejectRelay are now named RejectRelay, RejectRelayWarn and RejectRelayTemp.
479 See the usage information or the .conf file to see the list.
480 - Fix: A %Counts accumulator must use the same number of keys consistently.
481 This error caused some totals to be wildly incorrect, and the fatal perl error:
482 "Can't use string ("XXX") as a HASH ref while "strict refs" in use ..."
483 Fortunately, this error only occurred with a specific set of data, which
484 seemed not too common.
485 - Fix: remove Temp and Warn variants of RejectHeader and RejectBody - they
486 don't exist.
487 - Fix: Add Temp variant of RejectMilter
488 - New: Add ProcessLimit section for 2.5 stress messages
489 - New: accommodate postfix patch which also logs HELO name in smtpd's
490 "QID: client=..." log entries.
491 - Thanks: Noel Jones
492
493 2007-09-09 (version: 1.36.10)
494 - New: handle "reject: DATA from ... <DATA>: Data command rejected: ..."
495 - New: ignore "fingerprint=20:..." lines
496 - Thanks Farkas Levente
497
498 2007-09-01 (version: 1.36.9)
499 - Fix: remove rooted path in md5 file
500 - Fix: Makefile install-logwatch rule was missing a parenthesis
501 - Fix: Makefile updates from Till Mass
502
503 2007-08-31 (version: 1.36.8)
504 - Change: Include GPLv2 license
505 - Change: Include version number in tarball file name
506 - Internal: Move CVS log comments to Changes file
507
508 2007-08-31 (version: 1.36.7)
509 - Fix: capture older postfix RCPT from RBL reject entries. Thanks
510 Hugo van der Kooij
511
512 2007-08-15 (version: 1.36.6)
513 - Changed: for sorting purposes, lowercase localpart of rejected email addresses
514 - Change: Output help and version info on STDOUT for easier pipelines to a pager
515 - New: option --nodetail zeros out all detail levels, to more easily obtain
516 only specified detailed reports (eg: --nodetail --rejecthelo 1) will
517 only show a list of rejected HELOs in the details section.
518 - New: option --nosummary disables the summary section
519 - New: detailed section command line arguments can now be specified with the
520 prefix "no", to set the level to 0 (eg. --nomsgssent is equivalent to
521 --msgssent 0).
522 - Internal: split printReports into printSummaryReport and printDetailReport
523 - Internal: change variable Formats to the more obvious named Sections
524
525 2007-08-03 (version: 1.36.5)
526 - Changed: rejected addresses collected by domain, then localpart
527 - New: delay percentiles report. Config vars: show_delays and
528 delays_percentiles; command line --[no]delays
529 - Fix: Yes/True and No/False config values weren't being read properly
530 in standalone
531
532 2007-08-01 (version: 1.36.4)
533 - New: option --config_file allows specifying a configuration file via
534 command line. Options in configuration file act as though they were
535 set on the command line in order, with earlier settings being over-
536 ridden by most recent settings. Multiple config_file options may
537 be specified.
538 - Change: Summary title includes syslog_name in standalone mode
539 - Change: Remove some extraneous newlines
540
541 2007-07-24 (version: 1.36.3)
542 - Refine anvil connection rate exceeded messages
543
544 2007-07-13 (version: 1.36.2)
545 - Support FreeBSD (<facility.priority> precedes hostname in syslog)
546 - Thanks Clemens Fischer
547
548 2007-07-10
549 - Ignore "nss_ldap: reconnected to LDAP server ..."
550 - Handle "discard: header/body: messages
551 - Thanks Jay Chandler
552
553 2007-07-03
554 - Corrected some minor typos
555
556 2007-06-08
557 - Changed titles shown for sender notifications of (non-)delivery
558 and delay
559 - Corrected some incorrect config file variable names. Thanks: Nicolas
560
561 2007-06-02
562 - Do not strip <> when address is '<>' in Illegal address syntax
563
564 2007-06-01
565 - Changed warning output for smtpd messages:
566 "lost connection after CONNECT from unknown[unknown]".
567 Previous warning erroneously attributed this to pre-queue content
568 filter overloads, but the problem indicates a more general
569 smtpd overload.
570
571 2007-05-31
572 - Fix bug which caused config file to be required in standalone mode
573
574 2007-05-30
575 - Support delay_reject=no (debian: 426726)
576 - Ignore additional unmatched TLS debug messages
577 - Initial support for redirect messages
578 - Some corrected typos
579 - Thanks Jusin Pryzby
580
581 2007-05-25
582 - Experimental: syntax to limit Top N level 1 output lines. Variables
583 that control depth levels in detailed reports can be specified as
584 m.n, where m is the maximum level to output, and n specifies the number
585 of level 1 items output. Eg: $postfix_MsgsSent=2.10, will output
586 the top 10 level 1 items, with each item providing 2 levels of detail.
587 - Protect interpolated recipient addresses in cleanhostreply with \Q \E
588 e Add zero-width assertions and use strict IP RE in bycount sort subroutine
589 to match IP addresses more reliably
590
591 2007-05-09
592 - Ignore a few more policydweigh child or cache entries
593 - Escape metacharacters from being interpreted in recipient_delimiter
594 - Never split mailer-daemon, double-bounce, or when recipient_delimiter
595 is a "-" (dash) never split owner- or -request localparts
596
597 2007-05-08
598 - Support for running in standalone mode (independent of logwatch)
599 - Renamed script to "postfix-logwatch" to avoid confusion when running
600 in standalone mode. See the README.
601 - Add relay=virtual to "local" class for local vs. remote bounces
602
603 2007-05-07
604 - Handle and report postfix/policydweight lines (postfix_PolicydWeight)
605 - Handle and report Host offered STARTTLS lines
606 - Generalize "maildrop: Unable to create a dot-lock at <path>" messages
607 - Corrected typo that prevented fatal errors from being output
608
609 2007-04-26
610 - Consolidate similar MX errors
611 - set IP address to 127.0.0.1 when from=local, and reporting both host/hostip
612 - More cleanup (re-factor common code, replace most global variables with lexicals,
613 lowercase non-global variable names, shorten variable names, etc.)
614 - Capture postsuper hold messages
615
616 2007-04-25
617 - Support postfix 2.5 TLS message changes (smtpd_tls_loglevel > 0)
618 - Move 4xx temporary rejects into their own section (experimental feature)
619
620 2007-04-18
621 - Allow for hold messages that do not contain a recipient (Thanks John Wilcock)
622
623 2007-03-26
624 - Lowercase recipient addresses in several Reject sections
625
626 2007-03-22
627 - Accept spf.pobox.com URLs in Reject recipient address
628
629 2007-03-21
630 - Handle spf lines from older version of postfix-spf, and spf.pobox.com URLs
631
632 2007-03-20
633 - Capture and report postfix-spf lines
634 - New config variable postfix_PolicySPF
635
636 2007-03-13
637 - Capture and report reject_unknown_reverse_client_hostname.
638 Thanks Michael M.
639
640 2007-03-09
641 - Capture and report as config warning: "looking for plugins" NSF error
642
643 2007-03-03
644 - fix reject header|body RE to allow "local" as host/ip
645 - really ignore lines that don't match SyslogName;
646 - add inc_unmatched subroutine for easier debug of unmatched lines
647
648 2007-02-27
649 - Capture and summarize postfix-script output (starts, stops, refresh, etc.)
650 - Remove redundant chomps
651
652 2007-02-26
653 - Fix problem in sort routine where IP addresses were being captured
654 anywhere in an output line for comparison via pack 'C4' - only
655 attempt IP comparison if an IP address is the start of an output line
656 Thanks: Ian
657 - Provide support for syslog_name in Postfix 2.4 via postfix.conf
658 variable postfix_Syslog_Name
659 - Classify PIX workarounds based on type (there are several)
660 - Change summary output criteria to check for any non-zero Totals
661
662 2007-02-25
663 - Do not interpolate log lines into printf; they may contain % chars
664
665 2007-02-17
666 - Ensure no output occurs when nothing is captured
667
668 2007-02-15
669 - Place recipients and senders in their own keys, instead of combined
670
671 2007-02-14
672 - Fix countdown bug in Deliveries, as ncrpt does not account for always_bcc
673
674 2007-02-14
675 - Make reject and warn_if_reject distinct sections
676 - Track Qids to properly report messages and bytes sent / accepted
677 - Also track messages deferred (each of which may have many deferrals)
678 - Consider reject VRFY a reject and accumulate in reject totals
679 - Move header/body reject code in with the rest of the reject code
680 - Move 'MAIL from' reject code in with the rest of the reject code
681 - Remove unused variable
682 - Print row heading separator lines only when appropriate
683 - Move printing of report headings into printReports
684 - Change Sent header to Sent via SMTP (orthogonal to Sent via LMTP)
685
686 2007-02-09
687 - Better processing of remote server "host...said" replies
688 - Made maximum report width configurable in postfix.conf
689 - All lines in report now obey max report width
690
691 2007-02-07
692 - Changed all From -> To lines to To <- From. From address is often bogus
693 and To is more interesting to see.
694
695 2007-02-06
696 - Added new configuration variable "postfix_Recipient_Delimiter", which
697 can be set to match the postfix variable "recipient_delimiter".
698 When set, allows Delivered and Sent reports to be grouped by
699 email addresses minus their address extension.
700 - Liberalized the RE for capturing VFRY rejects
701 - Reverted change of primary SASL authenticated messages; primary
702 key is once again User, followed by Method. Unknown is reported
703 when these keys are not available.
704 - Created a SASL authenticated relayed messages which hits with
705 sasl_sender is present in smtpd messages
706 - Split orig_to email addresses into a subkey. Allows reports
707 to be grouped by primary "to" address, instead of various aliases.
708 - Move Host/HostIP into their own keys for Bounce Remote section
709 - For Pix Workaround section, format HostIP / Host the same as others
710 - Add 'o' option where missing in REs
711 - Fix configuration variable importing
712
713 2007-02-03
714 - Added VFRY reject section
715 - Changed primary key for SASL authentication section to IP/hostname,
716 as any of sasl_{method,sender,user} may be absent
717 - Added RFC 3463 DSN code messages to bounce/deferred sections
718 - More parsing of various "host...said" remote server messages
719 - Add Sent via LMTP section, removing lmtp-delivered mail out of Delivered.
720 Allows users with lmtp-based content filters to avoid double counting
721 (but only for message counts; byte counts are still about 50% too large).
722 Config variable postfix_MsgsSentLmtp controls display
723
724 2007-01-28
725 - Added pre-queue content-filter overload section
726 - Reworked Bounce (local/remote) and Deferred sections
727 - Fixed several reversed captures of Host and HostIP
728 - Format Host and HostIP in Numeric hostname section
729 - Thanks: Mike Horwath
730
731 2007-01-28
732 - Made Reason the primary key in Deliverable/Undeliverable 'sendmail -bv' tests
733 - Modified re_DSN RE to capture DSNs missing 3 number response code
734 - Enhanced SASL authenticated messages to capture missing log lines
735 - Added milter-reject section
736 - Updated 'Reject server configuration error' RE for older postfix versions
737 - Fix copy/paste error which caused use of incorrect variable in bad size limit
738 - Add Concurrency limit reached section
739 - Add "maildrop" to the list of relay's considered local in Bounce section
740
741 2007-01-23
742 - Aggregate recipient/sender address verification lines (Thanks Harald Geiger)
743 - Uppercase message in reject recipient section
744
745 2007-01-22
746 - Update REs to allow null sender in sender addresses
747
748 2007-01-19
749 - Capture and summarize "triggers FILTER" messages (Thanks Eray Aslan)
750 - Fix overly permissive Server configuration error RE (Thanks Harald Geiger)
751 - Add "Server configuration error" rejects to Reject totals
752 - Add "Insufficient system storage" rejects to Reject totals
753 - Make RejectSize report consistent with others
754
755 2007-01-17
756 - Update IP RE to support IPV6 (Thanks Harald Geiger)
757 - Aggregate reject recipient address caused by SPF (Thanks Harald Geiger)
758
759 2007-01-16
760 - Made Reject HELO/EHLO report consistent with others: demoted Helo=xxx
761 string (Thanks: Jorey Bump)
762 - Fixed incorrect TotalRejects summation (typo)
763 - "Accepted" / "Rejected" summation is no longer based on "Connection";
764 now Total = Accepted + Rejected
765 * Client may or may not close connection properly after reject
766 * Multiple rejects for single connection can occur
767 - ConnectToFailure header incorrectly indicated connection was "inbound"
768 - Included Makefile for ease of installation during this testing phase (Thanks: Jorey Bump)
769
770 2006-12-16
771 - Add pcre map warnings
772 - Reordered headings to produce more obvious correlation with percentage breakdowns
773 - Second key sort is now the illegal address in illegal address in SMTP command
774 - More optimization of log entry capturing
775 - Added Sent category capturing outbound SMTP connections
776 requires modified /usr/share/logwatch/default.conf/services/postfix.conf or
777 /etc/logwatch/conf/services/postfix.con
778 - Added ability to control max print depth on a per section basis
779 - Renamed Received to Accepted (to indicate accepted for delivery, and better opposite of Reject)
780 NOTE: Accepted shows less than that of pflogsumm's Received, which incorrectly
781 increments rejected messages
782 - Reduce Accepted by Deferred and Resent, which cause double counting
783 - Removed erroneous a-z in RE for capturing QIDs
784 - Added Panic section for postfix panic messages
785 - Fixed bug which failed to increment watchdog timers
786 - Started work on better debug capability
787
788 2006-12-13
789 - Removed extra blank line for Detail <= 5
790 - Filter many more debug lines
791 - Catch and group additional warning and fatal messages
792
793 2006-12-12
794 - Made reject header/body output consistent with each other
795 - Sort reject header/body first by recipient address, for tighter groupings - reject reason is typically random
796 - Trimmed excess whitespace in reject header/body reasons
797 - Added extra line between level 1 headings
798 - Group SASL authenticated messages by sasl_username
799 - Filter TLS and SASL debug messages (smtpd_tls_loglevel = 2)
800
801 Rewrite by Mike Cappella (MrC)
802 Revision 1.29 2007/01/27 20:21:46 mrc
803 - Provide more useful information and summaries
804 - Provide increasing detail as requested via --detail
805 - Provide ability to configure per section maximum detail
806 - Optimize and combine the numerous REs
807 - Capture and summarize many more log lines
808 - Pin important errors to top of report
809 - Sort by hits, IP, and lexically
810 - Handle IPv6 addresses
811 - Generalize log line capturing and reporting
812 - Eliminate excessive copy/paste reporting code
813 - Requires updated postfix.conf file
814 - Thanks: Eray Aslan, Jorey Bump, Harald Geiger, Bill Hudacek,
815 Frederic Jacquet, Geert Janssens, Leon Kolchinsky, Rob Myroon
816
817 Revision 1.28 2006/12/15 06:24:49 bjorn
818 Filtering "sender non-delivery notification", by Ivana Varekova.
819
820 Revision 1.27 2006/12/15 05:00:41 bjorn
821 Filter all held message logs, by Hugo van der Kooij.
822
823 Revision 1.26 2006/10/20 16:51:50 bjorn
824 Additional matching of sasl messages, by Willi Mann.
825
826 Revision 1.25 2006/08/13 21:25:55 bjorn
827 Updates to work with the Postfix 2.3.x series (due to log format changes),
828 by Mike Cappella.
829
830 Revision 1.24 2006/03/22 17:43:46 bjorn
831 Changes by Harald Geiger:
832 - ignore additional statistics: messages (Postfix 2.2)
833 - replaced several 5xx Codes by [0-9]+
834 (main reason is to make them match on 4xx if in soft_bounce=yes mode)
835 - a more generic "Client host rejected" reporting
836 - changed "Messages rejected:" to "Messages rejected from sender:"
837
838 Revision 1.23 2005/12/19 15:47:47 bjorn
839 Updates from Mike Cappella:
840 - Catches some of the Unknown Users messages from newer versions of postfix
841 - Consolidates a couple of REs
842 - Adds a cumulative total to each of the Unknown users and Header content rejection headers
843 - Adds a Body content rejection section
844
845 Revision 1.22 2005/11/22 18:30:47 bjorn
846 Detecting 'virtual alias table', by Kevin Old.
847
848 Revision 1.21 2005/08/23 23:54:38 mike
849 Fixed typo probably from Roland Hermans -mgt
850
851 Revision 1.20 2005/07/25 22:26:28 bjorn
852 Added "Sender address" to "554 Service unavailable" regexp, by Who Knows
853
854 Revision 1.19 2005/04/22 13:48:28 bjorn
855 This patch catches (un)deliverable messages and many more, which were
856 missing until now on mu new postfix-2.1.*, from Paweł Gołaszewski
857
858 Revision 1.18 2005/04/17 23:12:28 bjorn
859 Patches from Peter Bieringer and Willi Mann: ignoring more lines and
860 some blank spaces
861
862 Revision 1.17 2005/02/24 17:08:05 kirk
863 Applying consolidated patches from Mike Tremaine
864
865 Revision 1.7 2005/02/16 00:43:28 mgt
866 Added #vi tag to everything, updated ignore.conf with comments,
867 added emerge and netopia to the tree from Laurent -mgt
868
869 Revision 1.6 2005/02/13 23:50:42 mgt
870 Tons of patches from Pawel and PLD Linux folks...Thanks! -mgt
871
872 Revision 1.5 2004/10/06 21:42:53 mgt
873 patches from Pawel quien-sabe -mgt
874
875 Revision 1.4 2004/07/29 19:33:29 mgt
876 Chmod and removed perl call -mgt
877
878 Revision 1.3 2004/07/10 01:54:35 mgt
879 sync with kirk -mgt
880
881 Revision 1.13 2004/06/23 15:01:17 kirk
882 - Added more patches from blues@ds.pg.gda.pl
883
884 Revision 1.12 2004/06/21 14:59:05 kirk
885 Added tons of patches from Pawe? Go?aszewski" <blues@ds.pg.gda.pl>
886 Thanks, as always!
887
888 Revision 1.11 2004/06/21 13:42:02 kirk
889 From: Matthew Wise <matt@oatsystems.com>
890 This is more of a suggestion than a true patch submission. On a busy
891 postfix server the messages sent by section is really long and not
892 helpful. This patch finds and lists the top 10 senders by number of
893 messages.
894
895 Revision 1.10 2004/06/21 13:41:04 kirk
896 Patch from rod@nayfield.com
897
898 Revision 1.9.1 2004/02/22 16:44:01 rod
899 Added patch from rod@nayfield.com
900
901 Revision 1.9 2004/02/03 03:25:02 kirk
902 Added patch from quien-sabe@metaorg.com
903
904 Revision 1.8 2004/02/03 02:45:26 kirk
905 Tons of patches, and new 'oidentd' and 'shaperd' filters from
906 Pawe? Go?aszewski" <blues@ds.pg.gda.pl>
907
908 Revision 1.7 2003/12/15 18:35:03 kirk
909 Tons of patches from blues@ds.pg.gda.pl
910
911 Revision 1.6 2003/12/15 18:09:23 kirk
912 Added standard vi formatting commands at the bottom of all files.
913 Applied many patches from blues@ds.pg.gda.pl
914
915 Revision 1.5 2003/12/15 17:45:09 kirk
916 Added clamAV update log filter from lars@spinn.dk
917
918 Revision 1.4 2003/11/26 14:36:30 kirk
919 Applied patch from blues@ds.pg.gda.pl
920
921 Revision 1.3 2003/11/18 14:04:05 kirk
922 More patches from blues@ds.pg.gda.pl
923
924 Revision 1.2 2003/11/18 04:02:21 kirk
925 Patch from blues@ds.pg.gda.pl
926
927 Revision 1.1 2003/11/03 04:49:18 kirk
928 Added postfix filter from Sven Conrad <sconrad@receptec.net>
929
930 Revision 1.1 2002/03/29 15:32:14 kirk
931 Added some filters found in RH's release
932
933 Revision ??? 2000/07/12 Simon Liddington <sjl@zepler.org>
934 converted from sendmail to postfix Sven Conrad <scon@gmx.net>
935 added unknown users, relay denials
936
937 Revision 1.1 2003/03/21 21:10 sven
938 Initial revision
939 filters all postfix/<process> messages
0
1 GNU GENERAL PUBLIC LICENSE
2 Version 2, June 1991
3
4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 Everyone is permitted to copy and distribute verbatim copies
7 of this license document, but changing it is not allowed.
8
9 Preamble
10
11 The licenses for most software are designed to take away your
12 freedom to share and change it. By contrast, the GNU General Public
13 License is intended to guarantee your freedom to share and change free
14 software--to make sure the software is free for all its users. This
15 General Public License applies to most of the Free Software
16 Foundation's software and to any other program whose authors commit to
17 using it. (Some other Free Software Foundation software is covered by
18 the GNU Lesser General Public License instead.) You can apply it to
19 your programs, too.
20
21 When we speak of free software, we are referring to freedom, not
22 price. Our General Public Licenses are designed to make sure that you
23 have the freedom to distribute copies of free software (and charge for
24 this service if you wish), that you receive source code or can get it
25 if you want it, that you can change the software or use pieces of it
26 in new free programs; and that you know you can do these things.
27
28 To protect your rights, we need to make restrictions that forbid
29 anyone to deny you these rights or to ask you to surrender the rights.
30 These restrictions translate to certain responsibilities for you if you
31 distribute copies of the software, or if you modify it.
32
33 For example, if you distribute copies of such a program, whether
34 gratis or for a fee, you must give the recipients all the rights that
35 you have. You must make sure that they, too, receive or can get the
36 source code. And you must show them these terms so they know their
37 rights.
38
39 We protect your rights with two steps: (1) copyright the software, and
40 (2) offer you this license which gives you legal permission to copy,
41 distribute and/or modify the software.
42
43 Also, for each author's protection and ours, we want to make certain
44 that everyone understands that there is no warranty for this free
45 software. If the software is modified by someone else and passed on, we
46 want its recipients to know that what they have is not the original, so
47 that any problems introduced by others will not reflect on the original
48 authors' reputations.
49
50 Finally, any free program is threatened constantly by software
51 patents. We wish to avoid the danger that redistributors of a free
52 program will individually obtain patent licenses, in effect making the
53 program proprietary. To prevent this, we have made it clear that any
54 patent must be licensed for everyone's free use or not licensed at all.
55
56 The precise terms and conditions for copying, distribution and
57 modification follow.
58
59 GNU GENERAL PUBLIC LICENSE
60 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
62 0. This License applies to any program or other work which contains
63 a notice placed by the copyright holder saying it may be distributed
64 under the terms of this General Public License. The "Program", below,
65 refers to any such program or work, and a "work based on the Program"
66 means either the Program or any derivative work under copyright law:
67 that is to say, a work containing the Program or a portion of it,
68 either verbatim or with modifications and/or translated into another
69 language. (Hereinafter, translation is included without limitation in
70 the term "modification".) Each licensee is addressed as "you".
71
72 Activities other than copying, distribution and modification are not
73 covered by this License; they are outside its scope. The act of
74 running the Program is not restricted, and the output from the Program
75 is covered only if its contents constitute a work based on the
76 Program (independent of having been made by running the Program).
77 Whether that is true depends on what the Program does.
78
79 1. You may copy and distribute verbatim copies of the Program's
80 source code as you receive it, in any medium, provided that you
81 conspicuously and appropriately publish on each copy an appropriate
82 copyright notice and disclaimer of warranty; keep intact all the
83 notices that refer to this License and to the absence of any warranty;
84 and give any other recipients of the Program a copy of this License
85 along with the Program.
86
87 You may charge a fee for the physical act of transferring a copy, and
88 you may at your option offer warranty protection in exchange for a fee.
89
90 2. You may modify your copy or copies of the Program or any portion
91 of it, thus forming a work based on the Program, and copy and
92 distribute such modifications or work under the terms of Section 1
93 above, provided that you also meet all of these conditions:
94
95 a) You must cause the modified files to carry prominent notices
96 stating that you changed the files and the date of any change.
97
98 b) You must cause any work that you distribute or publish, that in
99 whole or in part contains or is derived from the Program or any
100 part thereof, to be licensed as a whole at no charge to all third
101 parties under the terms of this License.
102
103 c) If the modified program normally reads commands interactively
104 when run, you must cause it, when started running for such
105 interactive use in the most ordinary way, to print or display an
106 announcement including an appropriate copyright notice and a
107 notice that there is no warranty (or else, saying that you provide
108 a warranty) and that users may redistribute the program under
109 these conditions, and telling the user how to view a copy of this
110 License. (Exception: if the Program itself is interactive but
111 does not normally print such an announcement, your work based on
112 the Program is not required to print an announcement.)
113
114 These requirements apply to the modified work as a whole. If
115 identifiable sections of that work are not derived from the Program,
116 and can be reasonably considered independent and separate works in
117 themselves, then this License, and its terms, do not apply to those
118 sections when you distribute them as separate works. But when you
119 distribute the same sections as part of a whole which is a work based
120 on the Program, the distribution of the whole must be on the terms of
121 this License, whose permissions for other licensees extend to the
122 entire whole, and thus to each and every part regardless of who wrote it.
123
124 Thus, it is not the intent of this section to claim rights or contest
125 your rights to work written entirely by you; rather, the intent is to
126 exercise the right to control the distribution of derivative or
127 collective works based on the Program.
128
129 In addition, mere aggregation of another work not based on the Program
130 with the Program (or with a work based on the Program) on a volume of
131 a storage or distribution medium does not bring the other work under
132 the scope of this License.
133
134 3. You may copy and distribute the Program (or a work based on it,
135 under Section 2) in object code or executable form under the terms of
136 Sections 1 and 2 above provided that you also do one of the following:
137
138 a) Accompany it with the complete corresponding machine-readable
139 source code, which must be distributed under the terms of Sections
140 1 and 2 above on a medium customarily used for software interchange; or,
141
142 b) Accompany it with a written offer, valid for at least three
143 years, to give any third party, for a charge no more than your
144 cost of physically performing source distribution, a complete
145 machine-readable copy of the corresponding source code, to be
146 distributed under the terms of Sections 1 and 2 above on a medium
147 customarily used for software interchange; or,
148
149 c) Accompany it with the information you received as to the offer
150 to distribute corresponding source code. (This alternative is
151 allowed only for noncommercial distribution and only if you
152 received the program in object code or executable form with such
153 an offer, in accord with Subsection b above.)
154
155 The source code for a work means the preferred form of the work for
156 making modifications to it. For an executable work, complete source
157 code means all the source code for all modules it contains, plus any
158 associated interface definition files, plus the scripts used to
159 control compilation and installation of the executable. However, as a
160 special exception, the source code distributed need not include
161 anything that is normally distributed (in either source or binary
162 form) with the major components (compiler, kernel, and so on) of the
163 operating system on which the executable runs, unless that component
164 itself accompanies the executable.
165
166 If distribution of executable or object code is made by offering
167 access to copy from a designated place, then offering equivalent
168 access to copy the source code from the same place counts as
169 distribution of the source code, even though third parties are not
170 compelled to copy the source along with the object code.
171
172 4. You may not copy, modify, sublicense, or distribute the Program
173 except as expressly provided under this License. Any attempt
174 otherwise to copy, modify, sublicense or distribute the Program is
175 void, and will automatically terminate your rights under this License.
176 However, parties who have received copies, or rights, from you under
177 this License will not have their licenses terminated so long as such
178 parties remain in full compliance.
179
180 5. You are not required to accept this License, since you have not
181 signed it. However, nothing else grants you permission to modify or
182 distribute the Program or its derivative works. These actions are
183 prohibited by law if you do not accept this License. Therefore, by
184 modifying or distributing the Program (or any work based on the
185 Program), you indicate your acceptance of this License to do so, and
186 all its terms and conditions for copying, distributing or modifying
187 the Program or works based on it.
188
189 6. Each time you redistribute the Program (or any work based on the
190 Program), the recipient automatically receives a license from the
191 original licensor to copy, distribute or modify the Program subject to
192 these terms and conditions. You may not impose any further
193 restrictions on the recipients' exercise of the rights granted herein.
194 You are not responsible for enforcing compliance by third parties to
195 this License.
196
197 7. If, as a consequence of a court judgment or allegation of patent
198 infringement or for any other reason (not limited to patent issues),
199 conditions are imposed on you (whether by court order, agreement or
200 otherwise) that contradict the conditions of this License, they do not
201 excuse you from the conditions of this License. If you cannot
202 distribute so as to satisfy simultaneously your obligations under this
203 License and any other pertinent obligations, then as a consequence you
204 may not distribute the Program at all. For example, if a patent
205 license would not permit royalty-free redistribution of the Program by
206 all those who receive copies directly or indirectly through you, then
207 the only way you could satisfy both it and this License would be to
208 refrain entirely from distribution of the Program.
209
210 If any portion of this section is held invalid or unenforceable under
211 any particular circumstance, the balance of the section is intended to
212 apply and the section as a whole is intended to apply in other
213 circumstances.
214
215 It is not the purpose of this section to induce you to infringe any
216 patents or other property right claims or to contest validity of any
217 such claims; this section has the sole purpose of protecting the
218 integrity of the free software distribution system, which is
219 implemented by public license practices. Many people have made
220 generous contributions to the wide range of software distributed
221 through that system in reliance on consistent application of that
222 system; it is up to the author/donor to decide if he or she is willing
223 to distribute software through any other system and a licensee cannot
224 impose that choice.
225
226 This section is intended to make thoroughly clear what is believed to
227 be a consequence of the rest of this License.
228
229 8. If the distribution and/or use of the Program is restricted in
230 certain countries either by patents or by copyrighted interfaces, the
231 original copyright holder who places the Program under this License
232 may add an explicit geographical distribution limitation excluding
233 those countries, so that distribution is permitted only in or among
234 countries not thus excluded. In such case, this License incorporates
235 the limitation as if written in the body of this License.
236
237 9. The Free Software Foundation may publish revised and/or new versions
238 of the General Public License from time to time. Such new versions will
239 be similar in spirit to the present version, but may differ in detail to
240 address new problems or concerns.
241
242 Each version is given a distinguishing version number. If the Program
243 specifies a version number of this License which applies to it and "any
244 later version", you have the option of following the terms and conditions
245 either of that version or of any later version published by the Free
246 Software Foundation. If the Program does not specify a version number of
247 this License, you may choose any version ever published by the Free Software
248 Foundation.
249
250 10. If you wish to incorporate parts of the Program into other free
251 programs whose distribution conditions are different, write to the author
252 to ask for permission. For software which is copyrighted by the Free
253 Software Foundation, write to the Free Software Foundation; we sometimes
254 make exceptions for this. Our decision will be guided by the two goals
255 of preserving the free status of all derivatives of our free software and
256 of promoting the sharing and reuse of software generally.
257
258 NO WARRANTY
259
260 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 REPAIR OR CORRECTION.
269
270 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 POSSIBILITY OF SUCH DAMAGES.
279
280 END OF TERMS AND CONDITIONS
281
282 How to Apply These Terms to Your New Programs
283
284 If you develop a new program, and you want it to be of the greatest
285 possible use to the public, the best way to achieve this is to make it
286 free software which everyone can redistribute and change under these terms.
287
288 To do so, attach the following notices to the program. It is safest
289 to attach them to the start of each source file to most effectively
290 convey the exclusion of warranty; and each file should have at least
291 the "copyright" line and a pointer to where the full notice is found.
292
293 <one line to give the program's name and a brief idea of what it does.>
294 Copyright (C) <year> <name of author>
295
296 This program is free software; you can redistribute it and/or modify
297 it under the terms of the GNU General Public License as published by
298 the Free Software Foundation; either version 2 of the License, or
299 (at your option) any later version.
300
301 This program is distributed in the hope that it will be useful,
302 but WITHOUT ANY WARRANTY; without even the implied warranty of
303 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 GNU General Public License for more details.
305
306 You should have received a copy of the GNU General Public License along
307 with this program; if not, write to the Free Software Foundation, Inc.,
308 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
310 Also add information on how to contact you by electronic and paper mail.
311
312 If the program is interactive, make it output a short notice like this
313 when it starts in an interactive mode:
314
315 Gnomovision version 69, Copyright (C) year name of author
316 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 This is free software, and you are welcome to redistribute it
318 under certain conditions; type `show c' for details.
319
320 The hypothetical commands `show w' and `show c' should show the appropriate
321 parts of the General Public License. Of course, the commands you use may
322 be called something other than `show w' and `show c'; they could even be
323 mouse-clicks or menu items--whatever suits your program.
324
325 You should also get your employer (if you work as a programmer) or your
326 school, if any, to sign a "copyright disclaimer" for the program, if
327 necessary. Here is a sample; alter the names:
328
329 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
332 <signature of Ty Coon>, 1 April 1989
333 Ty Coon, President of Vice
334
335 This General Public License does not permit incorporating your program into
336 proprietary programs. If your program is a subroutine library, you may
337 consider it more useful to permit linking proprietary applications with the
338 library. If this is what you want to do, use the GNU Lesser General
339 Public License instead of this License.
0 program = postfix-logwatch
1 program_logwatch = postfix
2
3 src = $(program) \
4 $(program).conf \
5 $(program).conf-topn \
6 $(program).1 \
7 $(program).1.html \
8 Changes Bugs Makefile README LICENSE
9
10 prefix_logwatch = /etc/logwatch
11 prefix_standalone = /usr/local
12
13
14 scriptdir_logwatch = $(prefix_logwatch)/scripts/services
15 confdir_logwatch = $(prefix_logwatch)/conf/services
16
17 scriptdir_standalone = $(prefix_standalone)/bin
18 confdir_standalone = $(prefix_standalone)/etc
19 mandir_standalone = $(prefix_standalone)/man/man1
20
21 INSTALL = /usr/bin/install -c
22
23 install:
24 @echo 'Run "make install-logwatch" to install as a logwatch filter'
25 @echo 'Run "make install-standalone" to install as a standalone utility'
26 @echo 'Run "make install-all" to install both'
27
28 install-logwatch:
29 $(INSTALL) -d -m 0755 $(DESTDIR)$(scriptdir_logwatch) $(DESTDIR)$(confdir_logwatch) || exit 1;
30 $(INSTALL) -m 0644 $(program) $(DESTDIR)$(scriptdir_logwatch)/$(program_logwatch) || exit 1;
31 $(INSTALL) -m 0644 $(program).conf $(DESTDIR)$(confdir_logwatch)/$(program_logwatch).conf || exit 1;
32 # removes taint mode (-T) to run under logwatch
33 perl -e 'while (<>) { $$.==1 and s/\s+-T$$//; print "$$_";}' -i $(DESTDIR)$(scriptdir_logwatch)/$(program_logwatch)
34
35 install-standalone:
36 $(INSTALL) -d -m 0755 $(DESTDIR)$(scriptdir_standalone) $(DESTDIR)$(confdir_standalone) || exit 1;
37 $(INSTALL) -m 0755 $(program) $(DESTDIR)$(scriptdir_standalone)/$(program) || exit 1;
38 $(INSTALL) -m 0644 $(program).conf $(DESTDIR)$(confdir_standalone)/$(program).conf || exit 1;
39 $(INSTALL) -m 0644 $(program).1 $(DESTDIR)$(mandir_standalone)/$(program).1 || exit 1;
40
41 install-all: install-logwatch install-standalone
42
43 uninstall-logwatch:
44 -rm $(DESTDIR)$(scriptdir_logwatch)/$(program_logwatch) $(DESTDIR)$(confdir_logwatch)/$(program_logwatch).conf
45
46 uninstall-standalone:
47 -rm $(DESTDIR)$(scriptdir_standalone)/$(program) $(DESTDIR)$(confdir_standalone)/$(program).conf \
48 $(DESTDIR)$(mandir_standalone)/$(program).1
49
50 uninstall-all: uninstall-logwatch uninstall-standalone
51
52 PKGDIR = /tmp/$(program)-package
53
54 release:
55 ../../tools/build_from_modules $(program) >| $(program);
56 man -m . $(program) | ../../tools/man2html -t 'Man page: $(program)(1)' >| $(program).1.html;
57 vers=`egrep 'Version[ ]*=' $(program) | sed "s/.*'\(.*\)';/\1/"` ; \
58 echo Preparing version $$vers; \
59 rel=$(program)-$$vers ; \
60 $(INSTALL) -o 0 -g 0 -m 0755 -d $(PKGDIR)/$$rel ; \
61 $(INSTALL) -o 0 -g 0 -m 0644 $(src) $(PKGDIR)/$$rel ; \
62 cd $(PKGDIR) && \
63 tar -czvf $${rel}.tgz --group=0 --owner=0 $${rel} ; \
64 md5sum $${rel}.tgz > $(PKGDIR)/$${rel}.tgz.md5 ; \
65 chmod 644 $(PKGDIR)/$${rel}.tgz $(PKGDIR)/$${rel}.tgz.md5 ; \
66 cd $(PKGDIR) && rm -f $(program) $(program).tgz $(program).tgz.md5 ; \
67 ln -s $${rel} $(program) ; \
68 ln -s $${rel}.tgz $(program).tgz ; \
69 ln -s $${rel}.tgz.md5 $(program).tgz.md5
70
71 program:
72 ../../tools/build_from_modules $(program) >| $(program);
0 ##### Overview
1
2 The postfix-logwatch(1) utility is a Postfix MTA log parser, that pro-
3 duces summaries, details, and statistics regarding the operation of
4 Postfix.
5
6 This utility can be used as a standalone program, or as a Logwatch fil-
7 ter module to produce Postfix summary and detailed reports from within
8 Logwatch.
9
10 A key feature of postfix-logwatch is its ability to produce a very wide
11 range of reports with data grouped and sorted as much as possible to
12 reduce noise and highlight patterns. Brief summary reports provide a
13 quick overview of general Postfix operations and message delivery,
14 calling out warnings that may require attention. Detailed reports pro-
15 vide easy to scan, hierarchically-arranged and organized information,
16 with as much or little detail as desired.
17
18 ##### Installation: standalone
19
20 The included Makefile will install the postfix-logwatch utility
21 for you. Run:
22
23 make install-standalone
24
25 to install the utility and its configuration file. Installation
26 will default to /usr/local/bin and /usr/local/etc, respectively.
27
28 ##### Installation: logwatch
29
30 To use postfix-logwatch as a logwatch script, the files:
31
32 postfix-logwatch
33 postfix-logwatch.conf
34
35 will need to be installed into one of logwatch's known directories.
36 To avoid overwriting your existing default logwatch filter files,
37 the enclosed files can be installed into the global logwatch
38 installation directory, which is typically:
39
40 /etc/logwatch
41
42 The included Makefile can be used to install the files into
43 /etc/logwatch for you. To install the filter into an existing
44 logwatch installation, run:
45
46 make install-logwatch
47
48 NOTE: the files postfix-logwatch and postfix-logwatch.conf must be
49 renamed (by removing the "-logwatch" suffix), for logwatch to
50 function correctly. The Makefile takes care of this. The
51 Makefile also disables perl's taint mode (-T) when running under
52 logwatch. If you install the files manually, be sure to remove
53 the -T from the first line of the postfix filter.
54
55 For non-standard installations, you will need to determine your
56 global logwatch directory, and define "prefix" in the attached
57 Makefile.
58
59 Alternatively, you can manually copy the files to their proper
60 locations:
61
62 cp postfix-logwatch /etc/logwatch/scripts/services/postfix
63 cp postfix-logwatch.conf /etc/logwatch/conf/services/postfix.conf
64 [ remove -T from line 1 of /etc/logwatch/scripts/services/postfix ]
65
66 Optional:
67 cp postfix-logwatch.1 /usr/local/man/man1/postfix-logwatch.1
68
69 ##### Usage
70
71 The postfix-logwatch utility is used standalone as:
72
73 postfix-logwatch /path/to/maillog
74
75 For brief help:
76
77 postfix-logwatch --help
78
79 To use within logwatch:
80
81 logwatch --service postfix ...
82
83 See the postfix-logwatch(1) man page for complete details, and
84 see comments in the postfix-logwatch.conf file for additional
85 information.
86
87 Mike Cappella
88 lists-logreporters (at) cappella.us
89 last updated: 05/07/2008
0 #!/usr/bin/perl -T
1
2 ##########################################################################
3 # Postfix-logwatch: written and maintained by:
4 #
5 # Mike "MrC" Cappella <lists-general (at) cappella (dot) us>
6 # http://logreporters.sourceforge.net/
7 #
8 # Please send all comments, suggestions, bug reports regarding this
9 # program/module to the email address above. I will respond as quickly
10 # as possible. [MrC]
11 #
12 # Questions regarding the logwatch program itself should be directed to
13 # the logwatch mailing list (logwatch@logwatch.org).
14 #
15 # All work since Dec 12, 2006 (logwatch CVS revision 1.28)
16 # Copyright (C) 2006-2010 Mike Cappella
17 #
18 # This program is free software; you can redistribute it and/or
19 # modify it under the terms of the GNU General Public License
20 # as published by the Free Software Foundation; either version 2
21 # of the License, or (at your option) any later version.
22 #
23 # This program is distributed in the hope that it will be useful,
24 # but WITHOUT ANY WARRANTY; without even the implied warranty of
25 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 # GNU General Public License for more details.
27 #
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to the Free Software
30 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31
32 ##########################################################################
33 # The original postfix logwatch filter was written by
34 # Kenneth Porter, and has had many contributors over the years.
35 #
36 # CVS log removed: see Changes file for amavis-logwatch at
37 # http://logreporters.sourceforge.net/
38 # or included with the standalone postfix-logwatch distribution
39 ##########################################################################
40
41 ##########################################################################
42 #
43 # Test data included via inline comments starting with "#TD"
44 #
45
46 #use Devel::Size qw(size total_size);
47
48 package Logreporters;
49 use 5.008;
50 use strict;
51 use warnings;
52 no warnings "uninitialized";
53 use re 'taint';
54
55 our $Version = '1.39.05';
56 our $progname_prefix = 'postfix';
57
58 # Specifies the default configuration file for use in standalone mode.
59 my $config_file = "/usr/local/etc/${progname_prefix}-logwatch.conf";
60
61
62 #MODULE: ../Logreporters/Utils.pm
63 package Logreporters::Utils;
64
65 use 5.008;
66 use strict;
67 use re 'taint';
68 use warnings;
69
70 BEGIN {
71 use Exporter ();
72 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
73 $VERSION = '1.003';
74 @ISA = qw(Exporter);
75 @EXPORT = qw(&formathost &get_percentiles &get_percentiles2 &get_frequencies &commify &unitize
76 &get_usable_sectvars &add_section &begin_section_group &end_section_group
77 &get_version &unique_list);
78 @EXPORT_OK = qw(&gen_test_log);
79 }
80
81 use subs qw (@EXPORT @EXPORT_OK);
82
83
84 # Formats IP and hostname for even column spacing
85 #
86 sub formathost($ $) {
87 # $_[0] : hostip
88 # $_[1] : hostname;
89
90 if (! $Logreporters::Config::Opts{'unknown'} and $_[1] eq 'unknown') {
91 return $_[0];
92 }
93
94 return sprintf "%-$Logreporters::Config::Opts{'ipaddr_width'}s %s",
95 $_[0] eq '' ? '*unknown' : $_[0],
96 $_[1] eq '' ? '*unknown' : lc $_[1];
97 }
98
99 # Add a new section to the end of a section table
100 #
101 sub add_section($$$$$;$) {
102 my $sref = shift;
103 die "Improperly specified Section entry: $_[0]" if !defined $_[3];
104
105 my $entry = {
106 CLASS => 'DATA',
107 NAME => $_[0],
108 DETAIL => $_[1],
109 FMT => $_[2],
110 TITLE => $_[3],
111 };
112 $entry->{'DIVISOR'} = $_[4] if defined $_[4];
113 push @$sref, $entry;
114 }
115
116 {
117 my $group_level = 0;
118
119 # Begin a new section group. Groups can nest.
120 #
121 sub begin_section_group($;@) {
122 my $sref = shift;
123 my $group_name = shift;
124 my $entry = {
125 CLASS => 'GROUP_BEGIN',
126 NAME => $group_name,
127 LEVEL => ++$group_level,
128 HEADERS => [ @_ ],
129 };
130 push @$sref, $entry;
131 }
132
133 # Ends a section group.
134 #
135 sub end_section_group($;@) {
136 my $sref = shift;
137 my $group_name = shift;
138 my $entry = {
139 CLASS => 'GROUP_END',
140 NAME => $group_name,
141 LEVEL => --$group_level,
142 FOOTERS => [ @_ ],
143 };
144 push @$sref, $entry;
145 }
146 }
147
148 # Generate and return a list of section table entries or
149 # limiter key names, skipping any formatting entries.
150 # If 'namesonly' is set, limiter key names are returned,
151 # otherwise an array of section array records is returned.
152 sub get_usable_sectvars(\@ $) {
153 my ($sectref,$namesonly) = @_;
154 my (@sect_list, %unique_names);
155
156 foreach my $sref (@$sectref) {
157 #print "get_usable_sectvars: $sref->{NAME}\n";
158 next unless $sref->{CLASS} eq 'DATA';
159 if ($namesonly) {
160 $unique_names{$sref->{NAME}} = 1;
161 }
162 else {
163 push @sect_list, $sref;
164 }
165 }
166 # return list of unique names
167 if ($namesonly) {
168 return keys %unique_names;
169 }
170 return @sect_list;
171 }
172
173 # Print program and version info, preceeded by an optional string, and exit.
174 #
175 sub get_version() {
176
177 print STDOUT "@_\n" if ($_[0]);
178 print STDOUT "$Logreporters::progname: $Logreporters::Version\n";
179 exit 0;
180 }
181
182
183 # Returns a list of percentile values given a
184 # sorted array of numeric values. Uses the formula:
185 #
186 # r = 1 + (p(n-1)/100) = i + d (Excel method)
187 #
188 # r = rank
189 # p = desired percentile
190 # n = number of items
191 # i = integer part
192 # d = decimal part
193 #
194 # Arg1 is an array ref to the sorted series
195 # Arg2 is a list of percentiles to use
196
197 sub get_percentiles(\@ @) {
198 my ($aref,@plist) = @_;
199 my ($n, $last, $r, $d, $i, @vals, $Yp);
200
201 $last = $#$aref;
202 $n = $last + 1;
203 #printf "%6d" x $n . "\n", @{$aref};
204
205 #printf "n: %4d, last: %d\n", $n, $last;
206 foreach my $p (@plist) {
207 $r = 1 + ($p * ($n - 1) / 100.0);
208 $i = int ($r); # integer part
209 # domain: $i = 1 .. n
210 if ($i == $n) {
211 $Yp = $aref->[$last];
212 }
213 elsif ($i == 0) {
214 $Yp = $aref->[0];
215 print "CAN'T HAPPEN: $Yp\n";
216 }
217 else {
218 $d = $r - $i; # decimal part
219 #p = Y[i] + d(Y[i+1] - Y[i]), but since we're 0 based, use i=i-1
220 $Yp = $aref->[$i-1] + ($d * ($aref->[$i] - $aref->[$i-1]));
221 }
222 #printf "\np(%6.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d", $p, $r, $i, $d, $Yp;
223 push @vals, $Yp;
224 }
225
226 return @vals;
227 }
228
229 sub get_num_scores($) {
230 my $scoretab_r = shift;
231
232 my $totalscores = 0;
233
234 for (my $i = 0; $i < @$scoretab_r; $i += 2) {
235 $totalscores += $scoretab_r->[$i+1]
236 }
237
238 return $totalscores;
239 }
240
241 # scoretab
242 #
243 # (score1, n1), (score2, n2), ... (scoreN, nN)
244 # $i $i+1
245 #
246 # scores are 0 based (0 = 1st score)
247 sub get_nth_score($ $) {
248 my ($scoretab_r, $n) = @_;
249
250 my $i = 0;
251 my $n_cur_scores = 0;
252 #print "Byscore (", .5 * @$scoretab_r, "): "; for (my $i = 0; $i < $#$scoretab_r / 2; $i++) { printf "%9s (%d) ", $scoretab_r->[$i], $scoretab_r->[$i+1]; } ; print "\n";
253
254 while ($i < $#$scoretab_r) {
255 #print "Samples_seen: $n_cur_scores\n";
256 $n_cur_scores += $scoretab_r->[$i+1];
257 if ($n_cur_scores >= $n) {
258 #printf "range: %s %s %s\n", $i >= 2 ? $scoretab_r->[$i - 2] : '<begin>', $scoretab_r->[$i], $i+2 > $#$scoretab_r ? '<end>' : $scoretab_r->[$i + 2];
259 #printf "n: $n, i: %8d, n_cur_scores: %8d, score: %d x %d hits\n", $i, $n_cur_scores, $scoretab_r->[$i], $scoretab_r->[$i+1];
260 return $scoretab_r->[$i];
261 }
262
263 $i += 2;
264 }
265 print "returning last score $scoretab_r->[$i]\n";
266 return $scoretab_r->[$i];
267 }
268
269 sub get_percentiles2(\@ @) {
270 my ($scoretab_r, @plist) = @_;
271 my ($n, $last, $r, $d, $i, @vals, $Yp);
272
273 #$last = $#$scoretab_r - 1;
274 $n = get_num_scores($scoretab_r);
275 #printf "\n%6d" x $n . "\n", @{$scoretab_r};
276
277 #printf "\n\tn: %4d, @$scoretab_r\n", $n;
278 foreach my $p (@plist) {
279 ###print "\nPERCENTILE: $p\n";
280 $r = 1 + ($p * ($n - 1) / 100.0);
281 $i = int ($r); # integer part
282 if ($i == $n) {
283 #print "last:\n";
284 #$Yp = $scoretab_r->[$last];
285 $Yp = get_nth_score($scoretab_r, $n);
286 }
287 elsif ($i == 0) {
288 #$Yp = $scoretab_r->[0];
289 print "1st: CAN'T HAPPEN\n";
290 $Yp = get_nth_score($scoretab_r, 1);
291 }
292 else {
293 $d = $r - $i; # decimal part
294 #p = Y[i] + d(Y[i+1] - Y[i]), but since we're 0 based, use i=i-1
295 my $ithvalprev = get_nth_score($scoretab_r, $i);
296 my $ithval = get_nth_score($scoretab_r, $i+1);
297 $Yp = $ithvalprev + ($d * ($ithval - $ithvalprev));
298 }
299 #printf "p(%6.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d\n", $p, $r, $i, $d, $Yp;
300 push @vals, $Yp;
301 }
302
303 return @vals;
304 }
305
306
307
308 # Returns a list of frequency distributions given an incrementally sorted
309 # set of sorted scores, and an incrementally sorted list of buckets
310 #
311 # Arg1 is an array ref to the sorted series
312 # Arg2 is a list of frequency buckets to use
313 sub get_frequencies(\@ @) {
314 my ($aref,@blist) = @_;
315
316 my @vals = ( 0 ) x (@blist);
317 my @sorted_blist = sort { $a <=> $b } @blist;
318 my $bucket_index = 0;
319
320 OUTER: foreach my $score (@$aref) {
321 #print "Score: $score\n";
322 for my $i ($bucket_index .. @sorted_blist - 1) {
323 #print "\tTrying Bucket[$i]: $sorted_blist[$i]\n";
324 if ($score > $sorted_blist[$i]) {
325 $bucket_index++;
326 }
327 else {
328 #printf "\t\tinto Bucket[%d]\n", $bucket_index;
329 $vals[$bucket_index]++;
330 next OUTER;
331 }
332 }
333 #printf "\t\tinto Bucket[%d]\n", $bucket_index - 1;
334 $vals[$bucket_index - 1]++;
335 }
336
337 return @vals;
338 }
339
340 # Inserts commas in numbers for easier readability
341 #
342 sub commify ($) {
343 return undef if ! defined ($_[0]);
344
345 my $text = reverse $_[0];
346 $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
347 return scalar reverse $text;
348 }
349
350 # Unitize a number, and return appropriate printf formatting string
351 #
352 sub unitize($ $) {
353 my ($num, $fmt) = @_;
354 my $kilobyte = 2**10;
355 my $megabyte = 2**20;
356 my $gigabyte = 2**30;
357 my $terabyte = 2**40;
358
359 if ($num >= $terabyte) {
360 $num /= $terabyte;
361 $fmt .= '.3fT';
362 } elsif ($num >= $gigabyte) {
363 $num /= $gigabyte;
364 $fmt .= '.3fG';
365 } elsif ($num >= $megabyte) {
366 $num /= $megabyte;
367 $fmt .= '.3fM';
368 } elsif ($num >= $kilobyte) {
369 $num /= $kilobyte;
370 $fmt .= '.3fK';
371 } else {
372 $fmt .= 'd ';
373 }
374
375 return ($num, $fmt);
376 }
377
378 # Returns a sublist of the supplied list of elements in an unchanged order,
379 # where only the first occurrence of each defined element is retained
380 # and duplicates removed
381 #
382 # Borrowed from amavis 2.6.2
383 #
384 sub unique_list(@) {
385 my ($r) = @_ == 1 && ref($_[0]) ? $_[0] : \@_; # accept list, or a list ref
386 my (%seen);
387 my (@unique) = grep { defined($_) && !$seen{$_}++ } @$r;
388
389 return @unique;
390 }
391
392 # Generate a test maillog file from the '#TD' test data lines
393 # The test data file is placed in /var/tmp/maillog.autogen
394 #
395 # arg1: "postfix" or "amavis"
396 # arg2: path to postfix-logwatch or amavis-logwatch from which to read '#TD' data
397 #
398 # Postfix TD syntax:
399 # TD<service><QID>(<count>) log entry
400 #
401 sub gen_test_log($) {
402 my $scriptpath = shift;
403
404 my $toolname = $Logreporters::progname_prefix;
405 my $datafile = "/var/tmp/maillog-${toolname}.autogen";
406
407 die "gen_test_log: invalid toolname $toolname" if ($toolname !~ /^(postfix|amavis)$/);
408
409 eval {
410 require Sys::Hostname;
411 require Fcntl;
412 } or die "Unable to create test data file: required module(s) not found\n$@";
413
414 my $syslogtime = localtime;
415 $syslogtime =~ s/^....(.*) \d{4}$/$1/;
416
417 my ($hostname) = split /\./, Sys::Hostname::hostname();
418
419 # # avoid -T issues
420 # delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
421
422 my $flags = &Fcntl::O_CREAT|&Fcntl::O_WRONLY|&Fcntl::O_TRUNC;
423 sysopen(FH, $datafile, $flags) or die "Can't create test data file: $!";
424 print "Generating test log data file from $scriptpath: $datafile\n";
425
426 my $id;
427 @ARGV = ($scriptpath);
428 if ($toolname eq 'postfix') {
429 my %services = (
430 DEF => 'smtpd',
431 bQ => 'bounce',
432 cN => 'cleanup',
433 cQ => 'cleanup',
434 lQ => 'local',
435 m => 'master',
436 p => 'pickup',
437 pQ => 'pickup',
438 ppQ => 'pipe',
439 pfw => 'postfwd',
440 pg => 'postgrey',
441 pgQ => 'postgrey',
442 ps => 'postsuper',
443 qQ => 'qmgr',
444 s => 'smtp',
445 sQ => 'smtp',
446 sd => 'smtpd',
447 sdN => 'smtpd',
448 sdQ => 'smtpd',
449 spf => 'policy-spf',
450 vN => 'virtual',
451 vQ => 'virtual',
452 );
453 $id = 'postfix/smtp[12345]';
454
455 while (<>) {
456 if (/^\s*#TD([a-zA-Z]*[NQ]?)(\d+)?(?:\(([^)]+)\))? (.*)$/) {
457 my ($service,$count,$qid,$line) = ($1, $2, $3, $4);
458
459 #print "SERVICE: %s, QID: %s, COUNT: %s, line: %s\n", $service, $qid, $count, $line;
460
461 if ($service eq '') {
462 $service = 'DEF';
463 }
464 die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service});
465
466 $id = $services{$service} . '[123]';
467 $id = 'postfix/' . $id unless $services{$service} eq 'postgrey';
468 #print "searching for service: \"$service\"\n\tFound $id\n";
469 if ($service =~ /N$/) { $id .= ': NOQUEUE'; }
470 elsif ($service =~ /Q$/) { $id .= $qid ? $qid : ': DEADBEEF'; }
471
472 $line =~ s/ +/ /g;
473 $line =~ s/^ //g;
474 #print "$syslogtime $hostname $id: \"$line\"\n" x ($count ? $count : 1);
475 print FH "$syslogtime $hostname $id: $line\n" x ($count ? $count : 1);
476 }
477 }
478 }
479 else { #amavis
480 my %services = (
481 DEF => 'amavis',
482 dcc => 'dccproc',
483 );
484 while (<>) {
485 if (/^\s*#TD([a-z]*)(\d+)? (.*)$/) {
486 my ($service,$count,$line) = ($1, $2, $3);
487 if ($service eq '') {
488 $service = 'DEF';
489 }
490 die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service});
491 $id = $services{$service} . '[123]:';
492 if ($services{$service} eq 'amavis') {
493 $id .= ' (9999-99)';
494 }
495 print FH "$syslogtime $hostname $id $line\n" x ($count ? $count : 1)
496 }
497 }
498 }
499
500 close FH or die "Can't close $datafile: $!";
501 }
502
503 1;
504
505 #MODULE: ../Logreporters/Config.pm
506 package Logreporters::Config;
507
508 use 5.008;
509 use strict;
510 use re 'taint';
511 use warnings;
512
513
514 BEGIN {
515 use Exporter ();
516 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
517 $VERSION = '1.002';
518 @ISA = qw(Exporter);
519 @EXPORT = qw(&init_run_mode &add_option &get_options &init_cmdline &get_vars_from_file
520 &process_limiters &process_debug_opts &init_getopts_table_common &zero_opts
521 @Optspec %Opts %Configvars @Limiters %line_styles $fw1 $fw2 $sep1 $sep2
522 &D_CONFIG &D_ARGS &D_VARS &D_TREE &D_SECT &D_UNMATCHED &D_TEST &D_ALL
523 );
524 }
525
526 use subs @EXPORT;
527
528 our @Optspec = (); # options table used by Getopts
529
530 our %Opts = (); # program-wide options
531 our %Configvars = (); # configuration file variables
532 our @Limiters;
533
534 # Report separator characters and widths
535 our ($fw1,$fw2) = (22, 10);
536 our ($sep1,$sep2) = ('=', '-');
537
538 use Getopt::Long;
539
540
541 BEGIN {
542 import Logreporters::Utils qw(&get_usable_sectvars);
543 }
544
545 our %line_styles = (
546 truncate => 0,
547 wrap => 1,
548 full => 2,
549 );
550
551 sub init_run_mode($);
552 sub confighash_to_cmdline(\%);
553 sub get_vars_from_file(\% $);
554 sub process_limiters(\@);
555 sub add_option(@);
556 sub get_options($);
557 sub init_getopts_table_common(@);
558 sub set_supplemental_reports($$);
559 # debug constants
560 sub D_CONFIG () { 1<<0 }
561 sub D_ARGS () { 1<<1 }
562 sub D_VARS () { 1<<2 }
563 sub D_TREE () { 1<<3 }
564 sub D_SECT () { 1<<4 }
565 sub D_UNMATCHED () { 1<<5 }
566
567 sub D_TEST () { 1<<30 }
568 sub D_ALL () { 1<<31 }
569
570 my %debug_words = (
571 config => D_CONFIG,
572 args => D_ARGS,
573 vars => D_VARS,
574 tree => D_TREE,
575 sect => D_SECT,
576 unmatched => D_UNMATCHED,
577
578 test => D_TEST,
579 all => 0xffffffff,
580 );
581
582 # Clears %Opts hash and initializes basic running mode options in
583 # %Opts hash by setting keys: 'standalone', 'detail', and 'debug'.
584 # Call early.
585 #
586 sub init_run_mode($) {
587 my $config_file = shift;
588 $Opts{'debug'} = 0;
589
590 # Logwatch passes a filter's options via environment variables.
591 # When running standalone (w/out logwatch), use command line options
592 $Opts{'standalone'} = exists ($ENV{LOGWATCH_DETAIL_LEVEL}) ? 0 : 1;
593
594 # Show summary section by default
595 $Opts{'summary'} = 1;
596
597 if ($Opts{'standalone'}) {
598 process_debug_opts($ENV{'LOGREPORTERS_DEBUG'}) if exists ($ENV{'LOGREPORTERS_DEBUG'});
599 }
600 else {
601 $Opts{'detail'} = $ENV{'LOGWATCH_DETAIL_LEVEL'};
602 # XXX
603 #process_debug_opts($ENV{'LOGWATCH_DEBUG'}) if exists ($ENV{'LOGWATCH_DEBUG'});
604 }
605
606 # first process --debug, --help, and --version options
607 add_option ('debug=s', sub { process_debug_opts($_[1]); 1});
608 add_option ('version', sub { &Logreporters::Utils::get_version(); 1;});
609 get_options(1);
610
611 # now process --config_file, so that all config file vars are read first
612 add_option ('config_file|f=s', sub { get_vars_from_file(%Configvars, $_[1]); 1;});
613 get_options(1);
614
615 # if no config file vars were read
616 if ($Opts{'standalone'} and ! keys(%Configvars) and -f $config_file) {
617 print "Using default config file: $config_file\n" if $Opts{'debug'} & D_CONFIG;
618 get_vars_from_file(%Configvars, $config_file);
619 }
620 }
621
622 sub get_options($) {
623 my $pass_through = shift;
624 #$SIG{__WARN__} = sub { print "*** $_[0]*** options error\n" };
625 # ensure we're called after %Opts is initialized
626 die "get_options: program error: %Opts is emtpy" unless exists $Opts{'debug'};
627
628 my $p = new Getopt::Long::Parser;
629
630 if ($pass_through) {
631 $p->configure(qw(pass_through permute));
632 }
633 else {
634 $p->configure(qw(no_pass_through no_permute));
635 }
636 #$p->configure(qw(debug));
637
638 if ($Opts{'debug'} & D_ARGS) {
639 print "\nget_options($pass_through): enter\n";
640 printf "\tARGV(%d): ", scalar @ARGV;
641 print @ARGV, "\n";
642 print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts;
643 }
644
645 if ($p->getoptions(\%Opts, @Optspec) == 0) {
646 print STDERR "Use ${Logreporters::progname} --help for options\n";
647 exit 1;
648 }
649 if ($Opts{'debug'} & D_ARGS) {
650 print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts;
651 printf "\tARGV(%d): ", scalar @ARGV;
652 print @ARGV, "\n";
653 print "get_options: exit\n";
654 }
655 }
656
657 sub add_option(@) {
658 push @Optspec, @_;
659 }
660
661 # untaint string, borrowed from amavisd-new
662 sub untaint($) {
663 no re 'taint';
664
665 my ($str);
666 if (defined($_[0])) {
667 local($1); # avoid Perl taint bug: tainted global $1 propagates taintedness
668 $str = $1 if $_[0] =~ /^(.*)$/;
669 }
670
671 return $str;
672 }
673
674 sub init_getopts_table_common(@) {
675 my @supplemental_reports = @_;
676
677 print "init_getopts_table_common: enter\n" if $Opts{'debug'} & D_ARGS;
678
679 add_option ('help', sub { print STDOUT Logreporters::usage(undef); exit 0 });
680 add_option ('gen_test_log=s', sub { Logreporters::Utils::gen_test_log($_[1]); exit 0; });
681 add_option ('detail=i');
682 add_option ('nodetail', sub {
683 # __none__ will set all limiters to 0 in process_limiters
684 # since they are not known (Sections table is not yet built).
685 push @Limiters, '__none__';
686 # 0 = disable supplemental_reports
687 set_supplemental_reports(0, \@supplemental_reports);
688 });
689 add_option ('max_report_width=i');
690 add_option ('summary!');
691 add_option ('show_summary=i', sub { $Opts{'summary'} = $_[1]; 1; });
692 # untaint ipaddr_width for use w/sprintf() in Perl v5.10
693 add_option ('ipaddr_width=i', sub { $Opts{'ipaddr_width'} = untaint ($_[1]); 1; });
694
695 add_option ('sect_vars!');
696 add_option ('show_sect_vars=i', sub { $Opts{'sect_vars'} = $_[1]; 1; });
697
698 add_option ('syslog_name=s');
699 add_option ('wrap', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
700 add_option ('full', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
701 add_option ('truncate', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; });
702 add_option ('line_style=s', sub {
703 my $style = lc($_[1]);
704 my @list = grep (/^$style/, keys %line_styles);
705 if (! @list) {
706 print STDERR "Invalid line_style argument \"$_[1]\"\n";
707 print STDERR "Option line_style argument must be one of \"wrap\", \"full\", or \"truncate\".\n";
708 print STDERR "Use $Logreporters::progname --help for options\n";
709 exit 1;
710 }
711 $Opts{'line_style'} = $line_styles{lc($list[0])};
712 1;
713 });
714
715 add_option ('limit|l=s', sub {
716 my ($limiter,$lspec) = split(/=/, $_[1]);
717 if (!defined $lspec) {
718 printf STDERR "Limiter \"%s\" requires value (ex. --limit %s=10)\n", $_[1],$_[1];
719 exit 2;
720 }
721 foreach my $val (split(/(?:\s+|\s*,\s*)/, $lspec)) {
722 if ($val !~ /^\d+$/ and
723 $val !~ /^(\d*)\.(\d+)$/ and
724 $val !~ /^::(\d+)$/ and
725 $val !~ /^:(\d+):(\d+)?$/ and
726 $val !~ /^(\d+):(\d+)?:(\d+)?$/)
727 {
728 printf STDERR "Limiter value \"$val\" invalid in \"$limiter=$lspec\"\n";
729 exit 2;
730 }
731 }
732 push @Limiters, lc $_[1];
733 });
734
735 print "init_getopts_table_common: exit\n" if $Opts{'debug'} & D_ARGS;
736 }
737
738 sub get_option_names() {
739 my (@ret, @tmp);
740 foreach (@Optspec) {
741 if (ref($_) eq '') { # process only the option names
742 my $spec = $_;
743 $spec =~ s/=.*$//;
744 $spec =~ s/([^|]+)\!$/$1|no$1/g;
745 @tmp = split /[|]/, $spec;
746 #print "PUSHING: @tmp\n";
747 push @ret, @tmp;
748 }
749 }
750 return @ret;
751 }
752
753 # Set values for the configuration variables passed via hashref.
754 # Variables are of the form ${progname_prefix}_KEYNAME.
755 #
756 # Because logwatch lowercases all config file entries, KEYNAME is
757 # case-insensitive.
758 #
759 sub init_cmdline() {
760 my ($href, $configvar, $value, $var);
761
762 # logwatch passes all config vars via environment variables
763 $href = $Opts{'standalone'} ? \%Configvars : \%ENV;
764
765 # XXX: this is cheeze: need a list of valid limiters, but since
766 # the Sections table is not built yet, we don't know what is
767 # a limiter and what is an option, as there is no distinction in
768 # variable names in the config file (perhaps this should be changed).
769 my @valid_option_names = get_option_names();
770 die "Options table not yet set" if ! scalar @valid_option_names;
771
772 print "confighash_to_cmdline: @valid_option_names\n" if $Opts{'debug'} & D_ARGS;
773 my @cmdline = ();
774 while (($configvar, $value) = each %$href) {
775 if ($configvar =~ s/^${Logreporters::progname_prefix}_//o) {
776 # distinguish level limiters from general options
777 # would be easier if limiters had a unique prefix
778 $configvar = lc $configvar;
779 my $ret = grep (/^$configvar$/i, @valid_option_names);
780 if ($ret == 0) {
781 print "\tLIMITER($ret): $configvar = $value\n" if $Opts{'debug'} & D_ARGS;
782 push @cmdline, '-l', "$configvar" . "=$value";
783 }
784 else {
785 print "\tOPTION($ret): $configvar = $value\n" if $Opts{'debug'} & D_ARGS;
786 unshift @cmdline, $value if defined ($value);
787 unshift @cmdline, "--$configvar";
788 }
789 }
790 }
791 unshift @ARGV, @cmdline;
792 }
793
794 # Obtains the variables from a logwatch-style .conf file, for use
795 # in standalone mode. Returns an ENV-style hash of key/value pairs.
796 #
797 sub get_vars_from_file(\% $) {
798 my ($href, $file) = @_;
799 my ($var, $val);
800
801 print "get_vars_from_file: enter: processing file: $file\n" if $Opts{'debug'} & D_CONFIG;
802
803 my $message = undef;
804 my $ret = stat ($file);
805 if ($ret == 0) { $message = $!; }
806 elsif (! -r _) { $message = "Permission denied"; }
807 elsif ( -d _) { $message = "Is a directory"; }
808 elsif (! -f _) { $message = "Not a regular file"; }
809
810 if ($message) {
811 print STDERR "Configuration file \"$file\": $message\n";
812 exit 2;
813 }
814
815 my $prog = $Logreporters::progname_prefix;
816 open FILE, '<', "$file" or die "unable to open configuration file $file: $!";
817 while (<FILE>) {
818 chomp;
819 next if (/^\s*$/); # ignore all whitespace lines
820 next if (/^\*/); # ignore logwatch's *Service lines
821 next if (/^\s*#/); # ignore comment lines
822 if (/^\s*\$(${prog}_[^=\s]+)\s*=\s*"?([^"]+)"?$/o) {
823 ($var,$val) = ($1,$2);
824 if ($val =~ /^(?:no|false)$/i) { $val = 0; }
825 elsif ($val =~ /^(?:yes|true)$/i) { $val = 1; }
826 elsif ($val eq '') { $var =~ s/${prog}_/${prog}_no/; $val = undef; }
827
828 print "\t\"$var\" => \"$val\"\n" if $Opts{'debug'} & D_CONFIG;
829
830 $href->{$var} = $val;
831 }
832 }
833 close FILE or die "failed to close configuration handle for $file: $!";
834 print "get_vars_from_file: exit\n" if $Opts{'debug'} & D_CONFIG;
835 }
836
837 sub process_limiters(\@) {
838 my ($sectref) = @_;
839
840 my ($limiter, $var, $val, @errors);
841 my @l = get_usable_sectvars(@$sectref, 1);
842
843 if ($Opts{'debug'} & D_VARS) {
844 print "process_limiters: enter\n";
845 print "\tLIMITERS: @Limiters\n";
846 }
847 while ($limiter = shift @Limiters) {
848 my @matched = ();
849
850 printf "\t%-30s ",$limiter if $Opts{'debug'} & D_VARS;
851 # disable all limiters when limiter is __none__: see 'nodetail' cmdline option
852 if ($limiter eq '__none__') {
853 $Opts{$_} = 0 foreach @l;
854 next;
855 }
856
857 ($var,$val) = split /=/, $limiter;
858
859 if ($val eq '') {
860 push @errors, "Limiter \"$var\" requires value (ex. --limit limiter=10)";
861 next;
862 }
863
864 # try exact match first, then abbreviated match next
865 if (scalar (@matched = grep(/^$var$/, @l)) == 1 or scalar (@matched = grep(/^$var/, @l)) == 1) {
866 $limiter = $matched[0]; # unabbreviate limiter
867 print "MATCH: $var: $limiter => $val\n" if $Opts{'debug'} & D_VARS;
868 # XXX move limiters into section hash entry...
869 $Opts{$limiter} = $val;
870 next;
871 }
872 print "matched=", scalar @matched, ": @matched\n" if $Opts{'debug'} & D_VARS;
873
874 push @errors, "Limiter \"$var\" is " . (scalar @matched == 0 ? "invalid" : "ambiguous: @matched");
875 }
876 print "\n" if $Opts{'debug'} & D_VARS;
877
878 if (@errors) {
879 print STDERR "$_\n" foreach @errors;
880 exit 2;
881 }
882
883 # Set the default value of 10 for each section if no limiter exists.
884 # This allows output for each section should there be no configuration
885 # file or missing limiter within the configuration file.
886 foreach (@l) {
887 $Opts{$_} = 10 unless exists $Opts{$_};
888 }
889
890 # Enable collection for each section if a limiter is non-zero.
891 foreach (@l) {
892 #print "L is: $_\n";
893 #print "DETAIL: $Opts{'detail'}, OPTS: $Opts{$_}\n";
894 $Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0;
895 }
896 #print "OPTS: \n"; map { print "$_ => $Opts{$_}\n"} keys %Opts;
897 #print "COLLECTING: \n"; map { print "$_ => $Logreporters::TreeData::Collecting{$_}\n"} keys %Logreporters::TreeData::Collecting;
898 }
899
900 # Enable/disable supplemental reports
901 # arg1: 0=off, 1=on
902 # arg2,...: list of supplemental report keywords
903 sub set_supplemental_reports($$) {
904 my ($onoff,$aref) = @_;
905
906 $Opts{$_} = $onoff foreach (@$aref);
907 }
908
909 sub process_debug_opts($) {
910 my $optstring = shift;
911
912 my @errors = ();
913 foreach (split(/\s*,\s*/, $optstring)) {
914 my $word = lc $_;
915 my @matched = grep (/^$word/, keys %debug_words);
916
917 if (scalar @matched == 1) {
918 $Opts{'debug'} |= $debug_words{$matched[0]};
919 next;
920 }
921
922 if (scalar @matched == 0) {
923 push @errors, "Unknown debug keyword \"$word\"";
924 }
925 else { # > 1
926 push @errors, "Ambiguous debug keyword abbreviation \"$word\": (matches: @matched)";
927 }
928 }
929 if (@errors) {
930 print STDERR "$_\n" foreach @errors;
931 print STDERR "Debug keywords: ", join (' ', sort keys %debug_words), "\n";
932 exit 2;
933 }
934 }
935
936 # Zero the options controlling level specs and those
937 # any others passed via Opts key.
938 #
939 # Zero the options controlling level specs in the
940 # Detailed section, and set all other report options
941 # to disabled. This makes it easy via command line to
942 # disable the entire summary section, and then re-enable
943 # one or more sections for specific reports.
944 #
945 # eg. progname --nodetail --limit forwarded=2
946 #
947 sub zero_opts ($ @) {
948 my $sectref = shift;
949 # remaining args: list of Opts keys to zero
950
951 map { $Opts{$_} = 0; print "zero_opts: $_ => 0\n" if $Opts{'debug'} & D_VARS;} @_;
952 map { $Opts{$_} = 0 } get_usable_sectvars(@$sectref, 1);
953 }
954
955 1;
956
957 #MODULE: ../Logreporters/TreeData.pm
958 package Logreporters::TreeData;
959
960 use 5.008;
961 use strict;
962 use re 'taint';
963 use warnings;
964 no warnings "uninitialized";
965
966 BEGIN {
967 use Exporter ();
968 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
969 $VERSION = '1.001';
970 @ISA = qw(Exporter);
971 @EXPORT = qw(%Totals %Counts %Collecting $END_KEY);
972 @EXPORT_OK = qw(&printTree &buildTree);
973
974 }
975
976 use subs @EXPORT_OK;
977
978 BEGIN {
979 import Logreporters::Config qw(%line_styles);
980 }
981
982 # Totals and Counts are the log line accumulator hashes.
983 # Totals: maintains per-section grand total tallies for use in Summary section
984 # Counts: is a multi-level hash, which maintains per-level key totals.
985 our (%Totals, %Counts);
986
987 # The Collecting hash determines which sections will be captured in
988 # the Counts hash. Counts are collected only if a section is enabled,
989 # and this hash obviates the need to test both existence and
990 # non-zero-ness of the Opts{'keyname'} (either of which cause capture).
991 # XXX The Opts hash could be used ....
992 our %Collecting = ();
993
994 sub buildTree(\% $ $ $ $ $);
995 sub printTree($ $ $ $ $);
996 =pod
997 [ a:b:c, ... ]
998
999 which would be interpreted as follows:
1000
1001 a = show level a detail
1002 b = show at most b items at this level
1003 c = minimun count that will be shown
1004 =cut
1005
1006 sub printTree($ $ $ $ $) {
1007 my ($treeref, $lspecsref, $line_style, $max_report_width, $debug) = @_;
1008 my ($entry, $line);
1009 my $cutlength = $max_report_width - 3;
1010
1011 my $topn = 0;
1012 foreach $entry (sort bycount @$treeref) {
1013 ref($entry) ne "HASH" and die "Unexpected entry in tree: $entry\n";
1014
1015 #print "LEVEL: $entry->{LEVEL}, TOTAL: $entry->{TOTAL}, HASH: $entry, DATA: $entry->{DATA}\n";
1016
1017 # Once the top N lines have been printed, we're done
1018 if ($lspecsref->[$entry->{LEVEL}]{topn}) {
1019 if ($topn++ >= $lspecsref->[$entry->{LEVEL}]{topn} ) {
1020 print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n"
1021 unless ($debug) and do {
1022 $line = ' ' . ' ' x ($entry->{LEVEL} + 3) . '...';
1023 printf "%-130s L%d: topn reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{topn};
1024 };
1025 last;
1026 }
1027 }
1028
1029 # Once the item's count falls below the given threshold, we're done at this level
1030 # unless a top N is specified, as threshold has lower priority than top N
1031 elsif ($lspecsref->[$entry->{LEVEL}]{threshold}) {
1032 if ($entry->{TOTAL} <= $lspecsref->[$entry->{LEVEL}]{threshold}) {
1033 print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n"
1034 unless ($debug) and do {
1035 $line = ' ' . (' ' x ($entry->{LEVEL} + 3)) . '...';
1036 printf "%-130s L%d: threshold reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{threshold};
1037 };
1038 last;
1039 }
1040 }
1041
1042 $line = sprintf "%8d%s%s", $entry->{TOTAL}, ' ' x ($entry->{LEVEL} + 2), $entry->{DATA};
1043
1044 if ($debug) {
1045 printf "%-130s %-60s\n", $line, $entry->{DEBUG};
1046 }
1047
1048 # line_style full, or lines < max_report_width
1049
1050 #printf "MAX: $max_report_width, LEN: %d, CUTLEN $cutlength\n", length($line);
1051 if ($line_style == $line_styles{'full'} or length($line) <= $max_report_width) {
1052 print $line, "\n";
1053 }
1054 elsif ($line_style == $line_styles{'truncate'}) {
1055 print substr ($line,0,$cutlength), '...', "\n";
1056 }
1057 elsif ($line_style == $line_styles{'wrap'}) {
1058 my $leader = ' ' x 8 . ' ' x ($entry->{LEVEL} + 2);
1059 print substr ($line, 0, $max_report_width, ''), "\n";
1060 while (length($line)) {
1061 print $leader, substr ($line, 0, $max_report_width - length($leader), ''), "\n";
1062 }
1063 }
1064 else {
1065 die ('unexpected line style');
1066 }
1067
1068 printTree ($entry->{CHILDREF}, $lspecsref, $line_style, $max_report_width, $debug) if (exists $entry->{CHILDREF});
1069 }
1070 }
1071
1072 my $re_IP_strict = qr/\b(25[0-5]|2[0-4]\d|[01]?\d{1,2})\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})\b/;
1073 # XXX optimize this using packed default sorting. Analysis shows speed isn't an issue though
1074 sub bycount {
1075 # Sort by totals, then IP address if one exists, and finally by data as a string
1076
1077 local $SIG{__WARN__} = sub { print "*** PLEASE REPORT:\n*** $_[0]*** Unexpected: \"$a->{DATA}\", \"$b->{DATA}\"\n" };
1078
1079 $b->{TOTAL} <=> $a->{TOTAL}
1080
1081 ||
1082
1083 pack('C4' => $a->{DATA} =~ /^$re_IP_strict/o) cmp pack('C4' => $b->{DATA} =~ /^$re_IP_strict/o)
1084
1085 ||
1086
1087 $a->{DATA} cmp $b->{DATA}
1088 }
1089
1090 #
1091 # Builds a tree of REC structures from the multi-key %Counts hashes
1092 #
1093 # Parameters:
1094 # Hash: A multi-key hash, with keys being used as category headings, and leaf data
1095 # being tallies for that set of keys
1096 # Level: This current recursion level. Call with 0.
1097 #
1098 # Returns:
1099 # Listref: A listref, where each item in the list is a rec record, described as:
1100 # DATA: a string: a heading, or log data
1101 # TOTAL: an integer: which is the subtotal of this item's children
1102 # LEVEL: an integer > 0: representing this entry's level in the tree
1103 # CHILDREF: a listref: references a list consisting of this node's children
1104 # Total: The cummulative total of items found for a given invocation
1105 #
1106 # Use the special key variable $END_KEY, which is "\a\a" (two ASCII bell's) to end a,
1107 # nested hash early, or the empty string '' may be used as the last key.
1108
1109 our $END_KEY = "\a\a";
1110
1111 sub buildTree(\% $ $ $ $ $) {
1112 my ($href, $max_level_section, $levspecref, $max_level_global, $recurs_level, $show_unique, $debug) = @_;
1113 my ($subtotal, $childList, $rec);
1114
1115 my @treeList = ();
1116 my $total = 0;
1117
1118 foreach my $item (sort keys %$href) {
1119 if (ref($href->{$item}) eq "HASH") {
1120 #print " " x ($recurs_level * 4), "HASH: LEVEL $recurs_level: Item: $item, type: \"", ref($href->{$item}), "\"\n";
1121
1122 ($subtotal, $childList) = buildTree (%{$href->{$item}}, $max_level_section, $levspecref, $max_level_global, $recurs_level + 1, $debug);
1123
1124 if ($recurs_level < $max_level_global and $recurs_level < $max_level_section) {
1125 # me + children
1126 $rec = {
1127 DATA => $item,
1128 TOTAL => $subtotal,
1129 LEVEL => $recurs_level,
1130 CHILDREF => $childList,
1131 };
1132
1133 if ($debug) {
1134 $rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d",
1135 $recurs_level + 1, $max_level_global, $max_level_section,
1136 $levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $subtotal;
1137 }
1138 push (@treeList, $rec);
1139 }
1140 }
1141 else {
1142 if ($item ne '' and $item ne $END_KEY and $recurs_level < $max_level_global and $recurs_level < $max_level_section) {
1143 $rec = {
1144 DATA => $item,
1145 TOTAL => $href->{$item},
1146 LEVEL => $recurs_level,
1147 #CHILDREF => undef,
1148 };
1149 if ($debug) {
1150 $rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d",
1151 $recurs_level, $max_level_global, $max_level_section,
1152 $levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $href->{$item};
1153 }
1154 push (@treeList, $rec);
1155 }
1156 $subtotal = $href->{$item};
1157 }
1158
1159 $total += $subtotal;
1160 }
1161
1162 #print " " x ($recurs_level * 4), "LEVEL $recurs_level: Returning from recurs_level $recurs_level\n";
1163
1164 return ($total, \@treeList);
1165 }
1166
1167 1;
1168
1169 #MODULE: ../Logreporters/RegEx.pm
1170 package Logreporters::RegEx;
1171
1172 use 5.008;
1173 use strict;
1174 use re 'taint';
1175 use warnings;
1176
1177 BEGIN {
1178 use Exporter ();
1179 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
1180 $VERSION = '1.000';
1181 @ISA = qw(Exporter);
1182 @EXPORT = qw($re_IP);
1183 @EXPORT_OK = qw($re_DSN $re_QID $re_DDD);
1184 }
1185
1186 # IPv4 and IPv6
1187 # See syntax in RFC 2821 IPv6-address-literal,
1188 # eg. IPv6:2001:630:d0:f102:230:48ff:fe77:96e
1189 our $re_IP = '(?:(?:::(?:ffff:|FFFF:)?)?(?:\d{1,3}\.){3}\d{1,3}|(?:(?:IPv6:)?[\da-fA-F]{0,4}:){2}(?:[\da-fA-F]{0,4}:){0,5}[\da-fA-F]{0,4})';
1190 # IPv4 only
1191 #our $re_IP = qr/(?:\d{1,3}\.){3}(?:\d{1,3})/;
1192
1193 our $re_DSN = qr/(?:(?:\d{3})?(?: ?\d\.\d\.\d)?)/;
1194 our $re_QID = qr/[A-Z\d]+/;
1195 our $re_DDD = qr/(?:(?:conn_use=\d+ )?delay=-?[\d.]+(?:, delays=[\d\/.]+)?(?:, dsn=[\d.]+)?)/;
1196
1197 1;
1198
1199 #MODULE: ../Logreporters/Reports.pm
1200 package Logreporters::Reports;
1201
1202 use 5.008;
1203 use strict;
1204 use re 'taint';
1205 use warnings;
1206 no warnings "uninitialized";
1207
1208 BEGIN {
1209 use Exporter ();
1210 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
1211 $VERSION = '1.002';
1212 @ISA = qw(Exporter);
1213 @EXPORT = qw(&inc_unmatched &print_unmatched_report &print_percentiles_report2
1214 &print_summary_report &print_detail_report);
1215 @EXPORT_OK = qw();
1216 }
1217
1218 use subs @EXPORT_OK;
1219
1220 BEGIN {
1221 import Logreporters::Config qw(%Opts $fw1 $fw2 $sep1 $sep2 &D_UNMATCHED &D_TREE);
1222 import Logreporters::Utils qw(&commify &unitize &get_percentiles &get_percentiles2);
1223 import Logreporters::TreeData qw(%Totals %Counts &buildTree &printTree);
1224 }
1225
1226 my (%unmatched_list);
1227
1228 our $origline; # unmodified log line, for error reporting and debug
1229
1230 sub inc_unmatched($) {
1231 my ($id) = @_;
1232 $unmatched_list{$origline}++;
1233 print "UNMATCHED($id): \"$origline\"\n" if $Opts{'debug'} & D_UNMATCHED;
1234 }
1235
1236 # Print unmatched lines
1237 #
1238 sub print_unmatched_report() {
1239 return unless (keys %unmatched_list);
1240
1241 print "\n\n**Unmatched Entries**\n";
1242 foreach my $line (sort {$unmatched_list{$b}<=>$unmatched_list{$a} } keys %unmatched_list) {
1243 printf "%8d %s\n", $unmatched_list{$line}, $line;
1244 }
1245 }
1246
1247 =pod
1248 ****** Summary ********************************************************
1249 2 Miscellaneous warnings
1250
1251 20621 Total messages scanned ---------------- 100.00%
1252 662.993M Total bytes scanned 695,198,092
1253 ======== ================================================
1254
1255 19664 Ham ----------------------------------- 95.36%
1256 19630 Clean passed 95.19%
1257 34 Bad header passed 0.16%
1258
1259 942 Spam ---------------------------------- 4.57%
1260 514 Spam blocked 2.49%
1261 428 Spam discarded (no quarantine) 2.08%
1262
1263 15 Malware ------------------------------- 0.07%
1264 15 Malware blocked 0.07%
1265
1266
1267 1978 SpamAssassin bypassed
1268 18 Released from quarantine
1269 1982 Whitelisted
1270 3 Blacklisted
1271 12 MIME error
1272 51 Bad header (debug supplemental)
1273 28 Extra code modules loaded at runtime
1274 =cut
1275 # Prints the Summary report section
1276 #
1277 sub print_summary_report (\@) {
1278 my ($sections) = @_;
1279 my ($keyname,$cur_level);
1280 my @lines;
1281
1282 my $expand_header_footer = sub {
1283 my $line = undef;
1284
1285 foreach my $horf (@_) {
1286 # print blank line if keyname is newline
1287 if ($horf eq "\n") {
1288 $line .= "\n";
1289 }
1290 elsif (my ($sepchar) = ($horf =~ /^(.)$/o)) {
1291 $line .= sprintf "%s %s\n", $sepchar x 8, $sepchar x 50;
1292 }
1293 else {
1294 die "print_summary_report: unsupported header or footer type \"$horf\"";
1295 }
1296 }
1297 return $line;
1298 };
1299
1300 if ($Opts{'detail'} >= 5) {
1301 my $header = "****** Summary ";
1302 print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n\n";
1303 }
1304
1305 my @headers;
1306 foreach my $sref (@$sections) {
1307 # headers and separators
1308 die "Unexpected Section $sref" if (ref($sref) ne 'HASH');
1309
1310 # Start of a new section group.
1311 # Expand and save headers to output at end of section group.
1312 if ($sref->{CLASS} eq 'GROUP_BEGIN') {
1313 $cur_level = $sref->{LEVEL};
1314 $headers[$cur_level] = &$expand_header_footer(@{$sref->{HEADERS}});
1315 }
1316
1317 elsif ($sref->{CLASS} eq 'GROUP_END') {
1318 my $prev_level = $sref->{LEVEL};
1319
1320 # If this section had lines to output, tack on headers and footers,
1321 # removing extraneous newlines.
1322 if ($lines[$cur_level]) {
1323 # squish multiple blank lines
1324 if ($headers[$cur_level] and substr($headers[$cur_level],0,1) eq "\n") {
1325 if ( ! defined $lines[$prev_level][-1] or $lines[$prev_level][-1] eq "\n") {
1326 $headers[$cur_level] =~ s/^\n+//;
1327 }
1328 }
1329
1330 push @{$lines[$prev_level]}, $headers[$cur_level] if $headers[$cur_level];
1331 push @{$lines[$prev_level]}, @{$lines[$cur_level]};
1332 my $f = &$expand_header_footer(@{$sref->{FOOTERS}});
1333 push @{$lines[$prev_level]}, $f if $f;
1334 $lines[$cur_level] = undef;
1335 }
1336
1337 $headers[$cur_level] = undef;
1338 $cur_level = $prev_level;
1339 }
1340
1341 elsif ($sref->{CLASS} eq 'DATA') {
1342 # Totals data
1343 $keyname = $sref->{NAME};
1344 if ($Totals{$keyname} > 0) {
1345 my ($numfmt, $desc, $divisor) = ($sref->{FMT}, $sref->{TITLE}, $sref->{DIVISOR});
1346
1347 my $fmt = '%8';
1348 my $extra = ' %25s';
1349 my $total = $Totals{$keyname};
1350
1351 # Z format provides unitized or unaltered totals, as appropriate
1352 if ($numfmt eq 'Z') {
1353 ($total, $fmt) = unitize ($total, $fmt);
1354 }
1355 else {
1356 $fmt .= "$numfmt ";
1357 $extra = '';
1358 }
1359
1360 if ($divisor and $$divisor) {
1361 # XXX generalize this
1362 if (ref ($desc) eq 'ARRAY') {
1363 $desc = @$desc[0] . ' ' . @$desc[1] x (42 - 2 - length(@$desc[0]));
1364 }
1365
1366 push @{$lines[$cur_level]},
1367 sprintf "$fmt %-42s %6.2f%%\n", $total, $desc,
1368 $$divisor == $Totals{$keyname} ? 100.00 : $Totals{$keyname} * 100 / $$divisor;
1369 }
1370 else {
1371 push @{$lines[$cur_level]},
1372 sprintf "$fmt %-23s $extra\n", $total, $desc, commify ($Totals{$keyname});
1373 }
1374 }
1375 }
1376 else {
1377 die "print_summary_report: unexpected control...";
1378 }
1379 }
1380 print @{$lines[0]};
1381 print "\n";
1382 }
1383
1384 # Prints the Detail report section
1385 #
1386 # Note: side affect; deletes each key in Totals/Counts
1387 # after printout. Only the first instance of a key in
1388 # the Section table will result in Detail output.
1389 sub print_detail_report (\@) {
1390 my ($sections) = @_;
1391 my $header_printed = 0;
1392
1393 return unless (keys %Counts);
1394
1395 #use Devel::Size qw(size total_size);
1396
1397 foreach my $sref ( @$sections ) {
1398 next unless $sref->{CLASS} eq 'DATA';
1399 # only print detail for this section if DETAIL is enabled
1400 # and there is something in $Counts{$keyname}
1401 next unless $sref->{DETAIL};
1402 next unless exists $Counts{$sref->{NAME}};
1403
1404 my $keyname = $sref->{NAME};
1405 my $max_level = undef;
1406 my $print_this_key = 0;
1407
1408 my @levelspecs = ();
1409 clear_level_specs($max_level, \@levelspecs);
1410 if (exists $Opts{$keyname}) {
1411 $max_level = create_level_specs($Opts{$keyname}, $Opts{'detail'}, \@levelspecs);
1412 $print_this_key = 1 if ($max_level);
1413 }
1414 else {
1415 $print_this_key = 1;
1416 }
1417 #print_level_specs($max_level,\@levelspecs);
1418
1419 # at detail 5, print level 1, detail 6: level 2, ...
1420
1421 #print STDERR "building: $keyname\n";
1422 my ($count, $treeref) =
1423 buildTree (%{$Counts{$keyname}}, defined ($max_level) ? $max_level : 11,
1424 \@levelspecs, $Opts{'detail'} - 4, 0, $Opts{'debug'} & D_TREE);
1425
1426 if ($count > 0) {
1427 if ($print_this_key) {
1428 my $desc = $sref->{TITLE};
1429 $desc =~ s/^\s+//;
1430
1431 if (! $header_printed) {
1432 my $header = "****** Detail ($max_level) ";
1433 print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n";
1434 $header_printed = 1;
1435 }
1436 printf "\n%8d %s %s\n", $count, $desc,
1437 $Opts{'sect_vars'} ?
1438 ('-' x ($Opts{'max_report_width'} - 18 - length($desc) - length($keyname))) . " [ $keyname ] -" :
1439 '-' x ($Opts{'max_report_width'} - 12 - length($desc))
1440 }
1441
1442 printTree ($treeref, \@levelspecs, $Opts{'line_style'}, $Opts{'max_report_width'},
1443 $Opts{'debug'} & D_TREE);
1444 }
1445 #print STDERR "Total size Counts: ", total_size(\%Counts), "\n";
1446 #print STDERR "Total size Totals: ", total_size(\%Totals), "\n";
1447 $treeref = undef;
1448 $Totals{$keyname} = undef;
1449 delete $Totals{$keyname};
1450 delete $Counts{$keyname};
1451 }
1452 #print "\n";
1453 }
1454
1455 =pod
1456
1457 Print out a standard percentiles report
1458
1459 === Delivery Delays Percentiles ===============================================================
1460 0% 25% 50% 75% 90% 95% 98% 100%
1461 -----------------------------------------------------------------------------------------------
1462 Before qmgr 0.01 0.70 1.40 45483.70 72773.08 81869.54 87327.42 90966.00
1463 In qmgr 0.00 0.00 0.00 0.01 0.01 0.01 0.01 0.01
1464 Conn setup 0.00 0.00 0.00 0.85 1.36 1.53 1.63 1.70
1465 Transmission 0.03 0.47 0.92 1.61 2.02 2.16 2.24 2.30
1466 Total 0.05 1.18 2.30 45486.15 72776.46 81873.23 87331.29 90970.00
1467 ===============================================================================================
1468
1469 === Postgrey Delays Percentiles ===========================================================
1470 0% 25% 50% 75% 90% 95% 98% 100%
1471 -------------------------------------------------------------------------------------------
1472 Postgrey 727.00 727.00 727.00 727.00 727.00 727.00 727.00 727.00
1473 ===========================================================================================
1474
1475 tableref:
1476 data table: ref to array of arrays, first cell is label, subsequent cells are data
1477 title:
1478 table's title
1479 percentiles_str:
1480 string of space or comma separated integers, which are the percentiles
1481 calculated and output as table column data
1482 =cut
1483 sub print_percentiles_report2($$$) {
1484 my ($tableref, $title, $percentiles_str) = @_;
1485
1486 return unless @$tableref;
1487
1488 my $myfw2 = $fw2 - 1;
1489 my @percents = split /[ ,]/, $percentiles_str;
1490
1491 # Calc y label width from the hash's keys. Each key is padded with the
1492 # string "#: ", # where # is a single-digit sort index.
1493 my $y_label_max_width = 0;
1494 for (@$tableref) {
1495 $y_label_max_width = length($_->[0]) if (length($_->[0]) > $y_label_max_width);
1496 }
1497
1498 # Titles row
1499 my $col_titles_str = sprintf "%-${y_label_max_width}s" . "%${myfw2}s%%" x @percents , ' ', @percents;
1500 my $table_width = length($col_titles_str);
1501
1502 # Table header row
1503 my $table_header_str = sprintf "%s %s ", $sep1 x 3, $title;
1504 $table_header_str .= $sep1 x ($table_width - length($table_header_str));
1505
1506 print "\n", $table_header_str;
1507 print "\n", $col_titles_str;
1508 print "\n", $sep2 x $table_width;
1509
1510 my (@p, @coldata, @xformed);
1511 foreach (@$tableref) {
1512 my ($title, $ref) = ($_->[0], $_->[1]);
1513 #xxx my @sorted = sort { $a <=> $b } @{$_->[1]};
1514
1515 my @byscore = ();
1516
1517 for my $bucket (sort { $a <=> $b } keys %$ref) {
1518 #print "Key: $title: Bucket: $bucket = $ref->{$bucket}\n";
1519 # pairs: bucket (i.e. key), tally
1520 push @byscore, $bucket, $ref->{$bucket};
1521 }
1522
1523
1524 my @p = get_percentiles2 (@byscore, @percents);
1525 printf "\n%-${y_label_max_width}s" . "%${fw2}.2f" x scalar (@p), $title, @p;
1526 }
1527
1528 =pod
1529 foreach (@percents) {
1530 #printf "\n%-${y_label_max_width}s" . "%${fw2}.2f" x scalar (@p), substr($title,3), @p;
1531 printf "\n%3d%%", $title;
1532 foreach my $val (@{shift @xformed}) {
1533 my $unit;
1534 if ($val > 1000) {
1535 $unit = 's';
1536 $val /= 1000;
1537 }
1538 else {
1539 $unit = '';
1540 }
1541 printf "%${fw3}.2f%-2s", $val, $unit;
1542 }
1543 }
1544 =cut
1545
1546 print "\n", $sep1 x $table_width, "\n";
1547 }
1548
1549 sub clear_level_specs($ $) {
1550 my ($max_level,$lspecsref) = @_;
1551 #print "Zeroing $max_level rows of levelspecs\n";
1552 $max_level = 0 if (not defined $max_level);
1553 for my $x (0..$max_level) {
1554 $lspecsref->[$x]{topn} = undef;
1555 $lspecsref->[$x]{threshold} = undef;
1556 }
1557 }
1558
1559 # topn = 0 means don't limit
1560 # threshold = 0 means no min threshold
1561 sub create_level_specs($ $ $) {
1562 my ($optkey,$gdetail,$lspecref) = @_;
1563
1564 return 0 if ($optkey eq "0");
1565
1566 my $max_level = $gdetail; # default to global detail level
1567 my (@specsP1, @specsP2, @specsP3);
1568
1569 #printf "create_level_specs: key: %s => \"%s\", max_level: %d\n", $optkey, $max_level;
1570
1571 foreach my $sp (split /[\s,]+/, $optkey) {
1572 #print "create_level_specs: SP: \"$sp\"\n";
1573 # original level specifier
1574 if ($sp =~ /^\d+$/) {
1575 $max_level = $sp;
1576 #print "create_level_specs: max_level set: $max_level\n";
1577 }
1578 # original level specifier + topn at level 1
1579 elsif ($sp =~ /^(\d*)\.(\d+)$/) {
1580 if ($1) { $max_level = $1; }
1581 else { $max_level = $gdetail; } # top n specified, but no max level
1582
1583 # force top N at level 1 (zero based)
1584 push @specsP1, { level => 0, topn => $2, threshold => 0 };
1585 }
1586 # newer level specs
1587 elsif ($sp =~ /^::(\d+)$/) {
1588 push @specsP3, { level => undef, topn => 0, threshold => $1 };
1589 }
1590 elsif ($sp =~ /^:(\d+):(\d+)?$/) {
1591 push @specsP2, { level => undef, topn => $1, threshold => defined $2 ? $2 : 0 };
1592 }
1593 elsif ($sp =~ /^(\d+):(\d+)?:(\d+)?$/) {
1594 push @specsP1, { level => ($1 > 0 ? $1 - 1 : 0), topn => $2 ? $2 : 0, threshold => $3 ? $3 : 0 };
1595 }
1596 else {
1597 print STDERR "create_level_specs: unexpected levelspec ignored: \"$sp\"\n";
1598 }
1599 }
1600
1601 #foreach my $sp (@specsP3, @specsP2, @specsP1) {
1602 # printf "Sorted specs: L%d, topn: %3d, threshold: %3d\n", $sp->{level}, $sp->{topn}, $sp->{threshold};
1603 #}
1604
1605 my ($min, $max);
1606 foreach my $sp ( @specsP3, @specsP2, @specsP1) {
1607 ($min, $max) = (0, $max_level);
1608
1609 if (defined $sp->{level}) {
1610 $min = $max = $sp->{level};
1611 }
1612 for my $level ($min..$max) {
1613 #printf "create_level_specs: setting L%d, topn: %s, threshold: %s\n", $level, $sp->{topn}, $sp->{threshold};
1614 $lspecref->[$level]{topn} = $sp->{topn} if ($sp->{topn});
1615 $lspecref->[$level]{threshold} = $sp->{threshold} if ($sp->{threshold});
1616 }
1617 }
1618
1619 return $max_level;
1620 }
1621
1622 sub print_level_specs($ $) {
1623 my ($max_level,$lspecref) = @_;
1624 for my $level (0..$max_level) {
1625 printf "LevelSpec Row %d: %3d %3d\n", $level, $lspecref->[$level]{topn}, $lspecref->[$level]{threshold};
1626 }
1627 }
1628
1629
1630 1;
1631
1632 #MODULE: ../Logreporters/RFC3463.pm
1633 package Logreporters::RFC3463;
1634
1635 use 5.008;
1636 use strict;
1637 use re 'taint';
1638 use warnings;
1639
1640 BEGIN {
1641 use Exporter ();
1642 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
1643 $VERSION = '1.000';
1644 @ISA = qw(Exporter);
1645 @EXPORT = qw(&get_dsn_msg);
1646 }
1647
1648 use subs @EXPORT;
1649
1650 #-------------------------------------------------
1651 # Enhanced Mail System Status Codes (aka: extended status codes)
1652 #
1653 # RFC 3463 http://www.ietf.org/rfc/rfc3463.txt
1654 # RFC 4954 http://www.ietf.org/rfc/rfc4954.txt
1655 #
1656 # Class.Subject.Detail
1657 #
1658 my %dsn_codes = (
1659 class => {
1660 '2' => 'Success',
1661 '4' => 'Transient failure',
1662 '5' => 'Permanent failure',
1663 },
1664
1665 subject => {
1666 '0' => 'Other/Undefined status',
1667 '1' => 'Addressing status',
1668 '2' => 'Mailbox status',
1669 '3' => 'Mail system status',
1670 '4' => 'Network & routing status',
1671 '5' => 'Mail delivery protocol status',
1672 '6' => 'Message content/media status',
1673 '7' => 'Security/policy status',
1674 },
1675
1676 detail => {
1677 '0.0' => 'Other undefined status',
1678 '1.0' => 'Other address status',
1679 '1.1' => 'Bad destination mailbox address',
1680 '1.2' => 'Bad destination system address',
1681 '1.3' => 'Bad destination mailbox address syntax',
1682 '1.4' => 'Destination mailbox address ambiguous',
1683 '1.5' => 'Destination mailbox address valid',
1684 '1.6' => 'Mailbox has moved',
1685 '1.7' => 'Bad sender\'s mailbox address syntax',
1686 '1.8' => 'Bad sender\'s system address',
1687
1688 '2.0' => 'Other/Undefined mailbox status',
1689 '2.1' => 'Mailbox disabled, not accepting messages',
1690 '2.2' => 'Mailbox full',
1691 '2.3' => 'Message length exceeds administrative limit.',
1692 '2.4' => 'Mailing list expansion problem',
1693
1694 '3.0' => 'Other/Undefined mail system status',
1695 '3.1' => 'Mail system full',
1696 '3.2' => 'System not accepting network messages',
1697 '3.3' => 'System not capable of selected features',
1698 '3.4' => 'Message too big for system',
1699
1700 '4.0' => 'Other/Undefined network or routing status',
1701 '4.1' => 'No answer from host',
1702 '4.2' => 'Bad connection',
1703 '4.3' => 'Routing server failure',
1704 '4.4' => 'Unable to route',
1705 '4.5' => 'Network congestion',
1706 '4.6' => 'Routing loop detected',
1707 '4.7' => 'Delivery time expired',
1708
1709 '5.0' => 'Other/Undefined protocol status',
1710 '5.1' => 'Invalid command',
1711 '5.2' => 'Syntax error',
1712 '5.3' => 'Too many recipients',
1713 '5.4' => 'Invalid command arguments',
1714 '5.5' => 'Wrong protocol version',
1715 '5.6' => 'Authentication Exchange line too long',
1716
1717 '6.0' => 'Other/Undefined media error',
1718 '6.1' => 'Media not supported',
1719 '6.2' => 'Conversion required & prohibited',
1720 '6.3' => 'Conversion required but not supported',
1721 '6.4' => 'Conversion with loss performed',
1722 '6.5' => 'Conversion failed',
1723
1724 '7.0' => 'Other/Undefined security status',
1725 '7.1' => 'Delivery not authorized, message refused',
1726 '7.2' => 'Mailing list expansion prohibited',
1727 '7.3' => 'Security conversion required but not possible',
1728 '7.4' => 'Security features not supported',
1729 '7.5' => 'Cryptographic failure',
1730 '7.6' => 'Cryptographic algorithm not supported',
1731 '7.7' => 'Message integrity failure',
1732 },
1733
1734 # RFC 4954
1735 complete => {
1736 '2.7.0' => 'Authentication succeeded',
1737 '4.7.0' => 'Temporary authentication failure',
1738 '4.7.12' => 'Password transition needed',
1739 '5.7.0' => 'Authentication required',
1740 '5.7.8' => 'Authentication credentials invalid',
1741 '5.7.9' => 'Authentication mechanism too weak',
1742 '5.7.11' => 'Encryption required for requested authentication mechanism',
1743 },
1744 );
1745
1746 # Returns an RFC 3463 DSN messages given a DSN code
1747 #
1748 sub get_dsn_msg ($) {
1749 my $dsn = shift;
1750 my ($msg, $class, $subject, $detail);
1751
1752 return "*DSN unavailable" if ($dsn =~ /^$/);
1753
1754 unless ($dsn =~ /^(\d)\.((\d{1,3})\.\d{1,3})$/) {
1755 print "Error: not a DSN code $dsn\n";
1756 return "Invalid DSN";
1757 }
1758
1759 $class = $1; $subject = $3; $detail = $2;
1760
1761 #print "DSN: $dsn, Class: $class, Subject: $subject, Detail: $detail\n";
1762
1763 if (exists $dsn_codes{'class'}{$class}) {
1764 $msg = $dsn_codes{'class'}{$class};
1765 }
1766 if (exists $dsn_codes{'subject'}{$subject}) {
1767 $msg .= ': ' . $dsn_codes{'subject'}{$subject};
1768 }
1769 if (exists $dsn_codes{'complete'}{$dsn}) {
1770 $msg .= ': ' . $dsn_codes{'complete'}{$dsn};
1771 }
1772 elsif (exists $dsn_codes{'detail'}{$detail}) {
1773 $msg .= ': ' . $dsn_codes{'detail'}{$detail};
1774 }
1775
1776 #print "get_dsn_msg: $msg\n" if ($msg);
1777 return $dsn . ': ' . $msg;
1778 }
1779
1780 1;
1781
1782 #MODULE: ../Logreporters/PolicySPF.pm
1783 package Logreporters::PolicySPF;
1784
1785 use 5.008;
1786 use strict;
1787 use re 'taint';
1788 use warnings;
1789
1790 BEGIN {
1791 use Exporter ();
1792 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
1793 $VERSION = '1.000';
1794 @ISA = qw(Exporter);
1795 @EXPORT = qw(&postfix_policy_spf);
1796 }
1797
1798 use subs @EXPORT;
1799
1800 BEGIN {
1801 import Logreporters::RegEx qw($re_QID);
1802 import Logreporters::TreeData qw(%Totals %Counts $END_KEY);
1803 import Logreporters::Utils;
1804 import Logreporters::Reports qw(&inc_unmatched);
1805 }
1806
1807 # Handle postfix/policy_spf entries
1808 #
1809 # Mail::SPF::Result
1810 # Pass the SPF record designates the host to be allowed to send accept
1811 # Fail the SPF record has designated the host as NOT being allowed to send reject
1812 # SoftFail the SPF record has designated the host as NOT being allowed to send but is in transition accept but mark
1813 # Neutral the SPF record specifies explicitly that nothing can be said about validity accept
1814 # None the domain does not have an SPF record or the SPF record does not evaluate to a result accept
1815 # PermError a permanent error has occured (eg. badly formatted SPF record) unspecified
1816 # TempError a transient error has occured accept or reject
1817
1818 sub postfix_policy_spf($) {
1819 my $line = shift;
1820
1821 if (
1822 $line =~ /^Attribute: / or
1823 # handler sender_policy_framework: is decisive.
1824 $line =~ /^handler [^:]+/ or
1825 # decided action=REJECT Please see http://www.openspf.org/why.html?sender=jrzjcez%40telecomitalia.it&ip=81.178.62.236&receiver=protegate1.zmi.at
1826 $line =~ /^decided action=/ or
1827
1828 # pypolicyd-spf-0.7.1
1829 #
1830 # Read line: "request=smtpd_access_policy"
1831 # Found the end of entry
1832 # Config: {'Mail_From_reject': 'Fail', 'PermError_reject': 'False', 'HELO_reject': 'SPF_Not_Pass', 'defaultSeedOnly': 1, 'debugLevel': 4, 'skip_addresses': '127.0.0.0/8,::ffff:127.0.0.0//104,::1//128', 'TempError_Defer': 'False'}
1833 # spfcheck: pyspf result: "['Pass', 'sender SPF authorized', 'helo']"
1834 # ERROR: Could not match line "#helo pass and mfrom none"
1835 # Traceback (most recent call last):
1836 # File "/usr/local/bin/policyd-spf", line 405, in <module>
1837 # line = sys.stdin.readline()
1838 # KeyboardInterrupt
1839 $line =~ /^Read line: "/ or
1840 $line =~ /^Found the end of entry$/ or
1841 $line =~ /^Config: {/ or
1842 $line =~ /^spfcheck: pyspf result/ or
1843 $line =~ /^Starting$/ or
1844 $line =~ /^Normal exit$/ or
1845 $line =~ /^ERROR: Could not match line/ or
1846 $line =~ /^Traceback / or
1847 $line =~ /^KeyboardInterrupt/ or
1848 $line =~ /^\s\s+/
1849 )
1850 {
1851 #print "IGNORING...\n\tORIG: $::OrigLine\n";
1852 return
1853 }
1854
1855 # Keep policy-spf warnings in its section
1856 if (my ($warn,$msg) = $line =~ /^warning: ([^:]+): (.*)$/) {
1857 #TDspf warning: ignoring garbage: # No SPF
1858
1859 $msg =~ s/^# ?//;
1860 $Totals{'policyspf'}++;
1861 $Counts{'policyspf'}{'*Warning'}{ucfirst $warn}{$msg}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
1862 return;
1863 }
1864
1865 # pypolicyd-spf-0.7.1
1866
1867 # Fail; identity=helo; client-ip=192.168.0.1; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
1868 # Fail; identity=helo; client-ip=192.168.0.2; helo=example.com; envelope-from=<>; receiver=bogus@example.net
1869 # Neutral; identity=helo; client-ip=192.168.0.3; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
1870 # None; identity=helo; client-ip=192.168.0.4; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
1871 # None; identity=helo; client-ip=192.168.0.5; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
1872 # None; identity=mailfrom; client-ip=192.168.0.1; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
1873 # None; identity=mailfrom; client-ip=192.168.0.2; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
1874 # Pass; identity=helo; client-ip=192.168.0.2; helo=example.com; envelope-from=<>; receiver=bogus@example.net
1875 # Permerror; identity=helo; client-ip=192.168.0.4; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net
1876 # Softfail; identity=mailfrom; client-ip=192.168.0.6; helo=example.com; envelope-from=f@example.com; receiver=yahl@example.org
1877 if ($line =~ /^(Pass|Fail|None|Neutral|Permerror|Softfail|Temperror); (.*)$/) {
1878 my $result = $1;
1879 my %params = $2 =~ /([-\w]+)=([^;]+)/g;
1880 #$params{'s'} = '*unknown' unless $params{'s'};
1881 $Totals{'policyspf'}++;
1882 if ($Logreporters::TreeData::Collecting{'policyspf'}) {
1883 my ($id) = $params{'identity'};
1884 $id =~ s/mailfrom/envelope-from/;
1885
1886 $Counts{'policyspf'}{'Policy Action'}{"SPF: $result"}{join(': ',$params{'identity'},$params{$id})}{$params{'client-ip'}}{$params{'receiver'}}++;
1887 }
1888 return;
1889 }
1890 elsif ($line =~ /^ERROR /) {
1891 $line =~ s/^ERROR //;
1892 $Totals{'warningsother'}++; return unless ($Logreporters::TreeData::Collecting{'warningsother'});
1893 $Counts{'warningsother'}{"$Logreporters::service_name: $line"}++;
1894 return;
1895 }
1896
1897 # Strip QID if it exists, and trailing ": ", leaving just the message.
1898 $line =~ s/^(?:$re_QID|): //;
1899
1900 # other ignored
1901 if (
1902 $line =~ /^SPF \S+ \(.+?\): .*$/ or
1903 $line =~ /^Mail From/ or
1904 $line =~ /^:HELO check failed/ or # log entry has no space after :
1905 $line =~ /^testing:/
1906 )
1907 {
1908 #TDspf testing: stripped sender=jrzjcez@telecomitalia.it, stripped rcpt=hengstberger@adv.at
1909 # postfix-policyd-spf-perl-2.007
1910 #TDspf SPF pass (Mechanism 'ip4:10.0.0.2/22' matched): Envelope-from: foo@example.com
1911 #TDspf SPF pass (Mechanism 'ip4:10.10.10.10' matched): Envelope-from: anyone@sample.net
1912 #TDspf SPF pass (Mechanism 'ip4:10.10.10.10' matched): HELO/EHLO (Null Sender): mailout2.example.com
1913 #TDspf SPF fail (Mechanism '-all' matched): HELO/EHLO: mailout1.example.com
1914 #TDspf SPF none (No applicable sender policy available): Envelope-from: efrom@example.com
1915 #TDspf SPF permerror (Included domain 'example.com' has no applicable sender policy): Envelope-from: efrom@example.com
1916 #TDspf SPF permerror (Maximum DNS-interactive terms limit (10) exceeded): Envelope-from: efrom@example.com
1917 #TDspf Mail From (sender) check failed - Mail::SPF->new(10.0.0.1, , test.DNSreport.com) failed: 'identity' option must not be empty
1918 #TDspf HELO check failed - Mail::SPF->new(, , ) failed: Missing required 'identity' option
1919
1920 #TDspf SPF not applicable to localhost connection - skipped check
1921
1922 #print "IGNORING...\n\tLINE: $line\n\tORIG: \"$Logreporters::Reports::origline\"\n";
1923 return;
1924 }
1925
1926 my ($action, $domain, $ip, $message, $mechanism);
1927 ($domain, $ip, $message, $mechanism) = ('*unknown', '*unknown', '', '*unavailable');
1928 #print "LINE: '$line'\n";
1929
1930 # postfix-policyd-spf-perl: http://www.openspf.org/Software
1931 if ($line =~ /^Policy action=(.*)$/) {
1932 $line = $1;
1933
1934 #: Policy action=DUNNO
1935 return if $line =~ /^DUNNO/;
1936 # Policy action=PREPEND X-Comment: SPF not applicable to localhost connection - skipped check
1937 return if $line =~ /^PREPEND X-Comment: SPF not applicable to localhost connection - skipped check$/;
1938
1939 #print "LINE: '$line'\n";
1940 if ($line =~ /^DEFER_IF_PERMIT SPF-Result=\[?(.*?)\]?: (.*) of .*$/) {
1941 my ($lookup,$message) = ($1,$2);
1942 # Policy action=DEFER_IF_PERMIT SPF-Result=[10.0.0.1]: Time-out on DNS 'SPF' lookup of '[10.0.0.1]'
1943 # Policy action=DEFER_IF_PERMIT SPF-Result=example.com: 'SERVFAIL' error on DNS 'SPF' lookup of 'example.com'
1944 $message =~ s/^(.*?) on (DNS SPF lookup)$/$2: $1/;
1945 $message =~ s/'//g;
1946 $Totals{'policyspf'}++;
1947 $Counts{'policyspf'}{'Policy Action'}{'defer_if_permit'}{$message}{$lookup}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
1948 return;
1949 }
1950
1951 if ($line =~ /^550 Please see http:\/\/www\.openspf\.org\/Why\?(.*)$/) {
1952 # Policy action=550 Please see http://www.openspf.org/Why?s=mfrom&id=from%40example.com&ip=10.0.0.1&r=example.net
1953 # Policy action=550 Please see http://www.openspf.org/Why?s=helo;id=mailout03.example.com;ip=192.168.0.1;r=mx1.example.net
1954 # Policy action=550 Please see http://www.openspf.org/Why?id=someone%40example.com&ip=10.0.0.1&receiver=vps.example.net
1955
1956 my %params;
1957 for (split /[&;]/, $1) {
1958 my ($id,$val) = split /=/, $_;
1959 $params{$id} = $val;
1960 }
1961 $params{'id'} =~ s/^.*%40//;
1962 $params{'s'} = '*unknown' unless $params{'s'};
1963 #print "Please see...:\n\tMessage: $message\n\tIP: $ip\n\tDomain: $domain\n";
1964 $Totals{'policyspf'}++;
1965 $Counts{'policyspf'}{'Policy Action'}{'550 reject'}{'See http://www.openspf.org/Why?...'}{$params{'s'}}{$params{'ip'}}{$params{'id'}}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
1966 return;
1967 }
1968
1969 if ($line =~ /^[^:]+: (none|pass|fail|softfail|neutral|permerror|temperror) \((.+?)\) receiver=[^;]+(?:; (.*))?$/) {
1970 # iehc is identity, envelope-from, helo, client-ip
1971 my ($result,$message,$iehc,$subject) = ($1,$2,$3,undef);
1972 my %params = ();
1973 #TDspf Policy action=PREPEND Received-SPF: pass (bounces.example.com ... _spf.example.com: 10.0.0.1 is authorized to use 'from@bounces.example.com' in 'mfrom' identity (mechanism 'ip4:10.0.0.1/24' matched)) receiver=sample.net; identity=mfrom; envelope-from="from@bounces.example.com"; helo=out.example.com; client-ip=10.0.0.1
1974
1975 # Note: "identity=mailfrom" new in Mail::SPF version 2.006 Aug. 17
1976 #TDspf Policy action=PREPEND Received-SPF: pass (example.com: 10.0.0.1 is authorized to use 'from@example.com' in 'mfrom' identity (mechanism 'ip4:10.0.0.0/24' matched)) receiver=mx.example.com; identity=mailfrom; envelope-from="from@example.com"; helo=example.com; client-ip=10.0.0.1
1977
1978 #TDspf Policy action=PREPEND Received-SPF: none (example.com: No applicable sender policy available) receiver=sample.net; identity=mfrom; envelope-from="f@example.com"; helo=example.com; client-ip=10.0.0.1
1979
1980 #TDspf Policy action=PREPEND Received-SPF: neutral (example.com: Domain does not state whether sender is authorized to use 'f@example.com' in 'mfrom' identity (mechanism '?all' matched)) receiver=sample.net identity=mfrom; envelope-from="f@example.com"; helo="[10.0.0.1]"; client-ip=192.168.0.1
1981
1982 #TDspf Policy action=PREPEND Received-SPF: none (example.com: No applicable sender policy available) receiver=sample.net; identity=helo; helo=example.com; client-ip=192.168.0.1
1983 #TDspf Policy action=PREPEND Received-SPF: none (example.com: No applicable sender policy available) receiver=mx1.example
1984
1985 #print "LINE: $iehc\n";
1986 if ($iehc) {
1987 %params = $iehc =~ /([-\w]+)=([^;]+)/g;
1988
1989 if (exists $params{'identity'}) {
1990 $params{'identity'} =~ s/identity=//;
1991 if ($params{'identity'} eq 'mfrom' or $params{'identity'} eq 'mailfrom') {
1992 $params{'identity'} = 'mail from';
1993 }
1994 $params{'identity'} = uc $params{'identity'};
1995 }
1996 $params{'envelope-from'} =~ s/"//g if exists $params{'envelope-from'};
1997 #($helo = $params{'helo'}) =~ s/"//g if exists $params{'helo'};
1998 $ip = $params{'client-ip'} if exists $params{'client-ip'};
1999 }
2000
2001 $message =~ s/^([^:]+): // and $subject = $1;
2002
2003 if ($message =~ /^No applicable sender policy available$/) {
2004 $message = 'No sender policy';
2005 }
2006 elsif ($message =~ s/^(Junk encountered in mechanism) '(.*?)'/$1/) {
2007 #TDspf Policy action=PREPEND Received-SPF: permerror (example.com: Junk encountered in mechanism 'a:10.0.0.1') receiver=example.net; identity=mfrom; envelope-from="ef@example.com"; helo=h; client-ip=10.0.0.2
2008 $ip = formathost ($ip, 'mech: ' . $2);
2009 }
2010 elsif ($message =~ s/^(Included domain) '(.*?)' (has no .*)$/$1 $3/) {
2011 #TDspf Policy action=PREPEND Received-SPF: permerror (example.com: Included domain 's.example.net' has no applicable sender policy) receiver=x.sample.com; identity=mfrom; envelope-from="ef@example.com"; helo=example.net; client-ip=10.0.0.2
2012 $subject .= " (included: $2)";
2013 }
2014 elsif ($message =~ /^Domain does not state whether sender is authorized to use '.*?' in '\S+' identity \(mechanism '(.+?)' matched\)$/) {
2015 # Domain does not state whether sender is authorized to use 'returns@example.com' in 'mfrom' identity (mechanism '?all' matched))
2016 ($mechanism,$message) = ($1,'Domain does not state if sender authorized to use');
2017 }
2018 elsif ($message =~ /^(\S+) is (not )?authorized( by default)? to use '.*?' in '\S+' identity(?:, however domain is not currently prepared for false failures)? \(mechanism '(.+?)' matched\)$/) {
2019 # Sender is not authorized by default to use 'from@example.com' in 'mfrom' identity, however domain is not currently prepared for false failures (mechanism '~all' matched))
2020 # 192.168.1.10 is authorized by default to use 'from@example.com' in 'mfrom' identity (mechanism 'all' matched))
2021 $message = join (' ',
2022 $1 eq 'Sender' ? 'Sender' : 'IP', # canonicalize IP address
2023 $2 ? 'not authorized' : 'authorized',
2024 $3 ? 'by default to use' : 'to use',
2025 );
2026 $mechanism = $4;
2027 }
2028 elsif ($message =~ /^Maximum DNS-interactive terms limit \S+ exceeded$/) {
2029 $message = 'Maximum DNS-interactive terms limit exceeded';
2030 }
2031 elsif ($message =~ /^Invalid IPv4 prefix length encountered in (.*)$/) {
2032 $subject .= " (invalid: $1)";
2033 $message = 'Invalid IPv4 prefix length encountered';
2034 }
2035
2036 #print "Result: $result, Identity: $params{'identity'}, Mech: $mechanism, Subject: $subject, IP: $ip\n";
2037 $Totals{'policyspf'}++;
2038 if ($Logreporters::TreeData::Collecting{'policyspf'}) {
2039 $message = join (' ', $message, $params{'identity'}) if exists $params{'identity'};
2040 $Counts{'policyspf'}{'Policy Action'}{"SPF $result"}{$message}{'mech: ' .$mechanism}{$subject}{$ip}++
2041 }
2042 return;
2043 }
2044
2045 inc_unmatched('postfix_policy_spf(2)');
2046 return;
2047 }
2048
2049 =pod
2050 Mail::SPF::Query
2051 libmail-spf-query-perl 1:1.999
2052
2053 XXX incomplete
2054
2055 Some possible smtp_comment results:
2056 pass "localhost is always allowed."
2057 none "SPF", "domain of sender $query->{sender} does not designate mailers
2058 unknown $explanation, "domain of sender $query->{sender} does not exist"
2059 $query->{spf_error_explanation}, $query->is_looping
2060 $query->{spf_error_explanation}, $directive_set->{hard_syntax_error}
2061 $query->{spf_error_explanation}, "Missing SPF record at $query->{domain}"
2062 error $query->{spf_error_explanation}, $query->{error}
2063
2064 $result $explanation, $comment, $query->{directive_set}->{orig_txt}
2065
2066 Possible header_comment results:
2067 pass "$query->{spf_source} designates $ip as permitted sender"
2068 fail "$query->{spf_source} does not designate $ip as permitted sender"
2069 softfail "transitioning $query->{spf_source} does not designate $ip as permitted sender"
2070 /^unknown / "encountered unrecognized mechanism during SPF processing of $query->{spf_source}"
2071 unknown "error in processing during lookup of $query->{sender}"
2072 neutral "$ip is neither permitted nor denied by domain of $query->{sender}"
2073 error "encountered temporary error during SPF processing of $query->{spf_source}"
2074 none "$query->{spf_source} does not designate permitted sender hosts"
2075 "could not perform SPF query for $query->{spf_source}" );
2076 =cut
2077
2078 #TDspf 39053DC: SPF none: smtp_comment=SPF: domain of sender user@example.com does not designate mailers, header_comment=sample.net: domain of user@example.com does not designate permitted sender hosts
2079 #TDspf : SPF none: smtp_comment=SPF: domain of sender user@example.com does not designate mailers, header_comment=sample.net: domain of user@example.com does not designate permitted sender hosts
2080 #TDspf : SPF pass: smtp_comment=Please see http://www.openspf.org/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=sample.net: example.com MX mail.example.com A 10.0.0.1, header_comment=example.com: domain of user@example.com designates 10.0.0.1 as permitted sender
2081 #TDspf : SPF fail: smtp_comment=Please see http://www.openspf.org/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=sample.net, header_comment=sample.net: domain of user@example.com does not designate 10.0.0.1 as permitted sender
2082 #TDspf : : SPF none: smtp_comment=SPF: domain of sender does not designate mailers, header_comment=mx1.example.com: domain of does not designate permitted sender hosts
2083
2084 if (my ($result, $reply) = ($line =~ /^(SPF [^:]+): (.*)$/)) {
2085
2086 #print "result: $result\n\treply: $reply\n\tORIG: \"$Logreporters::Reports::origline\"\n";
2087
2088 if ($reply =~ /^(?:smtp_comment=)(.*)$/) {
2089 $reply = $1;
2090
2091 # SPF none
2092 if ($reply =~ /^SPF: domain of sender (?:(?:[^@]+@)?(\S+) )?does not designate mailers/) {
2093 $domain = $1 ? $1 : '*unknown';
2094 #print "result: $result: domain: $domain\n";
2095 }
2096 elsif ($reply =~ /^Please see http:\/\/[^\/]+\/why\.html\?sender=(?:.+%40)?([^&]+)&ip=([^&]+)/) {
2097 ($domain,$ip) = ($1,$2);
2098 #print "result: $result: domain: $domain, IP: $ip\n";
2099 }
2100
2101 # SPF unknown
2102 elsif ($reply =~ /^SPF record error: ([^,]+), .*: error in processing during lookup of (?:[^@]+\@)?(\S+)/) {
2103 ($message, $domain) = ($1, $2);
2104 #print "result: $result: domain: $domain, Problem: $message\n";
2105 }
2106 elsif ($reply =~ /^SPF record error: ([^,]+), .*: encountered unrecognized mechanism during SPF processing of domain (?:[^@]+\@)?(\S+)/) {
2107 ($message, $domain) = ($1,$2);
2108 #print "result: \"$result\": domain: $domain, Problem: $message\n";
2109 $result = "SPF permerror" if ($result =~ /SPF unknown mx-all/);
2110 }
2111 else {
2112 inc_unmatched('postfix_policy_spf(3)');
2113 return;
2114 }
2115 }
2116 else {
2117 inc_unmatched('postfix_policy_spf(4)');
2118 return;
2119 }
2120
2121 $Totals{'policyspf'}++;
2122 if ($message) {
2123 $Counts{'policyspf'}{'Policy Action'}{$result}{$domain}{$ip}{$message}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
2124 }
2125 else {
2126 $Counts{'policyspf'}{'Policy Action'}{$result}{$domain}{$ip}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'});
2127 }
2128 return;
2129 }
2130
2131
2132 inc_unmatched('postfix_policy_spf(5)');
2133 }
2134
2135 1;
2136
2137 #MODULE: ../Logreporters/Postfwd.pm
2138 package Logreporters::Postfwd;
2139
2140 use 5.008;
2141 use strict;
2142 use re 'taint';
2143 use warnings;
2144
2145 BEGIN {
2146 use Exporter ();
2147 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
2148 $VERSION = '1.000';
2149 @ISA = qw(Exporter);
2150 @EXPORT = qw(&postfix_postfwd);
2151 }
2152
2153 use subs @EXPORT;
2154
2155 BEGIN {
2156 import Logreporters::RegEx qw($re_QID);
2157 import Logreporters::TreeData qw(%Totals %Counts $END_KEY);
2158 import Logreporters::Utils;
2159 import Logreporters::Reports qw(&inc_unmatched);
2160 }
2161
2162 # postfwd: http://postfwd.org/
2163 #
2164 #
2165 sub postfix_postfwd($) {
2166 my $line = shift;
2167
2168 return if (
2169 #TDpfw [STATS] Counters: 213000 seconds uptime, 39 rules
2170 #TDpfw [LOGS info]: compare rbl: "example.com[10.1.0.7]" -> "localrbl.local"
2171 #TDpfw [DNSBL] object 10.0.0.1 listed on rbl:list.dnswl.org (answer: 127.0.15.0, time: 0s)
2172 $line =~ /^\[STATS\] / or
2173 $line =~ /^\[DNSBL\] / or
2174 $line =~ /^\[LOGS info\]/ or
2175 $line =~ /^Process Backgrounded/ or
2176 $line =~ /^Setting [ug]id to/ or
2177 $line =~ /^Binding to TCP port/ or
2178 $line =~ /^terminating\.\.\./ or
2179 $line =~ /^Setting status interval to \S+ seconds/ or
2180 $line =~ /^postfwd .+ ready for input$/ or
2181 $line =~ /postfwd .+ (?:starting|terminated)/
2182 );
2183
2184 my ($type,$rule,$id,$action,$host,$hostip,$recipient);
2185
2186 if ($line =~ /^\[(RULES|CACHE)\] rule=(\d+), id=([^,]+), client=([^[]+)\[([^]]+)\], sender=.*?, recipient=<(.*?)>,.*? action=(.*)$/) {
2187 #TDpfw [RULES] rule=0, id=OK_DNSWL, client=example.com[10.0.0.1], sender=<f@example.com>, recipient=<to@sample.net>, helo=<example.com>, proto=ESMTP, state=RCPT, delay=0s, hits=OK_DNSWL, action=DUNNO
2188 #TDpfw [CACHE] rule=14, id=GREY_NODNS, client=unknown[192.168.0.1], sender=<f@example.net>, recipient=<to@sample.com>, helo=<example.com>, proto=ESMTP, state=RCPT, delay=0s, hits=SET_NODNS;EVAL_DNSBLS;EVAL_RHSBLS;GREY_NODNS, action=greylist
2189 ($type,$rule,$id,$host,$hostip,$recipient,$action) = ($1,$2,$3,$4,$5,$6,$7);
2190 $recipient = '*unknown' if (not defined $recipient);
2191 $Counts{'postfwd'}{"Rule $rule"}{$id}{$action}{$type}{$recipient}{formathost($hostip,$host)}++ if ($Logreporters::TreeData::Collecting{'postfwd'});
2192 }
2193 elsif (($line =~ /Can't connect to TCP port/) or
2194 ($line =~ /Pid_file already exists for running process/)
2195 )
2196 {
2197 $line =~ s/^[-\d\/:]+ //; # strip leading date/time stamps 2009/07/18-20:09:49
2198 $Totals{'warningsother'}++; return unless ($Logreporters::TreeData::Collecting{'warningsother'});
2199 $Counts{'warningsother'}{"$Logreporters::service_name: $line"}++;
2200 return;
2201 }
2202
2203 # ignoring [DNSBL] lines
2204 #elsif ($line =~ /^\[DNSBL\] object (\S+) listed on (\S+) \(answer: ([^,]+), .*\)$/) {
2205 # #TDpfw [DNSBL] object 10.0.0.60 listed on rbl:list.dnswl.org (answer: 127.0.15.0, time: 0s)
2206 # ($type,$rbl) = split (/:/, $2);
2207 # $Counts{'postfwd'}{"DNSBL: $type"}{$rbl}{$1}{$3}{''}++ if ($Logreporters::TreeData::Collecting{'postfwd'});
2208 #}
2209 else {
2210 inc_unmatched('postfwd');
2211 return;
2212 }
2213
2214 $Totals{'postfwd'}++;
2215 }
2216
2217 1;
2218
2219 #MODULE: ../Logreporters/Postgrey.pm
2220 package Logreporters::Postgrey;
2221
2222 use 5.008;
2223 use strict;
2224 use re 'taint';
2225 use warnings;
2226
2227 my (%pgDelays,%pgDelayso);
2228
2229 BEGIN {
2230 use Exporter ();
2231 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
2232 $VERSION = '1.000';
2233 @ISA = qw(Exporter);
2234 @EXPORT = qw(&postfix_postgrey &print_postgrey_reports);
2235 }
2236
2237 use subs @EXPORT;
2238
2239 BEGIN {
2240 import Logreporters::RegEx qw($re_QID);
2241 import Logreporters::TreeData qw(%Totals %Counts $END_KEY);
2242 import Logreporters::Utils;
2243 import Logreporters::Config qw(%Opts);
2244 import Logreporters::Reports qw(&inc_unmatched &print_percentiles_report2);
2245 }
2246
2247 # postgrey: http://postgrey.schweikert.ch/
2248 #
2249 # Triplet: (client IP, envelope sender, envelope recipient address)
2250 #
2251 sub postfix_postgrey($) {
2252 my $line = shift;
2253
2254 return if (
2255 #TDpg cleaning up old logs...
2256 #TDpg cleaning up old entries...
2257 #TDpg cleaning clients database finished. before: 207, after: 207
2258 #TDpg cleaning main database finished. before: 3800, after: 2539
2259 #TDpg delayed 603 seconds: client=10.0.example.com, from=anyone@sample.net, to=joe@example.com
2260
2261 #TDpg Setting uid to "504"
2262 #TDpg Setting gid to "1002 1002"
2263 #TDpg Process Backgrounded
2264 #TDpg 2008/03/08-15:54:49 postgrey (type Net::Server::Multiplex) starting! pid(21961)
2265 #TDpg Binding to TCP port 10023 on host 127.0.0.1
2266 #TDpg 2007/01/25-14:58:24 Server closing!
2267 #TDpg Couldn't unlink "/var/run/postgrey.pid" [Permission denied]
2268 #TDpg ignoring garbage: <help>
2269 #TDpg unrecognized request type: ''
2270 #TDpg rm /var/spool/postfix/postgrey/log.0000000002
2271 #TDpg 2007/01/25-14:48:00 Pid_file already exists for running process (4775)... aborting at line 232 in file /usr/lib/perl5/vendor_perl/5.8.7/Net/Server.pm
2272
2273
2274 $line =~ /^cleaning / or
2275 $line =~ /^delayed / or
2276 $line =~ /^cleaning / or
2277 $line =~ /^Setting [ug]id/ or
2278 $line =~ /^Process Backgrounded/ or
2279 $line =~ /^Binding to / or
2280 $line =~ /^Couldn't unlink / or
2281 $line =~ /^ignoring garbage: / or
2282 $line =~ /^unrecognized request type/ or
2283 $line =~ /^rm / or
2284 # unanchored last
2285 $line =~ /Pid_file already exists/ or
2286 $line =~ /postgrey .* starting!/ or
2287 $line =~ /Server closing!/
2288 );
2289
2290 my ($action,$reason,$delay,$host,$ip,$sender,$recip);
2291
2292 if ($line =~ /^(?:$re_QID: )?action=(.*?), reason=(.*?)(?:, delay=(\d+))?, client_name=(.*?), client_address=(.*?)(?:, sender=(.*?))?(?:, +recipient=(.*))?$/o) {
2293 #TDpg action=greylist, reason=new, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
2294 #TDpgQ action=greylist, reason=new, client_name=example.com, client_address=10.0.0.1, sender=from@example.com
2295 #TDpgQ action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
2296 #TDpg action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net
2297 #TDpg action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, recipient=to@sample.net
2298 #TDpg action=pass, reason=triplet found, delay=99, client_name=example.com, client_address=10.0.0.1, recipient=to@sample.net
2299 ($action,$reason,$delay,$host,$ip,$sender,$recip) = ($1,$2,$3,$4,$5,$6,$7);
2300 $reason =~ s/^(early-retry) \(.* missing\)$/$1/;
2301 $recip = '*unknown' if (not defined $recip);
2302 $sender = '' if (not defined $sender);
2303
2304 $Totals{'postgrey'}++;
2305 if ($Logreporters::TreeData::Collecting{'postgrey'}) {
2306 $Counts{'postgrey'}{"\u$action"}{"\u$reason"}{formathost($ip,$host)}{$recip}{$sender}++;
2307
2308 if (defined $delay and $Logreporters::TreeData::Collecting{'postgrey_delays'}) {
2309 $pgDelays{'1: Total'}{$delay}++;
2310
2311 push @{$pgDelayso{'Postgrey'}}, $delay
2312 }
2313 }
2314 }
2315 elsif ($line =~ /^whitelisted: (.*?)(?:\[([^]]+)\])?$/) {
2316 #TDpg: whitelisted: example.com[10.0.0.1]
2317 $Totals{'postgrey'}++;
2318 if ($Logreporters::TreeData::Collecting{'postgrey'}) {
2319 $Counts{'postgrey'}{'Whitelisted'}{defined $2 ? formathost($2,$1) : $1}{$END_KEY}++;
2320 }
2321 }
2322 elsif ($line =~ /^tarpit whitelisted: (.*?)(?:\[([^]]+)\])?$/) {
2323 #TDpg: tarpit whitelisted: example.com[10.0.0.1]
2324 $Totals{'postgrey'}++;
2325 if ($Logreporters::TreeData::Collecting{'postgrey'}) {
2326 $Counts{'postgrey'}{'Tarpit whitelisted'}{defined $2 ? formathost($2,$1) : $1}{$END_KEY}++;
2327 }
2328 }
2329 else {
2330 inc_unmatched('postgrey');
2331 }
2332
2333 return;
2334 }
2335
2336 sub print_postgrey_reports() {
2337 #print STDERR "pgDelays memory usage: ", commify(Devel::Size::total_size(\%pgDelays)), "\n";
2338
2339 if ($Opts{'postgrey_delays'}) {
2340 my @table;
2341 for (sort keys %pgDelays) {
2342 # anon array ref: label, array ref of $Delay{key}
2343 push @table, [ substr($_,3), $pgDelays{$_} ];
2344 }
2345 if (@table) {
2346 print_percentiles_report2(\@table, "Postgrey Delays Percentiles", $Opts{'postgrey_delays_percentiles'});
2347 }
2348 }
2349 }
2350
2351 1;
2352
2353 #MODULE: ../Logreporters/PolicydWeight.pm
2354 package Logreporters::PolicydWeight;
2355
2356 use 5.008;
2357 use strict;
2358 use re 'taint';
2359 use warnings;
2360
2361 BEGIN {
2362 use Exporter ();
2363 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
2364 $VERSION = '1.000';
2365 @ISA = qw(Exporter);
2366 @EXPORT = qw(&postfix_policydweight);
2367 }
2368
2369 use subs @EXPORT;
2370
2371 BEGIN {
2372 import Logreporters::Reports qw(&inc_unmatched);
2373 import Logreporters::TreeData qw(%Totals %Counts);
2374 import Logreporters::Utils;
2375 }
2376
2377 # Handle postfix/policydweight entries
2378 #
2379 sub postfix_policydweight($) {
2380 my $line = shift;
2381 my ($r1, $code, $reason, $reason2);
2382
2383 if (
2384 $line =~ /^weighted check/ or
2385 $line =~ /^policyd-weight .* started and daemonized/ or
2386 $line =~ /^(cache|child|master): / or
2387 $line =~ /^cache (?:spawned|killed)/ or
2388 $line =~ /^child \d+ exited/ or
2389 $line =~ /^Daemon terminated/ or
2390 $line =~ /^Daemon terminated/
2391 )
2392 {
2393 #print "$OrigLine\n";
2394 return;
2395 }
2396
2397 if ($line =~ s/^decided action=//) {
2398 $line =~ s/; delay: \d+s$//; # ignore, eg.: "delay: 3s"
2399 #print "....\n\tLINE: $line\n\tORIG: '$Logreporters::Reports::origline'\n";
2400 if (($code,$r1) = ($line =~ /^(\d+)\s+(.*)$/ )) {
2401 my @problems = ();
2402 for (split /; */, $r1) {
2403
2404 if (/^Mail appeared to be SPAM or forged\. Ask your Mail\/DNS-Administrator to correct HELO and DNS MX settings or to get removed from DNSBLs/ ) {
2405 push @problems, 'spam/forged: bad DNS/hit DNSRBLs';
2406 }
2407 elsif (/^Your MTA is listed in too many DNSBLs/) {
2408 push @problems, 'too many DNSBLs';
2409 }
2410 elsif (/^temporarily blocked because of previous errors - retrying too fast\. penalty: \d+ seconds x \d+ retries\./) {
2411 push @problems, 'temp blocked: retrying too fast';
2412 }
2413 elsif (/^Please use DynDNS/) {
2414 push @problems, 'use DynDNS';
2415 }
2416 elsif (/^please relay via your ISP \([^)]+\)/) {
2417 push @problems, 'use ISP\'s relay';
2418 }
2419 elsif (/^in (.*)/) {
2420 push @problems, $1;
2421 }
2422 elsif (m#^check http://rbls\.org/\?q=#) {
2423 push @problems, 'see http://rbls.org';
2424 }
2425 elsif (/^MTA helo: .* \(helo\/hostname mismatch\)/) {
2426 push @problems, 'helo/hostname mismatch';
2427 }
2428 elsif (/^No DNS entries for your MTA, HELO and Domain\. Contact YOUR administrator\s+/) {
2429 push @problems, 'no DNS entries';
2430 }
2431 else {
2432 push @problems, $_;
2433 }
2434 }
2435
2436 $reason = $code; $reason2 = join (', ', @problems);
2437 }
2438 elsif ($line =~ s/^DUNNO\s+//) {
2439 #decided action=DUNNO multirecipient-mail - already accepted by previous query; delay: 0s
2440 $reason = 'DUNNO'; $reason2 = $line;
2441 }
2442 elsif ($line =~ s/^check_greylist//) {
2443 #decided action=check_greylist; delay: 16s
2444 $reason = 'Check greylist'; $reason2 = $line;
2445 }
2446 elsif ($line =~ s/^PREPEND X-policyd-weight:\s+//) {
2447 #decided action=PREPEND X-policyd-weight: using cached result; rate: -7.6; delay: 0s
2448 if ($line =~ /(using cached result); rate:/) {
2449 $reason = 'PREPEND X-policyd-weight: mail accepted'; $reason2 = "\u$1";
2450 }
2451 else {
2452 #decided action=PREPEND X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 P0F_LINUX=0 <client=10.0.0.1> <helo=example.com> <from=f@example.com> <to=t@sample.net>, rate: -7.6; delay: 2s
2453 $reason = 'PREPEND X-policyd-weight: mail accepted'; $reason2 = 'Varies';
2454 }
2455 }
2456 else {
2457 return;
2458 }
2459 }
2460 elsif ($line =~ /^err/) {
2461 # coerrce policyd-weight err's into general warnings
2462 $Totals{'startuperror'}++;
2463 $Counts{'startuperror'}{'Service: policyd-weight'}{$line}++ if ($Logreporters::TreeData::Collecting{'startuperror'});
2464 return;
2465 }
2466 else {
2467 inc_unmatched('policydweight');
2468 return;
2469 }
2470
2471 $Totals{'policydweight'}++;
2472 $Counts{'policydweight'}{$reason}{$reason2}++ if ($Logreporters::TreeData::Collecting{'policydweight'});
2473 }
2474
2475 1;
2476
2477
2478 package Logreporters;
2479
2480 BEGIN {
2481 import Logreporters::Utils;
2482 import Logreporters::Config;
2483 import Logreporters::TreeData qw(%Totals %Counts %Collecting printTree buildTree $END_KEY);
2484 import Logreporters::RegEx qw($re_IP $re_DSN $re_QID $re_DDD);
2485 import Logreporters::Reports;
2486 import Logreporters::RFC3463;
2487 import Logreporters::PolicySPF;
2488 import Logreporters::Postfwd;
2489 import Logreporters::Postgrey;
2490 import Logreporters::PolicydWeight;
2491 }
2492 use 5.008;
2493 use strict;
2494 use warnings;
2495 no warnings "uninitialized";
2496 use re 'taint';
2497
2498 use File::Basename;
2499 our $progname = fileparse($0);
2500
2501 my @supplemental_reports = qw(delays postgrey_delays);
2502
2503 # Default values for various options. These are used
2504 # to reset default values after an option has been
2505 # disabled (via undef'ing its value). This allows
2506 # a report to be disabled via config file or --nodetail,
2507 # but reenabled via subsequent command line option
2508 my %Defaults = (
2509 detail => 10, # report level detail
2510 max_report_width => 100, # maximum line width for report output
2511 line_style => undef, # lines > max_report_width, 0=truncate,1=wrap,2=full
2512 syslog_name => 'postfix', # service name (postconf(5), syslog_name)
2513 sect_vars => 0, # show section vars in detail report hdrs
2514 unknown => 1, # show 'unknown' in address/hostname pairs
2515 ipaddr_width => 15, # width for printing ip addresses
2516 delays => 1, # show message delivery delays report
2517 delays_percentiles => '0 25 50 75 90 95 98 100', # percentiles shown in delays report
2518 reject_reply_patterns => '5.. 4.. warn', # reject reply grouping patterns
2519 postgrey_delays => 1, # show postgrey delays report
2520 postgrey_delays_percentiles => '0 25 50 75 90 95 98 100', # percentiles shown in postgrey delays report
2521 );
2522
2523 my $usage_str = <<"END_USAGE";
2524 Usage: $progname [ ARGUMENTS ] [logfile ...]
2525 ARGUMENTS can be one or more of options listed below. Later options
2526 override earlier ones. Any argument may be abbreviated to an unambiguous
2527 length. Input is read from the named logfile(s), or STDIN.
2528
2529 --debug AREAS provide debug output for AREAS
2530 --help print usage information
2531 --version print program version
2532
2533 --config_file FILE, -f FILE use alternate configuration file FILE
2534 --ignore_services PATTERN ignore postfix/PATTERN services
2535 --syslog_name PATTERN only consider log lines that match
2536 syslog service name PATTERN
2537
2538 --detail LEVEL print LEVEL levels of detail
2539 (default: 10)
2540 --nodetail set all detail levels to 0
2541 --[no]summary display the summary section
2542
2543 --ipaddr_width WIDTH use WIDTH chars for IP addresses in
2544 address/hostname pairs
2545 --line_style wrap|full|truncate disposition of lines > max_report_width
2546 (default: truncate)
2547 --full same as --line_style=full
2548 --truncate same as --line_style=truncate
2549 --wrap same as --line_style=wrap
2550 --max_report_width WIDTH limit report width to WIDTH chars
2551 (default: 100)
2552 --limit L=V, -l L=V set level limiter L with value V
2553 --[no]unknown show the 'unknown' hostname in
2554 formatted address/hostnames pairs
2555 --[no]sect_vars [do not] show config file var/cmd line
2556 option names in section titles
2557
2558 --recipient_delimiter C split delivery addresses using
2559 recipient delimiter char C
2560 --reject_reply_patterns "R1 [R2 ...]" set reject reply patterns used in
2561 to group rejects to R1, [R2 ...],
2562 where patterns are [45][.0-9][.0-9]
2563 or "Warn" (default: 5.. 4.. Warn)
2564 Supplimental reports
2565 --[no]delays [do not] show msg delays percentiles report
2566 --delays_percentiles "P1 [P2 ...]" set delays report percentiles to
2567 P1 [P2 ...] (range: 0...100)
2568 --[no]postgrey_delays [do not] show postgrey delays percentiles
2569 report
2570 --postgrey_delays_percentiles "P1 [P2 ...]"
2571 set postgrey delays report percentiles to
2572 P1 [P2 ...] (range: 0...100)
2573 END_USAGE
2574
2575 my @RejectPats; # pattern list used to match against reject replys
2576 my @RejectKeys; # 1-to-1 with RejectPats, but with 'x' replacing '.' (for report output)
2577 my (%DeferredByQid, %SizeByQid, %AcceptedByQid, %Delays);
2578
2579 # local prototypes
2580 sub usage;
2581 sub init_getopts_table;
2582 sub init_defaults;
2583 sub build_sect_table;
2584 sub postfix_bounce;
2585 sub postfix_cleanup;
2586 sub postfix_panic;
2587 sub postfix_fatal;
2588 sub postfix_error;
2589 sub postfix_warning;
2590 sub postfix_script;
2591 sub postfix_postsuper;
2592 sub process_delivery_attempt;
2593 sub cleanhostreply;
2594 sub strip_ftph;
2595 sub get_reject_key;
2596 sub expand_bare_reject_limiters;
2597 sub create_ignore_list;
2598 sub in_ignore_list;
2599 sub header_body_checks;
2600 sub milter_common;
2601
2602 # lines that match any RE in this list will be ignored.
2603 # see create_ignore_list();
2604 my @ignore_list = ();
2605
2606 # The Sections table drives Summary and Detail reports. For each entry in the
2607 # table, if there is data avaialable, a line will be output in the Summary report.
2608 # Additionally, a sub-section will be output in the Detail report if both the
2609 # global --detail, and the section's limiter variable, are sufficiently high (a
2610 # non-existent section limiter variable is considered to be sufficiently high).
2611 #
2612 my @Sections = ();
2613
2614 # List of reject variants. See also: "Add reject variants" below, and conf file(s).
2615 my @RejectClasses = qw(
2616 rejectrelay rejecthelo rejectdata rejectunknownuser rejectrecip rejectsender
2617 rejectclient rejectunknownclient rejectunknownreverseclient rejectunverifiedclient
2618 rejectrbl rejectheader rejectbody rejectcontent rejectsize rejectmilter rejectproxy
2619 rejectinsufficientspace rejectconfigerror rejectverify rejectetrn rejectlookupfailure
2620 );
2621
2622 # Dispatch table of the list of supported policy services
2623 # XXX have add-ins register into the dispatch table
2624 my @policy_services = (
2625 [ qr/^postfwd/, \&Logreporters::Postfwd::postfix_postfwd ],
2626 [ qr/^postgrey/, \&Logreporters::Postgrey::postfix_postgrey ],
2627 [ qr/^policyd?-spf/, \&Logreporters::PolicySPF::postfix_policy_spf ],
2628 [ qr/^policyd-?weight/, \&Logreporters::PolicydWeight::postfix_policydweight ],
2629 );
2630
2631 # Initialize main running mode and basic opts
2632 init_run_mode($config_file);
2633
2634 # Configure the Getopts options table
2635 init_getopts_table();
2636
2637 # Place configuration file/environment variables onto command line
2638 init_cmdline();
2639
2640 # Initialize default values
2641 init_defaults();
2642
2643 # Process command line arguments, 0=no_permute,no_pass_through
2644 get_options(0);
2645
2646 # Build the Section table, after reject_reply_patterns is final
2647 build_sect_table();
2648
2649 # Expand bare rejects before generic processing
2650 expand_bare_reject_limiters();
2651
2652 # Run through the list of Limiters, setting the limiters in %Opts.
2653 process_limiters(@Sections);
2654
2655 # Set collection for any enabled supplemental sections
2656 foreach (@supplemental_reports) {
2657 $Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0;
2658 }
2659
2660 if (! defined $Opts{'line_style'}) {
2661 # default line style to full if detail >= 11, or truncate otherwise
2662 $Opts{'line_style'} =
2663 ($Opts{'detail'} > 10) ? $line_styles{'full'} : $line_styles{'truncate'};
2664 }
2665
2666 # Create the list of REs used to match against log lines
2667 create_ignore_list();
2668
2669 # Notes:
2670 #
2671 # - IN REs, always use /o flag or qr// at end of RE when RE uses interpolated vars
2672 # - In REs, email addresses may be empty "<>" - capture using *, not + ( eg. from=<.*?> )
2673 # - See additional notes below, search for "Note:".
2674 # - XXX indicates change, fix or thought required
2675
2676
2677 # Main processing loop
2678 #
2679 LINE: while ( <> ) {
2680 chomp;
2681 s/\s+$//;
2682 next unless length $_;
2683
2684 $Logreporters::Reports::origline = $_;
2685
2686 # Linux: Jul 1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
2687 # FreeBSD: Jul 1 20:08:06 <mail.info> mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1]
2688 # Aug 17 15:16:12 mailhost postfix/cleanup[14194]: [ID 197553 mail.info] EC2B339E5: message-id=<2616.EC2B339E5@example.com>
2689 # Dec 25 05:20:28 mailhost policyd-spf[14194]: [ID 27553 mail.info] ... policyd-spf stuff ...
2690
2691 next unless /^[A-Z][a-z]{2} [ \d]\d \d{2}:\d{2}:\d{2} (?:<[^>]+> )?(\S+) ($Opts{'syslog_name'}(?:\/([^:[]+))?)(?:\[\d+\])?: (?:\[ID \d+ \w+\.\w+\] )?(.*)$/o;
2692
2693 our $service_name = $3;
2694 my ($mailhost,$server_name,$p1) = ($1,$2,$4);
2695 #print "mailhost: $mailhost, servername: $server_name, servicename: $service_name, p1: $p1\n";
2696
2697 $service_name = $server_name unless $service_name;
2698 #print "service_name: $service_name\n";
2699
2700 # ignored postfix services...
2701 next if $service_name eq 'postlog';
2702 next if $service_name =~ /^$Opts{'ignore_services'}$/o;
2703
2704 # common log entries up front
2705 if ($p1 =~ s/^connect from //) {
2706 #TD25 connect from sample.net[10.0.0.1]
2707 #TD connect from mail.example.com[2001:dead:beef::1]
2708 #TD connect from localhost.localdomain[127.0.0.1]
2709 #TD connect from unknown[unknown]
2710 $Totals{'connectioninbound'}++;
2711 next unless ($Collecting{'connectioninbound'});
2712
2713 my $host = $p1; my $hostip;
2714 if (($host,$hostip) = ($host =~ /^([^[]+)\[([^]]+)\]/)) {
2715 $host = formathost($hostip,$host);
2716 }
2717 $Counts{'connectioninbound'}{$host}++;
2718 next;
2719 }
2720
2721 if ($p1 =~ /^disconnect from /) {
2722 #TD25 disconnect from sample.net[10.0.0.1]
2723 #TD disconnect from mail.example.com[2001:dead:beef::1]
2724 $Totals{'disconnection'}++;
2725 next;
2726 }
2727
2728 if ($p1 =~ s/^connect to //) {
2729 next if ($p1 =~ /^subsystem /);
2730 $Totals{'connecttofailure'}++;
2731 next unless ($Collecting{'connecttofailure'});
2732
2733 my ($host,$hostip,$reason,$port) = ($p1 =~ /^([^[]+)\[([^]]+)\](?::\d+)?: (.*?)(?:\s+\(port (\d+)\))?$/);
2734 # all "connect to" messages indicate a problem with the connection
2735 #TDs connect to example.org[10.0.0.1]: Connection refused (port 25)
2736 #TDs connect to mail.sample.com[10.0.0.1]: No route to host (port 25)
2737 #TDs connect to sample.net[192.168.0.1]: read timeout (port 25)
2738 #TDs connect to mail.example.com[10.0.0.1]: server dropped connection without sending the initial SMTP greeting (port 25)
2739 #TDs connect to mail.example.com[192.168.0.1]: server dropped connection without sending the initial SMTP greeting (port 25)
2740 #TDs connect to ipv6-1.example.com[2001:dead:beef::1]: Connection refused (port 25)
2741 #TDs connect to ipv6-2.example.com[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]: Connection refused (port 25)
2742 #TDs connect to ipv6-3.example.com[1080:0:0:0:8:800:200C:4171]: Connection refused (port 25)
2743 #TDs connect to ipv6-4.example.com[3ffe:2a00:100:7031::1]: Connection refused (port 25)
2744 #TDs connect to ipv6-5.example.com[1080::8:800:200C:417A]: Connection refused (port 25)
2745 #TDs connect to ipv6-6.example.com[::192.9.5.5]: Connection refused (port 25)
2746 #TDs connect to ipv6-7.example.com[::FFFF:129.144.52.38]: Connection refused (port 25)
2747 #TDs connect to ipv6-8.example.com[2010:836B:4179::836B:4179]: Connection refused (port 25)
2748 #TDs connect to mail.example.com[10.0.0.1]: server refused to talk to me: 452 try later (port 25)
2749
2750 $host = join(' :', $host, $port) if ($port and $port ne '25');
2751 # Note: See ConnectToFailure below
2752 if ($reason =~ /^server (refused to talk to me): (.*)$/) {
2753 $Counts{'connecttofailure'}{ucfirst($1)}{formathost($hostip,$host)}{$2}++;
2754 } else {
2755 $Counts{'connecttofailure'}{ucfirst($reason)}{formathost($hostip,$host)}{''}++;
2756 }
2757 next;
2758 }
2759
2760 =pod
2761 real 3m43.997s
2762 user 3m39.038s
2763 sys 0m3.005s
2764 =pod
2765 # Handle before panic, fatal, warning, so that service-specific code gets first crack
2766 # XXX replace w/dispatch table for add-ins, so user's can add their own...
2767 if ($service_name eq 'postfwd') { postfix_postfwd($p1); next; }
2768 if ($service_name eq 'postgrey') { postfix_postgrey($p1); next; }
2769 if ($service_name =~ /^policyd?-spf/) { postfix_policy_spf($p1); next; } # postfix/policy-spf
2770 if ($service_name =~ /^policyd-?weight/) { postfix_policydweight($p1); next; } # postfix/policydweight
2771
2772 =cut
2773 # Handle policy service handlers before panic, fatal, warning, etc.
2774 # messages so that service-specific code gets first crack.
2775 # 5:25
2776 foreach (@policy_services) {
2777 if ($service_name =~ $_->[0]) {
2778 #print "Calling policy service helper: $service_name:('$p1')\n";
2779 &{$_->[1]}($p1);
2780 next LINE;
2781 }
2782 };
2783 #=cut
2784
2785 # ^warning: ...
2786 # ^fatal: ...
2787 # ^panic: ...
2788 # ^error: ...
2789 if ($p1 =~ /^warning: +(.*)$/) { postfix_warning($1); next; }
2790 if ($p1 =~ /^fatal: +(.*)$/) { postfix_fatal($1); next; }
2791 if ($p1 =~ /^panic: +(.*)$/) { postfix_panic($1); next; }
2792 if ($p1 =~ /^error: +(.*)$/) { postfix_error($1); next; }
2793
2794 # output by all services that use table lookups - process before specific messages
2795 if ($p1 =~ /(?:lookup )?table (?:[^ ]+ )?has changed -- (?:restarting|exiting)$/) {
2796 #TD table hash:/var/mailman/data/virtual-mailman(0,lock|fold_fix) has changed -- restarting
2797 #TD table hash:/etc/postfix/helo_checks has changed -- restarting
2798 $Totals{'tablechanged'}++;
2799 next;
2800 }
2801
2802 # postfix/postscreen and postfix/verify services
2803 if ($service_name eq 'postscreen'
2804 or $service_name eq 'verify') { postfix_postscreen($p1); next; } # postfix/postscreen, postfix/verify
2805 if ($service_name eq 'dnsblog') { postfix_dnsblog($p1); next; } # postfix/dnsblog
2806 if ($service_name =~ /^cleanup/) { postfix_cleanup($p1); next; } # postfix/cleanup*
2807 if ($service_name =~ /^bounce/) { postfix_bounce($p1); next; } # postfix/bounce*
2808 if ($service_name eq 'postfix-script') { postfix_script($p1); next; } # postfix/postfix-script
2809 if ($service_name eq 'postsuper') { postfix_postsuper($p1); next; } # postfix/postsuper
2810
2811 my ($helo, $relay, $from, $origto, $to, $domain, $status,
2812 $type, $reason, $reason2, $filter, $site, $cmd, $qid,
2813 $rej_type, $reject_name, $host, $hostip, $dsn, $reply, $fmthost, $bytes);
2814
2815 $rej_type = undef;
2816
2817 # ^$re_QID: ...
2818 if ($p1 =~ s/^($re_QID): //o) {
2819 $qid = $1;
2820
2821 next if ($p1 =~ /^host \S*\[\S*\] said: 4\d\d/); # deferrals, picked up in "status=deferred"
2822
2823 if ($p1 =~ /^removed\s*$/ ) {
2824 # Note: See REMOVED elsewhere
2825 # 52CBDC2E0F: removed
2826 delete $SizeByQid{$qid} if (exists $SizeByQid{$qid});
2827 $Totals{'removedfromqueue'}++;
2828 next;
2829 }
2830
2831 # coerce into general warning
2832 if (($p1 =~ /^Cannot start TLS: handshake failure/) or
2833 ($p1 =~ /^non-E?SMTP response from/)) {
2834 postfix_warning($p1);
2835 next;
2836 }
2837
2838 if ($p1 eq 'status=deferred (bounce failed)') {
2839 #TDqQ status=deferred (bounce failed)
2840 $Totals{'bouncefailed'}++;
2841 next;
2842 }
2843
2844 # this test must preceed access checks below
2845 #TDsQ replace: header From: "Postmaster" <postmaster@webmail.example.com>: From: "Postmaster" <postmaster@webmail.example.org>
2846 if ($service_name eq 'smtp' and header_body_checks($p1)) {
2847 #print "main: header_body_checks\n";
2848 next;
2849 }
2850
2851 # Postfix access actions
2852 # REJECT optional text...
2853 # DISCARD optional text...
2854 # HOLD optional text...
2855 # WARN optional text...
2856 # FILTER transport:destination
2857 # REDIRECT user@domain
2858 # BCC user@domain (2.6 experimental branch)
2859 # The following actions are indistinguishable in the logs
2860 # 4NN text
2861 # 5NN text
2862 # DEFER_IF_REJECT optional text...
2863 # DEFER_IF_PERMIT optional text...
2864 # UCE restriction...
2865 # The following actions are not logged
2866 # PREPEND headername: headervalue
2867 # DUNNO
2868 #
2869 # Reject actions based on remote client information:
2870 # - one of host name, network address, envelope sender
2871 # or
2872 # - recipient address
2873
2874 # Template of access controls. Rejects look like the first line, other access actions the second.
2875 # ftph is envelope from, envelope to, proto and helo.
2876 # QID: ACTION STAGE from host[hostip]: DSN trigger: explanation; ftph
2877 # QID: ACTION STAGE from host[hostip]: trigger: explanation; ftph
2878
2879 # $re_QID: reject: RCPT|MAIL|CONNECT|HELO|DATA from ...
2880 # $re_QID: reject_warning: RCPT|MAIL|CONNECT|HELO|DATA from ...
2881 if ($p1 =~ /^(reject(?:_warning)?|discard|filter|hold|redirect|warn|bcc|replace): /) {
2882 my $action = $1;
2883 $p1 = substr($p1, length($action) + 2);
2884
2885 #print "action: \"$action\", p1: \"$p1\"\n";
2886 if ($p1 !~ /^(RCPT|MAIL|CONNECT|HELO|EHLO|DATA|VRFY|ETRN|END-OF-MESSAGE) from ([^[]+)\[([^]]+)\](?::\d+)?: (.*)$/) {
2887 inc_unmatched('unexpected access');
2888 next;
2889 }
2890 my ($stage,$host,$hostip,$p1) = ($1,$2,$3,$4); #print "stage: \"$stage\", host: \"$host\", hostip: \"$hostip\", p1: \"$p1\"\n";
2891 my ($efrom,$eto,$proto,$helo) = strip_ftph($p1); #print "efrom: \"$efrom\", eto: \"$eto\", proto: \"$proto\", helo: \"$helo\"\n";
2892 #print "p1 now: \"$p1\"\n";
2893
2894 # QID: ACTION STAGE from host[hostip]: DSN trigger: explanation; ftph
2895 #TDsdN reject_warning: VRFY from host[10.0.0.1]: 450 4.1.2 <<1F4@bs>>: Recipient address rejected: Domain not found; to=<<1F4@bs>> proto=SMTP helo=<friend>
2896 #TDsdN reject: VRFY from host[10.0.0.1]: 550 5.1.1 <:>: Recipient address rejected: User unknown in local recipient table; to=<:> proto=SMTP helo=<10.0.0.1>
2897 #TDsdN reject: VRFY from host[10.0.0.1]: 450 4.1.8 <to@example.com>: Sender address rejected: Domain not found; from=<f@sample.com> to=<eto@example.com> proto=SMTP
2898 #TDsdN reject: VRFY from host[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using zen.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; to=<u> proto=SMTP
2899 #TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.2 <to@example.com>: Recipient address rejected: User unknown in local recipient table; from=<> to=<eto@example.com> proto=SMTP helo=<sample.net>
2900 #TDsdN reject: RCPT from host[10.0.0.1]: 550 <to@example.com>: Recipient address rejected: User unknown in local recipient table; from=<> to=<eto@example.com> proto=SMTP helo=<sample.net>
2901 #TDsdN reject_warning: RCPT from host[10.0.0.1]: 550 <to@example.com>: Recipient address rejected: User unknown in local recipient table; from=<> to=<eto@example.com> proto=SMTP helo=<sample.net>
2902 #TDsdN reject: RCPT from host[10.0.0.1]: 550 5.1.1 <to@example.com>: Recipient address rejected: User unknown in virtual address table; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<localhost>
2903 #TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.1 <to@sample.net>: Recipient address rejected: User unknown in virtual mailbox table; from=<f@sample.net> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2904 #TDsdN reject: RCPT from host[10.0.0.1]: 550 5.5.0 <to@example.com>: Recipient address rejected: User unknown; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<[10.0.0.1]>
2905 #TDsdN reject: RCPT from host[10.0.0.1]: 450 <to@example.net>: Recipient address rejected: Greylisted; from=<f@sample.net> to=<eto@example.net> proto=ESMTP helo=<example.com>
2906 #TDsdN reject: RCPT from host[10.0.0.1]: 454 4.7.1 <to@sample.net>: Recipient address rejected: Access denied; from=<f@sample.com> to=<eto@sample.net> proto=SMTP helo=<example.com>
2907 #TDsdN reject_warning: RCPT from host[10.0.0.1]: 454 4.7.1 <to@sample.net>: Recipient address rejected: Access denied; from=<f@sample.net> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2908 #TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.2 <to@example.com>: Recipient address rejected: Domain not found; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<sample.net>
2909 #TDsdN reject: RCPT from host[10.0.0.1]: 554 <to@example.net>: Recipient address rejected: Please see http://www.openspf.org/why.html?sender=from%40example.net&ip=10.0.0.1&receiver=example.net; from=<from@example.net> to=<to@example.net> proto=ESMTP helo=<to@example.com>
2910 #TDsdN reject: RCPT from host[10.0.0.1]: 550 <to@example.net>: Recipient address rejected: undeliverable address: host example.net[192.168.0.1] said: 550 <unknown@example.net>: User unknown in virtual alias table (in reply to RCPT TO command); from=<from@example.com> to=<unknown@example.net> proto=SMTP helo=<mail.example.com>
2911 #TDsdN reject: RCPT from host[10.0.0.1]: 554 <to@example.com>: Recipient address rejected: Please see http://spf.pobox.com/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=mail; from=<user@example.com> to=<to@sample.net> proto=ESMTP helo=<10.0.0.1>
2912 #TDsdN reject: RCPT from host[10.0.0.1]: 554 <to@sample.net>: Relay access denied; from=<f@example.com> to=<eto@sample.net> proto=SMTP helo=<example.com>
2913 #TDsdN reject_warning: HELO from host[10.0.0.1]: 554 <to@sample.net>: Relay access denied; proto=SMTP helo=<example.com>
2914 #TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.8 <f@sample.net>: Sender address rejected: Domain not found; from=<f@sample.com> to=<to@example.com> proto=ESMTP helo=<sample.net>
2915 #TDsdN reject_warning: RCPT from host[10.0.0.1]: 450 4.1.8 <f@sample.net>: Sender address rejected: Domain not found; from=<f@sample.com> to=<to@example.com> proto=ESMTP helo=<sample.net>
2916 #TDsdN reject: RCPT from host[10.0.0.1]: 550 <f@example.net>: Sender address rejected: undeliverable address: host example.net[10.0.0.1] said: 550 <f@example.net>: User unknown in virtual alias table (in reply to RCPT TO command); from=<f@example.net> to=<eto@example.net> proto=SMTP helo=<example.com>
2917 #TDsdN reject_warning: RCPT from host[10.0.0.1]: 554 <host[10.0.0.1]>: Client host rejected: Access denied; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<friend>
2918 #TDsdN reject: RCPT from host[10.0.0.1]: 554 <host[10.0.0.1]>: Client host rejected: Optional text; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<friend>
2919 #TDsdN reject: CONNECT from host[10.0.0.1]: 503 5.5.0 <host[10.0.1]>: Client host rejected: Improper use of SMTP command pipelining; proto=SMTP
2920
2921 #TDsdN reject_warning: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from=<f@sample.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2922 #TDsdN reject: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from=<f@sample.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2923 #TDsdN reject: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; proto=ESMTP
2924 #TDsdN reject: RCPT from unk[10.0.0.1]: 550 5.7.1 Client host rejected: cannot find your reverse hostname, [10.0.0.1]
2925 #TDsdN reject: CONNECT from unk[unknown]: 421 4.7.1 Client host rejected: cannot find your reverse hostname, [unknown]; proto=SMTP
2926
2927 #TDsdN reject: RCPT from host[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using sbl-xbl.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<friend>
2928 #TDsdN reject_warning: RCPT from host[10.0.0.1]: 554 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using sbl-xbl.spamhaus.org; http://www.spamhaus.org/query/bl?ip=10.0.0.1; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<friend>
2929 #TDsdN reject: RCPT from host[10.0.0.1]: 554 Service denied; Client host [10.0.0.1] blocked using bl.spamcop.net; Blocked - see http://www.spamcop.net/bl.shtml?83.164.27.124; from=<bogus@example.com> to=<user@example.org> proto=ESMTP helo=<example.com>
2930 #TDsdN reject: RCPT from host[10.0.0.1]: 454 4.7.1 <localhost>: Helo command rejected: Access denied; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<localhost>
2931 #TDsdN reject_warning: RCPT from host[10.0.0.1]: 454 4.7.1 <localhost>: Helo command rejected: Access denied; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<localhost>
2932 #TDsdN reject: EHLO from host[10.0.0.1]: 504 5.5.2 <bogus>: Helo command rejected: need fully-qualified hostname; proto=SMTP helo=<bogus>
2933 #TDsdQ reject: DATA from host[10.0.0.1]: 550 5.5.3 <DATA>: Data command rejected: Multi-recipient bounce; from=<> proto=ESMTP helo=<localhost>
2934 #TDsdN reject: ETRN from host[10.0.0.1]: 554 5.7.1 <example.com>: Etrn command rejected: Access denied; proto=ESMTP helo=<example.com>
2935 #TDsdN reject: RCPT from host[10.0.0.1]: 452 Insufficient system storage; from=<f@sample.com> to=<eto@sample.net>
2936 #TDsdN reject_warning: RCPT from host[10.0.0.1]: 451 4.3.5 Server configuration error; from=<f@sample.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
2937 #TDsdN reject: RCPT from host[10.0.0.1]: 450 Server configuration problem; from=<f@sample.net> to=<eto@sample.com> proto=ESMTP helo=<sample.net>
2938 #TDsdN reject: MAIL from host[10.0.0.1]: 552 Message size exceeds fixed limit; proto=ESMTP helo=<localhost>
2939 #TDsdN reject: RCPT from unk[10.0.0.1]: 554 5.7.1 <unk[10.0.0.1]>: Unverified Client host rejected: Access denied; from=<f@sample.net> to=<eto@sample.com> proto=SMTP helo=<sample.net>
2940 #TDsdN reject: MAIL from host[10.0.0.1]: 451 4.3.0 <f@example.com>: Temporary lookup failure; from=<f@example.com> proto=ESMTP helo=<example.com>
2941
2942 # reject, reject_warning
2943 if ($action =~ /^reject/) {
2944 my ($recip);
2945
2946 if ($p1 !~ /^($re_DSN) (.*)$/o) {
2947 inc_unmatched('reject1');
2948 next;
2949 }
2950 ($dsn,$p1) = ($1,$2); #print "dsn: $dsn, p1: \"$p1\"\n";
2951 $fmthost = formathost($hostip,$host);
2952
2953 # reject_warning override temp or perm reject types
2954 $rej_type = ($action eq 'reject_warning' ? 'warn' : get_reject_key($dsn));
2955 #print "REJECT stage: '$rej_type'\n";
2956
2957 if ($Collecting{'byiprejects'} and substr($rej_type,0,1) eq '5') {
2958 $Counts{'byiprejects'}{$fmthost}++;
2959 }
2960
2961 if ($stage eq 'VRFY') {
2962 if ($p1 =~ /^(?:<(\S*)>: )?(.*);$/) {
2963 my ($trigger,$reason) = ($1,$2);
2964 $Totals{$reject_name = "${rej_type}rejectverify" }++; next unless ($Collecting{$reject_name});
2965
2966 if ($reason =~ /^Service unavailable; Client host \[[^]]+\] (blocked using [^;]*);/) {
2967 $reason = join (' ', 'Client host blocked using', $1);
2968 $trigger = '';
2969 }
2970 $Counts{$reject_name}{$reason}{$fmthost}{ucfirst($trigger)}++;
2971 } else {
2972 inc_unmatched('vrfyfrom');
2973 }
2974 next;
2975 }
2976
2977 # XXX there may be several semicolon-separated messages
2978 # Recipient address rejected: Unknown users and via check_recipient_access
2979 if ($p1 =~ /^<(.*)>: Recipient address rejected: ([^;]*);/) {
2980 ($recip,$reason) = ($1,$2);
2981
2982 my ($localpart,$domainpart);
2983 # Unknown users; local mailbox, alias, virtual, relay user, unspecified
2984 if ($recip eq '') { ($localpart, $domainpart) = ('<>', '*unspecified'); }
2985 else {
2986 ($localpart, $domainpart) = split (/@/, lc $recip);
2987 ($localpart, $domainpart) = ($recip, '*unspecified') if ($domainpart eq '');
2988 }
2989
2990 if ($reason =~ s/^User unknown *//) {
2991 $Totals{$reject_name = "${rej_type}rejectunknownuser" }++; next unless ($Collecting{$reject_name});
2992
2993 my ($table) = ($reason =~ /^in ((?:\w+ )+table)/);
2994 $table = 'Address table unavailable' if ($table eq ''); # when show_user_unknown_table_name=no
2995 $Counts{$reject_name}{ucfirst($table)}{$domainpart}{$localpart}{$fmthost}++;
2996 } else {
2997 # check_recipient_access
2998 $Totals{$reject_name = "${rej_type}rejectrecip" }++; next unless ($Collecting{$reject_name});
2999
3000 if ($reason =~ m{^Please see http://[^/]+/why\.html}) {
3001 $reason = 'SPF reject';
3002 }
3003 elsif ($reason =~ /^undeliverable address: host ([^[]+)\[([^]]+)\](?::\d+)? said:/) {
3004 $reason = 'undeliverable address: remote host rejected recipient';
3005 }
3006 $Counts{$reject_name}{ucfirst($reason)}{$domainpart}{$localpart}{$fmthost}++;
3007 }
3008
3009 } elsif ($p1 =~ /^<(.*?)>.* Relay access denied/) {
3010 $Totals{$reject_name = "${rej_type}rejectrelay" }++; next unless ($Collecting{$reject_name});
3011 $Counts{$reject_name}{$fmthost}{$eto}++;
3012
3013 } elsif ($p1 =~ /^<(.*)>: Sender address rejected: (.*);/) {
3014 $Totals{$reject_name = "${rej_type}rejectsender" }++; next unless ($Collecting{$reject_name});
3015 ($from,$reason) = ($1,$2);
3016
3017 if ($reason =~ /^undeliverable address: host ([^[]+)\[([^]]+)\](?::\d+)? said:/) {
3018 $reason = 'undeliverable address: remote host rejected sender';
3019 }
3020 $Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$from ne '' ? $from : '<>'}++;
3021
3022 } elsif ($p1 =~ /^(?:<.*>: )?Unverified Client host rejected: /) {
3023 # check_reverse_client_hostname_access (postfix 2.6+)
3024 $Totals{$reject_name = "${rej_type}rejectunverifiedclient" }++; next unless ($Collecting{$reject_name});
3025 $Counts{$reject_name}{$fmthost}{$helo}{$eto}{$efrom}++;
3026
3027 } elsif ($p1 =~ s/^(?:<.*>: )?Client host rejected: //) {
3028 # reject_unknown_client
3029 # client IP->name mapping fails
3030 # name->IP mapping fails
3031 # name->IP mapping =! client IP
3032 if ($p1 =~ /^cannot find your hostname/) {
3033 $Totals{$reject_name = "${rej_type}rejectunknownclient" }++; next unless ($Collecting{$reject_name});
3034 $Counts{$reject_name}{$fmthost}{$helo}{$eto}{$efrom}++;
3035 }
3036 # reject_unknown_reverse_client_hostname (no PTR record for client's IP)
3037 elsif ($p1 =~ /^cannot find your reverse hostname/) {
3038 $Totals{$reject_name = "${rej_type}rejectunknownreverseclient" }++; next unless ($Collecting{$reject_name});
3039 $Counts{$reject_name}{$hostip}++
3040 }
3041 else {
3042 $Totals{$reject_name = "${rej_type}rejectclient" }++; next unless ($Collecting{$reject_name});
3043 $p1 =~ s/;$//;
3044 $Counts{$reject_name}{ucfirst($p1)}{$fmthost}{$eto}{$efrom}++;
3045 }
3046 } elsif ($p1 =~ /^Service (?:temporarily )?(?:unavailable|denied)[^;]*; (?:(?:Unverified )?Client host |Sender address |Helo command )?\[[^ ]*\] blocked using ([^;]+);/) {
3047 # Note: similar code below: search RejectRBL
3048
3049 # postfix 2.1
3050 #TDsdN reject: RCPT from example.com[10.0.0.5]: 554 Service unavailable; Client host [10.0.0.5] blocked using bl.spamcop.net; Blocked - see http://www.spamcop.net/bl.shtml?10.0.0.5; from=<from@example.com> to=<to@example.net> proto=ESMTP helo=<example.com>
3051 # postfix 2.3+
3052 #TDsdN reject: RCPT from example.com[10.0.0.6]: 554 5.7.1 Service unavailable; Client host [10.0.0.6] blocked using bl.spamcop.net; Blocked - see http://www.spamcop.net/bl.shtml?10.0.0.6; from=<from@example.com> to=<to@example.net> proto=SMTP helo=<example.com>
3053 #TDsdN reject: RCPT from example.com[10.0.0.1]: 550 5.7.1 Service unavailable; Client host [10.0.0.1] blocked using Trend Micro RBL+. Please see http://www.mail-abuse.com/cgi-bin/lookup?ip_address=10.0.0.1; Mail from 10.0.0.1 blocked using Trend Micro Email Reputation database. Please see <http://www.mail-abuse.com/cgi-bin/lookup?10.0.0.1>; from=<from@example.com> to=<to@example.net> proto=SMTP helo=<10.0.0.1>
3054
3055 $Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name});
3056 ($site,$reason) = ($1 =~ /^(.+?)(?:$|(?:[.,] )(.*))/);
3057 $reason =~ s/^reason: // if ($reason);
3058 $Counts{$reject_name}{$site}{$fmthost}{$reason ? $reason : ''}++;
3059
3060 } elsif ($p1 =~ /^<.*>: Helo command rejected: (.*);$/) {
3061 $Totals{$reject_name = "${rej_type}rejecthelo" }++; next unless ($Collecting{$reject_name});
3062 $Counts{$reject_name}{ucfirst($1)}{$fmthost}{$helo}++;
3063
3064 } elsif ($p1 =~ /^<.*>: Etrn command rejected: (.*);$/) {
3065 $Totals{$reject_name = "${rej_type}rejectetrn" }++; next unless ($Collecting{$reject_name});
3066 $Counts{$reject_name}{ucfirst($1)}{$fmthost}{$helo}++;
3067
3068 } elsif ($p1 =~ /^<.*>: Data command rejected: (.*);$/) {
3069 $Totals{$reject_name = "${rej_type}rejectdata" }++; next unless ($Collecting{$reject_name});
3070 $Counts{$reject_name}{$1}{$fmthost}{$helo}++;
3071
3072 } elsif ($p1 =~ /^Insufficient system storage;/) {
3073 $Totals{'warninsufficientspace'}++; # force display in Warnings section also
3074 $Totals{$reject_name = "${rej_type}rejectinsufficientspace" }++; next unless ($Collecting{$reject_name});
3075 $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
3076
3077 } elsif ($p1 =~ /^Server configuration (?:error|problem);/) {
3078 $Totals{'warnconfigerror'}++; # force display in Warnings section also
3079 $Totals{$reject_name = "${rej_type}rejectconfigerror" }++; next unless ($Collecting{$reject_name});
3080 $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
3081
3082 } elsif ($p1 =~ /^Message size exceeds fixed limit;$/) {
3083 # Postfix responds with this message after a MAIL FROM:<...> SIZE=nnn command, where postfix consider's nnn excessive
3084 # Note: similar code below: search RejectSize
3085 # Note: reject_warning does not seem to occur
3086 $Totals{$reject_name = "${rej_type}rejectsize" }++; next unless ($Collecting{$reject_name});
3087 $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
3088
3089 } elsif ($p1 =~ /^<(.*?)>: Temporary lookup failure;/) {
3090 $Totals{$reject_name = "${rej_type}rejectlookupfailure" }++; next unless ($Collecting{$reject_name});
3091 $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++;
3092
3093 # This would capture all other rejects, but I think it might be more useful to add
3094 # additional capture sections based on user reports of uncapture lines.
3095 #
3096 #} elsif ( ($reason) = ($p1 =~ /^([^;]+);/)) {
3097 # $Totals{$rej_type . 'rejectother'}++;
3098 # $Counts{$rej_type . 'rejectother'}{$reason}++;
3099 } else {
3100 inc_unmatched('rejectother');
3101 }
3102 }
3103 # end of $re_QID: reject:
3104
3105 # QID: ACTION STAGE from host[hostip]: trigger: reason; ftph
3106 #
3107 #TDsdN warn: RCPT from host[10.0.0.1]: TEST access WARN action; from=<f@sample.com> to=<eto@example.com> proto=ESMTP helo=<sample.com>
3108 #TDsdN warn: RCPT from host[10.0.0.1]: ; from=<f@sample.com> to=<eto@example.com> proto=ESMTP helo=<sample.net>
3109 #TDsdN discard: RCPT from host[10.0.0.1]: <from@example.com>: Sender address TEST DISCARD action; from=<f@sample.com> to=<eto@example.com> proto=ESMTP helo=<sample.com>
3110 #TDsdN discard: RCPT from host[10.0.0.1]: <host[10.0.0.1]>: Client host TEST DISCARD action w/ip(client_checks); from=<f@sample.com> to=<eto@example.com> proto=ESMTP helo=<sample.com>
3111 #TDsdN discard: RCPT from host[10.0.0.1]: <host[10.0.0.1]>: Unverified Client host triggers DISCARD action; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<10.0.0.1>
3112 #TDsdN hold: RCPT from host[10.0.0.1]: <eto@example.com>: Recipient address triggers HOLD action; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<10.0.0.1>
3113 #TDsdN hold: RCPT from host[10.0.0.1]: <dummy>: Helo command optional text...; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<dummy>
3114 #TDsdN hold: RCPT from host[10.0.0.1]: <dummy>: Helo command triggers HOLD action; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<dummy>
3115 #TDsdN hold: DATA from host[10.0.0.1]: <dummy>: Helo command triggers HOLD action; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<dummy>
3116 #TDsdN filter: RCPT from host[10.0.0.1]: <>: Sender address triggers FILTER filter:somefilter; from=<> to=<eto@example.com> proto=SMTP helo=<sample.com>
3117 #TDsdN filter: RCPT from host[10.0.0.1]: <eto@example.com>: Recipient address triggers FILTER smtp-amavis:[127.0.0.1]:10024; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<sample.net>
3118 #TDsdN redirect: RCPT from host[10.0.0.1]: <example.com[10.0.0.1]>: Client host triggers REDIRECT root@localhost; from=<f@sample.net> to=<eto@example.com> proto=SMTP helo=<localhost>
3119 #TDsdN redirect: RCPT from host[10.0.0.1]: <eto@example.com>: Recipient address triggers REDIRECT root@localhost; from=<f@sample.net> to=<eto@example.com> proto=ESMTP helo=<sample.com>
3120
3121 # BCC action (postfix 2.6+)
3122 #TDsdN bcc: RCPT from host[10.0.0.1]: <user@example.com>: Sender address triggers BCC root@localhost; from=<f@sample.net> to=<eto@sample.com> proto=ESMTP helo=<sample.net>
3123
3124 # $re_QID: discard, filter, hold, redirect, warn, bcc, replace ...
3125 else {
3126 my $trigger;
3127 ($trigger,$reason) = ($p1 =~ /^(?:<(\S*)>: )?(.*);$/ );
3128 if ($trigger eq '') { $trigger = '*unavailable'; }
3129 else { $trigger =~ s/^<(.+)>$/$1/; }
3130 $reason = '*unavailable' if ($reason eq '');
3131 $fmthost = formathost ($hostip,$host);
3132 #print "trigger: \"$trigger\", reason: \"$reason\"\n";
3133
3134 # reason -> subject text
3135 # subject -> "Helo command" : smtpd_helo_restrictions
3136 # subject -> "Client host" : smtpd_client_restrictions
3137 # subject -> "Unverified Client host" : smtpd_client_restrictions
3138 # subject -> "Client certificate" : smtpd_client_restrictions
3139 # subject -> "Sender address" : smtpd_sender_restrictions
3140 # subject -> "Recipient address" : smtpd_recipient_restrictions
3141
3142 # subject -> "Data command" : smtpd_data_restrictions
3143 # subject -> "End-of-data" : smtpd_end_of_data_restrictions
3144 # subject -> "Etrn command" : smtpd_etrn_restrictions
3145
3146 # text -> triggers <ACTION> action|triggers <ACTION> <destination>|optional text...
3147
3148 my ($subject, $text) =
3149 ($reason =~ /^((?:Recipient|Sender) address|(?:Unverified )?Client host|Client certificate|(?:Helo|Etrn|Data) command|End-of-data) (.+)$/o);
3150 #printf "ACTION: '$action', SUBJECT: %-30s TEXT: \"$text\"\n", '"' . $subject . '"';
3151
3152 if ($action eq 'filter') {
3153 $Totals{'filtered'}++; next unless ($Collecting{'filtered'});
3154 # See "Note: Counts" before changing $Counts below re: Filtered
3155 $text =~ s/triggers FILTER //;
3156 if ($subject eq 'Recipient address') { $Counts{'filtered'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; }
3157 elsif ($subject =~ /Client host$/) { $Counts{'filtered'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; }
3158 else { $Counts{'filtered'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; }
3159 }
3160 elsif ($action eq 'redirect') {
3161 $Totals{'redirected'}++; next unless ($Collecting{'redirected'});
3162 $text =~ s/triggers REDIRECT //;
3163 # See "Note: Counts" before changing $Counts below re: Redirected
3164 if ($subject eq 'Recipient address') { $Counts{'redirected'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; }
3165 elsif ($subject =~ /Client host$/) { $Counts{'redirected'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; }
3166 else { $Counts{'redirected'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; }
3167 }
3168 # hold, discard, and warn allow "optional text"
3169 elsif ($action eq 'hold') {
3170 $Totals{'hold'}++; next unless ($Collecting{'hold'});
3171 # See "Note: Counts" before changing $Counts below re: Hold
3172 $subject = $reason unless $text eq 'triggers HOLD action';
3173 if ($subject eq 'Recipient address') { $Counts{'hold'}{$subject}{$trigger}{$efrom}{$fmthost}++; }
3174 elsif ($subject =~ /Client host$/) { $Counts{'hold'}{$subject}{$fmthost}{$eto}{$efrom}++; }
3175 else { $Counts{'hold'}{$subject}{$trigger}{$eto}{$fmthost}++; }
3176 }
3177 elsif ($action eq 'discard') {
3178 $Totals{'discarded'}++; next unless ($Collecting{'discarded'});
3179 # See "Note: Counts" before changing $Counts below re: Discarded
3180 $subject = $reason unless $text eq 'triggers DISCARD action';
3181 if ($subject eq 'Recipient address') { $Counts{'discarded'}{$subject}{$trigger}{$efrom}{$fmthost}++; }
3182 elsif ($subject =~ /Client host$/) { $Counts{'discarded'}{$subject}{$fmthost}{$eto}{$efrom}++; }
3183 else { $Counts{'discarded'}{$subject}{$trigger}{$eto}{$fmthost}++; }
3184 }
3185 elsif ($action eq 'warn') {
3186 $Totals{'warned'}++; next unless ($Collecting{'warned'});
3187 $Counts{'warned'}{$reason}{$fmthost}{$eto}{''}++;
3188 # See "Note: Counts" before changing $Counts above...
3189 }
3190 elsif ($action eq 'bcc') {
3191 $Totals{'bcced'}++; next unless ($Collecting{'bcced'});
3192 # See "Note: Counts" before changing $Counts below re: Filtered
3193 $text =~ s/triggers BCC //o;
3194 if ($subject eq 'Recipient address') { $Counts{'bcced'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; }
3195 elsif ($subject =~ /Client host$/) { $Counts{'bcced'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; }
3196 else { $Counts{'bcced'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; }
3197 }
3198 else {
3199 die "Unexpected ACTION: '$action'";
3200 }
3201 }
3202 }
3203
3204 elsif ($p1 =~ s/^client=(([^ ]*)\[([^ ]*)\](?::(?:\d+|unknown))?)//) {
3205 my ($hip,$host,$hostip) = ($1,$2,$3);
3206
3207 # Increment accepted when the client connection is made and smtpd has a QID.
3208 # Previously, accepted was being incorrectly incremented when the first qmgr
3209 # "from=xxx, size=nnn ..." line was seen. This is erroneous when the smtpd
3210 # client connection occurred outside the date range of the log being analyzed.
3211 $AcceptedByQid{$qid} = $hip;
3212 $Totals{'msgsaccepted'}++;
3213
3214 #TDsdQ client=unknown[192.168.0.1]
3215 #TDsdQ client=unknown[192.168.0.1]:unknown
3216 #TDsdQ client=unknown[192.168.0.1]:10025
3217 #TDsd client=example.com[192.168.0.1], helo=example.com
3218 #TDsdQ client=mail.example.com[2001:dead:beef::1]
3219
3220 #TDsdQ client=localhost[127.0.0.1], sasl_sender=someone@example.com
3221 #TDsdQ client=example.com[192.168.0.1], sasl_method=PLAIN, sasl_username=anyone@sample.net
3222 #TDsdQ client=example.com[192.168.0.1], sasl_method=LOGIN, sasl_username=user@example.com, sasl_sender=<id352ib@sample.net>
3223 #TDsdQ client=unknown[10.0.0.1], sasl_sender=user@examine.com
3224 next if ($p1 eq '');
3225 my ($method,$user,$sender) = ($p1 =~ /^(?:, sasl_method=([^,]+))?(?:, sasl_username=([^,]+))?(?:, sasl_sender=<?(.*)>?)?$/);
3226
3227 # sasl_sender occurs when AUTH verb is present in MAIL FROM, typically used for relaying
3228 # the username (eg. sasl_username) of authenticated users.
3229 if ($sender or $method or $user) {
3230 $Totals{'saslauth'}++; next unless ($Collecting{'saslauth'});
3231 $method ||= '*unknown method';
3232 $user ||= '*unknown user';
3233 $Counts{'saslauth'}{$user . ($sender ? " ($sender)" : '')}{$method}{formathost($hostip,$host)}++;
3234 }
3235 }
3236
3237 # ^$re_QID: ... (not access(5) action)
3238 elsif ($p1 =~ /^from=<(.*?)>, size=(\d+), nrcpt=(\d+)/) {
3239 my ($efrom,$bytes,$nrcpt) = ($1,$2,$3);
3240 #TDsdQ from=<FROM: SOME USER@example.com>, size=4051, nrcpt=1 (queue active)
3241 #TDsdQ(12) from=<anyone@example.com>, size=25302, nrcpt=2 (queue active)
3242 #TDsdQ from=<from@example.com>, size=5529, nrcpt=1 (queue active)
3243 #TDsdQ from=<from@example.net, @example.com>, size=5335, nrcpt=1 (queue active)
3244
3245 # Distinguish bytes accepted vs. bytes delivered due to multiple recips
3246
3247 # Increment bytes accepted on the first qmgr "from=..." line...
3248 next if (exists $SizeByQid{$qid});
3249 $SizeByQid{$qid} = $bytes;
3250 # ...but only when the smtpd "client=..." line has been seen too.
3251 # This under-counts when the smtpd "client=..." connection log entry and the
3252 # qmgr "from=..." log entry span differnt periods (as fed to postfix-logwatch).
3253 next if (! exists $AcceptedByQid{$qid});
3254
3255 $Totals{'bytesaccepted'} += $bytes;
3256
3257 $Counts{'envelopesenders'}{$efrom ne '' ? $efrom : '<>'}++ if ($Collecting{'envelopesenders'});
3258 if ($Collecting{'envelopesenderdomains'}) {
3259 my ($localpart, $domain);
3260 if ($efrom eq '') { ($localpart, $domain) = ('<>', '*unknown'); }
3261 else { ($localpart, $domain) = split (/@/, lc $efrom); }
3262
3263 $Counts{'envelopesenderdomains'}{$domain ne '' ? $domain : '*unknown'}{$localpart}++;
3264 }
3265 delete $AcceptedByQid{$qid}; # prevent incrementing BytesAccepted again
3266 }
3267
3268 ### sent, forwarded, bounced, softbounce, deferred, (un)deliverable
3269 elsif ($p1 =~ s/^to=<(.*?)>,(?: orig_to=<(.*?)>,)? relay=([^,]*).*, ($re_DDD), status=(\S+) //o) {
3270 ($relay,$status) = ($3,$5);
3271
3272 my ($to,$origto,$localpart,$domainpart,$dsn,$p1) = process_delivery_attempt ($1,$2,$4,$p1);
3273
3274 #TD 552B6C20E: to=<to@sample.com>, relay=mail.example.net[10.0.0.1]:25, delay=1021, delays=1020/0.04/0.56/0.78, dsn=2.0.0, status=sent (250 Ok: queued as 6EAC4719EB)
3275 #TD 552B6C20E: to=<to@sample.com>, relay=mail.example.net[10.0.0.1]:25, conn_use=2 delay=1021, delays=1020/0.04/0.56/0.78, dsn=2.0.0, status=sent (250 Ok: queued as 6EAC4719EB)
3276 #TD DD925BBE2: to=<to@example.net>, orig_to=<to-ext@example.net>, relay=mail.example.net[2001:dead:beef::1], delay=2, status=sent (250 Ok: queued as 5221227246)
3277
3278 ### sent
3279 if ($status eq 'sent') {
3280 if ($p1 =~ /forwarded as /) {
3281 $Totals{'bytesforwarded'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid});
3282 $Totals{'forwarded'}++; next unless ($Collecting{'forwarded'});
3283 $Counts{'forwarded'}{$domainpart}{$localpart}{$origto}++;
3284 }
3285 else {
3286 if ($service_name eq 'lmtp') {
3287 $Totals{'bytessentlmtp'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid});
3288 $Totals{'sentlmtp'}++; next unless ($Collecting{'sentlmtp'});
3289 $Counts{'sentlmtp'}{$domainpart}{$localpart}{$origto}++;
3290 }
3291 elsif ($service_name eq 'smtp') {
3292 $Totals{'bytessentsmtp'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid});
3293 $Totals{'sent'}++; next unless ($Collecting{'sent'});
3294 $Counts{'sent'}{$domainpart}{$localpart}{$origto}++;
3295 }
3296 # virtual, command, ...
3297 else {
3298 $Totals{'bytesdelivered'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid});
3299 $Totals{'delivered'}++; next unless ($Collecting{'delivered'});
3300 $Counts{'delivered'}{$domainpart}{$localpart}{$origto}++;
3301 }
3302 }
3303 }
3304
3305 elsif ($status eq 'deferred') {
3306 #TDsQ to=<to@example.com>, relay=none, delay=27077, delays=27077/0/0.57/0, dsn=4.4.3, status=deferred (Host or domain name not found. Name service error for name=example.com type=MX: Host not found, try again)
3307 #TDsQ to=<to@example.com>, relay=none, delay=141602, status=deferred (connect to mx1.example.com[10.0.0.1]: Connection refused)
3308 #TDsQ to=<to@example.com>, relay=none, delay=141602, status=deferred (delivery temporarily suspended: connect to example.com[192.168.0.1]: Connection refused)
3309 #TDsQ to=<to@example.com>, relay=none, delay=306142, delays=306142/0.04/0.18/0, dsn=4.4.1, status=deferred (connect to example.com[10.0.0.1]: Connection refused)
3310 #TDsQ to=<to@example.org>, relay=example.org[10.0.0.1], delay=48779, status=deferred (lost connection with mail.example.org[10.0.0.1] while sending MAIL FROM)
3311 #TDsQ to=<to@sample.net>, relay=sample.net, delay=26541, status=deferred (conversation with mail.example.com timed out while sending end of data -- message may be sent more than once)
3312 #TDsQ to=<to@sample.net>, relay=sample.net[10.0.0.1]:25, delay=322, delays=0.04/0/322/0, dsn=4.4.2, status=deferred (conversation with example.com[10.0.0.01] timed out while receiving the initial server greeting)
3313 #TDsQ to=<to@localhost>, orig_to=<toalias@localhost>, relay=none, delay=238024, status=deferred (delivery temporarily suspended: transport is unavailable)
3314
3315 # XXX postfix reports dsn=5.0.0, host's reply may contain its own dsn's such as 511 and #5.1.1
3316 # XXX should these be used instead?
3317 #TDsQ to=<to@sample.net>, relay=sample.net[10.0.0.1]:25, delay=5.7, delays=0.05/0.02/5.3/0.3, dsn=4.7.1, status=deferred (host sample.net[10.0.0.1] said: 450 4.7.1 <to@sample.net>: Recipient address rejected: Greylisted (in reply to RCPT TO command))
3318 #TDsQ to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=79799, delays=79797/0.02/0.4/1.3, dsn=4.0.0, status=deferred (host example.com[10.0.0.1] said: 450 <to@example.com>: User unknown in local recipient table (in reply to RCPT TO command))
3319 #TDsQ to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=97, delays=0.03/0/87/10, dsn=4.0.0, status=deferred (host example.com[10.0.0.1] said: 450 <to@example.com>: Recipient address rejected: undeliverable address: User unknown in virtual alias table (in reply to RCPT TO command))
3320
3321 ($reply,$fmthost) = cleanhostreply($p1,$relay,$to,$domainpart);
3322
3323 $Totals{'deferred'}++ if ($DeferredByQid{$qid}++ == 0);
3324 $Totals{'deferrals'}++; next unless ($Collecting{'deferrals'});
3325 $Counts{'deferrals'}{get_dsn_msg($dsn)}{$reply}{$domainpart}{$localpart}{$fmthost}++;
3326 }
3327
3328 ### bounced
3329 elsif ($status eq 'bounced' or $status eq 'SOFTBOUNCE') {
3330 # local agent
3331 #TDlQ to=<envto@example.com>, relay=local, delay=2.5, delays=2.1/0.22/0/0.21, dsn=5.1.1, status=bounced (unknown user: "friend")
3332
3333 # smtp agent
3334 #TDsQ to=<envto@example.com>, orig_to=<envto>, relay=sample.net[10.0.0.1]:25, delay=22, delays=0.02/0.09/22/0.07, dsn=5.0.0, status=bounced (host sample.net[10.0.0.1] said: 551 invalid address (in reply to MAIL FROM command))
3335
3336 #TDsQ to=<envto@example.com>, relay=sample.net[10.0.0.1]:25, delay=11, delays=0.13/0.07/0.98/0.52, dsn=5.0.0, status=bounced (host sample.net[10.0.0.1] said: 550 MAILBOX NOT FOUND (in reply to RCPT TO command))
3337 #TDsQ to=<envto@example.com>, orig_to=<envto>, relay=sample.net[10.0.0.1]:25, delay=22, delays=0.02/0.09/22/0.07, dsn=5.0.0, status=bounced (host sample.net[10.0.0.1] said: 551 invalid address (in reply to MAIL FROM command))
3338
3339
3340 #TDsQ to=<envto@example.com>, relay=none, delay=0.57, delays=0.57/0/0/0, dsn=5.4.6, status=bounced (mail for sample.net loops back to myself)
3341 #TDsQ to=<>, relay=none, delay=1, status=bounced (mail for sample.net loops back to myself)
3342 #TDsQ to=<envto@example.com>, relay=none, delay=0, status=bounced (Host or domain name not found. Name service error for name=unknown.com type=A: Host not found)
3343 # XXX verify these...
3344 #TD EB0B8770: to=<to@example.com>, orig_to=<postmaster>, relay=none, delay=1, status=bounced (User unknown in virtual alias table)
3345 #TD EB0B8770: to=<to@example.com>, orig_to=<postmaster>, relay=sample.net[192.168.0.1], delay=1.1, status=bounced (User unknown in relay recipient table)
3346 #TD D8962E54: to=<anyone@example.com>, relay=local, conn_use=2 delay=0.21, delays=0.05/0.02/0/0.14, dsn=4.1.1, status=SOFTBOUNCE (unknown user: "to")
3347 #TD F031C832: to=<to@sample.net>, orig_to=<alias@sample.net>, relay=local, delay=0.17, delays=0.13/0.01/0/0.03, dsn=5.1.1, status=bounced (unknown user: "to")
3348
3349 #TD C76431E2: to=<login@sample.net>, relay=local, delay=2, status=SOFTBOUNCE (host sample.net[192.168.0.1] said: 450 <login@sample.com>: User unknown in local recipient table (in reply to RCPT TO command))
3350 #TD 04B0702E: to=<anyone@example.com>, relay=example.com[10.0.0.1]:25, delay=12, delays=6.5/0.01/0.03/5.1, dsn=5.1.1, status=bounced (host example.com[10.0.0.1] said: 550 5.1.1 User unknown (in reply to RCPT TO command))
3351 #TD 9DAC8B2D: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=1.4, delays=0.04/0/0.27/1.1, dsn=5.0.0, status=bounced (host example.com[10.0.0.1] said: 511 sorry, no mailbox here by that name (#5.1.1 - chkuser) (in reply to RCPT TO command))
3352 #TD 79CB702D: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=0.3, delays=0.04/0/0.61/0.8, dsn=5.0.0, status=bounced (host example.com[10.0.0.1] said: 550 <to@example.com>, Recipient unknown (in reply to RCPT TO command))
3353 #TD 88B7A079: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=45, delays=0.03/0/5.1/40, dsn=5.0.0, status=bounced (host example.com[10.0.0.1] said: 550-"The recipient cannot be verified. Please check all recipients of this 550 message to verify they are valid." (in reply to RCPT TO command))
3354 #TD 47B7B074: to=<to@example.com>, relay=example.com[10.0.0.1]:25, delay=6.6, delays=6.5/0/0/0.11, dsn=5.1.1, status=bounced (host example.com[10.0.0.1] said: 550 5.1.1 <to@example.com> User unknown; rejecting (in reply to RCPT TO command))
3355 #TDppQ to=<withheld>, relay=dbmail-pipe, delay=0.15, delays=0.09/0.01/0/0.06, dsn=5.3.0, status=bounced (Command died with signal 11: "/usr/sbin/dbmail-smtp")
3356
3357 # print "bounce message from " . $to . " msg : " . $relay . "\n";
3358
3359 # See same code elsewhere "Note: Bounce"
3360 ### local bounce
3361 # XXX local v. remote bounce seems iffy, relative
3362 if ($relay =~ /^(?:none|local|virtual|127\.0\.0\.1|maildrop|avcheck)/) {
3363 $Totals{'bouncelocal'}++; next unless ($Collecting{'bouncelocal'});
3364 $Counts{'bouncelocal'}{get_dsn_msg($dsn)}{$domainpart}{ucfirst($p1)}{$localpart}++;
3365 }
3366 else {
3367 $Totals{'bounceremote'}++; next unless ($Collecting{'bounceremote'});
3368 ($reply,$fmthost) = cleanhostreply($p1,$relay,$to,$domainpart);
3369 $Counts{'bounceremote'}{get_dsn_msg($dsn)}{$domainpart}{$localpart}{$fmthost}{$reply}++;
3370 }
3371 }
3372
3373
3374 elsif ($status =~ 'undeliverable') {
3375 #TDsQ to=<u@example.com>, relay=sample.com[10.0.0.1], delay=0, dsn=5.0.0, status=undeliverable (host sample.com[10.0.0.1] refused to talk to me: 554 5.7.1 example.com Connection not authorized)
3376 #TDsQ to=<to@example.com>, relay=mx.example.com[10.0.0.1]:25, conn_use=2, delay=5.5, delays=0.03/0/0.21/5.3, dsn=5.0.0, status=undeliverable-but-not-cached (host mx.example.com[10.0.0.1] said: 550 RCPT TO:<to@example.com> User unknown (in reply to RCPT TO command))
3377 #TDvQ to=<u@example.com>, relay=virtual, delay=0.14, delays=0.06/0/0/0.08, dsn=5.1.1, status=undeliverable (unknown user: "u@example.com")
3378 #TDlQ to=<to@example.com>, relay=local, delay=0.02, delays=0.01/0/0/0, dsn=5.1.1, status=undeliverable-but-not-cached (unknown user: "to")
3379 $Totals{'undeliverable'}++; next unless ($Collecting{'undeliverable'});
3380 if ($p1 =~ /^unknown user: ".+?"$/) {
3381 $Counts{'undeliverable'}{get_dsn_msg($dsn)}{'Unknown user'}{$domainpart}{$localpart}{$origto ? $origto : ''}++;
3382 }
3383 else {
3384 my ($reply,$fmthost) = cleanhostreply($p1,'',$to ne '' ? $to : '<>',$domainpart);
3385 $Counts{'undeliverable'}{get_dsn_msg($dsn)}{$reply}{$domainpart}{$localpart}{$fmthost}++;
3386 }
3387 }
3388
3389 elsif ($status eq 'deliverable') {
3390 # address verification, sendmail -bv deliverable reports
3391 #TDvQ to=<u@example.com>, relay=virtual, delay=0.09, delays=0.03/0/0/0.06, dsn=2.0.0, status=deliverable (delivers to maildir)
3392 $Totals{'deliverable'}++; next unless ($Collecting{'deliverable'});
3393 my $dsn = ($p1 =~ s/^($re_DSN) // ? $1 : '*unavailable');
3394 $Counts{'deliverable'}{$dsn}{$p1}{$origto ? "$to ($origto)" : $to}++;
3395 }
3396
3397 else {
3398 # keep this as the last condition in this else clause
3399 inc_unmatched('unknownstatus');
3400 }
3401 } # end of sent, forwarded, bounced, softbounce, deferred, (un)deliverable
3402
3403 # pickup
3404 elsif ($p1 =~ /^(uid=\S* from=<.*?>)/) {
3405 #TDpQ2 uid=0 from=<root>
3406 $AcceptedByQid{$qid} = $1;
3407 $Totals{'msgsaccepted'}++;
3408 }
3409
3410 elsif ($p1 =~ /^from=<(.*?)>, status=expired, returned to sender$/) {
3411 #TDqQ from=<from@example.com>, status=expired, returned to sender
3412 $Totals{'returnedtosender'}++; next unless ($Collecting{'returnedtosender'});
3413 $Counts{'returnedtosender'}{$1 ne '' ? $1 : '<>'}++;
3414 }
3415
3416 elsif ($p1 =~ s/^host ([^[]+)\[([^]]+)\](?::\d+)? refused to talk to me://) {
3417 #TDsQ host mail.example.com[10.0.0.1] refused to talk to me: 553 Connections are being blocked due to previous incidents of abuse
3418 #TDsQ host mail.example.com[10.0.0.1] refused to talk to me: 501 Connection from 192.168.2.1 (XY) rejected
3419 # Note: See ConnectToFailure above
3420 $Totals{'connecttofailure'}++; next unless ($Collecting{'connecttofailure'});
3421 $Counts{'connecttofailure'}{'Refused to talk to me'}{formathost($2,$1)}{$p1}++;
3422 }
3423
3424 elsif ($p1 =~ /^lost connection with ([^[]*)\[([^]]+)\](?::\d+)? (while .*)$/) {
3425 # outbound smtp
3426 #TDsQ lost connection with sample.net[10.0.0.1] while sending MAIL FROM
3427 #TDsQ lost connection with sample.net[10.0.0.2] while receiving the initial server greeting
3428 $Totals{'connectionlostoutbound'}++; next unless ($Collecting{'connectionlostoutbound'});
3429 $Counts{'connectionlostoutbound'}{ucfirst($3)}{formathost($2,$1)}++;
3430 }
3431
3432 elsif ($p1 =~ /^conversation with ([^[]*)\[([^]]+)\](?::\d+)? timed out (while .*)$/) {
3433 #TDsQ conversation with sample.net[10.0.0.1] timed out while receiving the initial SMTP greeting
3434 # Note: see TimeoutInbound below
3435 $Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'});
3436 $Counts{'timeoutinbound'}{ucfirst($3)}{formathost($2,$1)}{''}++;
3437 }
3438
3439 elsif ($p1 =~ /^enabling PIX (<CRLF>\.<CRLF>) workaround for ([^[]+)\[([^]]+)\](?::\d+)?/ or
3440 $p1 =~ /^enabling PIX workarounds: (.*) for ([^[]+)\[([^]]+)\](?::\d+)?/) {
3441 #TDsQ enabling PIX <CRLF>.<CRLF> workaround for example.com[192.168.0.1]
3442 #TDsQ enabling PIX <CRLF>.<CRLF> workaround for mail.sample.net[10.0.0.1]:25
3443 #TDsQ enabling PIX workarounds: disable_esmtp delay_dotcrlf for spam.example.org[10.0.0.1]:25
3444 $Totals{'pixworkaround'}++; next unless ($Collecting{'pixworkaround'});
3445 $Counts{'pixworkaround'}{$1}{formathost($3,$2)}++;
3446 }
3447
3448 # milter-reject, milter-hold, milter-discard
3449 elsif ($p1 =~ s/^milter-//) {
3450 milter_common($p1);
3451 }
3452
3453 elsif ($p1 =~ s/^SASL (\[CACHED\] )?authentication failed; //) {
3454 #TDsQ SASL authentication failed; cannot authenticate to server smtp.example.com[10.0.0.1]: no mechanism available
3455 #TDsQ SASL authentication failed; server example.com[10.0.0.1] said: 535 Error: authentication failed
3456 #TDsQ SASL [CACHED] authentication failed; server example.com[10.0.0.1] said: 535 Error: authentication failed
3457 # see saslauthfail elsewhere
3458
3459 $Totals{'saslauthfail'}++; next unless ($Collecting{'saslauthfail'});
3460 my $cached = $1;
3461
3462 if ($p1 =~ /^(authentication protocol loop with server): ([^[]+)\[([^]]+)\](?::\d+)?$/) {
3463 ($reason,$host,$hostip,$reason2) = ($1,$2,$3,'');
3464 }
3465 elsif ($p1 =~ /^(cannot authenticate to server) ([^[]+)\[([^]]+)\](?::\d+)?: (.*)$/) {
3466 ($reason,$host,$hostip,$reason2) = ($1,$2,$3,$4);
3467 }
3468 elsif ($p1 =~ /^server ([^[]+)\[([^]]+)\](?::\d+)? said: (.+)$/) {
3469 ($reason,$host,$hostip,$reason2) = ('server ... said',$1,$2,$3);
3470 }
3471 else {
3472 inc_unmatched('saslauthfail');
3473 next;
3474 }
3475
3476 $reason .= ': ' . $reason2 if $reason2;
3477 $Counts{'saslauthfail'}{$cached . $reason}{formathost($hostip,$host)}++;
3478 }
3479
3480 else {
3481 # keep this as the last condition in this else clause
3482 inc_unmatched('unknownqid') if ! in_ignore_list ($p1);
3483 }
3484 }
3485 # end of $re_QID section
3486
3487 elsif ($p1 =~ /^(timeout|lost connection) (after [^ ]+)(?: \((?:approximately )?(\d+) bytes\))? from ([^[]*)\[([^]]+)\](?::\d+)?$/) {
3488 my ($lort,$reason,$bytes,$host,$hostip) = ($1,$2,$3,$4,$5);
3489 if ($lort eq 'timeout') {
3490 # see also TimeoutInbound in $re_QID section
3491 #TDsd timeout after RSET from example.com[192.168.0.1]
3492 #TDsd timeout after DATA (6253 bytes) from example.com[10.0.0.1]
3493
3494 $Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'});
3495 $Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++;
3496 } else {
3497 #TDsd lost connection after CONNECT from mail.example.com[192.168.0.1]
3498 # postfix 2.5:20071003
3499 #TDsd lost connection after DATA (494133 bytes) from localhost[127.0.0.1]
3500 # postfix 2.6:20080621
3501 #TDsd lost connection after DATA (approximately 0 bytes) from example.com[10.0.0.1]
3502
3503 $Totals{'connectionlostinbound'}++; next unless ($Collecting{'connectionlostinbound'});
3504 $Counts{'connectionlostinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++;
3505 }
3506 }
3507
3508 elsif ($p1 =~ /^(reject(?:_warning)?): RCPT from ([^[]+)\[([^]]+)\](?::\d+)?: ($re_DSN) Service (?:temporarily )?(?:unavailable|denied)[^;]*; (?:(?:Unverified )?Client host |Sender address |Helo command )?\[[^ ]*\] blocked using ([^;]+);/o) {
3509 my ($rej_type,$host,$hostip,$dsn,) = ($1,$2,$3,$4);
3510 ($site,$reason) = ($5 =~ /^(.+?)(?:$|(?:[.,] )(.*))/);
3511 $reason =~ s/^reason: // if ($reason);
3512 $rej_type = ($rej_type =~ /_warning/ ? 'warn' : get_reject_key($dsn));
3513 #print "REJECT RBL NOQ: '$rej_type'\n";
3514 # Note: similar code above: search RejectRBL
3515
3516 # This section required: postfix didn't always log QID (eg. postfix 1.1)
3517 # Also, "reason:" was probably always present in this case, but I'm not certain
3518 # postfix 1.1
3519 #TDsd reject_warning: RCPT from example.com[10.0.0.1]: 554 Service unavailable; [10.0.0.1] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.1; from=<from@example.com> to=<to@sample.net>
3520 #TDsd reject: RCPT from example.com[10.0.0.2]: 554 Service unavailable; [10.0.0.2] blocked using orbz.org, reason: Open relay. Please see http://orbz.org/?10.0.0.2; from=<from@example.com> to=<to@example.net>
3521 #TDsd reject: RCPT from unknown[10.0.0.3]: 554 Service unavailable; [10.0.0.3] blocked using bl.spamcop.net, reason: Blocked - see http://www.spamcop.net/bl.shtml?10.0.0.3; from=<from@example.net> to=<to@example.com>
3522 #TDsd reject: RCPT from example.com[10.0.0.4]: 554 Service unavailable; [10.0.0.4] blocked using sbl.spamhaus.org, reason: http://www.spamhaus.org/SBL/sbl.lasso?query=B12057; from=<from@example.net> to=<to@example.com>
3523
3524 if ($Collecting{'byiprejects'} and substr($rej_type,0,1) eq '5') {
3525 $fmthost = formathost($hostip,$host);
3526 $Counts{'byiprejects'}{$fmthost}++;
3527 }
3528
3529 $Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name});
3530 $Counts{$reject_name}{$site}{$fmthost ? $fmthost : formathost($hostip,$host)}{$reason ? $reason : ''}++;
3531 }
3532
3533 # proxy-reject, proxy-accept
3534 elsif ($p1 =~ s/^proxy-(reject|accept): ([^:]+): //) {
3535 # 2.7
3536 #TDsdN proxy-accept: END-OF-MESSAGE: 250 2.0.0 Ok: queued as 9BE3547AFE; from=<senderexample.com> to=<recipientexample.com> proto=ESMTP helo=<client.example.com>
3537 #TDsdN proxy-reject: END-OF-MESSAGE: 554 5.7.0 Reject, id=11912-03 - INFECTED: Eicar-Test-Signature; from=<root@example.com> to=<root@example.net> proto=ESMTP helo=<example.com>
3538
3539 next if $1 eq 'accept'; #ignore accepts
3540
3541 my ($stage) = ($2);
3542 my ($efrom,$eto,$proto,$helo) = strip_ftph($p1);
3543 #print "efrom: '$efrom', eto: '$eto', proto: '$proto', helo: '$helo'\n";
3544 #print "stage: '$stage', reply: '$p1'\n";
3545
3546 my ($dsn,$reject_name);
3547 ($dsn,$reply) = ($1,$2) if $p1 =~ /^($re_DSN) (.*)$/o;
3548 #print " dsn: '$dsn', reply: '$reply', key: ", get_reject_key($dsn), "\n";
3549 # DSN may not be present?
3550 if (! defined $dsn) {
3551 inc_unmatched('proxy-reject');
3552 next;
3553 }
3554
3555 $Totals{$reject_name = get_reject_key($dsn) . 'rejectproxy' }++; next unless ($Collecting{$reject_name});
3556 $Counts{$reject_name}{$stage}{$reply}{$eto}++;
3557 }
3558
3559 ### smtpd_tls_loglevel >= 1
3560 # Server TLS messages
3561 elsif (($status,$host,$hostip,$type) = ($p1 =~ /^(?:(Anonymous|Trusted|Untrusted) )?TLS connection established from ([^[]+)\[([^]]+)\](?::\d+)?: (.*)$/)) {
3562 #TDsd TLS connection established from example.com[192.168.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
3563 # Postfix 2.5+: status: Untrusted or Trusted
3564 #TDsd Untrusted TLS connection established from example.com[192.168.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
3565 #TDsd Anonymous TLS connection established from localhost[127.0.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)
3566
3567 $Totals{'tlsserverconnect'}++; next unless ($Collecting{'tlsserverconnect'});
3568 $Counts{'tlsserverconnect'}{$status ? "$status: $type" : $type}{formathost($hostip,$host)}++;
3569 }
3570
3571 # Client TLS messages
3572 elsif ( ($status,$host,$type) = ($p1 =~ /^(?:(Verified|Trusted|Untrusted) )?TLS connection established to ([^ ]*): (.*)$/o)) {
3573 #TD TLS connection established to example.com: TLSv1 with cipher AES256-SHA (256/256 bits)
3574 # Postfix 2.5+: peer verification status: Untrusted, Trusted or Verified when
3575 # server's trust chain is valid and peername is matched
3576 #TD Verified TLS connection established to 127.0.0.1[127.0.0.1]:26: TLSv1 with cipher DHE-DSS-AES256-SHA (256/256 bits)
3577
3578 $Totals{'tlsclientconnect'}++; next unless ($Collecting{'tlsclientconnect'});
3579 $Counts{'tlsclientconnect'}{$status ? "$status: $type" : $type}{$host}++;
3580 }
3581
3582 # smtp_tls_note_starttls_offer=yes
3583 elsif ($p1 =~ /^Host offered STARTTLS: \[(.*)\]$/o) {
3584 #TD Host offered STARTTLS: [mail.example.com]
3585 $Totals{'tlsoffered'}++; next unless ($Collecting{'tlsoffered'});
3586 $Counts{'tlsoffered'}{$1}++;
3587 }
3588
3589 ### smtpd_tls_loglevel >= 1
3590 elsif ($p1 =~ /^Unverified: (.*)/o) {
3591 #TD Unverified: subject_CN=(www|smtp|mailhost).(example.com|sample.net), issuer=someuser
3592 $Totals{'tlsunverified'}++; next unless ($Collecting{'tlsunverified'});
3593 $Counts{'tlsunverified'}{$1}++;
3594 }
3595
3596 # Note: no QID
3597 elsif (($host,$hostip,$dsn,$from,$to) = ($p1 =~ /^reject: RCPT from ([^[]+)\[([^]]+)\](?::\d+)?: ([45]52) Message size exceeds fixed limit; from=<(.*?)> to=<(.*?)>/)) {
3598 #TD reject: RCPT from size.example.com[192.168.0.1]: 452 Message size exceeds fixed limit; from=<from@example.com> to=<to@sample.net>
3599 #TD reject: RCPT from size.example.com[192.168.0.1]: 552 Message size exceeds fixed limit; from=<from@example.com> to=<to@sample.net> proto=ESMTP helo=<example.com>
3600 # Note: similar code above: search RejectSize
3601 # Note: reject_warning does not seem to occur
3602 if ($Collecting{'byiprejects'} and substr($dsn,0,1) eq '5') {
3603 $fmthost = formathost($hostip,$host);
3604 $Counts{'byiprejects'}{$fmthost}++;
3605 }
3606 $Totals{$reject_name = get_reject_key($dsn) . 'rejectsize' }++; next unless ($Collecting{$reject_name});
3607 $Counts{$reject_name}{$fmthost ? $fmthost : formathost($hostip,$host)}{$to}{$from ne '' ? $from : '<>'}++;
3608 }
3609
3610 elsif ($p1 =~ /looking for plugins in (.*)$/) {
3611 #TD looking for plugins in '/usr/lib/sasl2', failed to open directory, error: No such file or directory
3612 $Totals{'warnconfigerror'}++; next unless ($Collecting{'warnconfigerror'});
3613 $Counts{'warnconfigerror'}{$1}++;
3614 }
3615
3616 # SMTP/ESMTP protocol violations
3617 elsif ($p1 =~ /^(improper command pipelining) (after \S+) from ([^[]*)\[([^]]+)\](?::\d+)?$/) {
3618 # ProtocolViolation
3619 #TDsd postfix/smtpd[24928]: improper command pipelining after RCPT from unknown[192.168.0.1]
3620 my ($error,$stage,$host,$hostip) = ($1,$2,$3,$4);
3621 $Totals{'smtpprotocolviolation'}++; next unless ($Collecting{'smtpprotocolviolation'});
3622 $Counts{'smtpprotocolviolation'}{ucfirst($error)}{ucfirst($stage)}{formathost($hostip,$host)}++;
3623 }
3624
3625 elsif ($p1 =~ /^(too many errors) (after [^ ]*)(?: \((?:approximately )?\d+ bytes\))? from ([^[]*)\[([^]]+)\](?::\d+)?$/) {
3626 my ($error,$stage,$host,$hostip) = ($1,$2,$3,$4);
3627 #TDsd too many errors after AUTH from sample.net[10.0.0.1]
3628 #TDsd too many errors after DATA (0 bytes) from 1-0-0-10.example.com[10.0.0.1]
3629 $Totals{'smtpprotocolviolation'}++; next unless ($Collecting{'smtpprotocolviolation'});
3630 $Counts{'smtpprotocolviolation'}{ucfirst($error)}{ucfirst($stage)}{formathost($hostip,$host)}++;
3631 }
3632
3633 # coerce these into general warnings
3634 elsif ( $p1 =~ /^cannot load Certificate Authority data/ or
3635 $p1 =~ /^SSL_connect error to /)
3636 {
3637 #TDsQ Cannot start TLS: handshake failure
3638 #TDsd cannot load Certificate Authority data
3639 #TDs SSL_connect error to mail.example.com: 0
3640
3641 postfix_warning($p1);
3642 }
3643
3644 else {
3645 # add to the unmatched list if not on the ignore_list
3646 inc_unmatched('final') if ! in_ignore_list ($p1);
3647 }
3648 }
3649
3650 ########################################
3651 # Final tabulations, and report printing
3652
3653 for my $code (@RejectKeys) {
3654 for my $type (@RejectClasses) {
3655 $Totals{'totalrejects' . $code} += $Totals{$code . $type};
3656 }
3657
3658 if ($code =~ /^5/o) {
3659 $Totals{'totalrejects'} += $Totals{'totalrejects' . $code};
3660 }
3661 }
3662
3663 # XXX this was naive - the goal was to avoid recounting messages
3664 # released from quarantine, but externally introduced messages may
3665 # contain resent-message-id; trying to track only internally resent
3666 # messages does not seem useful.
3667 # make some corrections now, due to double counting
3668 #$Totals{'msgsaccepted'} -= $Totals{'resent'} if ($Totals{'msgsaccepted'} >= $Totals{'resent'});
3669
3670 $Totals{'totalacceptplusreject'} = $Totals{'msgsaccepted'} + $Totals{'totalrejects'};
3671
3672 # Print the Summary report if any key has non-zero data.
3673 # Note: must explicitely check for any non-zero data,
3674 # as Totals always has some keys extant.
3675 #
3676 if ($Opts{'summary'}) {
3677 for (keys %Totals) {
3678 if ($Totals{$_}) {
3679 print_summary_report (@Sections);
3680 last;
3681 }
3682 }
3683 }
3684
3685 # Print the Detail report, if detail is sufficiently high
3686 #
3687 if ($Opts{'detail'} >= 5) {
3688 #print STDERR "Counts memory usage: ", commify(Devel::Size::total_size(\%Counts)), "\n";
3689 #print STDERR "Delays memory usage: ", commify(Devel::Size::total_size(\%Delays)), "\n";
3690 print_detail_report(@Sections);
3691
3692 if ($Opts{'delays'}) {
3693 my @table;
3694 for (sort keys %Delays) {
3695 # anon array ref: label, array ref of $Delay{key}
3696 push @table, [ substr($_,3), $Delays{$_} ];
3697 }
3698 if (@table) {
3699 print_percentiles_report2(\@table, "Delivery Delays Percentiles", $Opts{'delays_percentiles'});
3700 }
3701 }
3702
3703 print_postgrey_reports();
3704
3705 }
3706
3707 # debug: show which ignore_list items are hit most
3708 #my %IGNORED;
3709 #for (sort { $IGNORED{$b} <=> $IGNORED{$a} } keys %IGNORED) {
3710 # printf "%10d: KEY: %s\n", $IGNORED{$_}, $_;
3711 #}
3712
3713 # Finally, print any unmatched lines
3714 #
3715 print_unmatched_report();
3716
3717 #
3718 # End of main
3719 #
3720 ##################################################
3721
3722 # Create the list of REs against which log lines are matched.
3723 # Lines that match any of the patterns in this list are ignored.
3724 #
3725 # Note: This table is created at runtime, due to a Perl bug which
3726 # I reported as perl bug #56202:
3727 #
3728 # http://rt.perl.org/rt3/Public/Bug/Display.html?id=56202
3729 #
3730 sub create_ignore_list() {
3731 # top 3 hitters up front
3732 push @ignore_list, qr/^statistics:/;
3733 push @ignore_list, qr/^setting up TLS connection (?:from|to)/;
3734 push @ignore_list, qr/^Verified: /;
3735 push @ignore_list, qr/^skipped, still being delivered/;
3736
3737 # SSL info at/above mail.info level
3738 push @ignore_list, qr/^read from [a-fA-F\d]{8}/;
3739 push @ignore_list, qr/^write to [a-fA-F\d]{8}/;
3740 push @ignore_list, qr/^[a-f\d]{4} [a-f\d]{2}/;
3741 push @ignore_list, qr/^[a-f\d]{4} - <SPACES/;
3742 push @ignore_list, qr/^[<>]+ /;
3743
3744 push @ignore_list, qr/^premature end-of-input (?:on|from) .* socket while reading input attribute name$/;
3745 push @ignore_list, qr/^certificate peer name verification failed/;
3746 push @ignore_list, qr/^Peer certi?ficate could not be verified$/; # missing i was a postfix typo
3747 push @ignore_list, qr/^Peer cert verify depth=/;
3748 push @ignore_list, qr/^Peer verification:/;
3749 push @ignore_list, qr/^Server certificate could not be verified/;
3750 push @ignore_list, qr/^cannot load .SA certificate and key data/;
3751 push @ignore_list, qr/^tlsmgr_cache_run_event/;
3752 push @ignore_list, qr/^SSL_accept/;
3753 push @ignore_list, qr/^SSL_connect:/;
3754 push @ignore_list, qr/^connection (?:closed|established)/;
3755 push @ignore_list, qr/^delete smtpd session/;
3756 push @ignore_list, qr/^put smtpd session/;
3757 push @ignore_list, qr/^save session/;
3758 push @ignore_list, qr/^Reusing old/;
3759 push @ignore_list, qr/^looking up session/;
3760 push @ignore_list, qr/^lookup smtpd session/;
3761 push @ignore_list, qr/^lookup \S+ type/;
3762 push @ignore_list, qr/^xsasl_(?:cyrus|dovecot)_/;
3763 push @ignore_list, qr/^watchdog_/;
3764 push @ignore_list, qr/^read smtpd TLS/;
3765 push @ignore_list, qr/^open smtpd TLS/;
3766 push @ignore_list, qr/^write smtpd TLS/;
3767 push @ignore_list, qr/^read smtp TLS cache entry/;
3768 push @ignore_list, qr/^starting TLS engine$/;
3769 push @ignore_list, qr/^initializing the server-side TLS/;
3770 push @ignore_list, qr/^global TLS level: /;
3771 push @ignore_list, qr/^auto_clnt_/;
3772 push @ignore_list, qr/^generic_checks:/;
3773 push @ignore_list, qr/^inet_addr_/;
3774 push @ignore_list, qr/^mac_parse:/;
3775 push @ignore_list, qr/^cert has expired/;
3776 push @ignore_list, qr/^daemon started/;
3777 push @ignore_list, qr/^master_notify:/;
3778 push @ignore_list, qr/^rewrite_clnt:/;
3779 push @ignore_list, qr/^rewrite stream/;
3780 push @ignore_list, qr/^dict_/;
3781 push @ignore_list, qr/^send attr /;
3782 push @ignore_list, qr/^match_/;
3783 push @ignore_list, qr/^input attribute /;
3784 push @ignore_list, qr/^Run-time/;
3785 push @ignore_list, qr/^Compiled against/;
3786 push @ignore_list, qr/^private\//;
3787 push @ignore_list, qr/^reject_unknown_/; # don't combine or shorten these reject_ patterns
3788 push @ignore_list, qr/^reject_unauth_/;
3789 push @ignore_list, qr/^reject_non_/;
3790 push @ignore_list, qr/^permit_/;
3791 push @ignore_list, qr/^idle timeout/;
3792 push @ignore_list, qr/^get_dns_/;
3793 push @ignore_list, qr/^dns_/;
3794 push @ignore_list, qr/^chroot /;
3795 push @ignore_list, qr/^process generation/;
3796 push @ignore_list, qr/^fsspace:/;
3797 push @ignore_list, qr/^master disconnect/;
3798 push @ignore_list, qr/^resolve_clnt/;
3799 push @ignore_list, qr/^ctable_/;
3800 push @ignore_list, qr/^extract_addr/;
3801 push @ignore_list, qr/^mynetworks:/;
3802 push @ignore_list, qr/^name_mask:/;
3803 #TDm reload -- version 2.6-20080814, configuration /etc/postfix
3804 #TDm reload configuration /etc/postfix
3805 push @ignore_list, qr/^reload (?:-- version \S+, )?configuration/;
3806 push @ignore_list, qr/^terminating on signal 15$/;
3807 push @ignore_list, qr/^verify error:num=/;
3808 push @ignore_list, qr/^verify return:/;
3809 push @ignore_list, qr/^nss_ldap: /;
3810 push @ignore_list, qr/^discarding EHLO keywords: /;
3811 push @ignore_list, qr/^sql auxprop plugin/;
3812 push @ignore_list, qr/^sql plugin/;
3813 push @ignore_list, qr/^sql_select/;
3814 push @ignore_list, qr/^auxpropfunc error/;
3815 push @ignore_list, qr/^commit transaction/;
3816 push @ignore_list, qr/^begin transaction/;
3817 push @ignore_list, qr/^maps_find: /;
3818 push @ignore_list, qr/^check_access: /;
3819 push @ignore_list, qr/^check_domain_access: /;
3820 push @ignore_list, qr/^check_mail_access: /;
3821 push @ignore_list, qr/^check_table_result: /;
3822 push @ignore_list, qr/^mail_addr_find: /;
3823 push @ignore_list, qr/^mail_addr_map: /;
3824 push @ignore_list, qr/^mail_flow_put: /;
3825 push @ignore_list, qr/^smtp_addr_one: /;
3826 push @ignore_list, qr/^smtp_connect_addr: /;
3827 push @ignore_list, qr/^smtp_connect_unix: trying: /;
3828 push @ignore_list, qr/^smtp_find_self: /;
3829 push @ignore_list, qr/^smtp_get: /;
3830 push @ignore_list, qr/^smtp_fputs: /;
3831 push @ignore_list, qr/^smtp_parse_destination: /;
3832 push @ignore_list, qr/^smtp_sasl_passwd_lookup: /;
3833 push @ignore_list, qr/^smtpd_check_/;
3834 push @ignore_list, qr/^smtpd_chat_notify: /;
3835 push @ignore_list, qr/^been_here: /;
3836 push @ignore_list, qr/^set_eugid: /;
3837 push @ignore_list, qr/^deliver_/;
3838 push @ignore_list, qr/^flush_send_file: queue_id/;
3839 push @ignore_list, qr/^milter_macro_lookup/;
3840 push @ignore_list, qr/^milter8/;
3841 push @ignore_list, qr/^skipping non-protocol event/;
3842 push @ignore_list, qr/^reply: /;
3843 push @ignore_list, qr/^event: /;
3844 push @ignore_list, qr/^trying... /;
3845 push @ignore_list, qr/ all milters$/;
3846 push @ignore_list, qr/^vstream_/;
3847 push @ignore_list, qr/^server features/;
3848 push @ignore_list, qr/^skipping event/;
3849 push @ignore_list, qr/^Using /;
3850 push @ignore_list, qr/^rec_(?:put|get): /;
3851 push @ignore_list, qr/^subject=/;
3852 push @ignore_list, qr/^issuer=/;
3853 push @ignore_list, qr/^pref /; # yes, multiple spaces
3854 push @ignore_list, qr/^request: \d/;
3855 push @ignore_list, qr/^done incoming queue scan$/;
3856 push @ignore_list, qr/^qmgr_/;
3857 push @ignore_list, qr/^trigger_server_accept_fifo: /;
3858 push @ignore_list, qr/^proxymap stream/;
3859 push @ignore_list, qr/^(?:start|end) sorted recipient list$/;
3860 push @ignore_list, qr/^connecting to \S+ port /;
3861 push @ignore_list, qr/^Write \d+ chars/;
3862 push @ignore_list, qr/^Read \d+ chars/;
3863 push @ignore_list, qr/^(?:lookup|delete) smtp session/;
3864 push @ignore_list, qr/^delete smtp session/;
3865 push @ignore_list, qr/^(?:reloaded|remove|looking for) session .* cache$/;
3866 push @ignore_list, qr/^(?:begin|end) \S+ address list$/;
3867 push @ignore_list, qr/^mapping DSN status/;
3868 push @ignore_list, qr/^record [A-Z]/;
3869 push @ignore_list, qr/^dir_/;
3870 push @ignore_list, qr/^transport_event/;
3871 push @ignore_list, qr/^read [A-Z](?: |$)/;
3872 push @ignore_list, qr/^relay: /;
3873 push @ignore_list, qr/^why: /;
3874 push @ignore_list, qr/^fp: /;
3875 push @ignore_list, qr/^path: /;
3876 push @ignore_list, qr/^level: /;
3877 push @ignore_list, qr/^recipient: /;
3878 push @ignore_list, qr/^delivered: /;
3879 push @ignore_list, qr/^queue_id: /;
3880 push @ignore_list, qr/^queue_name: /;
3881 push @ignore_list, qr/^user: /;
3882 push @ignore_list, qr/^sender: /;
3883 push @ignore_list, qr/^offset: /;
3884 push @ignore_list, qr/^offset: /;
3885 push @ignore_list, qr/^verify stream disconnect/;
3886 push @ignore_list, qr/^event_request_timer: /;
3887 push @ignore_list, qr/^smtp_sasl_authenticate: /;
3888 push @ignore_list, qr/^flush_add: /;
3889 push @ignore_list, qr/^disposing SASL state information/;
3890 push @ignore_list, qr/^starting new SASL client/;
3891 push @ignore_list, qr/^error: dict_ldap_connect: /;
3892 push @ignore_list, qr/^error: to submit mail, use the Postfix sendmail command/;
3893 push @ignore_list, qr/^local_deliver[:[]/;
3894 push @ignore_list, qr/^_sasl_plugin_load /;
3895 push @ignore_list, qr/^exp_type: /;
3896 push @ignore_list, qr/^wakeup [\dA-Z]/;
3897 push @ignore_list, qr/^defer (?:site|transport) /;
3898 push @ignore_list, qr/^local: /;
3899 push @ignore_list, qr/^exp_from: /;
3900 push @ignore_list, qr/^extension: /;
3901 push @ignore_list, qr/^owner: /;
3902 push @ignore_list, qr/^unmatched: /;
3903 push @ignore_list, qr/^domain: /;
3904 push @ignore_list, qr/^initializing the client-side TLS engine/;
3905 push @ignore_list, qr/^header_token: /;
3906 push @ignore_list, qr/^(?:PUSH|POP) boundary/;
3907 push @ignore_list, qr/^recipient limit \d+$/;
3908 push @ignore_list, qr/^scan_dir_next: found/;
3909 push @ignore_list, qr/^open (?:btree|incoming)/;
3910 push @ignore_list, qr/^Renamed to match inode number/;
3911 push @ignore_list, qr/^cleanup_[^:]+:/;
3912 push @ignore_list, qr/^(?:before|after) input_transp_cleanup: /;
3913 push @ignore_list, qr/^event_enable_read: /;
3914 push @ignore_list, qr/^report recipient to all milters /;
3915
3916 # non-anchored
3917 #push @ignore_list, qr/: Greylisted for /;
3918 push @ignore_list, qr/certificate verification (?:depth|failed for)/;
3919 push @ignore_list, qr/re-using session with untrusted certificate, look for details earlier in the log$/;
3920 push @ignore_list, qr/socket: wanted attribute: /;
3921 push @ignore_list, qr/ smtpd cache$/;
3922 push @ignore_list, qr/ old session$/;
3923 push @ignore_list, qr/fingerprint=/;
3924 push @ignore_list, qr/TLS cipher list "/;
3925 }
3926
3927 # Evaluates a given line against the list of ignore patterns.
3928 #
3929 sub in_ignore_list($) {
3930 my $line = shift;
3931
3932 foreach (@ignore_list) {
3933 #return 1 if $line =~ /$_/;
3934 if ($line =~ /$_/) {
3935 #$IGNORED{$_}++;
3936 return 1;
3937 }
3938 }
3939
3940 return 0;
3941 }
3942
3943 # Accepts common fields from a standard delivery attempt, processing then
3944 # and returning modified values
3945 #
3946 sub process_delivery_attempt ($ $ $ $) {
3947 my ($to,$origto,$DDD,$reason) = @_;
3948
3949 $reason =~ s/\((.*)\)/$1/; # Makes capturing nested parens easier
3950 # leave $to/$origto undefined, or strip < > chars if not null address (<>).
3951 defined $to and $to = ($to eq '') ? '<>' : lc $to;
3952 defined $origto and $origto = ($origto eq '') ? '<>' : lc $origto;
3953 my ($localpart, $domainpart) = split ('@', $to);
3954 ($localpart, $domainpart) = ($to, '*unspecified') if ($domainpart eq '');
3955 my ($dsn);
3956
3957 # If recipient_delimiter is set, break localpart into user + extension
3958 # and save localpart in origto if origto is empty
3959 #
3960 if ($Opts{'recipient_delimiter'} and $localpart =~ /\Q$Opts{'recipient_delimiter'}\E/o) {
3961
3962 # special cases: never split mailer-daemon or double-bounce
3963 # or owner- or -request if delim is "-" (dash).
3964 unless ($localpart =~ /^(?:mailer-daemon|double-bounce)$/i or
3965 ($Opts{'recipient_delimiter'} eq '-' and $localpart =~ /^owner-.|.-request$/i)) {
3966 my ($user,$extension) = split (/\Q$Opts{'recipient_delimiter'}\E/, $localpart, 2);
3967 $origto = $localpart if ($origto eq '');
3968 $localpart = $user;
3969 }
3970 }
3971
3972 unless (($dsn) = ($DDD =~ /dsn=(\d\.\d+\.\d+)/o)) {
3973 $dsn = '';
3974 }
3975
3976 if ($Collecting{'delays'} and $DDD =~ m{delay=([\d.]+)(?:, delays=([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+))?}) {
3977 # Message delivery time stamps
3978 # delays=a/b/c/d, where
3979 # a = time before queue manager, including message transmission
3980 # b = time in queue manager
3981 # c = connection setup including DNS, HELO and TLS;
3982 # d = message transmission time.
3983 if (defined $2) {
3984 $Delays{'1: Before qmgr'}{$2}++;
3985 $Delays{'2: In qmgr'}{$3}++;
3986 $Delays{'3: Conn setup'}{$4}++;
3987 $Delays{'4: Transmission'}{$5}++;
3988 }
3989 $Delays{'5: Total'}{$1}++;
3990 }
3991
3992 return ($to,$origto,$localpart,$domainpart,$dsn,$reason);
3993 }
3994
3995 # Processes postfix/bounce messages
3996 #
3997 sub postfix_bounce($) {
3998 my $line = shift;
3999 my $type;
4000
4001 $line =~ s/^(?:$re_QID): //o;
4002 if ($line =~ /^(sender|postmaster) non-delivery notification/o) {
4003 #TDbQ postmaster non-delivery notification: 7446BCD68
4004 #TDbQ sender non-delivery notification: 7446BCD68
4005 $type = 'Non-delivery';
4006 }
4007 elsif ($line =~ /^(sender|postmaster) delivery status notification/o ) {
4008 #TDbQ sender delivery status notification: 7446BCD68
4009 $type = 'Delivery';
4010 }
4011 elsif ($line =~ /^(sender|postmaster) delay notification: /o) {
4012 #TDbQ sender delay notification: AA61EC2F9A
4013 $type = 'Delayed';
4014 }
4015 else {
4016 inc_unmatched('bounce') if ! in_ignore_list($line);
4017 return;
4018 }
4019
4020 $Totals{'notificationsent'}++; return unless ($Collecting{'notificationsent'});
4021 $Counts{'notificationsent'}{$type}{$1}++;
4022 }
4023
4024 # Processes postfix/cleanup messages
4025 # cleanup always has a QID
4026 #
4027 sub postfix_cleanup($) {
4028 my $line = shift;
4029 my ($qid,$reply,$fmthost,$reject_name);
4030
4031 ($qid, $line) = ($1, $2) if ($line =~ /^($re_QID): (.*)$/o );
4032
4033 #TDcQ message-id=<C1BEA2A0.188572%from@example.com>
4034 return if ($line =~ /^message-id=/);
4035
4036 # milter-reject, milter-hold, milter-discard
4037 if ($line =~ s/^milter-//) {
4038 milter_common($line);
4039 return;
4040 }
4041
4042 ### cleanup bounced messages (always_bcc, recipient_bcc_maps, sender_bcc_maps)
4043 # Note: Bounce
4044 # See same code elsewhere "Note: Bounce"
4045 #TDcQ to=<envto@example.com>, relay=none, delay=0.11, delays=0.11/0/0/0, dsn=5.7.1, status=bounced optional text...
4046 #TDcQ to=<envto@example.com>, orig_to=<envto>, relay=none, delay=0.13, delays=0.13/0/0/0, dsn=5.7.1, status=bounced optional text...
4047 if ($line =~ /^to=<(.*?)>,(?: orig_to=<(.*?)>,)? relay=([^,]*).*, ($re_DDD), status=([^ ]+) (.*)$/o) {
4048 # ($to,$origto,$relay,$DDD,$status,$reason) = ($1,$2,$3,$4,$5,$6);
4049 if ($5 ne 'bounced' and $5 ne 'SOFTBOUNCE') {
4050 inc_unmatched('cleanupbounce');
4051 return;
4052 }
4053
4054 my ($to,$origto,$relay,$DDD,$reason) = ($1,$2,$3,$4,$6);
4055 my ($localpart,$domainpart,$dsn);
4056 ($to,$origto,$localpart,$domainpart,$dsn,$reason) = process_delivery_attempt ($to,$origto,$DDD,$reason);
4057
4058 ### local bounce
4059 # XXX local v. remote bounce seems iffy, relative
4060 if ($relay =~ /^(?:none|local|virtual|maildrop|127\.0\.0\.1|avcheck)/) {
4061 $Totals{'bouncelocal'}++; return unless ($Collecting{'bouncelocal'});
4062 $Counts{'bouncelocal'}{get_dsn_msg($dsn)}{$domainpart}{ucfirst($reason)}{$localpart}++;
4063 }
4064 ### remote bounce
4065 else {
4066 ($reply,$fmthost) = cleanhostreply($reason,$relay,$to ne '' ? $to : '<>',$domainpart);
4067 $Totals{'bounceremote'}++; return unless ($Collecting{'bounceremote'});
4068 $Counts{'bounceremote'}{get_dsn_msg($dsn)}{$domainpart}{$localpart}{$fmthost}{$reply}++;
4069 }
4070 }
4071
4072 # *header_checks and body_checks
4073 elsif (header_body_checks($line)) {
4074 #print "cleanup: header_body_checks\n";
4075 return;
4076 }
4077
4078 #TDcQ resent-message-id=4739073.1
4079 #TDcQ resent-message-id=<ARF+DXZwLECdxm@mail.example.com>
4080 #TDcQ resent-message-id=<B19-DVD42188E0example.com>? <120B11@samplepc>
4081 elsif ( ($line =~ /^resent-message-id=<?.+>?$/o )) {
4082 $Totals{'resent'}++;
4083 }
4084
4085 #TDcN unable to dlopen .../sasl2/libplain.so.2: .../sasl2/libplain.so.2: failed to map segment from shared object: Operation not permitted
4086 elsif ($line =~ /^unable to dlopen /) {
4087 # strip extraneous doubling of library path
4088 $line = "$1$2 $3" if ($line =~ /(unable to dlopen )([^:]+: )\2(.+)$/);
4089 postfix_warning($line);
4090 }
4091
4092 else {
4093 inc_unmatched('cleanup(last)') if ! in_ignore_list($line);
4094 }
4095 }
4096
4097 =pod
4098 header_body_checks
4099
4100 Handle cleanup's header_checks and body_checks, and smtp's smtp_body_checks/smtp_*header_checks
4101
4102 Possible actions that log are:
4103
4104 REJECT optional text...
4105 DISCARD optional text...
4106 FILTER transport:destination
4107 HOLD optional text...
4108 REDIRECT user@domain
4109 PREPEND text...
4110 REPLACE text...
4111 WARN optional text...
4112
4113 DUNNO and IGNORE are not logged
4114
4115 Returns:
4116 1: if line matched or handled
4117 0: otherwise
4118 =cut
4119
4120 sub header_body_checks($)
4121 {
4122 my $line = shift;
4123
4124 # bcc, discard, filter, hold, prepend, redirect, reject, replace, warning
4125 return 0 if ($line !~ /^[bdfhprw]/) or # short circuit alternation when no match possible
4126 ($line !~ /^(re(?:ject|direct|place)|filter|hold|discard|prepend|warning|bcc): (header|body|content) (.*)$/);
4127
4128 my ($action,$part,$p3) = ($1,$2,$3);
4129
4130 #print "header_body_checks: action: \"$action\", part: \"$part\", p3: \"$p3\"\n";
4131
4132 my ($trigger,$host,$eto,$p4,$fmthost,$reject_name);
4133 # $re_QID: reject: body ...
4134 # $re_QID: reject: header ...
4135 # $re_QID: reject: content ...
4136
4137
4138 if ($p3 =~ /^(.*) from ([^;]+); from=<.*?>(?: to=<(.*?)>)?(?: proto=\S*)?(?: helo=<.*?>)?(?:: (.*)|$)/) {
4139 ($trigger,$host,$eto,$p4) = ($1,$2,$3,$4);
4140
4141 # $action $part $trigger $host $eto $p4
4142 #TDcQ reject: body Subject: Cheap cialis from local; from=<root@localhost>: optional text...
4143 #TDcQ reject: body Quality replica watches!!! from hb.example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=SMTP helo=<example.com>: optional text...
4144 #TDcQ reject: header To: <user@example.com> from hb.example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: optional text...
4145 # message_reject_characters (postfix >= 2.3)
4146 #TDcQ reject: content Received: by example.com Postfix from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=.example.com>: 5.7.1 disallowed character
4147
4148 #TDcQ filter: header To: to@example.com from hb.example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: transport:destination
4149 #TDcQ hold: header Message-ID: <user@example.com> from localhost[127.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: optional text...
4150 #TDcQ hold: header Subject: Hold Test from local; from=<efrom@example.com> to=<eto@sample.net>: optional text...
4151 #TDcQ hold: header Received: by example.com...from x from local; from=<efrom@example.com>
4152 #TDcQ hold: header Received: from x.com (x.com[10.0.0.1])??by example.com (Postfix) with ESMTP id 630BF??for <X>; Thu, 20 Oct 2006 13:27: from example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
4153 # hold: header Received: from [10.0.0.1] by example.com Thu, 9 Jan 2008 18:06:06 -0500 from sample.net[10.0.0.2]; from=<> to=<to@example.com> proto=SMTP helo=<sample.net>: faked header
4154 #TDcQ redirect: header From: "Attn Men" <attn@example.com> from hb.example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: user@domain
4155 #TDcQ redirect: header From: "Superman" <attn@example.com> from hb.example.com[10.0.0.2]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: user@domain
4156 #TDcQ redirect: body Original drugs from hb.example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=SMTP helo=<example.com>: user@domain
4157 #TDcQ discard: header Subject: **SPAM** Blah... from hb.example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>
4158 #TDcQ prepend: header Rubble: Mr. from localhost[127.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: text...
4159 #TDcQ replace: header Rubble: flintstone from localhost[127.0.0.1]; from=<efrom@apple.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: text...
4160 #TDcQ warning: header Date: Tues, 99:34:67 from localhost[127.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: optional text...
4161 # BCC action (2.6 experimental branch)
4162 #TDcQ bcc: header To: to@example.com from hb.example.com[10.0.0.1]; from=<efrom@example.com> to=<eto@sample.net> proto=ESMTP helo=<example.com>: user@domain
4163
4164 # Note: reject_warning does not seem to occur
4165 }
4166
4167 else {
4168 # smtp_body_checks, smtp_header_checks, smtp_mime_header_checks, smtp_nested_header_checks (postfix >= 2.5)
4169 #TDsQ replace: header Sender: <from@example.com> : Sender: <fm2@sample.net>
4170
4171 $trigger = $p3; $host = ''; $eto = ''; $p4 = $part eq 'body' ? 'smtp_body_checks' : 'smtp_*header_checks';
4172
4173 #inc_unmatched('header_body_checks');
4174 #return 1;
4175 }
4176
4177 #print " trigger: \"$trigger\", host: \"$host\", eto: \"$eto\", p4: \"$p4\"\n";
4178 $trigger =~ s/\s+/ /g;
4179 $trigger = '*unknown reason' if ($trigger eq '');
4180 $eto = '*unknown' if ($eto eq '');
4181
4182 my ($trig,$trig_opt,$text);
4183 if ($part eq 'header') { ($trig = $trigger) =~ s/^([^:]+:).*$/Header check "$1"/; }
4184 elsif ($part eq 'body') { $trig = "Body check"; }
4185 else { $trig = "Content check"; } # message_reject_characters (postfix >= 2.3)
4186
4187 if ($p4 eq '') { $text = '*generic'; $trig_opt = $trig; }
4188 else { $text = $p4; $trig_opt = "$trig ($p4)"; }
4189
4190 if ($host eq 'local') { $fmthost = formathost('127.0.0.1', 'local'); }
4191 elsif ($host =~ /([^[]+)\[([^]]+)\]/) { $fmthost = formathost($2,$1); }
4192 else { $fmthost = '*unknown'; }
4193
4194 # Note: Counts
4195 # Ensure each $Counts{key} accumulator is consistently
4196 # used with the same number of hash key levels throughout the code.
4197 # For example, $Counts{'hold'} below has 4 keys; ensure that every
4198 # other usage of $Counts{'hold'} also has 4 keys. Currently, it is
4199 # OK to set the last key as '', but only the last.
4200
4201 if ($action eq 'reject') {
4202 $Counts{'byiprejects'}{$fmthost}++ if $Collecting{'byiprejects'};
4203
4204 # Note: no temporary or reject_warning
4205 # Note: no reply code - force into a 5xx reject
4206 # XXX this won't be seen if the user has no 5.. entry in reject_reply_patterns
4207 $Totals{$reject_name = "5xxreject$part" }++;
4208 $Counts{$reject_name}{$text}{$eto}{$fmthost}{$trigger}++ if $Collecting{$reject_name};
4209 }
4210 elsif ( $action eq 'filter' ) {
4211 $Totals{'filtered'}++;
4212 $Counts{'filtered'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++ if $Collecting{'filtered'};
4213 }
4214 elsif ( $action eq 'hold' ) {
4215 $Totals{'hold'}++;
4216 $Counts{'hold'}{$trig_opt}{$fmthost}{$eto}{$trigger}++ if $Collecting{'hold'};
4217 }
4218 elsif ( $action eq 'redirect' ) {
4219 $Totals{'redirected'}++;
4220 $Counts{'redirected'}{$trig}{$text}{$eto}{$fmthost}{$trigger}++ if $Collecting{'redirected'};
4221 }
4222 elsif ( $action eq 'discard' ) {
4223 $Totals{'discarded'}++;
4224 $Counts{'discarded'}{$trig}{$fmthost}{$eto}{$trigger}++ if $Collecting{'discarded'};
4225 }
4226 elsif ( $action eq 'prepend' ) {
4227 $Totals{'prepended'}++;
4228 $Counts{'prepended'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++ if $Collecting{'prepended'};
4229 }
4230 elsif ( $action eq 'replace' ) {
4231 $Totals{'replaced'}++;
4232 $Counts{'replaced'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++ if $Collecting{'replaced'};
4233 }
4234 elsif ( $action eq 'warning' ) {
4235 $Totals{'warned'}++;
4236 $Counts{'warned'}{$trig}{$fmthost}{$eto}{$trigger}++ if $Collecting{'warned'};
4237 }
4238 elsif ( $action eq 'bcc' ) {
4239 $Totals{'bcced'}++;
4240 $Counts{'bcced'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++ if $Collecting{'bcced'};
4241 }
4242 else {
4243 inc_unmatched('header_body_checks unexpected action');
4244 }
4245
4246 return 1;
4247 }
4248
4249
4250 # Handle common milter actions:
4251 # milter-reject, milter-hold, milter-discard
4252 # which are created by both smtpd and cleanup
4253 #
4254 sub milter_common($) {
4255 my $line = shift;
4256
4257 #TDsdN milter-reject: MAIL from milterS.example.com[10.0.0.1]: 553 5.1.7 address incomplete; proto=ESMTP helo=<example.com>
4258 #TDsdN milter-reject: CONNECT from milterS.example.com[10.0.0.2]: 451 4.7.1 Service unavailable - try again later; proto=SMTP
4259 #TDsdQ milter-reject: END-OF-MESSAGE from milterS.example.com[10.0.0.3]: 5.7.1 black listed URL host sample.com by ...uribl.com; from=<from@sample.com> to=<to@example.net> proto=ESMTP helo=<example.com>
4260 #TDsdQ milter-hold: END-OF-MESSAGE from milterS.example.com[10.0.0.4]: milter triggers HOLD action; from=<from@sample.com> to=<to@example.net> proto=ESMTP helo=<sample.com>
4261
4262 #TDcQ milter-reject: END-OF-MESSAGE from milterC.example.com[10.0.0.1]: 5.7.1 Some problem; from=<efrom@example.com> to=<eto@sample.net> proto=SMTP helo=<example.com>
4263 #TDcQ milter-reject: CONNECT from milterC.example.com[10.0.0.2]: 5.7.1 Some problem; proto=SMTP
4264 #TDcQ milter-hold: END-OF-MESSAGE from milterC.example.com[10.0.0.3]: milter triggers HOLD action; from=<efrom@example.com> to=<eto@example.net> proto=ESMTP helo=<example.com>
4265 #TDcQ milter-discard: END-OF-MESSAGE from milterC.example.com[10.0.0.4]: milter triggers DISCARD action; from=<efrom@example.com> to=<eto@example.net> proto=ESMTP helo=<example.com>
4266 # 84B82AC8B3: milter-reject: END-OF-MESSAGE from localhost[127.0.0.1]: 5.7.1 Blocked
4267
4268 my ($efrom,$eto,$proto,$helo) = strip_ftph($line);
4269 #print "efrom: '$efrom', eto: '$eto', proto: '$proto', helo: '$helo'\n";
4270 $line =~ s/;$//;
4271
4272 if ($line =~ /^(reject|hold|discard): (\S+) from ([^[]+)\[([^]]+)\](?::\d+)?: (.*)$/) {
4273
4274 my ($action,$stage,$host,$hostip,$reply) = ($1,$2,$3,$4,$5);
4275 #print "action: '$action', stage: '$stage', host: '$host', hostip: '$hostip', reply: '$reply'\n";
4276
4277 if ($action eq 'reject') {
4278 my ($dsn,$fmthost,$reject_name);
4279 ($dsn,$reply) = ($1,$2) if $reply =~ /^($re_DSN) (.*)$/o;
4280 #print " dsn: '$dsn', reply: '$reply'\n";
4281
4282 if ($Collecting{'byiprejects'} and substr($dsn,0,1) eq '5') {
4283 $fmthost = formathost($hostip,$host);
4284 $Counts{'byiprejects'}{$fmthost}++;
4285 }
4286 # Note: reject_warning does not seem to occur
4287 # Note: See rejectmilter elsewhere
4288 $Totals{$reject_name = get_reject_key($dsn) . 'rejectmilter' }++; return unless ($Collecting{$reject_name});
4289 $Counts{$reject_name}{$stage}{$fmthost ? $fmthost : formathost($hostip,$host)}{$reply}++;
4290 }
4291 # milter-hold
4292 elsif ($action eq 'hold') {
4293 $Totals{'hold'}++; return unless ($Collecting{'hold'});
4294 $Counts{'hold'}{'milter'}{$stage}{formathost($hostip,$host)}{$eto}++;
4295 }
4296 # milter-discard
4297 else { # $action eq 'discard'
4298 $Totals{'discarded'}++; return unless ($Collecting{'discarded'});
4299 $Counts{'discarded'}{'milter'}{$stage}{formathost($hostip,$host)}{$eto}++;
4300 }
4301
4302 }
4303 else {
4304 inc_unmatched('milter_common)');
4305 }
4306 }
4307
4308 sub postfix_dnsblog {
4309 my $line = shift;
4310
4311 #postfix/dnsblog[78598]: addr 192.168.0.1 blocked by domain zen.spamhaus.org as 127.0.0.11
4312 if ($line =~ /^addr (\S+) blocked by domain (\S+) as (\S+)$/) {
4313 $Counts{'dnsblog'}{$1}{$2}{$3}++ if $Collecting{'dnsblog'};
4314 }
4315 else {
4316 inc_unmatched('dnsblog') if ! in_ignore_list($line);
4317 return;
4318 }
4319 }
4320
4321 sub postfix_postscreen {
4322 my $line = shift;
4323
4324 #postfix/postscreen[32040]: PREGREET 20 after 0.31 from 192.168.0.1: HELO 10.0.0.1??
4325 #postfix/postscreen[26349]: HANGUP after 0.7 from 192.168.0.4
4326 #postfix/postscreen[13006]: PASS NEW 192.168.0.2
4327 #postfix/postscreen[19500]: PASS OLD 192.168.0.3
4328 #postfix/verify[46072]: cache /var/lib/postfix/verify.db full cleanup: retained=1726 dropped=28 entries
4329 return if ($line =~ /^cache /);
4330
4331 if (($line =~ /^(PREGREET) \d+ (?:after \S+)? from ([^:]+)/) or
4332 ($line =~ /^(HANGUP) (?:after \S+)? from ([\S]+)/)) {
4333 $Counts{'postscreen'}{lc $1}{$2}{$END_KEY}++ if $Collecting{'postscreen'};
4334 }
4335 elsif ($line =~ /^(WHITELISTED|BLACKLISTED|PASS \S+) (\S+)$/) {
4336 $Counts{'postscreen'}{lc $1}{$2}{$END_KEY}++ if $Collecting{'postscreen'};
4337 }
4338 elsif ($line =~ /^DNSBL rank (\S+) for ([\S]+)$/) {
4339 $Counts{'postscreen'}{'dnsbl'}{$2}{$1}++ if $Collecting{'postscreen'};
4340 }
4341 elsif ($line =~ /^reject: connect from ([^:]+):\d+: (.+)$/o) {
4342 # TDxxx reject: connect from 192.168.0.70:2212: all screening ports busy
4343 # not included in general rejects, as connection is not logged yet ???
4344 $Counts{'postscreen'}{'reject'}{$1}{$2}{$END_KEY}++ if $Collecting{'postscreen'};
4345 }
4346 else {
4347 inc_unmatched('postscreen') if ! in_ignore_list($line);
4348 return;
4349 }
4350
4351 $Totals{'postscreen'}++;
4352 }
4353
4354
4355 # Handles postfix/postsuper lines
4356 #
4357 sub postfix_postsuper($) {
4358 my $line = shift;
4359
4360 return if $line =~ /^Deleted: \d+ messages?$/;
4361
4362 if ($line =~ /^Placed on hold: (\d+) messages?$/o) {
4363 #TDps Placed on hold: 2 messages
4364 # Note: See Hold elsewhere
4365 $Totals{'hold'} += $1; return unless ($Collecting{'hold'});
4366 $Counts{'hold'}{'Postsuper'}{'localhost'}{"bulk hold: $1"}{''} += $1;
4367 }
4368 elsif ($line =~ /^Released from hold: (\d+) messages?$/o) {
4369 #TDps Released from hold: 1 message
4370 $Totals{'releasedfromhold'} += $1;
4371 }
4372 elsif ($line =~ /^Requeued: (\d+) messages?$/o) {
4373 #TDps Requeued: 1 message
4374 $Totals{'requeued'} += $1;
4375 }
4376 elsif (my($qid,$p2) = ($line =~ /($re_QID): (.*)$/)) {
4377 # postsuper double reports the following 3 lines
4378 return if ($p2 eq 'released from hold');
4379 return if ($p2 eq 'placed on hold');
4380 return if ($p2 eq 'requeued');
4381
4382 if ($p2 =~ /^removed\s*$/o) {
4383 # Note: See REMOVED elsewhere
4384 # 52CBDC2E0F: removed
4385 delete $SizeByQid{$qid} if (exists $SizeByQid{$qid});
4386 $Totals{'removedfromqueue'}++;
4387 }
4388 elsif (! in_ignore_list ($p2)) {
4389 inc_unmatched('postsuper2');
4390 }
4391 }
4392 elsif (! in_ignore_list ($line)) {
4393 inc_unmatched('postsuper1');
4394 }
4395 }
4396
4397 # Handles postfix panic: lines
4398 #
4399 sub postfix_panic($) {
4400 #TD panic: myfree: corrupt or unallocated memory block
4401 $Totals{'panicerror'}++; return unless ($Collecting{'panicerror'});
4402 $Counts{'panicerror'}{ucfirst($1)}++;
4403 }
4404
4405 # Handles postfix fatal: lines
4406 #
4407 sub postfix_fatal($) {
4408 my ($reason) = shift;
4409
4410 if ($reason =~ /^\S*\(\d+\): Message file too big$/o) {
4411 #TD fatal: root(0): Message file too big
4412 $Totals{'fatalfiletoobig'}++;
4413
4414
4415 # XXX its not clear this is at all useful - consider falling through to last case
4416 } elsif ( $reason =~ /^config variable (\S*): (.*)$/o ) {
4417 #TD fatal: config variable inet_interfaces: host not found: 10.0.0.1:2525
4418 #TD fatal: config variable inet_interfaces: host not found: all:2525
4419 $Totals{'fatalconfigerror'}++; return unless ($Collecting{'fatalconfigerror'});
4420 $Counts{'fatalconfigerror'}{ucfirst($reason)}++;
4421 }
4422 else {
4423 #TD fatal: watchdog timeout
4424 #TD fatal: bad boolean configuration: smtpd_use_tls =
4425
4426 #TDvN fatal: update queue file active/4B709F060E: File too large
4427 $reason =~ s/(^update queue file \w+\/)\w+:/$1*:/;
4428
4429 $Totals{'fatalerror'}++; return unless ($Collecting{'fatalerror'});
4430 $Counts{'fatalerror'}{ucfirst($reason)}++;
4431 }
4432 }
4433 # Handles postfix fatal: lines
4434 #
4435 sub postfix_error($) {
4436 my ($reason) = shift;
4437 # postfix/postfix-script[4271]: error: unknown command: 'rel'
4438
4439 $Totals{'error'}++; return unless ($Collecting{'fatalerror'});
4440 $Counts{'error'}{ucfirst($reason)}++;
4441 }
4442
4443 # Handles postfix warning: lines
4444 # and additional lines coerced into warnings
4445 #
4446 sub postfix_warning($) {
4447 my ($warning) = shift;
4448
4449 # Skip these
4450 return if ($warning =~ /$re_QID: skipping further client input$/o);
4451 return if ($warning =~ /^Mail system is down -- accessing queue directly$/o);
4452 return if ($warning =~ /^SASL authentication failure: (?:Password verification failed|no secret in database)$/o);
4453 return if ($warning =~ /^no MX host for .* has a valid A record$/o);
4454 return if ($warning =~ /^uid=\d+: Broken pipe$/o);
4455
4456 #TD warning: connect to 127.0.0.1:12525: Connection refused
4457 #TD warning: problem talking to server 127.0.0.1:12525: Connection refused
4458 #TD warning: valid_ipv4_hostaddr: invalid octet count:
4459
4460 my ($domain,$to,$type,$site,$helo,$cmd);
4461 my ($addr,$size,$hostip,$host,$port,$reason,$qid,$queue,$reason2,$process,$status,$service);
4462
4463 if (($hostip,$host,$reason) = ($warning =~ /^(?:smtpd_peer_init: )?([^:]+): hostname ([^ ]+) verification failed: (.*)$/) or
4464 ($hostip,$reason,$host) = ($warning =~ /^(?:smtpd_peer_init: )?([^:]+): (address not listed for hostname) (.*)$/)) {
4465 #TD warning: 10.0.0.1: hostname sample.com verification failed: Host not found
4466 #TD warning: smtpd_peer_init: 192.168.0.1: hostname example.com verification failed: Name or service not known
4467 #TD warning: 192.168.0.1: address not listed for hostname sample.net
4468 $Totals{'hostnameverification'}++; return unless ($Collecting{'hostnameverification'});
4469 $Counts{'hostnameverification'}{ucfirst($reason)}{formathost($hostip,$host)}++;
4470
4471 } elsif (($warning =~ /^$re_QID: queue file size limit exceeded$/o) or
4472 ($warning =~ /^uid=\d+: File too large$/o)) {
4473 $Totals{'warnfiletoobig'}++;
4474
4475 } elsif ($warning =~ /^database (?:[^ ]*) is older than source file ([\w\/]+)$/o) {
4476 #TD warning: database /etc/postfix/client_checks.db is older than source file /etc/postfix/client_checks
4477 $Totals{'databasegeneration'}++; return unless ($Collecting{'databasegeneration'});
4478 $Counts{'databasegeneration'}{$1}++;
4479
4480 } elsif (($reason,$qid,$reason2) = ($warning =~ /^(open active) ($re_QID): (.*)$/o) or
4481 ($reason,$qid,$reason2) = ($warning =~ /^qmgr_active_corrupt: (save corrupt file queue active) id ($re_QID): (.*)$/o) or
4482 ($qid,$reason,$reason2) = ($warning =~ /^($re_QID): (write queue file): (.*)$/o)) {
4483
4484 #TD warning: open active BDB9B1309F7: No such file or directory
4485 #TD warning: qmgr_active_corrupt: save corrupt file queue active id 4F4272F342: No such file or directory
4486 #TD warning: E669DE52: write queue file: No such file or directory
4487
4488 $Totals{'queuewriteerror'}++; return unless ($Collecting{'queuewriteerror'});
4489 $Counts{'queuewriteerror'}{"$reason: $reason2"}{$qid}++;
4490
4491 } elsif (($qid,$reason) = ($warning =~ /^qmgr_active_done_3_generic: remove ($re_QID) from active: (.*)$/o)) {
4492 #TD warning: qmgr_active_done_3_generic: remove AF0F223FC05 from active: No such file or directory
4493 $Totals{'queuewriteerror'}++; return unless ($Collecting{'queuewriteerror'});
4494 $Counts{'queuewriteerror'}{"remove from active: $reason"}{$qid}++;
4495
4496 } elsif (($queue,$qid) = ($warning =~ /^([^\/]*)\/($re_QID): Error writing message file$/o )) {
4497 #TD warning: maildrop/C9E66ADF: Error writing message file
4498 $Totals{'messagewriteerror'}++; return unless ($Collecting{'messagewriteerror'});
4499 $Counts{'messagewriteerror'}{$queue}{$qid}++;
4500
4501 } elsif (($process,$status) = ($warning =~ /^process ([^ ]*) pid \d+ exit status (\d+)$/o)) {
4502 #TD warning: process /usr/lib/postfix/smtp pid 9724 exit status 1
4503 $Totals{'processexit'}++; return unless ($Collecting{'processexit'});
4504 $Counts{'processexit'}{"Exit status $status"}{$process}++;
4505
4506 } elsif ($warning =~ /^mailer loop: (.*)$/o) {
4507 #TD warning: mailer loop: best MX host for example.com is local
4508 $Totals{'mailerloop'}++; return unless ($Collecting{'mailerloop'});
4509 $Counts{'mailerloop'}{$1}++;
4510
4511 } elsif ($warning =~ /^no (\S+) host for (\S+) has a valid address record$/) {
4512 #TDs warning: no MX host for example.com has a valid address record
4513 $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'});
4514 $Counts{'dnserror'}{"No $1 host has a valid address record"}{$2}{$END_KEY}++;
4515
4516 } elsif ($warning =~ /^(Unable to look up \S+ host) (.+)$/) {
4517 #TDsd warning: Unable to look up MX host for example.com: Host not found
4518 #TDsd warning: Unable to look up MX host mail.example.com for Sender address from@example.com: hostname nor servname provided, or not known
4519 #TDsd warning: Unable to look up NS host ns1.example.logal for Sender address bounce@example.local: No address associated with hostname
4520 $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'});
4521
4522 my ($problem,$target,$reason) = ($1, split(/: /,$2));
4523 $reason =~ s/, try again//;
4524
4525 if ($target =~ /^for (\S+)$/) {
4526 $Counts{'dnserror'}{$problem}{ucfirst($reason)}{$1}{$END_KEY}++;
4527 }
4528 elsif ($target =~ /^(\S+)( for \S+ address) (\S+)$/) {
4529 $Counts{'dnserror'}{$problem . lc($2)}{ucfirst($reason)}{$1}{$3}++;
4530 }
4531
4532 } elsif ($warning =~ /^((?:malformed|numeric) domain name in .+? of \S+ record) for (.*):(.*)?$/) {
4533 my ($problem,$domain,$reason) = ($1,$2,$3);
4534 #TDsd warning: malformed domain name in resource data of MX record for example.com:
4535 #TDsd warning: malformed domain name in resource data of MX record for example.com: mail.example.com\\032
4536 #TDsd warning: numeric domain name in resource data of MX record for sample.com: 192.168.0.1
4537 $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'});
4538 $Counts{'dnserror'}{ucfirst($problem)}{$domain}{$reason eq '' ? '*unknown' : $reason}{$END_KEY}++;
4539
4540 } elsif ($warning =~ /^numeric hostname: ([\S]+)$/) {
4541 #TD warning: numeric hostname: 192.168.0.1
4542 $Totals{'numerichostname'}++; return unless ($Collecting{'numerichostname'});
4543 $Counts{'numerichostname'}{$1}++;
4544
4545 } elsif ( ($host,$hostip,$port,$type,$reason) = ($warning =~ /^([^[]+)\[([^]]+)\](?::(\d+))? (sent \w+ header instead of SMTP command): (.*)$/) or
4546 ($type,$host,$hostip,$port,$reason) = ($warning =~ /^(non-E?SMTP command) from ([^[]+)\[([^]]+)\](?::(\d+))?: (.*)$/) or
4547 ($type,$host,$hostip,$port,$reason) = ($warning =~ /^(?:$re_QID: )?(non-E?SMTP response) from ([^[]+)\[([^]]+)\](?::(\d+))?:(?: (.*))?$/o)) {
4548 # ancient
4549 #TDsd warning: example.com[192.168.0.1] sent message header instead of SMTP command: From: "Someone" <40245426501example.com>
4550 # current
4551 #TDsd warning: non-SMTP command from sample.net[10.0.0.1]: Received: from 192.168.0.1 (HELO bogus.sample.com)
4552 #TDs warning: 6B01A8DEF: non-ESMTP response from mail.example.com[192.168.0.1]:25:
4553
4554 $Totals{'smtpconversationerror'}++; return unless ($Collecting{'smtpconversationerror'});
4555 $host .= ' :' . $port if ($port and $port ne '25');
4556 $Counts{'smtpconversationerror'}{ucfirst($type)}{formathost($hostip,$host)}{$reason}++;
4557
4558 } elsif ($warning =~ /^valid_hostname: (.*)$/o) {
4559 #TD warning: valid_hostname: empty hostname
4560 $Totals{'hostnamevalidationerror'}++; return unless ($Collecting{'hostnamevalidationerror'});
4561 $Counts{'hostnamevalidationerror'}{$1}++;
4562
4563 } elsif (($host,$hostip,$type,$reason) = ($warning =~ /^([^[]+)\[([^]]+)\](?::\d+)?: SASL (.*) authentication failed(.*)$/)) {
4564 #TDsd warning: unknown[10.0.0.1]: SASL LOGIN authentication failed: bad protocol / cancel
4565 #TDsd warning: example.com[192.168.0.1]: SASL DIGEST-MD5 authentication failed
4566 # see saslauthfail elsewhere
4567 $Totals{'saslauthfail'}++; return unless ($Collecting{'saslauthfail'});
4568 if ($reason) { $reason = $type . $reason; }
4569 else { $reason = $type; }
4570 $Counts{'saslauthfail'}{$reason}{formathost($hostip,$host)}++;
4571
4572 # XXX replace $re_IP below with RE that truely captures IPv4 and IPv6, without
4573 # XXX being too expensive. Existing re_IP is insufficient for IPv6.
4574 } elsif (($host,$site,$reason) = ($warning =~ /^([^:]*): RBL lookup error:.* Name service error for (?:name=)?$re_IP\.([^:]*): (.*)$/o)) {
4575 #TD warning: 192.168.0.1.sbl.spamhaus.org: RBL lookup error: Host or domain name not found. Name service error for name=192.168.0.1.sbl.spamhaus.org type=A: Host not found, try again
4576
4577 #TD warning: 10.0.0.1.relays.osirusoft.com: RBL lookup error: Name service error for 10.0.0.1.relays.osirusoft.com: Host not found, try again
4578 $Totals{'rblerror'}++; return unless ($Collecting{'rblerror'});
4579 $Counts{'rblerror'}{$site}{$reason}{$host}++;
4580
4581 } elsif (
4582 ($host,$hostip,$reason,$helo) = ($warning =~ /^host ([^[]+)\[([^]]+)\](?::\d+)? (greeted me with my own hostname) ([^ ]*)$/ ) or
4583 ($host,$hostip,$reason,$helo) = ($warning =~ /^host ([^[]+)\[([^]]+)\](?::\d+)? (replied to HELO\/EHLO with my own hostname) ([^ ]*)$/ )) {
4584 #TDs warning: host example.com[192.168.0.1] greeted me with my own hostname example.com
4585 #TDs warning: host example.com[192.168.0.1] replied to HELO/EHLO with my own hostname example.com
4586 $Totals{'heloerror'}++; return unless ($Collecting{'heloerror'});
4587 $Counts{'heloerror'}{ucfirst($reason)}{formathost($hostip,$host)}++;
4588
4589 } elsif (($size,$host,$hostip) = ($warning =~ /^bad size limit "([^"]+)" in EHLO reply from ([^[]+)\[([^]]+)\](?::\d+)?$/ )) {
4590 #TD warning: bad size limit "-679215104" in EHLO reply from example.com[192.168.0.1]
4591 $Totals{'heloerror'}++; return unless ($Collecting{'heloerror'});
4592 $Counts{'heloerror'}{"Bad size limit in EHLO reply"}{formathost($hostip,$host)}{"$size"}++;
4593
4594 } elsif ( ($host,$hostip,$cmd,$addr) = ($warning =~ /^Illegal address syntax from ([^[]+)\[([^]]+)\](?::\d+)? in ([^ ]*) command: (.*)/ )) {
4595 #TD warning: Illegal address syntax from example.com[192.168.0.1] in MAIL command: user@sample.net
4596 $addr =~ s/[<>]//g unless ($addr eq '<>');
4597 $Totals{'illegaladdrsyntax'}++; return unless ($Collecting{'illegaladdrsyntax'});
4598 $Counts{'illegaladdrsyntax'}{$cmd}{$addr}{formathost($hostip,$host)}++;
4599
4600 } elsif ($warning =~ /^(timeout|premature end-of-input) on (.+) while reading (.*)$/o
4601 or $warning =~ /^(malformed (?:base64|numerical)|unexpected end-of-input) from (.+) while reading (.*)$/o) {
4602
4603 #TDs warning: premature end-of-input on private/anvil while reading input attribute name
4604 #TDs warning: timeout on private/anvil while reading input attribute data
4605 #TDs warning: unexpected end-of-input from 127.0.0.1:10025 socket while reading input attribute name
4606 #TDs warning: malformed base64 data from %s while reading input attribute data: ...
4607 #TDs warning: malformed numerical data from %s while reading input attribute data: ...
4608
4609 $Totals{'attrerror'}++; return unless ($Collecting{'attrerror'});
4610 $Counts{'attrerror'}{$2}{$1}{$3}++;
4611
4612 } elsif ($warning =~ /^(.*): (bad command startup -- throttling)/o) {
4613 #TD warning: /usr/libexec/postfix/trivial-rewrite: bad command startup -- throttling
4614 $Totals{'startuperror'}++; return unless ($Collecting{'startuperror'});
4615 $Counts{'startuperror'}{ucfirst($2)}{$1}++;
4616
4617 } elsif ($warning =~ /(problem talking to service [^:]*): (.*)$/o) {
4618 #TD warning: problem talking to service rewrite: Connection reset by peer
4619 #TD warning: problem talking to service rewrite: Success
4620 $Totals{'communicationerror'}++; return unless ($Collecting{'communicationerror'});
4621 $Counts{'communicationerror'}{ucfirst($1)}{$2}++;
4622
4623 } elsif (my ($map,$key) = ($warning =~ /^$re_QID: ([^ ]*) map lookup problem for (.*)$/o)) {
4624 #TD warning: 6F74F74431: virtual_alias_maps map lookup problem for root@example.com
4625 $Totals{'mapproblem'}++; return unless ($Collecting{'mapproblem'});
4626 $Counts{'mapproblem'}{$map}{$key}++;
4627
4628 } elsif (($map,$reason) = ($warning =~ /^pcre map ([^,]+), (.*)$/o)) {
4629 #TD warning: pcre map /etc/postfix/body_checks, line 92: unknown regexp option "F": skipping this rule
4630 $Totals{'mapproblem'}++; return unless ($Collecting{'mapproblem'});
4631 $Counts{'mapproblem'}{$map}{$reason}++;
4632
4633 } elsif (($reason) = ($warning =~ /dict_ldap_lookup: (.*)$/o)) {
4634 #TD warning: dict_ldap_lookup: Search error 80: Internal (implementation specific) error
4635 $Totals{'ldaperror'}++; return unless ($Collecting{'ldaperror'});
4636 $Counts{'ldaperror'}{$reason}++;
4637
4638 } elsif (($type,$size,$host,$hostip,$service) = ($warning =~ /^(.+) limit exceeded: (\d+) from ([^[]+)\[([^]]+)\](?::\d+)? for service (.*)/ )) {
4639 #TDsd warning: Connection concurrency limit exceeded: 51 from example.com[192.168.0.1] for service smtp
4640 #TDsd warning: Connection rate limit exceeded: 20 from mail.example.com[192.168.0.1] for service smtp
4641 #TDsd warning: Connection rate limit exceeded: 30 from unknown[unknown] for service smtp
4642 #TDsd warning: Recipient address rate limit exceeded: 21 from example.com[10.0.0.1] for service smtp
4643 #TDsd warning: Message delivery request rate limit exceeded: 11 from example.com[10.0.0.1] for service smtp
4644 #TDsd warning: New TLS session rate limit exceeded: 49 from example.com[10.0.0.1] for service smtp
4645 $Totals{'anvil'}++; return unless ($Collecting{'anvil'});
4646 $Counts{'anvil'}{$service}{$type}{formathost($hostip,$host)}{$size}++;
4647
4648 } elsif (my ($extname,$intname,$limit) = ($warning =~ /service "([^"]+)" \(([^)]+)\) has reached its process limit "([^"]+)":/o)) {
4649 #TD warning: service "smtp" (25) has reached its process limit "50": new clients may experience noticeable delays
4650 $Totals{'processlimit'}++; return unless ($Collecting{'processlimit'});
4651 $Counts{'processlimit'}{'See http://www.postfix.org/STRESS_README.html'}{"$extname ($intname)"}{$limit}++;
4652
4653 } else {
4654 #TDsd warning: No server certs available. TLS won't be enabled
4655 #TDs warning: smtp_connect_addr: bind <localip>: Address already in use
4656
4657 # These two messages follow ProcessLimit message above
4658 #TDm warning: to avoid this condition, increase the process count in master.cf or reduce the service time per client
4659 #TDm warning: see http://www.postfix.org/STRESS_README.html for examples of stress-dependent configuration settings
4660 return if ($warning =~ /^to avoid this condition,/o);
4661 return if ($warning =~ /^see http:\/\/www\.postfix\.org\/STRESS_README.html/o);
4662
4663 #TDsd warning: 009314BD9E: read timeout on cleanup socket
4664 $warning =~ s/^$re_QID: (read timeout on \S+ socket)/$1/;
4665
4666 #TDsd warning: Read failed in network_biopair_interop with errno=0: num_read=0, want_read=11
4667 #TDs warning: Read failed in network_biopair_interop with errno=0: num_read=0, want_read=11
4668 $warning =~ s/^(Read failed in network_biopair_interop) with .*$/$1/;
4669
4670 $Totals{'warningsother'}++; return unless ($Collecting{'warningsother'});
4671 $Counts{'warningsother'}{$warning}++;
4672 }
4673 }
4674
4675 # Handles postfix/postfix-script lines
4676 #
4677 sub postfix_script($) {
4678 my $line = shift;
4679
4680 return if ($line =~ /^the Postfix mail system is running: PID: /o);
4681
4682 if ($line =~ /^starting the Postfix mail system/o) {
4683 $Totals{'postfixstart'}++;
4684 }
4685 elsif ($line =~ /^stopping the Postfix mail system/o) {
4686 $Totals{'postfixstop'}++;
4687 }
4688 elsif ($line =~ /^refreshing the Postfix mail system/o) {
4689 $Totals{'postfixrefresh'}++;
4690 }
4691 elsif ($line =~ /^waiting for the Postfix mail system to terminate/o) {
4692 $Totals{'postfixwaiting'}++;
4693 }
4694 elsif (! in_ignore_list ($line)) {
4695 inc_unmatched('postfix_script');
4696 }
4697 }
4698
4699 # Clean up a server's reply, to give some uniformity to reports
4700 #
4701 sub cleanhostreply($ $ $ $) {
4702 my ($hostreply,$relay,$recip,$domain) = @_;
4703
4704 my $fmtdhost = '';
4705 my ($r1, $r2, $dsn, $msg, $host, $event);
4706
4707 #print "RELAY: $relay, RECIP: $recip, DOMAIN: $domain\n";
4708 #print "HOSTREPLY: \"$hostreply\"\n";
4709 return ('Accepted', '*unknown') if $hostreply =~ /^25\d/o;
4710
4711 # Host or domain name not found. Name service error for name=example.com type=MX: Host not found...
4712 if ($hostreply =~ /^Host or domain name not found. Name service error for name=([^:]+): Host not found/o) {
4713 return ('Host not found', $1);
4714 }
4715
4716 if (($host,$dsn,$r1) = ($hostreply =~ /host (\S+) said: ($re_DSN)[\- :]*"?(.*)"?$/o)) {
4717 # Strip recipient address from host's reply - we already have it in $recip.
4718 $r1 =~ s/[<(]?\Q$recip\E[>)]?\W*//ig;
4719
4720 # Strip and capture "in reply to XYZ command" from host's reply
4721 if ($r1 =~ s/\s*[(]?(?:in reply to (.*) command)[)]?//o) {
4722 $r2 = ": $1";
4723 }
4724 $r1 =~ s/^Recipient address rejected: //o;
4725 # Canonicalize numerous forms of "recipient unknown"
4726 if ( $r1 =~ /^user unknown/i
4727 or $r1 =~ /^unknown user/i
4728 or $r1 =~ /^unknown recipient address/i
4729 or $r1 =~ /^invalid recipient/i
4730 or $r1 =~ /^recipient unknown/i
4731 or $r1 =~ /^sorry, no mailbox here by that name/i
4732 or $r1 =~ /^User is unknown/
4733 or $r1 =~ /^User not known/
4734 or $r1 =~ /^MAILBOX NOT FOUND/
4735 or $r1 =~ /^Recipient Rejected: No account by that name here/
4736 or $r1 =~ /^Recipient does not exist here/
4737 or $r1 =~ /The email account that you tried to reach does not exist./ # Google's long mess
4738 or $r1 =~ /(?:no such user|user unknown)/i
4739 )
4740 {
4741 #print "UNKNOWN RECIP: $r1\n";
4742 $r1 = 'Unknown recipient';
4743 }
4744 elsif ($r1 =~ /greylisted/oi) {
4745 #print "GREYLISTED RECIP: $r1\n";
4746 $r1 = 'Recipient greylisted';
4747 }
4748 elsif ($r1 =~ /^Message temporarily deferred - (\d\.\d+\.\d+)\. Please refer to (.+)$/o) {
4749 # Yahoo: 421 Message temporarily deferred - 4.16.51. Please refer to http://... (in reply to end of DATA command))
4750 $dsn = "$dsn $1"; $r1 = "see $2";
4751 }
4752 elsif ($r1 =~ /^Resources temporarily not available - Please try again later \[#(\d\.\d+\.\d+)\]\.$/o) {
4753 #Yahoo 451 Resources temporarily not available - Please try again later [#4.16.5].
4754 $dsn = "$dsn $1"; $r1 = "resources not available";
4755 }
4756 elsif ($r1 =~ /^Message temporarily deferred - (\[\d+\])/o) {
4757 # Yahoo: 451 Message temporarily deferred - [160]
4758 $dsn = "$dsn $1"; $r1 = '';
4759 }
4760 }
4761
4762 elsif ($hostreply =~ /^connect to (\S+): (.*)$/o) {
4763 #print "CONNECT: $hostreply\n";
4764 $host = $1; $r1 = $2; $r1 =~ s/server refused to talk to me/refused/;
4765 }
4766
4767 elsif ($hostreply =~ /^host (\S+) refused to talk to me: (.*)$/o) {
4768 $host = $1; $msg = $2;
4769 #print "HOSTREFUSED: $hostreply\n";
4770 #Yahoo: '421 Message from (10.0.0.1) temporarily deferred - 4.16.50. Please refer to http://...
4771 if ($msg =~ /^(\d+) Message from \([^)]+\) temporarily deferred - (\d\.\d+\.\d+)\. Please refer to (.+)$/) {
4772 $dsn = "$1 $2"; $msg = "see $3";
4773 }
4774 #$r1 = join(': ', 'refused', $msg);
4775 $r1 = $msg;
4776 }
4777 elsif ($hostreply =~ /^(delivery temporarily suspended): connect to (\S+): (.*)$/o) {
4778 #print "DELIVERY SUSP: $hostreply\n";
4779 $host = $2; $r1 = join(': ', $1, $3);
4780 }
4781 elsif ($hostreply =~ /^(delivery temporarily suspended: conversation) with (\S+) (.*)$/o) {
4782 # delivery temporarily suspended: conversation with example.com[10.0.0.1] timed out while receiving the initial server greeting)
4783 #print "DELIVERY SUSP2: $hostreply\n";
4784 $host = $2; $r1 = join(' ', $1, $3);
4785 }
4786 elsif (($event,$host,$r1) = ($hostreply =~ /^(lost connection|conversation) with (\S+) (.*)$/o)) {
4787 #print "LOST conv/conn: $hostreply\n";
4788 $r1 = join(' ',$event,$r1);
4789 }
4790 elsif ($hostreply =~ /^(.*: \S+maildrop: Unable to create a dot-lock) at .*$/o) {
4791 #print "MAILDROP: $hostreply\n";
4792 $r1 = $1;
4793 }
4794 elsif ($hostreply =~ /^mail for (\S+) loops back to myself/o) {
4795 #print "LOOP: $hostreply\n";
4796 $host = $1; $r1 = 'mailer loop';
4797 }
4798 elsif ($hostreply =~ /^unable to find primary relay for (\S+)$/o) {
4799 #print "NORELAY: $hostreply\n";
4800 $host = $1; $r1 = 'no relay found';
4801 }
4802 elsif ($hostreply =~ /^message size \d+ exceeds size limit \d+ of server (\S+)\s*$/o) {
4803 #print "TOOBIG: $hostreply\n";
4804 $host = $1; $r1 = 'message too big';
4805 }
4806 else {
4807 #print "UNMATCH: $hostreply\n";
4808 $r1 = $hostreply;
4809 }
4810
4811 #print "R1: $r1, R2: $r2\n";
4812 $r1 =~ s/for name=\Q$domain\E //ig;
4813
4814 if ($host eq '') {
4815 if ($relay =~ /([^[]+)\[([^]]+)\]/) {
4816 $fmtdhost = formathost($2,$1);
4817 }
4818 else {
4819 $fmtdhost = '*unknown';
4820 }
4821 }
4822 elsif ($host =~ /^([^[]+)\[([^]]+)\]/) {
4823 $fmtdhost = formathost($2,$1);
4824 }
4825 else {
4826 $fmtdhost = $host;
4827 }
4828
4829 return (($dsn ? "$dsn " : '' ) . "\u$r1$r2", $fmtdhost);
4830 }
4831
4832 # Strip and return from, to, proto, and helo information from a log line
4833 # From is set to the empty envelope sender <> as necessary, and To is
4834 # always lowercased.
4835 #
4836 # Note: modifies its input for efficiency
4837 #
4838 sub strip_ftph($) {
4839 my ($helo, $proto, $to, $from);
4840 #print "strip_ftph: '$_[0]\n";
4841 $helo = ($_[0] =~ s/\s+helo=<(.*?)>\s*$//) == 1 ? $1 : '*unavailable';
4842 $proto = ($_[0] =~ s/\s+proto=(\S+)\s*$//) == 1 ? $1 : '*unavailable';
4843 $to = ($_[0] =~ s/\s+to=<(.*?)>\s*$//) == 1 ? (lc($1) || '<>') : '*unavailable';
4844 $from = ($_[0] =~ s/\s+from=<(.*?)>\s*$//) == 1 ? ( $1 || '<>') : '*unavailable';
4845
4846 #print "helo: $helo, proto: $proto, to: $to, from: $from\n";
4847 #print "strip_ftph: final: '$_[0]'\n";
4848 return ($from,$to,$proto,$helo);
4849 }
4850
4851 # Initialize the Getopts option list. Requires the Section table to
4852 # be built already.
4853 #
4854 sub init_getopts_table() {
4855 print "init_getopts_table: enter\n" if $Opts{'debug'} & Logreporters::D_ARGS;
4856
4857 init_getopts_table_common(@supplemental_reports);
4858
4859 add_option ('recipient_delimiter=s');
4860 add_option ('delays!');
4861 add_option ('show_delays=i', sub { $Opts{'delays'} = $_[1]; 1; });
4862 add_option ('delays_percentiles=s');
4863 add_option ('reject_reply_patterns=s');
4864 add_option ('ignore_services=s');
4865 add_option ('postgrey_delays!');
4866 add_option ('postgrey_show_delays=i', sub { $Opts{'postgrey_delays'} = $_[1]; 1; });
4867 add_option ('postgrey_delays_percentiles=s');
4868 add_option ('unknown!', sub { $Opts{'unknown'} = $_[1]; 1; });
4869 add_option ('show_unknown=i', sub { $Opts{'unknown'} = $_[1]; 1; });
4870
4871 =pod
4872 # aliases and backwards compatibility
4873 add_option ('msgsdeferred=s', \$Opts{'deferred'});
4874 add_option ('msgsdelivered=s', \$Opts{'delivered'});
4875 add_option ('msgssent=s', \$Opts{'sent'});
4876 add_option ('msgssentlmtp=s', \$Opts{'sentlmtp'});
4877 add_option ('msgsforwarded=s', \$Opts{'forwarded'});
4878 add_option ('msgsresent=s', \$Opts{'resent'});
4879 add_option ('warn=s', \$Opts{'warned'});
4880 add_option ('held=s', \$Opts{'hold'});
4881 =cut
4882 }
4883
4884 # Builds the entire @Section table used for data collection
4885 #
4886 # Each Section entry has as many as six fields:
4887 #
4888 # 1. Section array reference
4889 # 2. Key to %Counts, %Totals accumulator hashes, and %Collecting hash
4890 # 3. Output in Detail report? (must also a %Counts accumulator)
4891 # 4. Numeric output format specifier for Summary report
4892 # 5. Section title for Summary and Detail reports
4893 # 6. A hash to a divisor used to calculate the percentage of a total for that key
4894 #
4895 # Use begin_section_group/end_section_group to create groupings around sections.
4896 #
4897 # Sections can be freely reordered if desired, but maintain proper group nesting.
4898 #
4899 #
4900 # The reject* entries of this table are dynamic, in that they are built based
4901 # upon the value of $Opts{'reject_reply_patterns'}, which can be specified by
4902 # either command line or configuration file. This allows various flavors, of
4903 # reject sections based on SMTP reply code (eg. 421 45x, 5xx, etc.). Instead
4904 # of creating special sections for each reject variant, the primary key of each
4905 # reject section could have been the SMTP reply code. However, this would
4906 # require special-case processing to distinguish 4xx temporary rejects from 5xx
4907 # permanent rejects in various Totals{'totalrejects*'} counts, and in the
4908 # Totals{'totalrejects'} tally.
4909 #
4910 # Sections can be freely reordered if desired.
4911 sub build_sect_table() {
4912 if ($Opts{'debug'} & Logreporters::D_SECT) {
4913 print "build_sect_table: enter\n";
4914 print "\treject patterns: $Opts{'reject_reply_patterns'}\n";
4915 }
4916 my $S = \@Sections;
4917
4918 # References to these are used in the Sections table below; we'll predeclare them.
4919 $Totals{'totalrejects'} = 0;
4920 $Totals{'totalrejectswarn'} = 0;
4921 $Totals{'totalacceptplusreject'} = 0;
4922
4923 # Configuration and critical errors appear first
4924
4925 # SECTIONREF, NAME, DETAIL, FMT, TITLE, DIVISOR
4926 begin_section_group ($S, 'warnings');
4927 add_section ($S, 'panicerror', 1, 'd', '*Panic: General panic');
4928 add_section ($S, 'fatalfiletoobig', 0, 'd', '*Fatal: Message file too big');
4929 add_section ($S, 'fatalconfigerror', 1, 'd', '*Fatal: Configuration error');
4930 add_section ($S, 'fatalerror', 1, 'd', '*Fatal: General fatal');
4931 add_section ($S, 'error', 1, 'd', '*Error: General error');
4932 add_section ($S, 'processlimit', 1, 'd', '*Warning: Process limit reached, clients may delay');
4933 add_section ($S, 'warnfiletoobig', 0, 'd', '*Warning: Queue file size limit exceeded');
4934 add_section ($S, 'warninsufficientspace', 0, 'd', '*Warning: Insufficient system storage error');
4935 add_section ($S, 'warnconfigerror', 1, 'd', '*Warning: Server configuration error');
4936 add_section ($S, 'queuewriteerror', 1, 'd', '*Warning: Error writing queue file');
4937 add_section ($S, 'messagewriteerror', 1, 'd', '*Warning: Error writing message file');
4938 add_section ($S, 'databasegeneration', 1, 'd', '*Warning: Database is older than source file');
4939 add_section ($S, 'mailerloop', 1, 'd', '*Warning: Mailer loop');
4940 add_section ($S, 'startuperror', 1, 'd', '*Warning: Startup error');
4941 add_section ($S, 'mapproblem', 1, 'd', '*Warning: Map lookup problem');
4942 add_section ($S, 'attrerror', 1, 'd', '*Warning: Error reading attribute data');
4943 add_section ($S, 'anvil', 1, 'd', '*Warning: Anvil limit reached');
4944 add_section ($S, 'processexit', 1, 'd', 'Process exited');
4945 add_section ($S, 'hold', 1, 'd', 'Placed on hold');
4946 add_section ($S, 'communicationerror', 1, 'd', 'Postfix communications error');
4947 add_section ($S, 'saslauthfail', 1, 'd', 'SASL authentication failed');
4948 add_section ($S, 'ldaperror', 1, 'd', 'LDAP error');
4949 add_section ($S, 'warningsother', 1, 'd', 'Miscellaneous warnings');
4950 add_section ($S, 'totalrejectswarn', 0, 'd', 'Reject warnings (warn_if_reject)');
4951 end_section_group ($S, 'warnings');
4952
4953 begin_section_group ($S, 'bytes', "\n");
4954 add_section ($S, 'bytesaccepted', 0, 'Z', 'Bytes accepted '); # Z means print scaled as in 1k, 1m, etc.
4955 add_section ($S, 'bytessentsmtp', 0, 'Z', 'Bytes sent via SMTP');
4956 add_section ($S, 'bytessentlmtp', 0, 'Z', 'Bytes sent via LMTP');
4957 add_section ($S, 'bytesdelivered', 0, 'Z', 'Bytes delivered');
4958 add_section ($S, 'bytesforwarded', 0, 'Z', 'Bytes forwarded');
4959 end_section_group ($S, 'bytes', $sep1);
4960
4961 begin_section_group ($S, 'acceptreject', "\n");
4962 begin_section_group ($S, 'acceptreject2', "\n");
4963 add_section ($S, 'msgsaccepted', 0, 'd', 'Accepted', \$Totals{'totalacceptplusreject'});
4964 add_section ($S, 'totalrejects', 0, 'd', 'Rejected', \$Totals{'totalacceptplusreject'});
4965 end_section_group ($S, 'acceptreject2', $sep2);
4966 add_section ($S, 'totalacceptplusreject', 0, 'd', 'Total', \$Totals{'totalacceptplusreject'});
4967 end_section_group ($S, 'acceptreject', $sep1);
4968
4969 # The various Reject sections are built dynamically based upon a list of reject reply keys,
4970 # which are user-configured via $Opts{'reject_reply_patterns'}
4971 @RejectPats = ();
4972 foreach my $rejpat (split /[ ,]/, $Opts{'reject_reply_patterns'}) {
4973 if ($rejpat !~ /^(warn|[45][\d.]{2})$/io) {
4974 print STDERR usage "Invalid pattern \"$rejpat\" in reject_reply_patterns";
4975 exit (2);
4976 }
4977 if (grep (/\Q$rejpat\E/, @RejectPats) == 0) {
4978 push @RejectPats, $rejpat
4979 }
4980 else {
4981 print STDERR "Ignoring duplicate pattern \"$rejpat\" in reject_reply_patterns\n";
4982 }
4983 }
4984 @RejectKeys = @RejectPats;
4985 for (@RejectKeys) {
4986 s/\./x/g;
4987 }
4988
4989 print "\tRejectPat: \"@RejectPats\", RejectKeys: \"@RejectKeys\"\n" if $Opts{'debug'} & Logreporters::D_SECT;
4990
4991 # Add reject variants
4992 foreach my $key (@RejectKeys) {
4993 $key = lc($key);
4994 my $keyuc = ucfirst($key);
4995 my $totalsref = \$Totals{'totalrejects' . $key};
4996 print "\t reject key: $key\n" if $Opts{'debug'} & Logreporters::D_SECT;
4997
4998 begin_section_group ($S, 'rejects', "\n");
4999 begin_section_group ($S, 'rejects2', "\n");
5000 add_section ($S, $key . 'rejectrelay', 1, 'd', $keyuc . ' Reject relay denied', $totalsref);
5001 add_section ($S, $key . 'rejecthelo', 1, 'd', $keyuc . ' Reject HELO/EHLO', $totalsref);
5002 add_section ($S, $key . 'rejectdata', 1, 'd', $keyuc . ' Reject DATA', $totalsref);
5003 add_section ($S, $key . 'rejectunknownuser', 1, 'd', $keyuc . ' Reject unknown user', $totalsref);
5004 add_section ($S, $key . 'rejectrecip', 1, 'd', $keyuc . ' Reject recipient address', $totalsref);
5005 add_section ($S, $key . 'rejectsender', 1, 'd', $keyuc . ' Reject sender address', $totalsref);
5006 add_section ($S, $key . 'rejectclient', 1, 'd', $keyuc . ' Reject client host', $totalsref);
5007 add_section ($S, $key . 'rejectunknownclient', 1, 'd', $keyuc . ' Reject unknown client host', $totalsref);
5008 add_section ($S, $key . 'rejectunknownreverseclient', 1, 'd', $keyuc . ' Reject unknown reverse client host', $totalsref);
5009 add_section ($S, $key . 'rejectunverifiedclient', 1, 'd', $keyuc . ' Reject unverified client host', $totalsref);
5010 add_section ($S, $key . 'rejectrbl', 1, 'd', $keyuc . ' Reject RBL', $totalsref);
5011 add_section ($S, $key . 'rejectheader', 1, 'd', $keyuc . ' Reject header', $totalsref);
5012 add_section ($S, $key . 'rejectbody', 1, 'd', $keyuc . ' Reject body', $totalsref);
5013 add_section ($S, $key . 'rejectcontent', 1, 'd', $keyuc . ' Reject content', $totalsref);
5014 add_section ($S, $key . 'rejectsize', 1, 'd', $keyuc . ' Reject message size', $totalsref);
5015 add_section ($S, $key . 'rejectmilter', 1, 'd', $keyuc . ' Reject milter', $totalsref);
5016 add_section ($S, $key . 'rejectproxy', 1, 'd', $keyuc . ' Reject proxy', $totalsref);
5017 add_section ($S, $key . 'rejectinsufficientspace', 1, 'd', $keyuc . ' Reject insufficient space', $totalsref);
5018 add_section ($S, $key . 'rejectconfigerror', 1, 'd', $keyuc . ' Reject server config error', $totalsref);
5019 add_section ($S, $key . 'rejectverify', 1, 'd', $keyuc . ' Reject VRFY', $totalsref);
5020 add_section ($S, $key . 'rejectetrn', 1, 'd', $keyuc . ' Reject ETRN', $totalsref);
5021 add_section ($S, $key . 'rejectlookupfailure', 1, 'd', $keyuc . ' Reject temporary lookup failure', $totalsref);
5022 end_section_group ($S, 'rejects2', $sep2);
5023 add_section ($S, 'totalrejects' . $key, 0, 'd', "Total $keyuc Rejects", $totalsref);
5024 end_section_group ($S, 'rejects', $sep1);
5025
5026 $Totals{'totalrejects' . $key} = 0;
5027 }
5028
5029 begin_section_group ($S, 'byiprejects', "\n");
5030 add_section ($S, 'byiprejects', 1, 'd', 'Reject by IP');
5031 end_section_group ($S, 'byiprejects');
5032
5033 begin_section_group ($S, 'general1', "\n");
5034 add_section ($S, 'connectioninbound', 1, 'd', 'Connections');
5035 add_section ($S, 'connectionlostinbound', 1, 'd', 'Connections lost (inbound)');
5036 add_section ($S, 'connectionlostoutbound', 1, 'd', 'Connections lost (outbound)');
5037 add_section ($S, 'disconnection', 0, 'd', 'Disconnections');
5038 add_section ($S, 'removedfromqueue', 0, 'd', 'Removed from queue');
5039 add_section ($S, 'delivered', 1, 'd', 'Delivered');
5040 add_section ($S, 'sent', 1, 'd', 'Sent via SMTP');
5041 add_section ($S, 'sentlmtp', 1, 'd', 'Sent via LMTP');
5042 add_section ($S, 'forwarded', 1, 'd', 'Forwarded');
5043 add_section ($S, 'resent', 0, 'd', 'Resent');
5044 add_section ($S, 'deferred', 1, 'd', 'Deferred');
5045 add_section ($S, 'deferrals', 1, 'd', 'Deferrals');
5046 add_section ($S, 'bouncelocal', 1, 'd', 'Bounced (local)');
5047 add_section ($S, 'bounceremote', 1, 'd', 'Bounced (remote)');
5048 add_section ($S, 'bouncefailed', 1, 'd', 'Bounce failure');
5049 add_section ($S, 'postscreen', 1, 'd', 'Postscreen');
5050 add_section ($S, 'dnsblog', 1, 'd', 'DNSBL log');
5051
5052 add_section ($S, 'envelopesenders', 1, 'd', 'Envelope senders');
5053 add_section ($S, 'envelopesenderdomains', 1, 'd', 'Envelope sender domains');
5054
5055 add_section ($S, 'bcced', 1, 'd', 'BCCed');
5056 add_section ($S, 'filtered', 1, 'd', 'Filtered');
5057 add_section ($S, 'redirected', 1, 'd', 'Redirected');
5058 add_section ($S, 'discarded', 1, 'd', 'Discarded');
5059 add_section ($S, 'prepended', 1, 'd', 'Prepended');
5060 add_section ($S, 'replaced', 1, 'd', 'Replaced');
5061 add_section ($S, 'warned', 1, 'd', 'Warned');
5062
5063 add_section ($S, 'requeued', 0, 'd', 'Requeued messages');
5064 add_section ($S, 'returnedtosender', 1, 'd', 'Expired and returned to sender');
5065 add_section ($S, 'notificationsent', 1, 'd', 'Notifications sent');
5066
5067 add_section ($S, 'policyspf', 1, 'd', 'Policy SPF');
5068 add_section ($S, 'policydweight', 1, 'd', 'Policyd-weight');
5069 add_section ($S, 'postfwd', 1, 'd', 'Postfwd');
5070 add_section ($S, 'postgrey', 1, 'd', 'Postgrey');
5071 end_section_group ($S, 'general1');
5072
5073 begin_section_group ($S, 'general2', "\n");
5074 add_section ($S, 'connecttofailure', 1, 'd', 'Connection failures (outbound)');
5075 add_section ($S, 'timeoutinbound', 1, 'd', 'Timeouts (inbound)');
5076 add_section ($S, 'heloerror', 1, 'd', 'HELO/EHLO conversations errors');
5077 add_section ($S, 'illegaladdrsyntax', 1, 'd', 'Illegal address syntax in SMTP command');
5078 add_section ($S, 'released', 0, 'd', 'Released from hold');
5079 add_section ($S, 'rblerror', 1, 'd', 'RBL lookup errors');
5080 add_section ($S, 'dnserror', 1, 'd', 'DNS lookup errors');
5081 add_section ($S, 'numerichostname', 1, 'd', 'Numeric hostname');
5082 add_section ($S, 'smtpconversationerror', 1, 'd', 'SMTP dialog errors');
5083 add_section ($S, 'hostnameverification', 1, 'd', 'Hostname verification errors');
5084 add_section ($S, 'hostnamevalidationerror', 1, 'd', 'Hostname validation errors');
5085 add_section ($S, 'smtpprotocolviolation', 1, 'd', 'SMTP protocol violations');
5086 add_section ($S, 'deliverable', 1, 'd', 'Deliverable (address verification)');
5087 add_section ($S, 'undeliverable', 1, 'd', 'Undeliverable (address verification)');
5088 add_section ($S, 'tablechanged', 0, 'd', 'Restarts due to lookup table change');
5089 add_section ($S, 'pixworkaround', 1, 'd', 'PIX workaround enabled');
5090 add_section ($S, 'tlsserverconnect', 1, 'd', 'TLS connections (server)');
5091 add_section ($S, 'tlsclientconnect', 1, 'd', 'TLS connections (client)');
5092 add_section ($S, 'saslauth', 1, 'd', 'SASL authenticated messages');
5093 add_section ($S, 'tlsunverified', 1, 'd', 'TLS certificate unverified');
5094 add_section ($S, 'tlsoffered', 1, 'd', 'Host offered TLS');
5095 end_section_group ($S, 'general2');
5096
5097 begin_section_group ($S, 'postfixstate', "\n");
5098 add_section ($S, 'postfixstart', 0, 'd', 'Postfix start');
5099 add_section ($S, 'postfixstop', 0, 'd', 'Postfix stop');
5100 add_section ($S, 'postfixrefresh', 0, 'd', 'Postfix refresh');
5101 add_section ($S, 'postfixwaiting', 0, 'd', 'Postfix waiting to terminate');
5102 end_section_group ($S, 'postfixstate');
5103
5104
5105 if ($Opts{'debug'} & Logreporters::D_SECT) {
5106 print "\tSection table\n";
5107 printf "\t\t%s\n", (ref($_) eq 'HASH' ? $_->{NAME} : $_) foreach @Sections;
5108 print "build_sect_table: exit\n"
5109 }
5110 }
5111
5112 # XXX create array of defaults for detail <5, 5-9, >10
5113 sub init_defaults() {
5114 map { $Opts{$_} = $Defaults{$_} unless exists $Opts{$_} } keys %Defaults;
5115 if (! $Opts{'standalone'}) {
5116 # LOGWATCH
5117 # these take affect if no env present (eg. nothing in conf file)
5118 # 0 to 4 nodelays
5119
5120 if ($Opts{'detail'} < 5) { # detail 0 to 4, disable all supplimental reports
5121 $Opts{'delays'} = 0;
5122 $Opts{'postgrey_delays'} = 0;
5123 }
5124 }
5125 }
5126
5127
5128 # XXX ensure something is matched?
5129 # XXX cache values so we don't have to substitute X for . each time
5130 #match $dsn against list for best fit
5131 sub get_reject_key($) {
5132 my $reply = shift;
5133 my $replyorig = $reply;
5134 ($reply) = split / /, $reply;
5135 for (my $i = 0; $i <= $#RejectPats; $i++) {
5136 #print "TRYING: $RejectPats[$i]\n";
5137 # we'll allow extended DSNs to match (eg. 5.7.1 will match 5..)
5138 if ($reply =~ /^$RejectPats[$i]/) { # no /o here, pattern varies
5139 #print "MATCHED: orig: $replyorig, reply $reply matched pattern $RejectPats[$i], returning $RejectKeys[$i]\n";
5140 return $RejectKeys[$i];
5141 }
5142 }
5143 #print "NOT MATCHED: REPLY CODE: '$replyorig', '$reply'\n";
5144 return;
5145 }
5146
5147 # Replace bare reject limiters with specific reject limiters
5148 # based on reject_reply_patterns
5149 #
5150 sub expand_bare_reject_limiters()
5151 {
5152 # don't reorder the list of limiters. This breaks --nodetail followed by a
5153 # bare reject such as --limit rejectrbl=10. Reordering is no longer necessary
5154 # since process_limiters was instituted and using the special __none__ pseudo-
5155 # limiter to indicate the position at which --nodefailt was found on the command
5156 # line.
5157 # my ($limiter, @reject_limiters, @non_reject_limiters);
5158 my ($limiter, @new_list);
5159
5160 # XXX check if limiter matches just one in rejectclasses
5161 while ($limiter = shift @Limiters) {
5162 if ($limiter =~ /^reject[^_]/) {
5163 foreach my $reply_code (@RejectKeys) {
5164 printf "bare_reject: \L$reply_code$limiter\n" if $Opts{'debug'} & Logreporters::D_VARS;
5165 #push @reject_limiters, lc($reply_code) . $limiter;
5166 push @new_list, lc($reply_code) . $limiter;
5167 }
5168 }
5169 elsif ($limiter =~ /^(?:[45]\.\.|Warn)reject[^_]/) {
5170 $limiter =~ s/^([45])\.\./$1xx/;
5171 #push @reject_limiters, lc $limiter;
5172 push @new_list, lc $limiter;
5173 }
5174 else {
5175 #push @non_reject_limiters, $limiter;
5176 push @new_list, $limiter;
5177 }
5178 }
5179 #@Limiters = (@reject_limiters, @non_reject_limiters);
5180 @Limiters = @new_list;
5181 }
5182
5183
5184 # Return a usage string, built from:
5185 # arg1 +
5186 # $usage_str +
5187 # a string built from each usable entry in the @Sections table.
5188 # reject patterns are special cased to minimize the number of
5189 # command line options presented.
5190 #
5191 sub usage($) {
5192 my $ret = "";
5193 $ret = "@_\n" if ($_[0]);
5194
5195 $ret .= $usage_str;
5196 my ($name, $desc, %reject_types);
5197 foreach my $sect (get_usable_sectvars(@Sections, 0)) {
5198
5199 if (my ($code,$rej) = ($sect->{NAME} =~ /^(...|warn)(reject.*)$/oi)) {
5200 $rej = lc $rej;
5201 next if (exists $reject_types{$rej});
5202 $reject_types{$rej}++;
5203 $name = '[###]' . $rej;
5204 $desc = '###' . substr($sect->{TITLE}, length($code));
5205 }
5206 else {
5207 $name = lc $sect->{NAME};
5208 $desc = $sect->{TITLE};
5209 }
5210 $ret .= sprintf " --%-38s%s\n", "$name" . ' LEVEL', "$desc";
5211 }
5212 $ret .= "\n";
5213 return $ret;
5214 }
5215
5216 1;
5217
5218 # vi: shiftwidth=3 tabstop=3 syntax=perl et
0 .TH POSTFIX-LOGWATCH 1
1 .ad
2 .fi
3 .SH NAME
4 postfix-logwatch
5 \-
6 A Postfix log parser and analysis utility
7 .SH "SYNOPSIS"
8 .na
9 .nf
10 .fi
11 \fBpostfix-logwatch\fR [\fIoptions\fR] [\fIlogfile ...\fR]
12 .SH DESCRIPTION
13 .ad
14 .fi
15 The \fBpostfix-logwatch\fR(1) utility is a Postfix MTA log parser
16 that produces summaries, details, and statistics regarding
17 the operation of Postfix.
18 .PP
19 This utility can be used as a
20 standalone program, or as a Logwatch filter module to produce
21 Postfix summary and detailed reports from within Logwatch.
22 .PP
23 \fBPostfix-logwatch\fR is able to produce
24 a wide range of reports with data grouped and sorted as much as possible
25 to reduce noise and highlight patterns.
26 Brief summary reports provide a
27 quick overview of general Postfix operations and message
28 delivery, calling out warnings that may require attention.
29 Detailed reports provide easy to scan, hierarchically-arranged
30 and organized information, with as much or little detail as
31 desired.
32 .PP
33 \fBPostfix-logwatch\fR outputs two principal sections: a \fBSummary\fR section
34 and a \fBDetailed\fR section.
35 For readability and quick scanning, all event or hit counts appear in the left column,
36 followed by brief description of the event type, and finally additional
37 statistics or count representations may appear in the rightmost column.
38
39 The following segment from a sample Summary report illustrates:
40 .RS 4
41 .nf
42
43 ****** Summary ********************************************
44
45 81 *Warning: Connection rate limit reached (anvil)
46 146 Warned
47
48 68.310M Bytes accepted 71,628,177
49 97.645M Bytes delivered 102,388,245
50 ======== ================================================
51
52 3464 Accepted 41.44%
53 4895 Rejected 58.56%
54 -------- ------------------------------------------------
55 8359 Total 100.00%
56 ======== ================================================
57
58 .fi
59 .RE 0
60 The report warns that anvil's connection rate was hit 81 times,
61 a Postfix access check WARN action was logged 146 times, and
62 a total of 68.310 megabytes (71,628,177 bytes) were accepted
63 into the Postfix system, delivering 97.645 megabytes of
64 data (due to multiple recipients).
65 The Accepted and Rejected lines show that Postfix accepted 3464 (41.44% of the total
66 messages) and rejected 4895 (the remaining 58.56%) of the 8359
67 total messages (temporary rejects show up elsewhere).
68 .PP
69 There are dozens of sub-sections available in the \fBDetailed\fR report, each of
70 whose output can be controlled in various ways.
71 Each sub-section attempts to group and present the most meaningful data at superior levels,
72 while pushing less useful or \fInoisy\fR data towards inferior levels.
73 The goal is to provide as much benefit as possible from smart grouping of
74 data, to allow faster report scanning, pattern identification, and problem solving.
75 Data is always sorted in descending order by count, and then numerically by IP address
76 or alphabetically as appropriate.
77 .PP
78 The following MX errors segment from a sample \fBDetailed\fR report
79 illustrates the basic hierarchical level structure of \fBpostfix-logwatch\fR:
80 .RS 4
81 .nf
82
83 ****** Detailed *******************************************
84
85 261 MX errors --------------------------------------
86 261 Unable to look up MX host
87 222 Host not found
88 73 foolishspammer.local
89 60 completely.bogus.domain.example
90 11 friend.example.com
91 39 No address associated with hostname
92 23 dummymx.sample.net
93 16 pushn.spam.sample.com
94
95 .fi
96 .RE 0
97 .PP
98 The \fBpostfix-logwatch\fR utility reads from STDIN or from the named Postfix
99 \fIlogfile\fR.
100 Multiple \fIlogfile\fR arguments may be specified, each processed
101 in order.
102 The user running \fBpostfix-logwatch\fR must have read permission on
103 each named log file.
104 .PP
105 .SS Options
106 The options listed below affect the operation of \fBpostfix-logwatch\fR.
107 Options specified later on the command line override earlier ones.
108 Any option may be abbreviated to an unambiguous length.
109
110 .IP "\fB-f \fIconfig_file\fR"
111 .PD 0
112 .IP "\fB--config_file \fIconfig_file\fR"
113 .PD
114 Use an alternate configuration file \fIconfig_file\fR instead of
115 the default.
116 This option may be used more than once.
117 Multiple configuration files will be processed in the order presented on the command line.
118 See \fBCONFIGURATION FILE\fR below.
119 .IP "\fB--debug \fIkeywords\fR"
120 Output debug information during the operation of \fBpostfix-logwatch\fR.
121 The parameter \fIkeywords\fR is one or more comma or space separated keywords.
122 To obtain the list of valid keywords, use --debug xxx where xxx is any invalid keyword.
123 .IP "\fB--[no]delays\fR"
124 Enables (disables) output of the message delays percentiles report.
125 The delays percentiles report shows percentiles for each of the 4 delivery latency times reported
126 by Postfix (available in version 2.3 and later) in the form \fBdelays=\fIa\fR/\fIb\fR/\fIc\fR/\fId\fR, where
127 \fIa\fR is the amount of time before the active queue (includes time for previous delivery attempts and time in the deferred queue),
128 \fIb\fR is the amount of time in the active queue up to delivery agent handoff,
129 \fIc\fR is the amount of time spent making connections (including DNS, HELO and TLS) and
130 \fId\fR is the amount of time spent delivering the message.
131 The total delay shown comes from the \fBdelay=\fR field in a message delivery log line.
132
133 \fBNote:\fR This report may consume a large amount of memory; if you have no use for it, disable the delays report.
134
135 .IP "\fB--delays_percentiles \fIp1 [p2 ...]\fR"
136 Specifies the percentiles to be used in the message delays percentiles report.
137 The percentiles \fIp1\fR, \fIp2\fR, \fI...\fR range from 0 to 100, inclusively.
138 The order of the list is not sorted - the report will output the percentiles
139 columns in the order you specify.
140 .IP "\fB--detail \fIlevel\fR"
141 Sets the maximum detail level for \fBpostfix-logwatch\fR to \fIlevel\fR.
142 This option is global, overriding any other output limiters described below.
143
144 The \fBpostfix-logwatch\fR utility
145 produces a \fBSummary\fR section, a \fBDetailed\fR section, and
146 additional report sections.
147 With \fIlevel\fR less than 5, \fBpostfix-logwatch\fR will produce
148 only the \fBSummary\fR section.
149 At \fIlevel\fR 5 and above, the \fBDetailed\fR section, and any
150 additional report sections are candidates for output.
151 Each incremental increase in \fIlevel\fR generates one additional
152 hierarchical sub-level of output in the \fBDetailed\fR section of the report.
153 At \fIlevel\fR 10, all levels are output.
154 Lines that exceed the maximum report width (specified with
155 \fBmax_report_width\fR) will be cut.
156 Setting \fIlevel\fR to 11 will prevent lines in the report from being cut (see also \fB--line_style\fR).
157 .IP "\fB--help\fR"
158 Print usage information and a brief description about command line options.
159 .IP "\fB--ignore_service \fIpattern\fR"
160 Ignore log lines that contain the postfix service name \fBpostfix/\fIservice\fR.
161 The parameter \fIservice\fR is a regular expression.
162
163 \fBNote:\fR if you use parenthesis in your regular expression, be sure they are cloistering
164 and not capturing: use \fB(?:\fIpattern\fB)\fR instead of \fB(\fIpattern\fB)\fR.
165 .IP "\fB--ipaddr_width \fIwidth\fR"
166 Specifies that IP addresses in address/hostname pairs should be printed
167 with a field width of \fIwidth\fR characters.
168 Increasing the default may be useful for systems using long IPv6 addresses.
169 .IP "\fB-l limiter=levelspec\fR"
170 .PD 0
171 .IP "\fB--limit limiter=levelspec\fR"
172 .PD
173 Sets the level limiter \fIlimiter\fR with the specification \fIlevelspec\fR.
174 .IP "\fB--line_style \fIstyle\fR"
175 Specifies how to handle long report lines.
176 Three styles are available: \fBfull\fR, \fBtruncate\fR, and \fBwrap\fR.
177 Setting \fIstyle\fR to \fBfull\fR will prevent cutting lines to \fBmax_report_width\fR;
178 this is what occurs when \fBdetail\fR is 11 or higher.
179 When \fIstyle\fR is \fBtruncate\fR (the default),
180 long lines will be truncated according to \fBmax_report_width\fR.
181 Setting \fIstyle\fR to \fBwrap\fR will wrap lines longer than \fBmax_report_width\fR such that
182 left column hit counts are not obscured.
183 This option takes precedence over the line style implied by the \fBdetail\fR level.
184 The options \fB--full\fR, \fB--truncate\fR, and \fB--wrap\fR are synonyms.
185 .IP "\fB--nodetail\fR"
186 Disables the \fBDetailed\fR section of the report, and all supplemental reports.
187 This option provides a convenient mechanism to quickly disable all sections
188 under the \fBDetailed\fR report, where subsequent command line
189 options may re-enable one or more sections to create specific reports.
190 .IP "\fB--[no]summary\fR"
191 .IP "\fB--show_summary\fR"
192 Enables (disables) displaying of the the \fBSummary\fR section of the report.
193 The variable Posfix_Show_Summary in used in a configuration file.
194 .IP "\fB--recipient_delimiter \fIdelimiter\fR"
195 Split email delivery addresses using the recipient delimiter character \fIdelimiter\fR.
196 This should generally match
197 the \fBrecipient_delimiter\fR specified in the Postfix parameter
198 file \fBmain.cf\fR, or the default value indicated in
199 \fBpostconf -d recipient_delimiter\fR.
200 This is very useful for obtaining per-alias statistics
201 when a recipient delimeter is used for mail delivery.
202 .IP "\fB--reject_reply_patterns \fIr1 [r2 ...]\fR"
203 Specifies the list of reject reply patterns used to create reject groups.
204 Each entry in the list \fIr1 [r2 ...]\fR must be either a three character
205 regular expression reply code of the form [45][0-9.][0-9.], or the word "Warn".
206 The "." in the regular expression is a literal dot which matches any reject reply subcode;
207 this wildcarding allows creation of broad rejects groups.
208 List order is preserved, in that reject reports will be output in the same order as
209 the entries in the list.
210 Specific reject reply codes will take priority over wildcard patterns, regardless of
211 the list order.
212
213 The default list is "5.. 4.. Warn", which creates three groups of rejects:
214 permanent rejects, temporary reject failures, and reject warnings (as in warn_if_reject).
215
216 This feature allows, for example, distinguishing 421 transmission
217 channel closures from 45x errors (eg. 450 mailbox unavailable, 451
218 local processing errors, 452 insufficient storage).
219 Such a grouping would be configured with the list: "421 4.. 5.. Warn".
220 See RFC 2821 for more information about reply codes.
221
222 See also \fBCONFIGURATION FILE\fR regarding using \fBreject_reply_patterns\fR within a configuration file.
223 .IP "\fB--[no]sect_vars\fR"
224 .PD 0
225 .IP "\fB--show_sect_vars \fIboolean\fR"
226 .PD
227 Enables (disables) supplementing each \fBDetailed\fR section title
228 with the name of that section's level limiter.
229 The name displayed is the command line option (or configuration
230 file variable) used to limit that section's output.
231 .
232 With the large number of level limiters available in \fBpostfix-logwatch\fR,
233 this a convenient mechanism for determining exactly which level limiter
234 affects a section.
235 .IP "\fB--syslog_name \fInamepat\fR"
236 Specifies the syslog service name that \fBpostfix-logwatch\fR uses
237 to match syslog lines.
238 Only log lines whose service name matches
239 the perl regular expression \fInamepat\fR will be used by
240 \fBpostfix-logwatch\fR; all non-matching lines are silently ignored.
241 This is useful when a pre-installed Postfix package uses a name
242 other than the default (\fBpostfix\fR), or when multiple Postfix
243 instances are in use and per-instance reporting is desired.
244
245 The pattern \fInamepat\fR should match the \fBsyslog_name\fR configuration
246 parameter specified in the Postfix parameter file \fBmain.cf\fR, the
247 master control file \fBmaster.cf\fR, or the default value as indicated
248 by the output of \fBpostconf -d syslog_name\fR.
249
250 \fBNote:\fR if you use parenthesis in your regular expression, be sure they are cloistering
251 and not capturing: use \fB(?:\fIpattern\fB)\fR instead of \fB(\fIpattern\fB)\fR.
252 .IP "\fB--[no]unknown\fR"
253 .PD 0
254 .IP "\fB--show_unknown \fIboolean\fR"
255 .PD
256 Enables (disables) display of the postfix-generated name of 'unknown' in formated IP/hostname pairs in \fBDetailed\fR reports.
257 Default: enabled.
258 .IP "\fB--version\fR"
259 Print \fBpostfix-logwatch\fR version information.
260 .SS Level Limiters
261 .PP
262 The output of every section in the \fBDetailed\fR report is controlled by a level limiter.
263 The name of the level limiter variable will be output when the \fBsect_vars\fR option is set.
264 Level limiters are set either via command line in standalone mode with \fB--limit \fIlimiter\fB=\fIlevelspec\fR option,
265 or via configuration file variable \fB$postfix_\fIlimiter\fB=\fIlevelspec\fR.
266 Each limiter requires a \fIlevelspec\fR argument, which is described below in \fBLEVEL CONTROL\fR.
267
268 The list of level limiters is shown below.
269
270 There are several level limiters that control reject sub-sections (eg. \fBrejectbody\fR, \fBrejectsender\fR, etc.).
271 Because the list of reject variants is not known until runtime after \fBreject_reply_patterns\fR is seen, these reject limiters are shown below generically,
272 with the prefix \fB###\fR.
273 To use one of these reject limiters, substitute \fB###\fR with one of the reject reply codes in effect,
274 replacing each dot with an \fBx\fR character.
275 For example, using the default \fBreject_reply_patterns\fR list of "5.. 4.. Warn", three \fBrejectbody\fR variants are valid:
276 \fB--limit 5xxrejectbody\fR, \fB--limit 4xxrejectbody\fR and \fB--limit warnrejectbody\fR.
277 As a convenience, you may entirely eliminate the \fB###\fR prefix, and instead use the bare \fBreject\fIXXX\fR option, and
278 all reject level limiter variations will be auto-generated based on the \fBreject_reply_patterns\fR list.
279 For example, the command line segment:
280 .nf
281
282 ... --reject_reply_patterns "421 5.." \\
283 --limit rejectrbl="1:10:"
284
285 .fi
286 would automatically become:
287 .nf
288
289 ... --reject_reply_patterns "421 5.." \\
290 --limit 421rejectrbl="1:10:" --limit 5xxrejectrbl="1:10:"
291
292 .fi
293 See \fBreject_reply_patterns\fR above, and comments in the configuration file \fBpostfix-logwatch.conf\fR.
294
295 .de TQ
296 . br
297 . ns
298 . TP \\$1
299 ..
300
301 [ THIS SECTION IS NOT YET COMPLETE ]
302
303 .PD 0
304 .IP "\fBAttrError"
305 Errors obtaining attribute data from service.
306 .IP "\fBBCCed"
307 Messages that triggered access, header_checks or body_checks BCC action. (postfix 2.6 experimental branch)
308 .IP "\fBBounceLocal"
309 .IP "\fBBounceRemote"
310 Local and remote bounces.
311 A bounce is considered a local bounce if the relay was one of none, local, virtual,
312 avcheck, maildrop or 127.0.0.1.
313 .IP "\fBByIpRejects"
314 Regrouping by client host IP address of all 5xx (permanent) reject variants.
315 .IP "\fBCommunicationError"
316 Postfix errors talking to one of its services.
317 .IP "\fBAnvil"
318 Anvil rate or concurrency limits.
319 .IP "\fBConnectionInbound"
320 Connections made to the \fBsmtpd\fR server.
321 .IP "\fBConnectionLostInbound"
322 Connections lost to the \fBsmtpd\fR server.
323 .IP "\fBConnectionLostOutbound"
324 Connections lost during \fBsmtp\fR communications with remote MTA.
325 .IP "\fBConnectToFailure"
326 Failures reported by \fBsmtp\fR when connecting to remote MTA.
327 .IP "\fBDatabaseGeneration"
328 Warnings noted when binary database map file requires \fBpostmap\fR update from newer source file.
329 .IP "\fBDeferrals"
330 .IP "\fBDeferred"
331 Message delivery deferrals.
332 A single \fBdeferred\fR message will have one or more \fBdeferrals\fR many times.
333 .IP "\fBDeliverable"
334 Address verification indicates recipient address is deliverable.
335 .IP "\fBDelivered"
336 Number of messages handed-off to a delivery agent such as local or virtual.
337 .IP "\fBDiscarded"
338 Messages that triggered access, header_checks or body_checks DISCARD action.
339 .IP "\fBDNSError"
340 Any one of several errors encounted during DNS lookups.
341 .IP "\fBEnvelopeSenderDomains"
342 List of sending domains. (2 levels: envelope sender domain, localpart)
343 .IP "\fBEnvelopeSenders"
344 List of envelope senders. (1 level: envelope sender)
345 .IP "\fBError"
346 Postfix general \fBerror\fR messages.
347 .IP "\fBFatalConfigError"
348 Fatal main.cf or master.cf configuration errors.
349 .IP "\fBFatalError"
350 Postfix general \fBfatal\fR messages.
351 .IP "\fBFiltered"
352 Messages that triggered access, header_checks or body_checks FILTER action.
353 .IP "\fBForwarded"
354 Messages forwarded by MDA for one address class to another (eg. local -> virtual).
355 .IP "\fBHeloError"
356 XXXXXXXXXXX
357 .IP "\fBHold"
358 Messages that were placed on hold by postsuper, or triggered by access, header_checks or body_checks HOLD action.
359 .IP "\fBHostnameValidationError"
360 Invalid hostname detected.
361 .IP "\fBHostnameVerification"
362 Lookup of hostname does not map back to the IP of the peer (ie. the remote system connecting to \fBsmtpd\fR).
363 .IP "\fBIllegalAddrSyntax"
364 Illegal syntax in an email address provided during the MAIL FROM or RCPT TO dialog.
365 .IP "\fBLdapError"
366 Any LDAP errors during LDAP lookup.
367 .IP "\fBMailerLoop"
368 An MX lookup for the best mailer to use to deliver mail would result in a sending to ourselves.
369 .IP "\fBMapProblem"
370 Problem with an access table map that needs correcting.
371 .IP "\fBMessageWriteError"
372 Postfix encountered an error when trying to create a message file somewhere in the spool directory.
373 .IP "\fBNumericHostname"
374 A hostname was found that was numeric, instead of alphabetic.
375 .IP "\fBPanicError"
376 Postfix general \fBpanic\fR messages.
377 .IP "\fBPixWorkaround"
378 Workarounds were enabled to avoid remote Cisco PIX SMTP "fixups".
379 .IP "\fBPolicydWeight"
380 Summarization of policyweight/policydweight results.
381 .IP "\fBPolicySpf"
382 Summarization of PolicySPF results.
383 .IP "\fBPostgrey"
384 Summarization of Postgrey results.
385 .IP "\fBPostscreen"
386 Summarization of 2.7's postscreen and verify services.
387 .IP "\fBDNSBLog"
388 Summarization of 2.7's dnsblog service.
389 .IP "\fBPrepended"
390 Messages that triggered header_checks or body_checks PREPEND action.
391 .IP "\fBProcessExit"
392 Postfix services that exited unexpectedly.
393 .IP "\fBProcessLimit"
394 A Postfix service has reached or exceeded the maximum number of processes allowed.
395 .IP "\fBQueueWriteError"
396 Problems writing a Postfix queue file.
397 .IP "\fBRblError"
398 Lookup errors for RBLs.
399 .IP "\fBRedirected"
400 Messages that triggered access, header_checks or body_checks REDIRECT action.
401 .IP "\fB###RejectBody"
402 Messages that triggered body_checks REJECT action.
403 .IP "\fB###RejectClient"
404 Messages rejected by client access controls (smtpd_client_restrictions).
405 .IP "\fB###RejectConfigError"
406 Message rejected due to server configuration errors.
407 .IP "\fB###RejectContent"
408 Messages rejected by message_reject_characters.
409 .IP "\fB###RejectData"
410 Messages rejected at DATA stage in SMTP conversation (smtpd_data_restrictions).
411 .IP "\fB###RejectEtrn"
412 Messages rejected at ETRN stage in SMTP conversation (smtpd_etrn_restrictions).
413 .IP "\fB###RejectHeader"
414 Messages that triggered header_checks REJECT action.
415 .IP "\fB###RejectHelo"
416 Messages rejected at HELO/EHLO stage in SMTP conversation (smtpd_helo_restrictions).
417 .IP "\fB###RejectInsufficientSpace"
418 Messages rejected due to insufficient storage space.
419 .IP "\fB###RejectLookupFailure"
420 Messages rejected due to temporary DNS lookup failures.
421 .IP "\fB###RejectMilter"
422 Milter rejects. No reject reply code is available for these rejects, but an extended 5.7.1 DSN is provided.
423 These rejects are forced into the generic 5xx rejects group.
424 If you redefine \fBreject_reply_patterns\fR such that it does not contain the pattern \fB5..\fR, milter rejects
425 will not be output.
426 .IP "\fB###RejectRbl"
427 Messages rejected by an RBL hit.
428 .IP "\fB###RejectRecip"
429 Messages rejected by recipient access controls (smtpd_recipient_restrictions).
430 .IP "\fB###RejectRelay"
431 Messages rejected by relay access controls.
432 .IP "\fB###RejectSender"
433 Messages rejected by sender access controls (smtpd_sender_restrictions).
434 .IP "\fB###RejectSize"
435 Messages rejected due to excessive message size.
436 .IP "\fB###RejectUnknownClient"
437 Messages rejected by unknown client access controls.
438 .IP "\fB###RejectUnknownReverseClient"
439 Messages rejected by unknown reverse client access controls.
440 .IP "\fB###RejectUnknownUser"
441 Messages rejected by unknown user access controls.
442 .IP "\fB###RejectUnverifiedClient"
443 Messages rejected by unverified client access controls.
444 .IP "\fB###RejectVerify"
445 Messages rejected dueo to address verification failures.
446 .IP "\fBReplaced"
447 Messages that triggered header_checks or body_checks REPLACE action.
448 .IP "\fBReturnedToSender"
449 Messages returned to sender due to exceeding queue lifetime (maximal_queue_lifetime).
450 .IP "\fBSaslAuth"
451 SASL authentication successes, includes SASL method, username, and sender when present.
452 .IP "\fBSaslAuthFail"
453 SASL authentication failures.
454 .IP "\fBSent"
455 Messages sent via the SMTP delivery agent.
456 .IP "\fBSentLmtp"
457 Messages sent via the LMTP delivery agent.
458 .IP "\fBSmtpConversationError"
459 Errors during the SMTP/ESMTP dialog.
460 .IP "\fBSmtpProtocolViolation"
461 Protocol violation during the SMTP/ESMTP dialog.
462 .IP "\fBStartupError"
463 Errors during Postfix server startup.
464 .IP "\fBTimeoutInbound"
465 Connections to \fBsmtpd\fR that timed out.
466 .IP "\fBTlsClientConnect"
467 TLS client connections.
468 .IP "\fBTlsOffered"
469 TLS communication offerred.
470 .IP "\fBTlsServerConnect"
471 TLS server connections.
472 .IP "\fBTlsUnverified"
473 Unverified TLS connections.
474 .IP "\fBUndeliverable"
475 Address verification indicates recipient address is undeliverable.
476 .IP "\fBWarn"
477 Messages that triggered access, header_checks or body_checks WARN action.
478 .IP "\fBWarnConfigError"
479 Warnings regarding Postfix configuration errors.
480 .IP "\fBWarningsOther"
481 Postfix general \fBwarning\fR messages.
482
483 .PD
484 .SH LEVEL CONTROL
485 .ad
486 .fi
487 The \fBDetailed\fR section of the report consists of a number of sub-sections,
488 each of which is controlled both globally and independently.
489 Two settings influence the output provided in the \fBDetailed\fR report:
490 a global detail level (specified with \fB--detail\fR) which has final (big hammer)
491 output-limiting control over the \fBDetailed\fR section,
492 and sub-section specific detail settings (small hammer), which allow further limiting
493 of the output for a sub-section.
494 Each sub-section may be limited to a specific depth level, and each sub-level may be limited with top N or threshold limits.
495 The \fIlevelspec\fR argument to each of the level limiters listed above is used to accomplish this.
496
497 It is probably best to continue explanation of sub-level limiting with the following well-known outline-style hierarchy, and
498 some basic examples:
499 .nf
500
501 level 0
502 level 1
503 level 2
504 level 3
505 level 4
506 level 4
507 level 2
508 level 3
509 level 4
510 level 4
511 level 4
512 level 3
513 level 4
514 level 3
515 level 1
516 level 2
517 level 3
518 level 4
519 .fi
520 .PP
521 The simplest form of output limiting suppresses all output below a specified level.
522 For example, a \fIlevelspec\fR set to "2" shows only data in levels 0 through 2.
523 Think of this as collapsing each sub-level 2 item, thus hiding all inferior levels (3, 4, ...),
524 to yield:
525 .nf
526
527 level 0
528 level 1
529 level 2
530 level 2
531 level 1
532 level 2
533 .fi
534 .PP
535 Sometimes the volume of output in a section is too great, and it is useful to suppress any data that does not exceed a certain threshold value.
536 Consider a dictionary spam attack, which produces very lengthy lists of hit-once recipient email or IP addresses.
537 Each sub-level in the hierarchy can be threshold-limited by setting the \fIlevelspec\fR appropriately.
538 Setting \fIlevelspec\fR to the value "2::5" will suppress any data at level 2 that does not exceed a hit count of 5.
539 .PP
540 Perhaps producing a top N list, such as top 10 senders, is desired.
541 A \fIlevelspec\fR of "3:10:" limits level 3 data to only the top 10 hits.
542 .PP
543 With those simple examples out of the way, a \fIlevelspec\fR is defined as a whitespace- or comma-separated list of one or more of the following:
544 .IP "\fIl\fR"
545 Specifies the maximum level to be output for this sub-section, with a range from 0 to 10.
546 if \fIl\fR is 0, no levels will be output, effectively disabling the sub-section
547 (level 0 data is already provided in the Summary report, so level 1 is considered the first useful level in the \fBDetailed\fR report).
548 Higher values will produce output up to and including the specified level.
549 .IP "\fIl\fB.\fIn\fR"
550 Same as above, with the addition that \fIn\fR limits this section's level 1 output to
551 the top \fIn\fR items.
552 The value for \fIn\fR can be any integer greater than 1.
553 (This form of limiting has less utility than the syntax shown below. It is provided for
554 backwards compatibility; users are encouraged to use the syntax below).
555 .IP "\fIl\fB:\fIn\fB:\fIt\fR"
556 This triplet specifies level \fIl\fR, top \fIn\fR, and minimum threshold \fIt\fR.
557 Each of the values are integers, with \fIl\fR being the level limiter as described above, \fIn\fR being
558 a top \fIn\fR limiter for the level \fIl\fR, and \fIt\fR being the threshold limiter for level \fIl\fR.
559 When both \fIn\fR and \fIt\fR are specified, \fIn\fR has priority, allowing top \fIn\fR lists (regardless of
560 threshold value).
561 If the value of \fIl\fR is omitted, the specified values for \fIn\fR and/or \fIt\fR are used for
562 all levels available in the sub-section.
563 This permits a simple form of wildcarding (eg. place minimum threshold limits on all levels).
564 However, specific limiters always override wildcard limiters.
565 The first form of level limiter may be included in \fIlevelspec\fR to restrict output, regardless of how many triplets are present.
566 .PP
567 All three forms of limiters are effective only when \fBpostfix-logwatch\fR's detail level is 5
568 or greater (the \fBDetailed\fR section is not activated until detail is at least 5).
569 .PP
570 See the \fBEXAMPLES\fR section for usage scenarios.
571 .SH CONFIGURATION FILE
572 .ad
573 \fBPostfix-logwatch\fR can read configuration settings from a configuration file.
574 Essentially, any command line option can be placed into a configuration file, and
575 these settings are read upon startup.
576
577 Because \fBpostfix-logwatch\fR can run either standalone or within Logwatch,
578 to minimize confusion, \fBpostfix-logwatch\fR inherits Logwatch's configuration
579 file syntax requirements and conventions.
580 These are:
581 .IP \(bu 4'.
582 White space lines are ignored.
583 .IP \(bu 4'.
584 Lines beginning with \fB#\fR are ignored
585 .IP \(bu 4'.
586 Settings are of the form:
587 .nf
588
589 \fIoption\fB = \fIvalue\fR
590
591 .fi
592 .IP \(bu 4'.
593 Spaces or tabs on either side of the \fB=\fR character are ignored.
594 .IP \(bu 4'.
595 Any \fIvalue\fR protected in double quotes will be case-preserved.
596 .IP \(bu 4'.
597 All other content is reduced to lowercase (non-preserving, case insensitive).
598 .IP \(bu 4'.
599 All \fBpostfix-logwatch\fR configuration settings must be prefixed with "\fB$postfix_\fR" or
600 \fBpostfix-logwatch\fR will ignore them.
601 .IP \(bu 4'.
602 When running under Logwatch, any values not prefixed with "\fB$postfix_\fR" are
603 consumed by Logwatch; it only passes to \fBpostfix-logwatch\fR (via environment variable)
604 settings it considers valid.
605 .IP \(bu 4'.
606 The values \fBTrue\fR and \fBYes\fR are converted to 1, and \fBFalse\fR and \fBNo\fR are converted to 0.
607 .IP \(bu 4'.
608 Order of settings is not preserved within a configuration file (since settings are passed
609 by Logwatch via environment variables, which have no defined order).
610 .PP
611 To include a command line option in a configuration file,
612 prefix the command line option name with the word "\fB$postfix_\fR".
613 The following configuration file setting and command line option are equivalent:
614 .nf
615
616 \fB$postfix_Line_Style = Truncate\fR
617
618 \fB--line_style Truncate\fR
619
620 .fi
621 Level limiters are also prefixed with \fB$postfix_\fR, but on the command line are specified with the \fB--limit\fR option:
622 .nf
623
624 \fB$postfix_Sent = 2\fR
625
626 \fB--limit Sent=2\fR
627
628 .fi
629
630
631 The order of command line options and configuration file processing occurs as follows:
632 1) The default configuration file is read if it exists and no \fB--config_file\fR was specified on a command line.
633 2) Configuration files are read and processed in the order found on the command line.
634 3) Command line options override any options already set either via command line or from any configuration file.
635
636 Command line options are interpreted when they are seen on the command line, and later options will override previously set options.
637 The notable exception is with limiter variables, which are interpreted in the order found, but only after all other options have been processed.
638 This allows \fB--reject_reply_patterns\fR to determine the dynamic list of the various reject limiters.
639
640 See also \fB--reject_reply_patterns\fR.
641 .SH "EXIT STATUS"
642 .na
643 .nf
644 .ad
645 .fi
646 The \fBpostfix-logwatch\fR utility exits with a status code of 0, unless an error
647 occurred, in which case a non-zero exit status is returned.
648 .SH "EXAMPLES"
649 .na
650 .nf
651 .ad
652 .fi
653 .SS Running Standalone
654 \fBNote:\fR \fBpostfix-logwatch\fR reads its log data from one or more named Postfix log files, or from STDIN.
655 For brevity, where required, the examples below use the word \fIfile\fR as the command line
656 argument meaning \fI/path/to/postfix.log\fR.
657 Obviously you will need to substitute \fIfile\fR with the appropriate path.
658 .nf
659 .PP
660 To run \fBpostfix-logwatch\fR in standalone mode, simply run:
661 .nf
662 .RS 4
663 .PP
664 \fBpostfix-logwatch \fIfile\fR
665 .RE 0
666 .nf
667 .PP
668 A complete list of options and basic usage is available via:
669 .nf
670 .RS 4
671 .PP
672 \fBpostfix-logwatch --help\fR
673 .RE 0
674 .nf
675 .PP
676 To print a summary only report of Postfix log data:
677 .nf
678 .RS 4
679 .PP
680 \fBpostfix-logwatch --detail 1 \fIfile\fR
681 .RE 0
682 .fi
683 .PP
684 To produce a summary report and a one-level detail report for May 25th:
685 .nf
686 .RS 4
687 .PP
688 \fBgrep 'May 25' \fIfile\fB | postfix-logwatch --detail 5\fR
689 .RE 0
690 .fi
691 .PP
692 To produce only a top 10 list of Sent email domains, the summary report and detailed reports
693 are first disabled.
694 Since commands line options are read and enabled left-to-right,
695 the Sent section is re-enabled to level 1 with a level 1 top 10 limiter:
696 .nf
697 .RS 4
698 .PP
699 \fBpostfix-logwatch --nosummary --nodetail --limit sent='1 1:10:' \fIfile\fR
700 .RE 0
701 .fi
702 .PP
703 The following command and its sample output shows a more complex level limiter example.
704 The command gives the top 3 Sent email addresses from the top 5 domains,
705 in addition, all level 3 items with a hit count of 2 or less are suppressed (in the Sent sub-section,
706 this happens to be email's Original To address).
707 Ellipses indicate top N or threshold-limited data:
708 .nf
709 .RS 4
710 .PP
711 \fBpostfix-logwatch --nosummary --nodetail \\
712 --limit sent '1:5: 2:3: 3::2' \fIfile\fR
713 .nf
714
715 1762 Sent via SMTP -----------------------------------
716 352 example.com
717 310 joe
718 255 joe.bob@virtdomain.example.com
719 7 info@virtdomain.example.com
720 21 pooryoda3
721 11 hot93uh
722 ...
723 244 sample.net
724 97 buzz
725 26 leroyjones
726 14 sally
727 ...
728 152 example.net
729 40 jim_jameson
730 23 sam_sampson
731 19 paul_paulson
732 ...
733 83 sample.us
734 44 root
735 39 jenny1
736 69 dom3.example.us
737 10 kay
738 7 ron
739 6 mrsmith
740 ...
741 ...
742 .fi
743 .RE 0
744 .fi
745 .PP
746 The next command uses both \fBreject_reply_patterns\fR and level limiters to see 421 RBL rejects,
747 threshold-limiting level 2 output to hits greater than 5 (level 2 in the Reject RBL sub-section
748 is the client's IP address / hostname pair).
749 This makes for a very nice RBL offenders list, shown in the sample output
750 (note the use of the unambiguous, abbreviated command line option reject_reply_pat):
751 .nf
752 .RS 4
753 .PP
754 \fBpostfix-logwatch --reject_reply_pat '421 4.. 5.. Warn' \\
755 --nosummary --nodetail --limit 421rejectrbl='2 2::5' \fIfile\fR
756 .nf
757
758 300 421 Reject RBL ---------------------------------------
759 243 zen.spamhaus.org=127.0.0.2
760 106 10.0.0.129 129.0.0.example.com
761 41 192.168.10.70 hostx10.sample.net
762 40 192.168.42.39 hostz42.sample.net
763 15 10.1.1.152 dsl-10-1-1-152.example.us
764 14 10.10.10.122 mail122.sample.com
765 7 192.168.3.44 smalltime-spammer.example.com
766 ...
767 48 zen.spamhaus.org=127.0.0.4
768 17 10.29.124.92 10-29-124-92.adsl-static.sample.us
769 ...
770 8 zen.spamhaus.org=127.0.0.11
771 ...
772 1 zen.spamhaus.org=127.0.0.10
773 ...
774 .fi
775 .RE 4
776 .SS Running within Logwatch
777 \fBNote:\fR Logwatch versions prior to 7.3.6, unless configured otherwise, required the \fB--print\fR option to print to STDOUT instead of sending reports via email.
778 Since version 7.3.6, STDOUT is the default output destination, and the \fB--print\fR option has been replaced
779 by \fB--output stdout\fR. Check your configuration to determine where report output will be directed, and add the appropriate option to the commands below.
780 .PP
781 To print a summary report for today's Postfix log data:
782 .nf
783 .RS 4
784 .PP
785 \fBlogwatch --service postfix --range today --detail 1\fR
786 .RE 0
787 .nf
788 .PP
789 To print a report for today's Postfix log data, with one level
790 of detail in the \fBDetailed\fR section:
791 .nf
792 .RS 4
793 .PP
794 \fBlogwatch --service postfix --range today --detail 5\fR
795 .RE 0
796 .fi
797 .PP
798 To print a report for yesterday, with two levels of detail in the \fBDetailed\fR section:
799 .nf
800 .RS 4
801 .PP
802 \fBlogwatch --service postfix --range yesterday --detail 6\fR
803 .RE 0
804 .fi
805 .PP
806 To print a report from Dec 12th through Dec 14th, with four levels of detail in the \fBDetailed\fR section:
807 .nf
808 .RS 4
809 .PP
810 \fBlogwatch --service postfix --range \\
811 'between 12/12 and 12/14' --detail 8\fR
812 .RE 0
813 .PP
814 To print a report for today, with all levels of detail:
815 .nf
816 .RS 4
817 .PP
818 \fBlogwatch --service postfix --range today --detail 10\fR
819 .RE 0
820 .PP
821 Same as above, but leaves long lines uncut:
822 .nf
823 .RS 4
824 .PP
825 \fBlogwatch --service postfix --range today --detail 11\fR
826 .RE 0
827
828 .SH "ENVIRONMENT"
829 .na
830 .nf
831 .ad
832 .fi
833 The \fBpostfix-logwatch\fR program uses the following (automatically set) environment
834 variables when running under Logwatch:
835 .IP \fBLOGWATCH_DETAIL_LEVEL\fR
836 This is the detail level specified with the Logwatch command line argument \fB--detail\fR
837 or the \fBDetail\fR setting in the ...conf/services/postfix.conf configuration file.
838 .IP \fBLOGWATCH_DEBUG\fR
839 This is the debug level specified with the Logwatch command line argument \fB--debug\fR.
840 .IP \fBpostfix_\fIxxx\fR
841 The Logwatch program passes all settings \fBpostfix_\fIxxx\fR in the configuration file ...conf/services/postfix.conf
842 to the \fBpostfix\fR filter (which is actually named .../scripts/services/postfix) via environment variable.
843 .SH "FILES"
844 .na
845 .nf
846 .SS Standalone mode
847 .IP "/usr/local/bin/postfix-logwatch"
848 The \fBpostfix-logwatch\fR program
849 .IP "/usr/local/etc/postfix-logwatch.conf"
850 The \fBpostfix-logwatch\fR configuration file in standalone mode
851 .SS Logwatch mode
852 .IP "/etc/logwatch/scripts/services/postfix"
853 The Logwatch \fBpostfix\fR filter
854 .IP "/etc/logwatch/conf/services/postfix.conf"
855 The Logwatch \fBpostfix\fR filter configuration file
856 .SH "SEE ALSO"
857 .na
858 .nf
859 logwatch(8), system log analyzer and reporter
860 .SH "README FILES"
861 .na
862 .ad
863 .nf
864 README, an overview of \fBpostfix-logwatch\fR
865 Changes, the version change list history
866 Bugs, a list of the current bugs or other inadequacies
867 Makefile, the rudimentary installer
868 LICENSE, the usage and redistribution licensing terms
869 .SH "LICENSE"
870 .na
871 .nf
872 .ad
873 This program is free software; you can redistribute it and/or
874 modify it under the terms of the GNU General Public License
875 as published by the Free Software Foundation; either version 2
876 of the License, or (at your option) any later version.
877
878 This program is distributed in the hope that it will be useful,
879 but WITHOUT ANY WARRANTY; without even the implied warranty of
880 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
881 GNU General Public License for more details.
882
883 You should have received a copy of the GNU General Public License
884 along with this program; if not, write to the Free Software
885 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
886 .SH "AUTHOR(S)"
887 .na
888 .nf
889 Mike Cappella
890
891 .fi
892 The original \fBpostfix\fR Logwatch filter was written by
893 Kenneth Porter, and has had many contributors over the years.
894 They are entirely not responsible for any errors, problems or failures since the current author's
895 hands have touched the source code.
0 <!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
1 "http://www.w3.org/TR/html4/loose.dtd">
2 <html> <head>
3 <meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
4 <title> Man page: postfix-logwatch(1) </title>
5 </head> <body> <pre>
6 </pre> </body> </html>
0 #
1 # postfix.conf / postfix-logwatch.conf
2 #
3 # This is the postfix-logwatch configuration file.
4
5 # Lines in this file are of the format:
6 #
7 # VAR = VALUE
8 # *VAR = VALUE
9 # $VAR = VALUE
10 #
11 # Whitespace surrounding the = assignment character is removed. Variable names
12 # and values are case insensitive. Double quotes can be used to preserve case and
13 # whitespace.
14 #
15 # Variables beginning with a * are used only by logwatch.
16 # Variables beginning with a $ are used only by the postfix-logwatch filter.
17 # Variables beginning with neither * nor $ are used only by logwatch, with the
18 # exception of the Detail variable which is passed via environment to the
19 # postfix-logwatch filter.
20 #
21 # Any of the equivalent boolean values below may be used where appropriate:
22 #
23 # 1, Yes, True, On
24 # 0, No, False, Off
25 #
26 # Lines that begin with a # are comment lines. Blank and whitespace lines
27 # are ignored. Whitespace at the beginning and end of a line is ignored.
28 #
29
30 # Specifies the title used in the logwatch report
31 #
32 Title = "Postfix"
33
34 # Specifies the logwatch logfile group
35 #
36 LogFile = maillog
37
38 # Specifies the global, maximum detail level
39 #
40 #Detail = 10
41
42 # The *OnlyService selector is used solely by logwatch to select log lines
43 # to pass to the postfix-logwatch filter. And postfix-logwatch uses the
44 # $postfix_Syslog_Name variable for log line selection.
45 #
46 # When used in logwatch, both the *OnlyService and $postfix_Syslog_Name
47 # variables below should contain essentially the same REs so that lines passed
48 # by logwatch are also selected by postfix-logwatch. Note that *OnlyService
49 # also includes the /<postfix service name> (eg. postfix/smtpd).
50 #
51 # If you change postfix's syslog_name for any postfix service, you will need to
52 # replace "postfix" below with an appropriate RE to capture the desired log entries.
53 # Do likewise for *OnlyService above when used under logwatch. For example, the
54 # settings:
55 #
56 # *OnlyService = "postfix\d?/[-a-zA-Z\d]*"
57 # $postfix_Syslog_Name = "postfix\d?"
58 #
59 # will capture postfix/smtpd, postfix2/virtual, ..., postfix9/cleanup
60 #
61 # Note: If you use parenthesis in your regular expression, be sure they
62 # are cloistering and not capturing: use (?:pattern) instead of (pattern).
63 #
64 # Performance Note:
65 # If you do not wish to analyze any or all of postgrey, postfwd, or policyd-spf
66 # consider simplifying $postfix_Syslog_Name to increase log scanning performance. The
67 # more complex the RE, the longer the scan time to select/reject a log line. The
68 # difference in scan times between the simple string 'postfix' and the more complex
69 # alternation RE that includes postfix, postgrey, postfwd and policyd-spf is about 40%.
70 #
71 # Includes: postfix/smtpd, etc, postfix/policy-spf
72 #*OnlyService = "postfix/[-\w]*"
73 #$postfix_Syslog_Name = "postfix"
74 # Includes: postfix/smtpd, etc, postfix/policy-spf, postgrey, postfwd, policyd-spf
75 *OnlyService = "(?:post(?:fix|grey|fwd)|policyd-spf)(?:/[-\w]*)?"
76 $postfix_Syslog_Name = "(?:post(?:fix|grey|fwd)|policyd-spf)"
77
78 # Ignored postfix services
79 #
80 # Ignores postfix services postfix/SERVICE, where SERVICE is an RE
81 # pattern. The example below will ignore log lines whose syslog
82 # name is "postfix/myservice".
83 #$postfix_Ignore_Service = "myservice"
84
85 # Specifies the maximum report width for Detail <= 10,
86 # or when postfix_Line_Style is not set to Truncate
87 #
88 $postfix_Max_Report_Width = 100
89
90 # Specifies how to handle line lengths greater than Max_Report_Width.
91 # Options are Truncate (default), Wrap, or Full.
92 # for Detail <= 10
93 #
94 $postfix_Line_Style = Truncate
95
96 # Set the variable below to the value set for "recipient_delimiter"
97 # in your postfix configuration, if you want your recipient email
98 # addresses split into their user + extension.
99 #
100 #$postfix_Recipient_Delimiter = "+"
101
102 # Width of IP addresses for columnar output. Change to 40 for IPv6 addresses
103 #$postfix_ipaddr_width = 40
104 $postfix_ipaddr_width = 15
105
106 # Show delays percentiles report. For command line, use --[no]delays,
107 # without an argument.
108 #
109 $postfix_Show_Delays = Yes
110
111 # Show names of detail section variables/command line options in
112 # detail report titles. For command line, use --[no]sect_vars,
113 # without an argument.
114 #
115 $postfix_Show_Sect_Vars = No
116
117 # Show the postfix-reported hostname of 'unknown' in formatted
118 # ip/hostname pairs. For command line, use --[no]unknown,
119 # without an argument.
120 #
121 $postfix_Show_Unknown = Yes
122
123 # Show the summary section. For command line, use --[no]summary,
124 # without an argument.
125 $postfix_Show_Summary = Yes
126
127 # Specifies the percentiles shown in the delivery delays report
128 # Valid values are from 0 to 100, inclusive.
129 $postfix_Delays_Percentiles = "0 25 50 75 90 95 98 100"
130
131 # Specifies the list of reject sections that will be output in
132 # reports (eg. 5xx permanent or 4xx temporary failures).
133 # Each entry in the comma or whitespace separated list consists of 3
134 # characters, where the first is either 4 or 5, and second and third
135 # are a digit or a dot "." match-anything character. Also allowed is
136 # the keyword "Warn" (which is used for postfix "warn_if_reject" rejects).
137 # In PCRE (perl regular expression) terms, any pattern that matches:
138 #
139 # ^([45][0-9.][0-9.]|Warn)$
140 #
141 # is acceptable.
142 #
143 # Typical reject codes:
144 #
145 # 421 Service not available, closing transmission channel
146 # 450 Requested mail action not taken: mailbox unavailable
147 # 451 Requested action aborted: local error in processing
148 # 452 Requested action not taken: insufficient system storage
149 #
150 # 500 Syntax error, command unrecognized
151 # 501 Syntax error in parameters or arguments
152 # 502 Command not implemented
153 # 503 Bad sequence of commands
154 # 504 Command parameter not implemented
155 # 550 Requested action not taken: mailbox unavailable
156 # 551 User not local; please try <forward-path>
157 # 552 Requested mail action aborted: exceeded storage allocation
158 # 553 Requested action not taken: mailbox name not allowed
159 # 554 Transaction failed
160 #
161 # Specific codes take priority over wildcard patterns. The default list
162 # is: "5.. 4.. Warn".
163 #
164 # See also the various Reject... level limiters below
165 #
166 $postfix_Reject_Reply_Patterns = "5.. 4.. Warn"
167
168 # Level Limiters
169 #
170 # The variables below control the maximum output level for a given
171 # category. A level of 1 indicates only one level of detailed output in
172 # the Detailed report section. The Summary section is only available
173 # at logwatch --Detail level >= 5. Increasing the Detail level
174 # by one adds one level of additional detail in the Summary section.
175 #
176 # For example, Detail 5 would output one additional level of detail,
177 # Detail 6 two levels, etc. all the way up to 10. Finally, Detail
178 # 11 yields uncropped lines of output.
179 #
180 # You can control the maximum number of level 1 lines by appending
181 # a period and a number. The value 2.10 would indicate 2 levels
182 # of detail, but only 10 level-1 lines. For example, setting
183 # $postfix_Sent = 1.20 yields a top 20 list of Messages Sent.
184 #
185 # A more useful form of limiting uses triplets in the form l:n:t.
186 # This triplet specifies level l, top n, and minimum threshold t.
187 # Each of the values are integers, with l being the level limiter
188 # as described above, n being a top n limiter for the level l, and
189 # t being the threshold limiter for level l. When both n and t
190 # are specified, n has priority, allowing top n lists (regardless
191 # of threshold value). If the value of l is omitted, the speci-
192 # fied values for n and/or t are used for all levels available in
193 # the sub-section. This permits a simple form of wildcarding (eg.
194 # place minimum threshold limits on all levels). However, spe-
195 # cific limiters always override wildcard limiters. The first
196 # form of level limiter may be included in levelspec to restrict
197 # output, regardless of how many triplets are present.
198
199 $postfix_Sent = 1
200 $postfix_SentLmtp = 1
201 $postfix_Delivered = 1
202 $postfix_Forwarded = 1
203 $postfix_ConnectionLostInbound = 1
204 $postfix_TimeoutInbound = 1
205 $postfix_ConnectToFailure = 2
206
207 # Disabled by default to reduce noise and consume less memory.
208 # Enable at will
209 $postfix_EnvelopeSenders = 0
210 $postfix_EnvelopeSenderDomains = 0
211 $postfix_ConnectionInbound = 0
212 # Reject by IP report
213 $postfix_ByIpRejects = 0
214
215 $postfix_PanicError = 10
216 $postfix_FatalError = 10
217 $postfix_Error = 10
218 # warnings
219 $postfix_Anvil = 3
220 $postfix_AttrError = 10
221 $postfix_CommunicationError = 10
222 $postfix_DatabaseGeneration = 10
223 $postfix_DNSError = 10
224 $postfix_HeloError = 10
225 $postfix_HostnameValidationError = 10
226 $postfix_HostnameVerification = 10
227 $postfix_IllegalAddrSyntax = 10
228 $postfix_LdapError = 10
229 $postfix_MailerLoop = 10
230 $postfix_MapProblem = 10
231 $postfix_MessageWriteError = 10
232 $postfix_NumericHostname = 10
233 $postfix_ProcessExit = 10
234 $postfix_ProcessLimit = 10
235 $postfix_QueueWriteError = 10
236 $postfix_RBLError = 10
237 $postfix_SaslAuthFail = 10
238 $postfix_SmtpConversationError = 10
239 $postfix_StartupError = 10
240 $postfix_WarningsOther = 10
241
242 # Common access control actions
243 $postfix_Bcced = 10
244 $postfix_Discarded = 10
245 $postfix_Filtered = 10
246 $postfix_Hold = 10
247 $postfix_Prepended = 10
248 $postfix_Redirected = 10
249 $postfix_Replaced = 10
250 $postfix_Warned = 10
251 # DUNNO action not logged
252 # IGNORE action not logged
253 # REJECT actions are below
254
255 # Rejects
256 # The following are generic reject types, which are automatically
257 # expanded into each reject variant, based on the reply patterns
258 # listed in Reject_Reply_Patterns. By default, each item in the
259 # list below becomes 4xxReject..., 5xxReject..., and WarnReject...
260 $postfix_RejectBody = 10
261 $postfix_RejectClient = 10
262 $postfix_RejectConfigError = 10
263 $postfix_RejectContent = 10
264 $postfix_RejectData = 10
265 $postfix_RejectEtrn = 10
266 $postfix_RejectHeader = 10
267 $postfix_RejectHelo = 10
268 $postfix_RejectInsufficientSpace = 10
269 $postfix_RejectLookupFailure = 10
270 $postfix_RejectMilter = 10
271 $postfix_RejectProxy = 10
272 $postfix_RejectRBL = 10
273 $postfix_RejectRecip = 10
274 $postfix_RejectRelay = 10
275 $postfix_RejectSender = 10
276 $postfix_RejectSize = 10
277 $postfix_RejectUnknownClient = 10
278 $postfix_RejectUnknownReverseClient = 10
279 $postfix_RejectUnknownUser = 10
280 $postfix_RejectUnverifiedClient = 3
281 $postfix_RejectVerify = 10
282
283 # For more precise control, you can comment out any of the reject
284 # types above and specify each variant manually, but the list must
285 # be consistent with the values specified in Reject_Reply_Patterns.
286 #
287 # For example, you could comment out $postfix_RejectHelo above, and
288 # instead uncomment the three RejectHelo variants, allowing you to
289 # specify different level limiters to each variant:
290 #
291 # Permanent 5xx variant
292 # $postfix_5xxRejectHelo = 1
293 # Temporary 4xx variant
294 # $postfix_4xxRejectHelo = 2
295 # Warn_if_reject variant
296 # $postfix_WarnRejectHelo = 2
297 #
298
299 $postfix_Deferred = 10
300 $postfix_Deferrals = 10
301 $postfix_BounceLocal = 10
302 $postfix_BounceRemote = 10
303
304 $postfix_Discarded = 10
305 $postfix_ReturnedToSender = 10
306 $postfix_NotificationSent = 10
307 $postfix_ConnectionLostOutbound = 10
308
309 $postfix_Deliverable = 10
310 $postfix_Undeliverable = 10
311 $postfix_PixWorkaround = 10
312 $postfix_SaslAuth = 10
313 $postfix_TlsServerConnect = 10
314 $postfix_TlsClientConnect = 10
315 $postfix_TlsUnverified = 10
316 $postfix_TlsOffered = 10
317 $postfix_SMTPProtocolViolation = 10
318
319 $postfix_Postscreen = 1
320 $postfix_DNSBLog = 1
321
322 $postfix_PolicySPF = 10
323 $postfix_PolicydWeight = 10
324 $postfix_Postgrey = 10
325
326 # vi: shiftwidth=3 tabstop=3 et
0 #
1 # postfix.conf / postfix-logwatch.conf
2 #
3 # This is the postfix-logwatch configuration file.
4
5 # Lines in this file are of the format:
6 #
7 # VAR = VALUE
8 # *VAR = VALUE
9 # $VAR = VALUE
10 #
11 # Whitespace surrounding the = assignment character is removed. Variable names
12 # and values are case insensitive. Double quotes can be used to preserve case and
13 # whitespace.
14 #
15 # Variables beginning with a * are used only by logwatch.
16 # Variables beginning with a $ are used only by the postfix-logwatch filter.
17 # Variables beginning with neither * nor $ are used only by logwatch, with the
18 # exception of the Detail variable which is passed via environment to the
19 # postfix-logwatch filter.
20 #
21 # Any of the equivalent boolean values below may be used where appropriate:
22 #
23 # 1, Yes, True, On
24 # 0, No, False, Off
25 #
26 # Lines that begin with a # are comment lines. Blank and whitespace lines
27 # are ignored. Whitespace at the beginning and end of a line is ignored.
28 #
29
30 # Specifies the title used in the logwatch report
31 #
32 Title = "Postfix"
33
34 # Specifies the logwatch logfile group
35 #
36 LogFile = maillog
37
38 # Specifies the global, maximum detail level
39 #
40 #Detail = 10
41
42 # The *OnlyService selector is used solely by logwatch to select log lines
43 # to pass to the postfix-logwatch filter. And postfix-logwatch uses the
44 # $postfix_Syslog_Name variable for log line selection.
45 #
46 # When used in logwatch, both the *OnlyService and $postfix_Syslog_Name
47 # variables below should contain essentially the same REs so that lines passed
48 # by logwatch are also selected by postfix-logwatch. Note that *OnlyService
49 # also includes the /<postfix service name> (eg. postfix/smtpd).
50 #
51 # If you change postfix's syslog_name for any postfix service, you will need to
52 # replace "postfix" below with an appropriate RE to capture the desired log entries.
53 # Do likewise for *OnlyService above when used under logwatch. For example, the
54 # settings:
55 #
56 # *OnlyService = "postfix\d?/[-a-zA-Z\d]*"
57 # $postfix_Syslog_Name = "postfix\d?"
58 #
59 # will capture postfix/smtpd, postfix2/virtual, ..., postfix9/cleanup
60 #
61 # Note: If you use parenthesis in your regular expression, be sure they
62 # are cloistering and not capturing: use (?:pattern) instead of (pattern).
63 #
64 # Performance Note:
65 # If you do not wish to analyze any or all of postgrey, postfwd, or policyd-spf
66 # consider simplifying $postfix_Syslog_Name to increase log scanning performance. The
67 # more complex the RE, the longer the scan time to select/reject a log line. The
68 # difference in scan times between the simple string 'postfix' and the more complex
69 # alternation RE that includes postfix, postgrey, postfwd and policyd-spf is about 40%.
70 #
71 # Includes: postfix/smtpd, etc, postfix/policy-spf
72 #*OnlyService = "postfix/[-\w]*"
73 #$postfix_Syslog_Name = "postfix"
74 # Includes: postfix/smtpd, etc, postfix/policy-spf, postgrey, postfwd, policyd-spf
75 *OnlyService = "(?:post(?:fix|grey|fwd)|policyd-spf)(?:/[-\w]*)?"
76 $postfix_Syslog_Name = "(?:post(?:fix|grey|fwd)|policyd-spf)"
77
78 # Ignored postfix services
79 #
80 # Ignores postfix services postfix/SERVICE, where SERVICE is an RE
81 # pattern. The example below will ignore log lines whose syslog
82 # name is "postfix/myservice".
83 #$postfix_Ignore_Service = "myservice"
84
85 # Specifies the maximum report width for Detail <= 10,
86 # or when postfix_Line_Style is not set to Truncate
87 #
88 $postfix_Max_Report_Width = 100
89
90 # Specifies how to handle line lengths greater than Max_Report_Width.
91 # Options are Truncate (default), Wrap, or Full.
92 # for Detail <= 10
93 #
94 $postfix_Line_Style = Truncate
95
96 # Set the variable below to the value set for "recipient_delimiter"
97 # in your postfix configuration, if you want your recipient email
98 # addresses split into their user + extension.
99 #
100 #$postfix_Recipient_Delimiter = "+"
101
102 # Width of IP addresses for columnar output. Change to 40 for IPv6 addresses
103 #$postfix_ipaddr_width = 40
104 $postfix_ipaddr_width = 15
105
106 # Show delays percentiles report. For command line, use --[no]delays,
107 # without an argument.
108 #
109 $postfix_Show_Delays = Yes
110
111 # Show names of detail section variables/command line options in
112 # detail report titles. For command line, use --[no]sect_vars,
113 # without an argument.
114 #
115 $postfix_Show_Sect_Vars = No
116
117 # Show the postfix-reported hostname of 'unknown' in formatted
118 # ip/hostname pairs. For command line, use --[no]unknown,
119 # without an argument.
120 #
121 $postfix_Show_Unknown = Yes
122
123 # Show the summary section. For command line, use --[no]summary,
124 # without an argument.
125 $postfix_Show_Summary = Yes
126
127 # Specifies the percentiles shown in the delivery delays report
128 # Valid values are from 0 to 100, inclusive.
129 $postfix_Delays_Percentiles = "0 25 50 75 90 95 98 100"
130
131 # Specifies the list of reject sections that will be output in
132 # reports (eg. 5xx permanent or 4xx temporary failures).
133 # Each entry in the comma or whitespace separated list consists of 3
134 # characters, where the first is either 4 or 5, and second and third
135 # are a digit or a dot "." match-anything character. Also allowed is
136 # the keyword "Warn" (which is used for postfix "warn_if_reject" rejects).
137 # In PCRE (perl regular expression) terms, any pattern that matches:
138 #
139 # ^([45][0-9.][0-9.]|Warn)$
140 #
141 # is acceptable.
142 #
143 # Typical reject codes:
144 #
145 # 421 Service not available, closing transmission channel
146 # 450 Requested mail action not taken: mailbox unavailable
147 # 451 Requested action aborted: local error in processing
148 # 452 Requested action not taken: insufficient system storage
149 #
150 # 500 Syntax error, command unrecognized
151 # 501 Syntax error in parameters or arguments
152 # 502 Command not implemented
153 # 503 Bad sequence of commands
154 # 504 Command parameter not implemented
155 # 550 Requested action not taken: mailbox unavailable
156 # 551 User not local; please try <forward-path>
157 # 552 Requested mail action aborted: exceeded storage allocation
158 # 553 Requested action not taken: mailbox name not allowed
159 # 554 Transaction failed
160 #
161 # Specific codes take priority over wildcard patterns. The default list
162 # is: "5.. 4.. Warn".
163 #
164 # See also the various Reject... level limiters below
165 #
166 $postfix_Reject_Reply_Patterns = "5.. 4.. Warn"
167
168 # Level Limiters
169 #
170 # The variables below control the maximum output level for a given
171 # category. A level of 1 indicates only one level of detailed output in
172 # the Detailed report section. The Summary section is only available
173 # at logwatch --Detail level >= 5. Increasing the Detail level
174 # by one adds one level of additional detail in the Summary section.
175 #
176 # For example, Detail 5 would output one additional level of detail,
177 # Detail 6 two levels, etc. all the way up to 10. Finally, Detail
178 # 11 yields uncropped lines of output.
179 #
180 # You can control the maximum number of level 1 lines by appending
181 # a period and a number. The value 2.10 would indicate 2 levels
182 # of detail, but only 10 level-1 lines. For example, setting
183 # $postfix_Sent = 1.20 yields a top 20 list of Messages Sent.
184 #
185 # A more useful form of limiting uses triplets in the form l:n:t.
186 # This triplet specifies level l, top n, and minimum threshold t.
187 # Each of the values are integers, with l being the level limiter
188 # as described above, n being a top n limiter for the level l, and
189 # t being the threshold limiter for level l. When both n and t
190 # are specified, n has priority, allowing top n lists (regardless
191 # of threshold value). If the value of l is omitted, the speci-
192 # fied values for n and/or t are used for all levels available in
193 # the sub-section. This permits a simple form of wildcarding (eg.
194 # place minimum threshold limits on all levels). However, spe-
195 # cific limiters always override wildcard limiters. The first
196 # form of level limiter may be included in levelspec to restrict
197 # output, regardless of how many triplets are present.
198
199 $postfix_Sent = "1:10:1 2::1"
200 $postfix_SentLmtp = "1:10:1 2::1"
201 $postfix_Delivered = "1:10:1"
202 $postfix_Forwarded = "1 1:10:1"
203 $postfix_ConnectionLostInbound = 1
204 $postfix_TimeoutInbound = "2 :10:1"
205 $postfix_ConnectToFailure = 2
206
207 $postfix_EnvelopeSenders = "2 1:10:1"
208 $postfix_EnvelopeSenderDomains = "1 1:20:1"
209 $postfix_ConnectionInbound = "1 1:20:1"
210
211 # Reject by IP report
212 $postfix_ByIpRejects = 0
213
214 $postfix_PanicError = 10
215 $postfix_FatalError = 10
216 $postfix_Error = 10
217 # warnings
218 $postfix_Anvil = 2
219 $postfix_AttrError = 10
220 $postfix_CommunicationError = 10
221 $postfix_DatabaseGeneration = 10
222 $postfix_DNSError = 3
223 $postfix_HeloError = 10
224 $postfix_HostnameValidationError = 10
225 $postfix_HostnameVerification = "2::1"
226 $postfix_IllegalAddrSyntax = 10
227 $postfix_LdapError = 10
228 $postfix_MailerLoop = 10
229 $postfix_MapProblem = 10
230 $postfix_MessageWriteError = 10
231 $postfix_NumericHostname = 10
232 $postfix_ProcessExit = 10
233 $postfix_ProcessLimit = 10
234 $postfix_QueueWriteError = 10
235 $postfix_RBLError = 10
236 $postfix_SaslAuthFail = 10
237 $postfix_SmtpConversationError = 10
238 $postfix_StartupError = 10
239 $postfix_WarningsOther = 10
240
241 # Common access control actions
242 $postfix_Bcced = 10
243 $postfix_Discarded = 10
244 $postfix_Filtered = 10
245 $postfix_Hold = 10
246 $postfix_Prepended = 10
247 $postfix_Redirected = 10
248 $postfix_Replaced = 10
249 $postfix_Warned = 10
250 # DUNNO action not logged
251 # IGNORE action not logged
252 # REJECT actions are below
253
254 # Rejects
255 # The following are generic reject types, which are automatically
256 # expanded into each reject variant, based on the reply patterns
257 # listed in Reject_Reply_Patterns. By default, each item in the
258 # list below becomes 4xxReject..., 5xxReject..., and WarnReject...
259 $postfix_RejectBody = "2 :10:1"
260 $postfix_RejectClient = "2 :10:1"
261 $postfix_RejectConfigError = "2 :10:1"
262 $postfix_RejectContent = "2 :10:1"
263 $postfix_RejectData = "1 :10:1"
264 $postfix_RejectEtrn = "2 :10:1"
265 $postfix_RejectHeader = "2 :10:1"
266 $postfix_RejectHelo = "2 :10:1"
267 $postfix_RejectInsufficientSpace = "2 :10:1"
268 $postfix_RejectLookupFailure = "2 :10:1"
269 $postfix_RejectMilter = "2 :10:1"
270 $postfix_RejectProxy = "2 :10:1"
271 $postfix_RejectRBL = "2 :10:1"
272 $postfix_RejectRecip = "2 :10:1"
273 $postfix_RejectRelay = "1 :10:1"
274 $postfix_RejectSender = "2 :10:1"
275 $postfix_RejectSize = "2 :10:1"
276 $postfix_RejectUnknownClient = "2 :10:1"
277 $postfix_RejectUnknownReverseClient = "2 :10:1"
278 $postfix_RejectUnknownUser = "2 :10:1"
279 $postfix_RejectUnverifiedClient = "2 :10:1"
280 $postfix_RejectVerify = "2 :10:1"
281
282 # For more precise control, you can comment out any of the reject
283 # types above and specify each variant manually, but the list must
284 # be consistent with the values specified in Reject_Reply_Patterns.
285 #
286 # For example, you could comment out $postfix_RejectHelo above, and
287 # instead uncomment the three RejectHelo variants, allowing you to
288 # specify different level limiters to each variant:
289 #
290 # Permanent 5xx variant
291 # $postfix_5xxRejectHelo = 1
292 # Temporary 4xx variant
293 # $postfix_4xxRejectHelo = 2
294 # Warn_if_reject variant
295 # $postfix_WarnRejectHelo = 2
296 #
297
298 $postfix_Deferred = 10
299 $postfix_Deferrals = 10
300 $postfix_BounceLocal = 10
301 $postfix_BounceRemote = "4 ::1"
302
303 $postfix_Discarded = 10
304 $postfix_ReturnedToSender = 10
305 $postfix_NotificationSent = 10
306 $postfix_ConnectionLostOutbound = 10
307
308 $postfix_Deliverable = 10
309 $postfix_Undeliverable = 10
310 $postfix_PixWorkaround = 10
311 $postfix_SaslAuth = 10
312 $postfix_TlsServerConnect = 10
313 $postfix_TlsClientConnect = 10
314 $postfix_TlsUnverified = 10
315 $postfix_TlsOffered = 10
316 $postfix_SmtpProtocolViolation = "2 :10:1"
317
318 $postfix_Postscreen = 1
319 $postfix_DNSBLog = 1
320
321 $postfix_PolicySPF = "3::10 4::1"
322 $postfix_PolicydWeight = 10
323 $postfix_Postgrey = "4 3::1"
324
325
326 # vi: shiftwidth=3 tabstop=3 et
4444 fi \
4545 done
4646
47 # Copy logwatch-postfix files
48 cp -a debian/contrib/logwatch-postfix/postfix-logwatch $(SHAREDIR)/scripts/services/postfix
49 cp -a debian/contrib/logwatch-postfix/postfix-logwatch.conf $(SHAREDIR)/default.conf/services/postfix.conf
50
4751 mkdir -p $(DESTDIR)/usr/sbin
4852
4953 #Ensure all configuration files have 0644